Yalantis/uCrop · error · CImgArgumentException

save_jxl(): bytes_per_pixel must be in [0, 2] and less than…

Error message

save_jxl(): bytes_per_pixel must be in [0, 2] and less than or equal to sizeof(T)

What it means

save_jxl() accepts bytes_per_pixel of 0 (auto), 1 (8-bit), or 2 (16-bit), and it must not exceed sizeof(T) of the image's pixel type. Otherwise CImg throws CImgArgumentException, because the requested output bit depth cannot be represented from the underlying storage type.

Solutions

  1. Pass bytes_per_pixel within [0,2] and <= sizeof(T): use 0 (auto) unless you specifically need 8/16-bit output.
  2. If you need 16-bit JXL from byte data, first convert the image: CImg<unsigned short> out = img * 257; then save with bytes_per_pixel=2.
  3. Clamp the parameter at the call site based on the image type.

Example fix

// before
CImg<unsigned char> img(...);
img.save_jxl("out.jxl", 1.0f, 2); // sizeof(T)=1 < 2 -> throw
// after
CImg<unsigned short> out = img * 257;
out.save_jxl("out.jxl", 1.0f, 2);
Defensive patterns

Strategy: validation

Validate before calling

if (bytesPerPixel > 2 || sizeof(T) < bytesPerPixel) {
  throw std::invalid_argument("bytes_per_pixel must be in [0,2] and <= sizeof(T)");
}
img.save_jxl("out.jxl", 1.0f, bytesPerPixel);

Prevention

When it happens

Trigger: Calling img.save_jxl(path, distance, bpp) with bpp==3 or higher, or with bpp larger than sizeof(T) — e.g. bytes_per_pixel==2 on a CImg<char>/CImg<unsigned char> (sizeof(T)==1).

Common situations: Copy-pasted save calls where the bpp parameter was tuned for float images (sizeof 4) then reused on byte images, or hardcoded 16-bit output requested on 8-bit data.

Related errors


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

Appendix: source

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

    const CImg<T>& _save_jxl(const char *const filename, const float distance=1.0f,
                             const unsigned int bytes_per_pixel=0) const {
      if (!filename)
        throw CImgArgumentException(_cimg_instance
                                    "save_jxl(): Specified filename is (null).",
                                    cimg_instance);
      if (_spectrum > 4)
        throw CImgArgumentException(_cimg_instance
                                    "save_jxl(): JPEG XL only supports at most 4 channels.",
                                    cimg_instance);
      double stmin, stmax = (double)max_min(stmin);
      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
        cimg::warn(_cimg_instance
                   "save_jxl(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
                   cimg_instance,
                   stmin,stmax,filename);
      if (bytes_per_pixel>2 || sizeof(T)<bytes_per_pixel)
        throw CImgArgumentException(_cimg_instance
                                    "save_jxl(): bytes_per_pixel must be in [0, 2] and less than or equal to sizeof(T)",
                                    cimg_instance);
      if (_depth>1)
        cimg::warn(_cimg_instance
                   "save_jxl(): Instance is volumetric, only the first slice will be saved in file '%s'.",
                   cimg_instance,
                   filename);
#ifndef cimg_use_jxl
      cimg::unused(distance);
      return save_other(filename);
#else
      std::FILE *file = cimg::fopen(filename, "wb");
      cimg_uint32 nChannels = _spectrum;
      bool hasAlpha = _spectrum==2 || _spectrum==4;
      if (hasAlpha) --nChannels;
      unsigned int bytesPerPixel = bytes_per_pixel;
      if (bytesPerPixel==0)
          bytesPerPixel = stmax<256?1:2;

View on GitHub (pinned to f788b534b4)