mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-11-24 08:47:01 +08:00
port png transparency fixes from biggustave changes #346
This commit is contained in:
@@ -2,18 +2,43 @@
|
||||
{
|
||||
internal class Palette
|
||||
{
|
||||
public bool HasAlphaValues { get; private set; }
|
||||
|
||||
public byte[] Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a palette object. Input palette data length from PLTE chunk must be a multiple of 3.
|
||||
/// </summary>
|
||||
public Palette(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
Data = new byte[data.Length * 4 / 3];
|
||||
var dataIndex = 0;
|
||||
for (var i = 0; i < data.Length; i += 3)
|
||||
{
|
||||
Data[dataIndex++] = data[i];
|
||||
Data[dataIndex++] = data[i + 1];
|
||||
Data[dataIndex++] = data[i + 2];
|
||||
Data[dataIndex++] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds transparency values from tRNS chunk.
|
||||
/// </summary>
|
||||
public void SetAlphaValues(byte[] bytes)
|
||||
{
|
||||
HasAlphaValues = true;
|
||||
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
Data[i * 4 + 3] = bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
public Pixel GetPixel(int index)
|
||||
{
|
||||
var start = index * 3;
|
||||
|
||||
return new Pixel(Data[start], Data[start + 1], Data[start + 2], 255, false);
|
||||
var start = index * 4;
|
||||
return new Pixel(Data[start], Data[start + 1], Data[start + 2], Data[start + 3], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
internal class Png
|
||||
{
|
||||
private readonly RawPngData data;
|
||||
private readonly bool hasTransparencyChunk;
|
||||
|
||||
/// <summary>
|
||||
/// The header data from the PNG image.
|
||||
@@ -28,12 +29,13 @@
|
||||
/// <summary>
|
||||
/// Whether the image has an alpha (transparency) layer.
|
||||
/// </summary>
|
||||
public bool HasAlphaChannel => (Header.ColorType & ColorType.AlphaChannelUsed) != 0;
|
||||
public bool HasAlphaChannel => (Header.ColorType & ColorType.AlphaChannelUsed) != 0 || hasTransparencyChunk;
|
||||
|
||||
internal Png(ImageHeader header, RawPngData data)
|
||||
internal Png(ImageHeader header, RawPngData data, bool hasTransparencyChunk)
|
||||
{
|
||||
Header = header;
|
||||
this.data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
this.hasTransparencyChunk = hasTransparencyChunk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
|
||||
internal static class PngOpener
|
||||
{
|
||||
public static Png Open(Stream stream, IChunkVisitor chunkVisitor = null)
|
||||
public static Png Open(Stream stream, IChunkVisitor chunkVisitor = null) => Open(stream, new PngOpenerSettings
|
||||
{
|
||||
ChunkVisitor = chunkVisitor
|
||||
});
|
||||
|
||||
public static Png Open(Stream stream, PngOpenerSettings settings)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
@@ -41,7 +46,12 @@
|
||||
{
|
||||
if (hasEncounteredImageEnd)
|
||||
{
|
||||
throw new InvalidOperationException($"Found another chunk {header} after already reading the IEND chunk.");
|
||||
if (settings?.DisallowTrailingData == true)
|
||||
{
|
||||
throw new InvalidOperationException($"Found another chunk {header} after already reading the IEND chunk.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var bytes = new byte[header.Length];
|
||||
@@ -61,7 +71,11 @@
|
||||
throw new InvalidOperationException($"Palette data must be multiple of 3, got {header.Length}.");
|
||||
}
|
||||
|
||||
palette = new Palette(bytes);
|
||||
// Ignore palette data unless the header.ColorType indicates that the image is paletted.
|
||||
if (imageHeader.ColorType.HasFlag(ColorType.PaletteUsed))
|
||||
{
|
||||
palette = new Palette(bytes);
|
||||
}
|
||||
|
||||
break;
|
||||
case "IDAT":
|
||||
@@ -74,6 +88,19 @@
|
||||
throw new NotSupportedException($"Encountered critical header {header} which was not recognised.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (header.Name)
|
||||
{
|
||||
case "tRNS":
|
||||
// Add transparency to palette, if the PLTE chunk has been read.
|
||||
if (palette != null)
|
||||
{
|
||||
palette.SetAlphaValues(bytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
read = stream.Read(crc, 0, crc.Length);
|
||||
if (read != 4)
|
||||
@@ -89,7 +116,7 @@
|
||||
throw new InvalidOperationException($"CRC calculated {result} did not match file {crcActual} for chunk: {header.Name}.");
|
||||
}
|
||||
|
||||
chunkVisitor?.Visit(stream, imageHeader, header, bytes, crc);
|
||||
settings?.ChunkVisitor?.Visit(stream, imageHeader, header, bytes, crc);
|
||||
}
|
||||
|
||||
memoryStream.Flush();
|
||||
@@ -108,7 +135,7 @@
|
||||
|
||||
bytesOut = Decoder.Decode(bytesOut, imageHeader, bytesPerPixel, samplesPerPixel);
|
||||
|
||||
return new Png(imageHeader, new RawPngData(bytesOut, bytesPerPixel, imageHeader.Width, imageHeader.InterlaceMethod, palette, imageHeader.ColorType));
|
||||
return new Png(imageHeader, new RawPngData(bytesOut, bytesPerPixel, palette, imageHeader), palette?.HasAlphaValues ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/UglyToad.PdfPig/Images/Png/PngOpenerSettings.cs
Normal file
16
src/UglyToad.PdfPig/Images/Png/PngOpenerSettings.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace UglyToad.PdfPig.Images.Png
|
||||
{
|
||||
internal class PngOpenerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The code to execute whenever a chunk is read. Can be <see langword="null"/>.
|
||||
/// </summary>
|
||||
public IChunkVisitor ChunkVisitor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to throw if the image contains data after the image end marker.
|
||||
/// <see langword="false"/> by default.
|
||||
/// </summary>
|
||||
public bool DisallowTrailingData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Provides convenience methods for indexing into a raw byte array to extract pixel values.
|
||||
/// </summary>
|
||||
internal class RawPngData
|
||||
@@ -13,17 +13,16 @@
|
||||
private readonly Palette palette;
|
||||
private readonly ColorType colorType;
|
||||
private readonly int rowOffset;
|
||||
private 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="width">The width of the image in pixels.</param>
|
||||
/// <param name="interlaceMethod">The interlace method used.</param>
|
||||
/// <param name="palette">The palette for images using indexed colors.</param>
|
||||
/// <param name="colorType">The color type.</param>
|
||||
public RawPngData(byte[] data, int bytesPerPixel, int width, InterlaceMethod interlaceMethod, Palette palette, ColorType colorType)
|
||||
/// <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)
|
||||
{
|
||||
@@ -32,25 +31,45 @@
|
||||
|
||||
this.data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
this.bytesPerPixel = bytesPerPixel;
|
||||
this.width = width;
|
||||
this.palette = palette;
|
||||
this.colorType = colorType;
|
||||
rowOffset = interlaceMethod == InterlaceMethod.Adam7 ? 0 : 1;
|
||||
|
||||
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)
|
||||
{
|
||||
var pixelsPerByte = (8 / bitDepth);
|
||||
|
||||
var bytesInRow = (1 + (width / pixelsPerByte));
|
||||
|
||||
var byteIndexInRow = x / pixelsPerByte;
|
||||
var paletteIndex = (1 + (y * bytesInRow)) + byteIndexInRow;
|
||||
|
||||
var b = data[paletteIndex];
|
||||
|
||||
if (bitDepth == 8)
|
||||
{
|
||||
return palette.GetPixel(b);
|
||||
}
|
||||
|
||||
var withinByteIndex = x % pixelsPerByte;
|
||||
var rightShift = 8 - ((withinByteIndex + 1) * bitDepth);
|
||||
var indexActual = (b >> rightShift) & ((1 << bitDepth) - 1);
|
||||
|
||||
return palette.GetPixel(indexActual);
|
||||
}
|
||||
|
||||
var rowStartPixel = (rowOffset + (rowOffset * y)) + (bytesPerPixel * width * y);
|
||||
|
||||
var pixelStartIndex = rowStartPixel + (bytesPerPixel * x);
|
||||
|
||||
var first = data[pixelStartIndex];
|
||||
|
||||
if (palette != null)
|
||||
{
|
||||
return palette.GetPixel(first);
|
||||
}
|
||||
|
||||
switch (bytesPerPixel)
|
||||
{
|
||||
case 1:
|
||||
|
||||
Reference in New Issue
Block a user