Intervention/image · warning · InvalidArgumentException

Hex color has an incorrect length

Error message

Hex color has an incorrect length

What it means

After the structural pattern match, HexColorDecoder::decode() splits the hex portion by length: 3 or 4 digits are split per character, 6 or 8 per pair, and any other length throws this InvalidArgumentException. The built-in pattern can only capture 3, 4, 6 or 8 digits, so through public APIs this branch is defensive and effectively unreachable; it fires only when a subclass overrides the protected PATTERN constant with one that admits 5- or 7-digit groups (NamedColorDecoder extends this class and relies on the same code path).

Source

Thrown at src/Colors/Rgb/Decoders/HexColorDecoder.php:60

    }

    /**
     * Decode hexadecimal rgb colors with and without transparency.
     *
     * @throws InvalidArgumentException
     * @throws ColorDecoderException
     */
    public function decode(mixed $input): ColorInterface
    {
        if (preg_match(static::PATTERN, $input, $matches) !== 1) {
            throw new InvalidArgumentException('Hex color has an invalid format');
        }

        // split into hex chunks
        $values = match (strlen($matches['hex'])) {
            3, 4 => str_split($matches['hex']),
            6, 8 => str_split($matches['hex'], 2),
            default => throw new InvalidArgumentException('Hex color has an incorrect length'),
        };

        // convert to decimal
        $values = array_map(function (string $value): int {
            return match (strlen($value)) {
                1 => (int) hexdec($value . $value),
                2 => (int) hexdec($value),
                default => throw new ColorDecoderException('Failed to decode hex color'),
            };
        }, $values);

        // normalize
        $values = count($values) === 3 ? array_pad($values, 4, 255) : $values;
        $values = array_map(fn(int $value): float => $value / 255, $values);

        return Rgb::colorFromNormalized($values);
    }
}

View on GitHub (pinned to 5598b9e397)

Solutions

  1. Keep overridden patterns limited to 3, 4, 6 or 8 captured hex digits
  2. If you must accept other lengths, override decode() as well and normalize to a supported length first
  3. Map exotic notations to a canonical 6- or 8-digit hex string before decoding

Example fix

// before
class MyHexDecoder extends HexColorDecoder {
    protected const string PATTERN = '/^#?(?P<hex>[a-f\d]+)$/i'; // allows any length
}

// after
class MyHexDecoder extends HexColorDecoder {
    protected const string PATTERN = '/^#?(?P<hex>[a-f\d]{6})$/i'; // 6 digits only
}
Defensive patterns

Strategy: validation

Validate before calling

if (!in_array(strlen(ltrim($hex, '#')), [3, 4, 6, 8], true)) {
    throw new \RuntimeException('Hex color length must be 3, 4, 6 or 8 digits');
}
$color = \Intervention\Image\Colors\Rgb\Color::parse($hex);

Type guard

function hasValidHexLength(string $hex): bool
{
    return in_array(strlen(ltrim($hex, '#')), [3, 4, 6, 8], true);
}

Try / catch

try {
    $decoder = new \Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder();
    $color = $decoder->decode($hex);
} catch (\Intervention\Image\Exceptions\InvalidArgumentException $e) {
    $hex = str_pad(ltrim($hex, '#'), 6, '0'); // normalize to 6 digits and retry
    $color = $decoder->decode($hex);
}

Prevention

When it happens

Trigger: A custom subclass of HexColorDecoder overriding PATTERN to capture hex strings whose length is 5 or 7, then calling decode(); direct instantiation is not affected because the stock pattern never yields those lengths.

Common situations: Extending HexColorDecoder to accept additional hex notations (e.g. 5-digit forms) without updating the length handling; forks adjusting the regex during experimentation.

Related errors


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