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.
This commit is contained in:
RoosterDragon
2023-10-20 18:22:24 +01:00
committed by Matthias Mailänder
parent 304fc458eb
commit 678b238c1c

View File

@@ -45,6 +45,7 @@ namespace OpenRA.FileFormats
var data = new List<byte>(); var data = new List<byte>();
Type = SpriteFrameType.Rgba32; Type = SpriteFrameType.Rgba32;
byte bitDepth = 8;
while (true) while (true)
{ {
var length = IPAddress.NetworkToHostOrder(s.ReadInt32()); var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
@@ -66,7 +67,7 @@ namespace OpenRA.FileFormats
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32()); Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32()); Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
var bitDepth = ms.ReadUInt8(); bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadUInt8(); var colorType = (PngColorType)ms.ReadUInt8();
if (IsPaletted(bitDepth, colorType)) if (IsPaletted(bitDepth, colorType))
Type = SpriteFrameType.Indexed8; Type = SpriteFrameType.Indexed8;
@@ -136,14 +137,34 @@ namespace OpenRA.FileFormats
{ {
var pxStride = PixelStride; var pxStride = PixelStride;
var rowStride = Width * pxStride; var rowStride = Width * pxStride;
var pixelsPerByte = 8 / bitDepth;
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
Span<byte> prevLine = new byte[rowStride]; Span<byte> prevLine = new byte[rowStride];
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
var filter = (PngFilter)ds.ReadUInt8(); var filter = (PngFilter)ds.ReadUInt8();
ds.ReadBytes(Data, y * rowStride, rowStride); ds.ReadBytes(Data, y * rowStride, sourceRowStride);
var line = Data.AsSpan(y * rowStride, rowStride); 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) switch (filter)
{ {
case PngFilter.None: case PngFilter.None:
@@ -269,7 +290,7 @@ namespace OpenRA.FileFormats
static bool IsPaletted(byte bitDepth, PngColorType colorType) 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; return true;
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha)) if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))