Yalantis/uCrop · error · CImgArgumentException

save_inr(): Specified filename is (null).

Error message

save_inr(): Specified filename is (null).

What it means

The INR (Inrimage) writer has two entry points: one taking an open FILE* and one taking a filename, both funnelling into _save_inr(). It throws CImgArgumentException when both the FILE* and the filename are null, because there is nowhere to write. This is a pure precondition check before the image is even inspected.

Source

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

    }

    //! Save image as an INRIMAGE-4 file.
    /**
      \param filename Filename, as a C-string.
      \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions.
    **/
    const CImg<T>& save_inr(const char *const filename, const float *const voxel_size=0) const {
      return _save_inr(0,filename,voxel_size);
    }

    //! Save image as an INRIMAGE-4 file \overloading.
    const CImg<T>& save_inr(std::FILE *const file, const float *const voxel_size=0) const {
      return _save_inr(file,0,voxel_size);
    }

    const CImg<T>& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const {
      if (!file && !filename)
        throw CImgArgumentException(_cimg_instance
                                    "save_inr(): Specified filename is (null).",
                                    cimg_instance);
      if (is_empty()) { cimg::fempty(file,filename); return *this; }

      int inrpixsize = -1;
      const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
      if (!cimg::strcasecmp(pixel_type(),"uint8")) {
        inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1;
      }
      if (!cimg::strcasecmp(pixel_type(),"int8")) {
        inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1;
      }
      if (!cimg::strcasecmp(pixel_type(),"uint16")) {
        inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2;
      }
      if (!cimg::strcasecmp(pixel_type(),"int16")) {
        inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2;
      }

View on GitHub (pinned to f788b534b4)

Solutions

  1. Ensure exactly one of the output targets is valid: check filename non-null before calling, or check the FILE* from fopen before passing it.
  2. Fix the source of the null (fopen failure, missing config value, unset env var) and handle that failure at its origin.
  3. If you have neither a path nor a stream by design, choose a save overload that takes a filename and construct one.
  4. Add an app-level validation wrapper that rejects both-null inputs with a message naming where the path should come from.

Example fix

// before
std::FILE *f = fopen(path, "wb"); // may be NULL
img.save_inr(f);

// after
std::FILE *f = fopen(path, "wb");
if (!f) throw std::runtime_error(std::string("cannot open ") + path);
img.save_inr(f);
Defensive patterns

Strategy: validation

Validate before calling

if (!file && !(filename && *filename))
  throw std::invalid_argument("save_inr: need a FILE* or a filename");

Type guard

bool has_output_target(std::FILE* f, const char* p) { return f != nullptr || (p && *p); }

Try / catch

try {
  img.save_inr(path.c_str());
} catch (const CImgArgumentException& e) {
  std::fprintf(stderr, "INR save failed, no output target: %s\n", e.what());
}

Prevention

When it happens

Trigger: Calling CImg::save_inr(filename) with a null filename; calling the FILE* overload with a null FILE* (e.g. fopen failed) and no filename fallback; generic save(0) dispatch hitting the inr branch.

Common situations: fopen() returned NULL and its result was forwarded to save_inr; uninitialized filename pointer from config/env; wrapper APIs that pass through optional path/stream arguments where both ended up unset.

Related errors


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