|
|
|
@ -24,6 +24,26 @@ namespace ImageSharp.Formats |
|
|
|
/// </summary>
|
|
|
|
private static readonly Dictionary<int, byte[]> ColorTypes = new Dictionary<int, byte[]>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The amount to increment when processing each column per scanline for each interlaced pass
|
|
|
|
/// </summary>
|
|
|
|
private static readonly int[] Adam7ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The index to start at when processing each column per scanline for each interlaced pass
|
|
|
|
/// </summary>
|
|
|
|
private static readonly int[] Adam7FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The index to start at when processing each row per scanline for each interlaced pass
|
|
|
|
/// </summary>
|
|
|
|
private static readonly int[] Adam7FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The amount to increment when processing each row per scanline for each interlaced pass
|
|
|
|
/// </summary>
|
|
|
|
private static readonly int[] Adam7RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Reusable buffer for reading chunk types.
|
|
|
|
/// </summary>
|
|
|
|
@ -285,10 +305,13 @@ namespace ImageSharp.Formats |
|
|
|
/// <summary>
|
|
|
|
/// Calculates the scanline length.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The <see cref="int"/> representing the length.</returns>
|
|
|
|
private int CalculateScanlineLength() |
|
|
|
/// <param name="width">The width of the row.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="int"/> representing the length.
|
|
|
|
/// </returns>
|
|
|
|
private int CalculateScanlineLength(int width) |
|
|
|
{ |
|
|
|
int scanlineLength = this.header.Width * this.header.BitDepth * this.bytesPerPixel; |
|
|
|
int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; |
|
|
|
|
|
|
|
int amount = scanlineLength % 8; |
|
|
|
if (amount != 0) |
|
|
|
@ -311,7 +334,7 @@ namespace ImageSharp.Formats |
|
|
|
where TPacked : struct |
|
|
|
{ |
|
|
|
this.bytesPerPixel = this.CalculateBytesPerPixel(); |
|
|
|
this.bytesPerScanline = this.CalculateScanlineLength() + 1; |
|
|
|
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; |
|
|
|
this.bytesPerSample = 1; |
|
|
|
if (this.header.BitDepth >= 8) |
|
|
|
{ |
|
|
|
@ -321,7 +344,14 @@ namespace ImageSharp.Formats |
|
|
|
dataStream.Position = 0; |
|
|
|
using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) |
|
|
|
{ |
|
|
|
this.DecodePixelData(compressedStream, pixels); |
|
|
|
if (this.header.InterlaceMethod == InterlaceMode.Adam7) |
|
|
|
{ |
|
|
|
this.DecodeInterlacedPixelData(compressedStream, pixels); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.DecodePixelData(compressedStream, pixels); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -398,6 +428,96 @@ namespace ImageSharp.Formats |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Decodes the raw interlaced pixel data row by row
|
|
|
|
/// <see href="https://github.com/juehv/DentalImageViewer/blob/8a1a4424b15d6cc453b5de3f273daf3ff5e3a90d/DentalImageViewer/lib/jiu-0.14.3/net/sourceforge/jiu/codecs/PNGCodec.java"/>
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|
|
|
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
|
|
|
|
/// <param name="compressedStream">The compressed pixel data stream.</param>
|
|
|
|
/// <param name="pixels">The image pixel accessor.</param>
|
|
|
|
private void DecodeInterlacedPixelData<TColor, TPacked>(Stream compressedStream, PixelAccessor<TColor, TPacked> pixels) |
|
|
|
where TColor : struct, IPackedPixel<TPacked> |
|
|
|
where TPacked : struct |
|
|
|
{ |
|
|
|
for (int pass = 0; pass < 7; pass++) |
|
|
|
{ |
|
|
|
int y = Adam7FirstRow[pass]; |
|
|
|
int numColumns = this.ComputeColumnsAdam7(pass); |
|
|
|
|
|
|
|
if (numColumns == 0) |
|
|
|
{ |
|
|
|
// This pass contains no data; skip to next pass
|
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; |
|
|
|
|
|
|
|
while (y < this.header.Height) |
|
|
|
{ |
|
|
|
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline); |
|
|
|
byte[] scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline); |
|
|
|
|
|
|
|
// Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero.
|
|
|
|
Array.Clear(previousScanline, 0, this.bytesPerScanline); |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); |
|
|
|
|
|
|
|
FilterType filterType = (FilterType)scanline[0]; |
|
|
|
|
|
|
|
switch (filterType) |
|
|
|
{ |
|
|
|
case FilterType.None: |
|
|
|
|
|
|
|
NoneFilter.Decode(scanline); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case FilterType.Sub: |
|
|
|
|
|
|
|
SubFilter.Decode(scanline, bytesPerInterlaceScanline, this.bytesPerPixel); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case FilterType.Up: |
|
|
|
|
|
|
|
UpFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case FilterType.Average: |
|
|
|
|
|
|
|
AverageFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case FilterType.Paeth: |
|
|
|
|
|
|
|
PaethFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
throw new ImageFormatException("Unknown filter type."); |
|
|
|
} |
|
|
|
|
|
|
|
this.ProcessDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); |
|
|
|
|
|
|
|
Swap(ref scanline, ref previousScanline); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
ArrayPool<byte>.Shared.Return(previousScanline); |
|
|
|
ArrayPool<byte>.Shared.Return(scanline); |
|
|
|
} |
|
|
|
|
|
|
|
y += Adam7RowIncrement[pass]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Processes the de-filtered scanline filling the image pixel data
|
|
|
|
/// </summary>
|
|
|
|
@ -406,17 +526,20 @@ namespace ImageSharp.Formats |
|
|
|
/// <param name="defilteredScanline">The de-filtered scanline</param>
|
|
|
|
/// <param name="row">The current image row.</param>
|
|
|
|
/// <param name="pixels">The image pixels</param>
|
|
|
|
private void ProcessDefilteredScanline<TColor, TPacked>(byte[] defilteredScanline, int row, PixelAccessor<TColor, TPacked> pixels) |
|
|
|
/// <param name="startIndex">The column start index. Always 0 for none interlaced images.</param>
|
|
|
|
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
|
|
|
|
private void ProcessDefilteredScanline<TColor, TPacked>(byte[] defilteredScanline, int row, PixelAccessor<TColor, TPacked> pixels, int startIndex = 0, int increment = 1) |
|
|
|
where TColor : struct, IPackedPixel<TPacked> |
|
|
|
where TPacked : struct |
|
|
|
{ |
|
|
|
TColor color = default(TColor); |
|
|
|
|
|
|
|
switch (this.PngColorType) |
|
|
|
{ |
|
|
|
case PngColorType.Grayscale: |
|
|
|
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); |
|
|
|
byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); |
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
byte intensity = (byte)(newScanline1[x] * factor); |
|
|
|
color.PackFromBytes(intensity, intensity, intensity, 255); |
|
|
|
@ -427,7 +550,7 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
case PngColorType.GrayscaleWithAlpha: |
|
|
|
|
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
int offset = 1 + (x * this.bytesPerPixel); |
|
|
|
|
|
|
|
@ -448,7 +571,7 @@ namespace ImageSharp.Formats |
|
|
|
{ |
|
|
|
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
|
|
|
|
// channel and we should try to read it.
|
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
int index = newScanline[x]; |
|
|
|
int pixelOffset = index * 3; |
|
|
|
@ -472,7 +595,7 @@ namespace ImageSharp.Formats |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
int index = newScanline[x]; |
|
|
|
int pixelOffset = index * 3; |
|
|
|
@ -490,7 +613,7 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
case PngColorType.Rgb: |
|
|
|
|
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
int offset = 1 + (x * this.bytesPerPixel); |
|
|
|
|
|
|
|
@ -506,7 +629,7 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
case PngColorType.RgbWithAlpha: |
|
|
|
|
|
|
|
for (int x = 0; x < this.header.Width; x++) |
|
|
|
for (int x = startIndex; x < this.header.Width; x += increment) |
|
|
|
{ |
|
|
|
int offset = 1 + (x * this.bytesPerPixel); |
|
|
|
|
|
|
|
@ -570,7 +693,7 @@ namespace ImageSharp.Formats |
|
|
|
this.header.ColorType = data[9]; |
|
|
|
this.header.CompressionMethod = data[10]; |
|
|
|
this.header.FilterMethod = data[11]; |
|
|
|
this.header.InterlaceMethod = data[12]; |
|
|
|
this.header.InterlaceMethod = (InterlaceMode)data[12]; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -596,10 +719,9 @@ namespace ImageSharp.Formats |
|
|
|
throw new NotSupportedException("The png specification only defines 0 as filter method."); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.header.InterlaceMethod != 0) |
|
|
|
if (this.header.InterlaceMethod != InterlaceMode.None && this.header.InterlaceMethod != InterlaceMode.Adam7) |
|
|
|
{ |
|
|
|
// TODO: Support interlacing
|
|
|
|
throw new NotSupportedException("Interlacing is not supported."); |
|
|
|
throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); |
|
|
|
} |
|
|
|
|
|
|
|
this.PngColorType = (PngColorType)this.header.ColorType; |
|
|
|
@ -715,5 +837,21 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); |
|
|
|
} |
|
|
|
|
|
|
|
private int ComputeColumnsAdam7(int pass) |
|
|
|
{ |
|
|
|
int width = this.header.Width; |
|
|
|
switch (pass) |
|
|
|
{ |
|
|
|
case 0: return (width + 7) / 8; |
|
|
|
case 1: return (width + 3) / 8; |
|
|
|
case 2: return (width + 3) / 4; |
|
|
|
case 3: return (width + 1) / 4; |
|
|
|
case 4: return (width + 1) / 2; |
|
|
|
case 5: return width / 2; |
|
|
|
case 6: return width; |
|
|
|
default: throw new ArgumentException($"Not a valid pass index: {pass}"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |