{"record":{"id":"2eea9e7c82836413","repo":"d2phap/ImageGlass","slug":"ige-frame-too-large-to-encode","errorCode":null,"errorMessage":"IGE: frame too large to encode.","messagePattern":"IGE: frame too large to encode\\.","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"source/ImageGlass.Lib/Plugins/NativeCodecProxy.cs","lineNumber":520,"sourceCode":"            cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);\n    }\n\n\n    /// <summary>\n    /// Copies an <see cref=\"SKImage\"/> into a freshly allocated host-owned BGRA8 unpremultiplied\n    /// buffer, reusing <paramref name=\"pixels\"/> when it is already big enough.\n    /// </summary>\n    private static void ReadPixelsInto(SKImage image, ref byte* pixels, ref nuint capacity,\n        out IGPixelBuffer buffer)\n    {\n        // Unpremul because that is what IGPixelFormat.Bgra8Unorm means to a plugin. Do NOT copy\n        // SkiaCodec.ToMagick, which uses Premul: handing premultiplied bytes over is a silent\n        // dark-halo bug on any image with alpha.\n        var info = new SKImageInfo(image.Width, image.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul);\n        var stride = checked(info.Width * 4);\n        var byteCount = (long)stride * info.Height;\n\n        if (byteCount > int.MaxValue) throw new InvalidDataException(\"IGE: frame too large to encode.\");\n\n        if (capacity < (nuint)byteCount)\n        {\n            if (pixels != null) System.Runtime.InteropServices.NativeMemory.Free(pixels);\n            pixels = (byte*)System.Runtime.InteropServices.NativeMemory.Alloc((nuint)byteCount);\n            capacity = (nuint)byteCount;\n        }\n\n        // Always copy: PeekPixels returns null for a GPU-backed image, its layout is whatever the\n        // image happens to be, and a plugin-decoded image is backed by ANOTHER plugin's memory.\n        if (!image.ReadPixels(info, (nint)pixels, stride, 0, 0))\n        {\n            throw new InvalidDataException(\"IGE: could not read the source pixels.\");\n        }\n\n        buffer = new IGPixelBuffer\n        {\n            Data = pixels,","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/d2phap/ImageGlass/blob/4a3c4feceffc5a8bb5e56ba836509634aaae47a9/source/ImageGlass.Lib/Plugins/NativeCodecProxy.cs#L502-L538","documentation":"Thrown by NativeCodecProxy.ReadPixelsInto when the decoded frame's byte count (stride * height, where stride = width * 4 for BGRA8888) exceeds int.MaxValue (~2 GiB). This is the plugin-host analog of the SKImage/SKBitmap pixel ceiling documented in CLAUDE.md: no managed single-allocation pixel buffer can exceed int.MaxValue bytes, so the host refuses rather than allocate a buffer it cannot index.","triggerScenarios":"Produced at NativeCodecProxy.cs:520 when (long)stride * info.Height > int.MaxValue after the checked multiply. Happens for extremely large decoded frames — e.g. a >32767×32767 BGRA image, or any frame whose width*height*4 overflows Int32.","commonSituations":"An oversized raster being handed to a plugin codec as source pixels (e.g. feeding a re-encode of a gigapixel image); a codec that ignores downscale requests and decodes at full native resolution; a malformed file with absurd header dimensions that the codec naively honored.","solutions":["Ensure the source SKImage is downscaled before reaching ReadPixelsInto — apply the same shrink-to-fit logic used in SkiaCodec.GetDecodableImageInfo.","Reject files whose declared dimensions would exceed the int.MaxValue byte ceiling at the codec selection stage, before decode.","If you control the input, feed a smaller image; if not, document that the codec cannot encode frames past ~32767² at 4 bpp.","Cap the decoded image dimensions in the upstream pipeline (ViewerControl / PhotoManager) before invoking the plugin encoder."],"exampleFix":"// before\nvar stride = checked(info.Width * 4);\nvar byteCount = (long)stride * info.Height;\nif (byteCount > int.MaxValue) throw new InvalidDataException(\"IGE: frame too large to encode.\");\n\n// after — refuse earlier and tell the caller the safe ceiling\nvar stride = checked(info.Width * 4);\nvar byteCount = (long)stride * info.Height;\nif (byteCount > int.MaxValue)\n    throw new InvalidDataException($\"IGE: frame too large to encode: {info.Width}x{info.Height} = {byteCount} bytes \" +\n        $\"(max {int.MaxValue}). Downscale the source image before encoding.\");","handlingStrategy":"validation","validationCode":"// Reject frames whose BGRA byte count would overflow Int32 before encoding.\nconst long MaxBytes = int.MaxValue;\nlong byteCount = (long)image.Width * image.Height * 4L;\nif (byteCount > MaxBytes)\n    throw new InvalidDataException($\"Frame {image.Width}x{image.Height} ({byteCount} bytes) exceeds the {MaxBytes}-byte ceiling; downscale first.\");","typeGuard":"static bool FitsInt32PixelCeiling(SKImage img, int bytesPerPixel) =>\n    (long)img.Width * img.Height * bytesPerPixel <= int.MaxValue;","tryCatchPattern":"try { NativeCodecProxy.ReadPixelsInto(image, ref pixels, ref capacity, out var buf); }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"frame too large\"))\n{ image = DownscaleToFit(image, int.MaxValue, 4); /* retry */ }","preventionTips":["Apply the same shrink-to-fit logic used in SkiaCodec.GetDecodableImageInfo before invoking the plugin encoder path.","Always compute the pixel byte count as long (width*height*bytesPerPixel) and compare against int.MaxValue.","Reject absurd header dimensions at codec-selection time before any decode runs.","Document the per-codec pixel ceiling so callers pre-downscale."],"tags":["codec","native","size-limit","memory","encode"],"backgroundTag":null,"analyzedSha":"4a3c4feceffc5a8bb5e56ba836509634aaae47a9","analyzedAt":"2026-08-13T16:58:15.523Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}