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
- Ensure exactly one of the output targets is valid: check filename non-null before calling, or check the FILE* from fopen before passing it.
- Fix the source of the null (fopen failure, missing config value, unset env var) and handle that failure at its origin.
- If you have neither a path nor a stream by design, choose a save overload that takes a filename and construct one.
- 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
- Check fopen() result before passing a FILE* to save_inr
- Ensure at least one of stream/path is set in wrapper APIs
- Never pass both null stream and null filename
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
- load_inr(): Specified filename is (null).
- load_other(): Specified filename is (null).
- save_minc2(): Specified filename is (null).
- save_analyze(): Specified filename is (null).
- save_exr(): Specified filename is (null).
AI-assisted analysis of Yalantis/uCrop@f788b534b4 (2026-09-08).
Data as JSON: /api/errors/6d2c15e7212f210b.
Report an issue: GitHub.