120 lines
No EOL
4.3 KiB
C#
120 lines
No EOL
4.3 KiB
C#
namespace Uwaa.PNG;
|
|
|
|
/// <summary>
|
|
/// Provides convenience methods for indexing into a raw byte array to extract pixel values.
|
|
/// </summary>
|
|
internal class RawPngData
|
|
{
|
|
readonly byte[] data;
|
|
readonly int bytesPerPixel;
|
|
readonly int width;
|
|
readonly Palette? palette;
|
|
readonly ColorType colorType;
|
|
readonly int rowOffset;
|
|
readonly int bitDepth;
|
|
|
|
/// <summary>
|
|
/// Create a new <see cref="RawPngData"/>.
|
|
/// </summary>
|
|
/// <param name="data">The decoded pixel data as bytes.</param>
|
|
/// <param name="bytesPerPixel">The number of bytes in each pixel.</param>
|
|
/// <param name="palette">The palette for the image.</param>
|
|
/// <param name="imageHeader">The image header.</param>
|
|
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;
|
|
}
|
|
} |