namespace Uwaa.PNG; /// /// Provides convenience methods for indexing into a raw byte array to extract pixel values. /// internal class RawPngData { readonly byte[] data; readonly int bytesPerPixel; readonly int width; readonly Palette? palette; readonly ColorType colorType; readonly int rowOffset; readonly int bitDepth; /// /// Create a new . /// /// The decoded pixel data as bytes. /// The number of bytes in each pixel. /// The palette for the image. /// The image header. public RawPngData(byte[] data, int bytesPerPixel, Palette? palette, ImageHeader imageHeader) { if (width < 0) { throw new ArgumentOutOfRangeException($"Width must be greater than or equal to 0, got {width}."); } this.data = data ?? throw new ArgumentNullException(nameof(data)); this.bytesPerPixel = bytesPerPixel; this.palette = palette; width = imageHeader.Width; colorType = imageHeader.ColorType; rowOffset = imageHeader.InterlaceMethod == InterlaceMethod.Adam7 ? 0 : 1; bitDepth = imageHeader.BitDepth; } public Pixel GetPixel(int x, int y) { if (palette != null) { int pixelsPerByte = 8 / bitDepth; int bytesInRow = 1 + (width / pixelsPerByte); int byteIndexInRow = x / pixelsPerByte; int paletteIndex = 1 + (y * bytesInRow) + byteIndexInRow; byte b = data[paletteIndex]; if (bitDepth == 8) return palette.GetPixel(b); int withinByteIndex = x % pixelsPerByte; int rightShift = 8 - ((withinByteIndex + 1) * bitDepth); int indexActual = (b >> rightShift) & ((1 << bitDepth) - 1); return palette.GetPixel(indexActual); } int rowStartPixel = rowOffset + (rowOffset * y) + (bytesPerPixel * width * y); int pixelStartIndex = rowStartPixel + (bytesPerPixel * x); byte first = data[pixelStartIndex]; switch (bytesPerPixel) { case 1: return new Pixel(first); case 2: switch (colorType) { case ColorType.None: { byte second = data[pixelStartIndex + 1]; byte value = ToSingleByte(first, second); return new Pixel(value, value, value, 255); } default: return new Pixel(first, first, first, data[pixelStartIndex + 1]); } case 3: return new Pixel(first, data[pixelStartIndex + 1], data[pixelStartIndex + 2], 255); case 4: switch (colorType) { case ColorType.None | ColorType.AlphaChannelUsed: { byte second = data[pixelStartIndex + 1]; byte firstAlpha = data[pixelStartIndex + 2]; byte secondAlpha = data[pixelStartIndex + 3]; byte gray = ToSingleByte(first, second); byte alpha = ToSingleByte(firstAlpha, secondAlpha); return new Pixel(gray, gray, gray, alpha); } default: return new Pixel(first, data[pixelStartIndex + 1], data[pixelStartIndex + 2], data[pixelStartIndex + 3]); } case 6: return new Pixel(first, data[pixelStartIndex + 2], data[pixelStartIndex + 4], 255); case 8: return new Pixel(first, data[pixelStartIndex + 2], data[pixelStartIndex + 4], data[pixelStartIndex + 6]); default: throw new InvalidOperationException($"Unreconized number of bytes per pixel: {bytesPerPixel}."); } } static byte ToSingleByte(byte first, byte second) { int us = (first << 8) + second; byte result = (byte)Math.Round(255 * us / (double)ushort.MaxValue); return result; } }