Yalantis/uCrop · error · CImgInstanceException

HSItoRGB(): Instance is not a HSI image.

Error message

HSItoRGB(): Instance is not a HSI image.

What it means

CImg<T>::HSItoRGB() converts HSI pixel values back to RGB and requires the image to have exactly 3 channels (H, S and I planes). With any other spectrum the conversion is undefined, so it throws CImgInstanceException. Like its RGB counterpart, it validates channel count only, not that the values are genuinely in HSI form.

Source

Thrown at ucrop/src/main/jni/CImg.h:37102

          H = 60*(C==0?0:M==R?cimg::mod((G - B)/C,(Tfloat)6):M==G?(B - R)/C + 2:(R - G)/C + 4),
          S = sum<=0?0:1 - 3*m/sum,
          I = sum/(3*255);
        p1[N] = (T)H;
        p2[N] = (T)S;
        p3[N] = (T)I;
      }
      return *this;
    }

    //! Convert pixel values from RGB to HSI color spaces \newinstance.
    CImg<Tfloat> get_RGBtoHSI() const {
      return CImg<Tfloat>(*this,false).RGBtoHSI();
    }

    //! Convert pixel values from HSI to RGB color spaces.
    CImg<T>& HSItoRGB() {
      if (_spectrum!=3)
        throw CImgInstanceException(_cimg_instance
                                    "HSItoRGB(): Instance is not a HSI image.",
                                    cimg_instance);

      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
      const longT whd = (longT)width()*height()*depth();
      cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256))
      for (longT N = 0; N<whd; ++N) {
        const Tfloat
          H = cimg::mod((Tfloat)p1[N]/60,(Tfloat)6),
          S = (Tfloat)p2[N],
          I = (Tfloat)p3[N],
          Z = 1 - cimg::abs(cimg::mod(H,(Tfloat)2) - 1),
          C = I*S/(1 + Z),
          X = C*Z,
          m = I*(1 - S)/3;
        Tfloat R, G, B;
        switch ((int)H) {
        case 0 : R = C; G = X; B = 0; break;

View on GitHub (pinned to f788b534b4)

Solutions

  1. Ensure spectrum()==3 before calling; for grayscale, replicate the channel to 3 and set S=0, I=value (or just skip conversion since it's already intensity-like).
  2. Strip alpha (channels(0,2)) before the conversion.
  3. Check for intermediate save/load steps that change channel count between RGBtoHSI and HSItoRGB.
  4. Wrap round trips in a sanity assert: assert(hsi.spectrum()==3).

Example fix

// before
CImg<float> gray = hsi.get_channel(0); // someone extracted one channel
gray.HSItoRGB(); // throws
// after
CImg<float> rgb = hsi; // keep all 3 channels
rgb.HSItoRGB();
Defensive patterns

Strategy: type-guard

Validate before calling

template<typename T> bool is3Channel(const CImg<T>& img) { return img.spectrum() == 3; }
if (is3Channel(img)) img.HSItoRGB();

Type guard

template<typename T> bool isHsiShape(const CImg<T>& img) { return img.spectrum() == 3; }

Try / catch

try {
  img.HSItoRGB();
} catch (const CImgInstanceException& e) {
  std::fprintf(stderr, "HSItoRGB requires 3 channels, image has %d\n", img.spectrum());
}

Prevention

When it happens

Trigger: Calling img.HSItoRGB() on a grayscale (spectrum=1) or RGBA (spectrum=4) image; losing channels between RGBtoHSI() and HSItoRGB() (e.g. saving/loading in between, or channels(0,1) extraction).

Common situations: Round-trip pipelines where a save to a grayscale format silently drops to 1 channel before the inverse conversion; alpha-channel images; applying HSItoRGB to a raw 1-channel buffer.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of Yalantis/uCrop@f788b534b4 (2026-09-08). Data as JSON: /api/errors/c5d35f5166799f5d. Report an issue: GitHub.