Improve PNG parsing performance.

Switch on the filter once per row rather than once per byte. This allows each row to be processed with a much tighter loop.
This commit is contained in:
RoosterDragon
2023-06-10 10:19:07 +01:00
committed by Matthias Mailänder
parent 855e839b77
commit 06df75ffee

View File

@@ -67,7 +67,7 @@ namespace OpenRA.FileFormats
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32()); Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
var bitDepth = ms.ReadUInt8(); var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadByte(); var colorType = (PngColorType)ms.ReadUInt8();
if (IsPaletted(bitDepth, colorType)) if (IsPaletted(bitDepth, colorType))
Type = SpriteFrameType.Indexed8; Type = SpriteFrameType.Indexed8;
else if (colorType == PngColorType.Color) else if (colorType == PngColorType.Color)
@@ -75,9 +75,9 @@ namespace OpenRA.FileFormats
Data = new byte[Width * Height * PixelStride]; Data = new byte[Width * Height * PixelStride];
var compression = ms.ReadByte(); var compression = ms.ReadUInt8();
/*var filter = */ms.ReadByte(); /*var filter = */ms.ReadUInt8();
var interlace = ms.ReadByte(); var interlace = ms.ReadUInt8();
if (compression != 0) if (compression != 0)
throw new InvalidDataException("Compression method not supported"); throw new InvalidDataException("Compression method not supported");
@@ -95,7 +95,7 @@ namespace OpenRA.FileFormats
Palette = new Color[256]; Palette = new Color[256];
for (var i = 0; i < length / 3; i++) for (var i = 0; i < length / 3; i++)
{ {
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte(); var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
Palette[i] = Color.FromArgb(r, g, b); Palette[i] = Color.FromArgb(r, g, b);
} }
@@ -108,7 +108,7 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Non-Palette indexed PNG are not supported."); throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
for (var i = 0; i < length; i++) for (var i = 0; i < length; i++)
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]); Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
break; break;
} }
@@ -137,21 +137,56 @@ namespace OpenRA.FileFormats
var pxStride = PixelStride; var pxStride = PixelStride;
var rowStride = Width * pxStride; var rowStride = Width * pxStride;
var prevLine = new byte[rowStride]; var prevLine = Span<byte>.Empty;
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
var filter = (PngFilter)ds.ReadByte(); var filter = (PngFilter)ds.ReadUInt8();
var line = ds.ReadBytes(rowStride); ds.ReadBytes(Data, y * rowStride, rowStride);
var line = Data.AsSpan(y * rowStride, rowStride);
for (var i = 0; i < rowStride; i++) switch (filter)
line[i] = i < pxStride {
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0) case PngFilter.None:
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]); break;
case PngFilter.Sub:
Array.Copy(line, 0, Data, y * rowStride, rowStride); for (var i = pxStride; i < rowStride; i++)
line[i] += line[i - pxStride];
break;
case PngFilter.Up:
for (var i = 0; i < rowStride; i++)
line[i] += prevLine[i];
break;
case PngFilter.Average:
for (var i = 0; i < pxStride; i++)
line[i] += Average(0, prevLine[i]);
for (var i = pxStride; i < rowStride; i++)
line[i] += Average(line[i - pxStride], prevLine[i]);
break;
case PngFilter.Paeth:
for (var i = 0; i < pxStride; i++)
line[i] += Paeth(0, prevLine[i], 0);
for (var i = pxStride; i < rowStride; i++)
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
break;
default:
throw new InvalidOperationException("Unsupported Filter");
}
prevLine = line; prevLine = line;
} }
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
} }
} }
@@ -228,34 +263,9 @@ namespace OpenRA.FileFormats
return isPng; return isPng;
} }
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
{
switch (f)
{
case PngFilter.None: return x;
case PngFilter.Sub: return (byte)(x + a);
case PngFilter.Up: return (byte)(x + b);
case PngFilter.Average: return (byte)(x + (a + b) / 2);
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
default:
throw new InvalidOperationException("Unsupported Filter");
}
}
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
[Flags] [Flags]
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 } enum PngColorType : byte { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter { None, Sub, Up, Average, Paeth } enum PngFilter : byte { None, Sub, Up, Average, Paeth }
static bool IsPaletted(byte bitDepth, PngColorType colorType) static bool IsPaletted(byte bitDepth, PngColorType colorType)
{ {