SixLabors/ImageSharp · error · InvalidImageContentException

The image is not a valid QOI image.

Error message

The image is not a valid QOI image.

What it means

QoiDecoderCore.ThrowInvalidImageContentException throws InvalidImageContentException with 'The image is not a valid QOI image.' as a shared guard for any structural mismatch found while parsing a QOI stream. It is called from ProcessHeader (bad magic marker, channels, or color-space bytes) and from ProcessPixels (unexpected end of stream / missing end marker), so any decode-stage QOI structural failure funnels here.

Solutions

  1. Confirm the file really is QOI: check the first four bytes are 'qoif' and the last 8 bytes are the end marker (0x01 followed by seven zero bytes).
  2. Re-export the image with a spec-compliant QOI encoder; if truncated, re-transfer the file.
  3. Let Image.Identify detect the real format first, then load with the matching decoder.
  4. Catch InvalidImageContentException around the load and surface a 'not a valid QOI file' message.

Example fix

// before
using var image = Image.Load("mystery.qoi");
// after
if (!File.ReadAllBytes("mystery.qoi").AsSpan(0, 4).SequenceEqual("qoif"u8))
{
    throw new InvalidDataException("File lacks the QOI 'qoif' magic.");
}
using var image = Image.Load("mystery.qoi");
Defensive patterns

Strategy: validation

Validate before calling

// Verify QOI structural markers before decode.
byte[] all = File.ReadAllBytes(path);
bool ok = all.Length >= 22
    && all.AsSpan(0, 4).SequenceEqual("qoif"u8)
    && all[^8] == 0x01 && all[^7..].ToArray().All(b => b == 0);
if (!ok) throw new InvalidDataException("File is not a structurally valid QOI image.");

Try / catch

try { using var img = Image.Load(path); }
catch (InvalidImageContentException) { HandleUnknownFormat(path); }

Prevention

When it happens

Trigger: Loading a file that starts with the wrong 4-byte magic ('qoif' expected); a channels byte that is not 3 or 4; pixel data that terminates before the 0x01 8-byte QOI end marker is found during ProcessPixels.

Common situations: Renaming a non-QOI file to .qoi; truncated uploads/failed downloads cutting off the end marker; encoders omitting the footer; probing unknown binaries with the QOI decoder.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs:132

        int channels = stream.ReadByte();
        if (channels is -1 or (not 3 and not 4))
        {
            ThrowInvalidImageContentException();
        }

        int colorSpace = stream.ReadByte();
        if (colorSpace is -1 or (not 0 and not 1))
        {
            ThrowInvalidImageContentException();
        }

        this.header = new QoiHeader(width, height, (QoiChannels)channels, (QoiColorSpace)colorSpace);
    }

    [DoesNotReturn]
    private static void ThrowInvalidImageContentException()
        => throw new InvalidImageContentException("The image is not a valid QOI image.");

    private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
        where TPixel : unmanaged, IPixel<TPixel>
    {
        using IMemoryOwner<Rgba32> previouslySeenPixelsBuffer = this.memoryAllocator.Allocate<Rgba32>(64, AllocationOptions.Clean);
        Span<Rgba32> previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan();
        Rgba32 previousPixel = new(0, 0, 0, 255);

        // We save the pixel to avoid losing the fully opaque black pixel
        // See https://github.com/phoboslab/qoi/issues/258
        int pixelArrayPosition = GetArrayPosition(previousPixel);
        previouslySeenPixels[pixelArrayPosition] = previousPixel;
        byte operationByte;
        Rgba32 readPixel = default;
        Span<byte> pixelBytes = MemoryMarshal.CreateSpan(ref Unsafe.As<Rgba32, byte>(ref readPixel), 4);
        TPixel pixel = default;

        for (int i = 0; i < this.header.Height; i++)

View on GitHub (pinned to 59ce6af6fc)