From 678b238c1c2616fdb622db03e393281f72cda111 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 20 Oct 2023 18:22:24 +0100 Subject: [PATCH] Teach PNG decoder to handle indexed bit depths of 1, 2 or 4. The PNG decoder, when dealing when indexed images with a palette, could only decode a bit depth of 8. Teach it to decode depths of 1, 2 and 4 as well. As the palette data is exposed to consumers of the PNG class, unpack the data into a 8 bit depth so consumers don't need to also handle the new bit depths. --- OpenRA.Game/FileFormats/Png.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/OpenRA.Game/FileFormats/Png.cs b/OpenRA.Game/FileFormats/Png.cs index 6a229d9bb7..347326c283 100644 --- a/OpenRA.Game/FileFormats/Png.cs +++ b/OpenRA.Game/FileFormats/Png.cs @@ -45,6 +45,7 @@ namespace OpenRA.FileFormats var data = new List(); Type = SpriteFrameType.Rgba32; + byte bitDepth = 8; while (true) { var length = IPAddress.NetworkToHostOrder(s.ReadInt32()); @@ -66,7 +67,7 @@ namespace OpenRA.FileFormats Width = IPAddress.NetworkToHostOrder(ms.ReadInt32()); Height = IPAddress.NetworkToHostOrder(ms.ReadInt32()); - var bitDepth = ms.ReadUInt8(); + bitDepth = ms.ReadUInt8(); var colorType = (PngColorType)ms.ReadUInt8(); if (IsPaletted(bitDepth, colorType)) Type = SpriteFrameType.Indexed8; @@ -136,14 +137,34 @@ namespace OpenRA.FileFormats { var pxStride = PixelStride; var rowStride = Width * pxStride; + var pixelsPerByte = 8 / bitDepth; + var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte); Span prevLine = new byte[rowStride]; for (var y = 0; y < Height; y++) { var filter = (PngFilter)ds.ReadUInt8(); - ds.ReadBytes(Data, y * rowStride, rowStride); + ds.ReadBytes(Data, y * rowStride, sourceRowStride); var line = Data.AsSpan(y * rowStride, rowStride); + // If the source has a bit depth of 1, 2 or 4 it packs multiple pixels per byte. + // Unpack to bit depth of 8, yielding 1 pixel per byte. + // This makes life easier for consumers of palleted data. + if (bitDepth < 8) + { + var mask = 0xFF >> (8 - bitDepth); + for (var i = sourceRowStride - 1; i >= 0; i--) + { + var packed = line[i]; + for (var j = 0; j < pixelsPerByte; j++) + { + var dest = i * pixelsPerByte + j; + if (dest < line.Length) // Guard against last byte being only partially packed + line[dest] = (byte)(packed >> (8 - (j + 1) * bitDepth) & mask); + } + } + } + switch (filter) { case PngFilter.None: @@ -269,7 +290,7 @@ namespace OpenRA.FileFormats static bool IsPaletted(byte bitDepth, PngColorType colorType) { - if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color)) + if (bitDepth <= 8 && colorType == (PngColorType.Indexed | PngColorType.Color)) return true; if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))