{"record":{"id":"cc10e503adfe3f97","repo":"d2phap/ImageGlass","slug":"ige-native-codec-codecid-returned-an-unsuppor-cc10e5","errorCode":null,"errorMessage":"IGE: Native codec '{CodecId}' returned an unsupported pixel buffer layout.","messagePattern":"IGE: Native codec '(.+?)' returned an unsupported pixel buffer layout\\.","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"source/ImageGlass.Lib/Plugins/NativeCodecProxy.cs","lineNumber":839,"sourceCode":"            CodecApiPtr = (nint)_codecApi,\n            Buffer = buffer,\n            PluginId = _plugin.PluginId,\n            LiveToken = _plugin.LiveToken,\n        };\n\n        // Wrap the plugin pointer in SKData with a release callback, then build the SKImage.\n        var byteCount = checked(buffer.Stride * buffer.Height);\n        var data = SKData.Create((nint)buffer.Data, byteCount,\n            PluginPixelBufferRelease.ReleaseData, carrier);\n\n        var image = SKImage.FromPixels(info, data, buffer.Stride);\n\n        if (image is null)\n        {\n            // Skia rejected the layout; release the plugin buffer ourselves.\n            data?.Dispose();\n            carrier.ReleaseFromHost();\n            throw new InvalidDataException(\n                $\"IGE: Native codec '{CodecId}' returned an unsupported pixel buffer layout.\");\n        }\n\n        return image;\n    }\n\n\n    /// <summary>\n    /// Maps an <see cref=\"IGPixelFormat\"/> to the corresponding Skia color/alpha types.\n    /// Returns (<see cref=\"SKColorType.Unknown\"/>, <see cref=\"SKAlphaType.Unknown\"/>) when the\n    /// host has no compatible Skia format for the buffer.\n    /// </summary>\n    internal static (SKColorType ColorType, SKAlphaType AlphaType) MapPixelFormat(IGPixelFormat format)\n    {\n        return format switch\n        {\n            IGPixelFormat.Bgra8Unorm => (SKColorType.Bgra8888, SKAlphaType.Unpremul),\n            IGPixelFormat.Rgba8Unorm => (SKColorType.Rgba8888, SKAlphaType.Unpremul),","sourceCodeStart":821,"sourceCodeEnd":857,"githubUrl":"https://github.com/d2phap/ImageGlass/blob/4a3c4feceffc5a8bb5e56ba836509634aaae47a9/source/ImageGlass.Lib/Plugins/NativeCodecProxy.cs#L821-L857","documentation":"Thrown by NativeCodecProxy.WrapPluginBufferAsImage when SKImage.FromPixels returns null even though the pixel buffer, format, and dimensions individually passed validation. Skia rejected the combined layout — usually because the stride is too small for the declared width/color type, or the total byte count (stride * height) does not match the SKData length passed to FromPixels.","triggerScenarios":"Produced at NativeCodecProxy.cs:839 when SKImage.FromPixels(info, data, buffer.Stride) returns null. The plugin's IGPixelBuffer passed field-level checks but the stride/width/height combination is inconsistent for Skia.","commonSituations":"Plugin sets Stride smaller than width*bytesPerPixel (Skia requires stride >= info.RowBytes); plugin computes Stride without alignment padding the plugin actually wrote; plugin declares a width in pixels but lays out bytes with a different bpp than MapPixelFormat assumed; ABI field-order mismatch swapping Stride with another field.","solutions":["Have the plugin compute Stride as at least width * bytesPerPixel for the chosen IGPixelFormat, with any padding it actually writes.","Verify the byte count the plugin allocated matches stride * height exactly (the host wraps stride*height in SKData at NativeCodecProxy.cs:837).","Check the IGPixelBuffer struct field order in the plugin matches the SDK header exactly — a swapped Stride is the most subtle cause.","Disable the plugin and decode with a built-in codec until the layout bug is fixed upstream."],"exampleFix":"// before\nvar image = SKImage.FromPixels(info, data, buffer.Stride);\nif (image is null) { data?.Dispose(); carrier.ReleaseFromHost(); throw new InvalidDataException(...); }\n\n// after — validate stride before Skia sees it so the error names the real problem\nvar minStride = info.RowBytes;\nif (buffer.Stride < minStride)\n    throw new InvalidDataException(\n        $\"IGE: Native codec '{CodecId}' returned stride {buffer.Stride} < required {minStride} \" +\n        $\"for {buffer.Width}x{buffer.Height} {info.ColorType}.\");\nvar image = SKImage.FromPixels(info, data, buffer.Stride);\nif (image is null) { data?.Dispose(); carrier.ReleaseFromHost(); throw new InvalidDataException(...); }","handlingStrategy":"validation","validationCode":"// Validate stride vs width/bytesPerPixel before handing to Skia.\nint bpp = info.BytesPerPixel;\nif (buffer.Stride < info.RowBytes)\n    throw new InvalidDataException($\"Plugin stride {buffer.Stride} < required {info.RowBytes}\");\nif ((long)buffer.Stride * buffer.Height > int.MaxValue)\n    throw new InvalidDataException(\"Plugin buffer byte count overflows Int32\");","typeGuard":"static bool IsConsistentLayout(in IGPixelBuffer b, SKImageInfo info) =>\n    b.Stride >= info.RowBytes && (long)b.Stride * b.Height <= int.MaxValue;","tryCatchPattern":"try { image = proxy.WrapPluginBufferAsImage(in buffer, colorSpace); }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"unsupported pixel buffer layout\"))\n{ _log.Warn($\"Plugin {proxy.CodecId} stride/layout rejected by Skia (stride={buffer.Stride}, w={buffer.Width}, h={buffer.Height})\"); return BuiltInFallback(metadata); }","preventionTips":["Compute Stride in the plugin as at least width * bytesPerPixel, including any padding actually written.","Match the IGPixelBuffer field order between plugin and host SDK exactly.","Check Stride >= info.RowBytes before calling SKImage.FromPixels so the error names the real problem.","Verify the SKData byte count equals stride * height when wrapping the plugin pointer."],"tags":["plugin","codec","native","skiasharp","pixel-buffer","stride"],"backgroundTag":null,"analyzedSha":"4a3c4feceffc5a8bb5e56ba836509634aaae47a9","analyzedAt":"2026-08-13T16:58:15.523Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}