Yalantis/uCrop · error · CImgArgumentException

draw_ellipse(): Specified color is (null).

Error message

draw_ellipse(): Specified color is (null).

What it means

draw_ellipse() requires a pointer to a color buffer (at least spectrum() elements) and throws CImgArgumentException when that pointer is null. CImg draw routines deliberately guard against null colors because pixels would otherwise be written from an invalid read. The check happens after cheap early-outs (empty image, unfilled-with-empty-pattern, off-canvas), so a valid on-canvas call with color==0 throws.

Source

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

    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                          const tc *const color, const float opacity,
                          const unsigned int pattern) {
      CImgList<t> eig = tensor.get_symmetric_eigen();
      const CImg<t> &val = eig[0], &vec = eig[1];
      return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)),
                          std::atan2(vec(0,1),vec(0,0))*180/cimg::PI,
                          color,opacity,pattern);
    }

    template<typename tc>
    CImg<T>& _draw_ellipse(const int x0, const int y0, const float radius1, const float radius2, const float angle,
                           const tc *const color, const float opacity,
                           const unsigned int pattern, const bool is_filled) {
      if (is_empty() || (!is_filled && !pattern)) return *this;
      const float radiusM = std::max(radius1,radius2);
      if (radius1<0 || radius2<0 || x0 - radiusM>=width() || y0 + radiusM<0 || y0 - radiusM>=height()) return *this;
      if (!color)
        throw CImgArgumentException(_cimg_instance
                                    "draw_ellipse(): Specified color is (null).",
                                    cimg_instance);
      const int iradius1 = (int)cimg::round(radius1), iradius2 = (int)cimg::round(radius2);
      if (!iradius1 && !iradius2) return draw_point(x0,y0,color,opacity);
      if (iradius1==iradius2) {
        if (is_filled) return draw_circle(x0,y0,iradius1,color,opacity);
        else if (pattern==~0U) return draw_circle(x0,y0,iradius1,color,opacity,pattern);
      }
      const float ang = (float)(angle*cimg::PI/180);

      if (!is_filled) { // Outlined
        const float ca = std::cos(ang), sa = std::sin(ang);
        CImg<int> points((unsigned int)cimg::round(6*radiusM),2);
        cimg_forX(points,k) {
          const float
            _ang = (float)(2*cimg::PI*k/points._width),
            X = (float)(radius1*std::cos(_ang)),
            Y = (float)(radius2*std::sin(_ang));

View on GitHub (pinned to f788b534b4)

Solutions

  1. Pass a valid color buffer, e.g. const unsigned char col[]={255,0,0}; sized to at least img.spectrum() channels.
  2. Check that the variable holding the color is actually assigned before the draw call (guard for null).
  3. If drawing with opacity only and no color makes sense in your code, restructure: color is mandatory, so supply one (e.g. white/black).
  4. When wrapping CImg from JNI/managed code, convert the managed color array to a native pointer and reject null before crossing the boundary.

Example fix

// before
const unsigned char* color = getColor(); // may be null
img.draw_ellipse(x,y,r1,r2,0.0f,color);
// after
const unsigned char* color = getColor();
if (!color) color = defaultWhite; // e.g. static const unsigned char def[3]={255,255,255};
img.draw_ellipse(x,y,r1,r2,0.0f,color);
Defensive patterns

Strategy: validation

Validate before calling

if (!color) color = defaultColor; // before draw_ellipse
// optional: assert channel count
assert(sizeof(color)/sizeof(color[0]) >= img.spectrum());

Type guard

template<typename T> bool hasColor(const T* c){ return c != nullptr; }

Try / catch

try { img.draw_ellipse(x,y,r1,r2,angle,color,opacity); }
catch (CImgArgumentException& e) { log_error("draw_ellipse: %s", e.what()); }

Prevention

When it happens

Trigger: Calling img.draw_ellipse(x0,y0,r1,r2,angle,NULL) or passing an uninitialized/unassigned color pointer, e.g. a pointer to a deleted or never-allocated buffer, or 0 literal in a code path where the color argument was conditionally computed.

Common situations: Color loaded from config or computed at runtime and the load failed silently leaving nullptr; calling a draw overload with the wrong argument order so that what lands in the color slot is null; C# / JNI wrappers passing null byte arrays for the color.

Related errors


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