Intervention/image · error · ModifierException

Failed to apply Intervention\Image\Drivers\Gd\Modifiers\Draw

Error message

Failed to apply Intervention\Image\Drivers\Gd\Modifiers\DrawBezierModifier, division by zero

What it means

For borders wider than 1px the GD driver offsets each interpolated curve segment along its normal, dividing the half border width by sqrt(dx^2 + dy^2) for every adjacent point pair. When two adjacent points of the interpolated polygon are identical the segment length is 0 and the normal is undefined, so the modifier aborts with ModifierException instead of dividing by zero. Interpolation points are cast to int, so curves whose samples collapse onto the same pixel (identical or sub-pixel spaced control points) hit this guard. It requires borderSize > 1, because 1px borders use a line-based path that tolerates duplicates.

Source

Thrown at src/Drivers/Gd/Modifiers/DrawBezierModifier.php:259

     */
    private function calculateBorderSegments(array $polygon): array
    {
        $innerPolygon = [];
        $outerPolygon = [];
        $offset = $this->drawable->borderSize() / 2;
        $total = count($polygon);

        for ($i = 0; $i < $total; $i += 2) {
            if (!array_key_exists($i + 2, $polygon) || !array_key_exists($i + 3, $polygon)) {
                continue;
            }

            $dx = $polygon[$i + 2] - $polygon[$i];
            $dy = $polygon[$i + 3] - $polygon[$i + 1];
            $dxySqrt = sqrt($dx * $dx + $dy * $dy);

            if ($dxySqrt === 0.0) {
                throw new ModifierException('Failed to apply ' . self::class . ', division by zero');
            }

            $scale = $offset / $dxySqrt;
            $ox = -$dy * $scale;
            $oy = $dx * $scale;

            $innerPolygon[] = $ox + $polygon[$i];
            $innerPolygon[] = $oy + $polygon[$i + 1];
            $innerPolygon[] = $ox + $polygon[$i + 2];
            $innerPolygon[] = $oy + $polygon[$i + 3];

            $scale = -$offset / $dxySqrt;
            $ox = -$dy * $scale;
            $oy = $dx * $scale;

            $outerPolygon[] = $ox + $polygon[$i];
            $outerPolygon[] = $oy + $polygon[$i + 1];
            $outerPolygon[] = $ox + $polygon[$i + 2];

View on GitHub (pinned to 5598b9e397)

Solutions

  1. Ensure consecutive control points are distinct - offset them by at least 1-2 pixels
  2. Use a 1px border (->border($color, 1)), which connects points with lines and tolerates duplicates
  3. Skip the border entirely for degenerate or tiny curves
  4. Catch ModifierException and drop/reduce the border as a fallback

Example fix

// before: consecutive control points identical + thick border
$image->drawBezier(fn($b) => $b
    ->point(20, 20)->point(20, 20)->point(120, 100)
    ->border('ff0000', 4));

// after: distinct control points
$image->drawBezier(fn($b) => $b
    ->point(20, 20)->point(60, 20)->point(120, 100)
    ->border('ff0000', 4));
Defensive patterns

Strategy: validation

Validate before calling

// reject bezier drawables with duplicate consecutive points before drawing
$points = iterator_to_array($bezier);
for ($i = 1, $n = count($points); $i < $n; $i++) {
    $same = $points[$i]->x() === $points[$i - 1]->x()
        && $points[$i]->y() === $points[$i - 1]->y();

    if ($same) {
        throw new InvalidArgumentException('Duplicate consecutive bezier points');
    }
}

$image->drawBezier($bezier);

Type guard

use Intervention\Image\Geometry\Bezier;

function hasDistinctAdjacentPoints(Bezier $bezier): bool
{
    $points = iterator_to_array($bezier);
    for ($i = 1, $n = count($points); $i < $n; $i++) {
        if ($points[$i]->x() === $points[$i - 1]->x()
            && $points[$i]->y() === $points[$i - 1]->y()) {
            return false;
        }
    }

    return true;
}

Try / catch

use Intervention\Image\Exceptions\ModifierException;

try {
    $image->drawBezier($bezier);
} catch (ModifierException $e) {
    // degenerate curve: fall back to a thin border, which tolerates duplicates
    $bezier->setBorder($bezier->borderColor(), 1);
    $image->drawBezier($bezier);
}

Prevention

When it happens

Trigger: $image->drawBezier() with ->border($color, 2) or thicker where two consecutive control points are identical (e.g. first() equals second()); a cubic whose first three points coincide; a curve so small that consecutive 0.05-step interpolation samples round to the same integer pixel; all coordinates defaulting to (0, 0).

Common situations: Data-driven drawing where points come from variables that were never offset; reusing one Point instance for multiple slots; scaling vector overlays below 1px so all samples collapse.

Related errors


AI-assisted analysis of Intervention/image@5598b9e397 (2026-08-23). Data as JSON: /api/errors/4c6b2a3c3a25d12d. Report an issue: GitHub.