Yalantis/uCrop · warning

save_rgb(): image instance has not exactly 3 channels, for…

Error message

save_rgb(): image instance has not exactly 3 channels, for file '%s'.

What it means

save_rgb() writes raw 24-bit RGB bytes (no header); CImg warns when the image does not have exactly 3 channels because the raw dump will not match the expected interleaved RGB layout — data will be misinterpreted or channels dropped depending on count. Emitted via cimg::warn(), the save still proceeds.

Solutions

  1. Append/drop channels to reach exactly 3: grayscale -> img.get_resize(...,1,1,1,3) or repeat channel; RGBA -> img.get_channels(0,2)
  2. Use save_ppm() or save_png() which handle arbitrary channel counts with proper headers
  3. If writing single-channel data, use save_pgm()/save_pnk() instead

Example fix

// before
grayImage.save_rgb("out.rgb");      // not 3 channels
// after
CImg<unsigned char> rgb = grayImage.get_resize(-100,-100,-100,3);
rgb.save_rgb("out.rgb");
Defensive patterns

Strategy: validation

Validate before calling

if (img.spectrum() != 3) img = img.get_resize(-100, -100, -100, 3); // replicate or truncate to 3 channels
img.save_rgb("out.rgb");

Type guard

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

Try / catch

try { img.save_rgb("out.rgb"); } catch (CImgException& e) { /* handle */ }

Prevention

When it happens

Trigger: Calling save_rgb() on a grayscale image (_spectrum==1), an RGBA image (_spectrum==4), or any other channel count != 3.

Common situations: Exporting frames for external tools that expect raw RGB; converting from a color format that kept alpha; loading grayscale images and writing them as if they were RGB.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

      \param filename Filename, as a C-string.
    **/
    const CImg<T>& save_rgb(const char *const filename) const {
      return _save_rgb(0,filename);
    }

    //! Save image as a RGB file \overloading.
    const CImg<T>& save_rgb(std::FILE *const file) const {
      return _save_rgb(file,0);
    }

    const CImg<T>& _save_rgb(std::FILE *const file, const char *const filename) const {
      if (!file && !filename)
        throw CImgArgumentException(_cimg_instance
                                    "save_rgb(): Specified filename is (null).",
                                    cimg_instance);
      if (is_empty()) { cimg::fempty(file,filename); return *this; }
      if (_spectrum!=3)
        cimg::warn(_cimg_instance
                   "save_rgb(): image instance has not exactly 3 channels, for file '%s'.",
                   cimg_instance,
                   filename?filename:"(FILE*)");

      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const ulongT wh = (ulongT)_width*_height;
      unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer;
      const T
        *ptr1 = data(0,0,0,0),
        *ptr2 = _spectrum>1?data(0,0,0,1):0,
        *ptr3 = _spectrum>2?data(0,0,0,2):0;
      switch (_spectrum) {
      case 1 : { // Scalar image
        for (ulongT k = 0; k<wh; ++k) {
          const unsigned char val = (unsigned char)*(ptr1++);
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = val;

View on GitHub (pinned to f788b534b4)