SixLabors/ImageSharp · error · ImageFormatException

ANI bitmap resources require exactly one color plane.

Error message

ANI bitmap resources require exactly one color plane.

What it means

Thrown by AniEncoderCore.Encode when encoding with bitmapResources enabled and the ANI metadata's Planes value is not 0 (unset/default) or 1. ICO/BMP resources embedded in an ANI must declare exactly one color plane, so any other plane count is rejected up front.

Solutions

  1. Set AniMetadata.Planes to 1 (or 0 to let the encoder default it) before saving as ANI.
  2. Remove the explicit Planes override so the default is used.
  3. Encode frames as ICO/CUR resources instead of bitmapResources if multi-plane data is genuinely required.

Example fix

// before
metadata.Planes = 2;
await image.SaveAsAniAsync(stream, aniOptions);
// after
metadata.Planes = 1;
await image.SaveAsAniAsync(stream, aniOptions);
Defensive patterns

Strategy: validation

Validate before calling

bool planesOk = aniMetadata.Planes is 0 or 1;
if (!planesOk) throw new InvalidOperationException("ANI bitmap resources need Planes = 1.");

Try / catch

try { await image.SaveAsAniAsync(stream, options); } catch (ImageFormatException ex) when (ex.Message.Contains("color plane")) { /* fix metadata */ }

Prevention

When it happens

Trigger: Calling image.SaveAsAni (or Image.Save with AniEncoder) with bitmapResources configured while AniMetadata.Planes holds a value other than 0 or 1, e.g. 2 or 4.

Common situations: Copying Planes metadata from multi-plane sources, manually setting incorrect plane counts, misunderstanding the ICO one-plane requirement.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of SixLabors/ImageSharp@59ce6af6fc (2026-09-13). Data as JSON: /api/errors/888ba7971b00a23f. Report an issue: GitHub.

Appendix: source

Thrown at src/ImageSharp/Formats/Ani/AniEncoderCore.cs:70

        AniMetadata imageMetadata = image.Metadata.GetAniMetadata();
        AniFrameMetadata firstMetadata = image.Frames.RootFrame.Metadata.GetAniMetadata();
        AniFrameFormat firstFormat = firstMetadata.FrameFormat;
        bool bitmapResources = firstFormat is AniFrameFormat.Bmp;
        bool writeSequence = imageMetadata.Flags.HasFlag(AniHeaderFlags.ContainsSequence);
        uint displayRate = firstMetadata.FrameDelay is 0 ? imageMetadata.DisplayRate : firstMetadata.FrameDelay;
        bool hasVariableRates = false;
        int groupCount = 0;
        int maxGroupSize = 1;

        if (bitmapResources && imageMetadata.BitCount is not (0 or 1 or 2 or 4 or 8 or 16 or 24 or 32))
        {
            throw new ImageFormatException("ANI bitmap resources require a supported bit depth.");
        }

        if (bitmapResources && imageMetadata.Planes is not (0 or 1))
        {
            throw new ImageFormatException("ANI bitmap resources require exactly one color plane.");
        }

        // This validation pass derives the fixed ANI header and largest icon directory without allocating a grouping graph.
        // Encoding repeats the linear grouping scan below, trading a cheap pass for zero per-group collections.
        for (int frameIndex = 0; frameIndex < image.Frames.Count;)
        {
            AniFrameMetadata metadata = image.Frames[frameIndex].Metadata.GetAniMetadata();
            int groupSize = 1;

            if (metadata.FrameFormat is not (AniFrameFormat.Ico or AniFrameFormat.Cur or AniFrameFormat.Bmp))
            {
                // FrameFormat is public metadata and therefore must be validated before any container bytes are written.
                throw new ImageFormatException("ANI contains an unsupported embedded frame format.");
            }

            // Positive sequence numbers group adjacent resolution variants; non-positive values form independent steps.
            if (metadata.SequenceNumber > 0)
            {

View on GitHub (pinned to 59ce6af6fc)