Yalantis/uCrop · error · CImgInstanceException

XYZtoRGB(): Instance is not a XYZ image.

Error message

XYZtoRGB(): Instance is not a XYZ image.

What it means

XYZtoRGB() converts CIE XYZ values back to RGB in place and requires exactly 3 channels (_spectrum==3); the same spectrum check as the other conversions triggers this exception when it fails. It reads and writes the three channel planes directly.

Solutions

  1. Guard with if (img.spectrum()==3) before the call.
  2. Reduce 4-channel XYZ buffers with channels(0,2).
  3. Expand 1-channel luminance buffers with get_append before conversion.
  4. Catch CImgInstanceException and log img.spectrum() for diagnosis.

Example fix

// before
CImg<float> xyz = loadXYZ(); // may include alpha
xyz.XYZtoRGB();
// after
CImg<float> xyz = loadXYZ();
if (xyz.spectrum()==4) xyz.channels(0,2);
assert(xyz.spectrum()==3);
xyz.XYZtoRGB();
Defensive patterns

Strategy: validation

Validate before calling

if (img.spectrum()==4) img.channels(0,2);
if (img.spectrum()!=3) throw std::runtime_error("XYZ image must have 3 channels");

Type guard

bool isXYZ(const cimg_library::CImg<T>& img) { return img.spectrum()==3; }

Try / catch

try {
  img.XYZtoRGB();
} catch (const cimg_library::CImgInstanceException& e) {
  std::cerr << "XYZtoRGB needs 3 channels; got " << img.spectrum() << std::endl;
}

Prevention

When it happens

Trigger: Calling img.XYZtoRGB() on grayscale, RGBA, or empty images, or on XYZ buffers produced with a different channel layout.

Common situations: Color pipelines where XYZ intermediates were stored with extra channels (e.g.XYZA); grayscale XYZ luminance-only buffers; round-trip code skipping the spectrum check.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

          p2[N] = (T)(0.22248840*R + 0.71690369*G + 0.06060791*B);
          p3[N] = (T)(0.01391602*R + 0.09706116*G + 0.71392822*B);
        }
      }
      return *this;
    }

    //! Convert pixel values from RGB to XYZ color spaces \newinstance.
    CImg<Tfloat> get_RGBtoXYZ(const bool use_D65=true) const {
      return CImg<Tfloat>(*this,false).RGBtoXYZ(use_D65);
    }

    //! Convert pixel values from XYZ to RGB color spaces.
    /**
       \param use_D65 Tell to use the D65 illuminant (D50 otherwise).
    **/
    CImg<T>& XYZtoRGB(const bool use_D65=true) {
      if (_spectrum!=3)
        throw CImgInstanceException(_cimg_instance
                                    "XYZtoRGB(): Instance is not a XYZ 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,2048))
      for (longT N = 0; N<whd; ++N) {
        const Tfloat
          X = (Tfloat)p1[N]*255,
          Y = (Tfloat)p2[N]*255,
          Z = (Tfloat)p3[N]*255;
        if (use_D65) {
          p1[N] = (T)cimg::cut(3.2404542*X - 1.5371385*Y - 0.4985314*Z,0,255);
          p2[N] = (T)cimg::cut(-0.9692660*X + 1.8760108*Y + 0.0415560*Z,0,255);
          p3[N] = (T)cimg::cut(0.0556434*X - 0.2040259*Y + 1.0572252*Z,0,255);
        } else {
          p1[N] = (T)cimg::cut(3.134274799724*X  - 1.617275708956*Y - 0.490724283042*Z,0,255);
          p2[N] = (T)cimg::cut(-0.978795575994*X + 1.916161689117*Y + 0.033453331711*Z,0,255);

View on GitHub (pinned to f788b534b4)