Yalantis/uCrop · error · CImgArgumentException

draw_circle(): Specified color is (null).

Error message

draw_circle(): Specified color is (null).

What it means

The filled-circle overload of draw_circle() throws CImgArgumentException when the color pointer is null. Like the other CImg drawing primitives it validates color after cheap early returns (empty image, radius<0 or fully off-canvas), so the throw fires only for calls that would otherwise draw. The color must point to at least spectrum() channel values.

Source

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

    }

    //! Draw a filled 2D circle.
    /**
       \param x0 X-coordinate of the circle center.
       \param y0 Y-coordinate of the circle center.
       \param radius  Circle radius.
       \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
       \param opacity Drawing opacity.
       \note
       - Circle version of the Bresenham's algorithm is used.
    **/
    template<typename tc>
    CImg<T>& draw_circle(const int x0, const int y0, int radius,
                         const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this;
      if (!color)
        throw CImgArgumentException(_cimg_instance
                                    "draw_circle(): Specified color is (null).",
                                    cimg_instance);
      if (!radius) return draw_point(x0,y0,color,opacity);
      cimg_init_scanline(opacity);
      if (y0>=0 && y0<height()) cimg_draw_scanline(x0 - radius,x0 + radius,y0,color,opacity,1);
      for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x<y; ) {
        if (f>=0) {
          const int x1 = x0 - x, x2 = x0 + x, y1 = y0 - y, y2 = y0 + y;
          if (y1>=0 && y1<height()) cimg_draw_scanline(x1,x2,y1,color,opacity,1);
          if (y2>=0 && y2<height()) cimg_draw_scanline(x1,x2,y2,color,opacity,1);
          f+=(ddFy+=2); --y;
        }
        const bool no_diag = y!=(x++);
        ++(f+=(ddFx+=2));
        const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x;
        if (no_diag) {
          if (y1>=0 && y1<height()) cimg_draw_scanline(x1,x2,y1,color,opacity,1);
          if (y2>=0 && y2<height()) cimg_draw_scanline(x1,x2,y2,color,opacity,1);

View on GitHub (pinned to f788b534b4)

Solutions

  1. Supply a valid color array matching the image's spectrum, e.g. unsigned char col[3]={0,255,0}.
  2. Null-check the color before calling and substitute a default or return early.
  3. Verify the overload you call: the pattern-based overload forwards to draw_ellipse, but both require non-null color.
  4. In binding layers, reject null color arrays with a clear managed-language exception before invoking native code.

Example fix

// before
img.draw_circle(cx,cy,r,palette.get(name)); // palette.get() can return NULL
// after
const unsigned char* c = palette.get(name);
if (!c) c = fallbackColor;
img.draw_circle(cx,cy,r,c);
Defensive patterns

Strategy: validation

Validate before calling

if (!color) color = fallbackColor;
img.draw_circle(cx,cy,radius,color);

Type guard

template<typename T> bool isValidColor(const T* c, int channels){ return c != nullptr; }

Try / catch

try { img.draw_circle(x0,y0,r,color,opacity); }
catch (CImgArgumentException& e) { log_error("draw_circle: %s", e.what()); }

Prevention

When it happens

Trigger: img.draw_circle(x0,y0,radius,NULL) with a literal null; a color buffer pointer that was never initialized or already freed; wrapper code (JNI/other language binding) passing a null array through as the color parameter.

Common situations: Theme/palette lookup returning null for an unknown color name; refactoring that changed argument order so the opacity or radius slot ends up as the color parameter; deserialized color struct with missing data.

Related errors


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