Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#244 Add support for interlaced PNG encoding #955

Merged
32 changes: 30 additions & 2 deletions src/ImageSharp/Formats/Png/Adam7.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
Expand Down Expand Up @@ -31,6 +31,34 @@ internal static class Adam7
/// </summary>
public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 };

/// <summary>
/// Gets the width of the block.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="pass">The pass.</param>
/// <returns>
/// The <see cref="int" />
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeBlockWidth(int width, int pass)
{
return (width + ColumnIncrement[pass] - 1 - FirstColumn[pass]) / ColumnIncrement[pass];
}

/// <summary>
/// Gets the height of the block.
/// </summary>
/// <param name="height">The height.</param>
/// <param name="pass">The pass.</param>
/// <returns>
/// The <see cref="int" />
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeBlockHeight(int height, int pass)
{
return (height + RowIncrement[pass] - 1 - FirstRow[pass]) / RowIncrement[pass];
}

/// <summary>
/// Returns the correct number of columns for each interlaced pass.
/// </summary>
Expand All @@ -53,4 +81,4 @@ public static int ComputeColumns(int width, int passIndex)
}
}
}
}
}
7 changes: 6 additions & 1 deletion src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal interface IPngEncoderOptions
/// <summary>
/// Gets the threshold of characters in text metadata, when compression should be used.
/// </summary>
int CompressTextThreshold { get; }
int TextCompressionThreshold { get; }

/// <summary>
/// Gets the gamma value, that will be written the image.
Expand All @@ -52,5 +52,10 @@ internal interface IPngEncoderOptions
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }

/// <summary>
/// Gets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
PngInterlaceMode? InterlaceMethod { get; }
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Png/PngConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal static class PngConstants
};

/// <summary>
/// The header bytes as a big endian coded ulong.
/// The header bytes as a big-endian coded ulong.
/// </summary>
public const ulong HeaderValue = 0x89504E470D0A1A0AUL;

Expand Down
29 changes: 13 additions & 16 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal sealed class PngDecoderCore
private readonly byte[] buffer = new byte[4];

/// <summary>
/// Reusable crc for validating chunks.
/// Reusable CRC for validating chunks.
/// </summary>
private readonly Crc32 crc = new Crc32();

Expand Down Expand Up @@ -106,12 +106,7 @@ internal sealed class PngDecoderCore
private int currentRow = Adam7.FirstRow[0];

/// <summary>
/// The current pass for an interlaced PNG.
/// </summary>
private int pass;

/// <summary>
/// The current number of bytes read in the current scanline.
/// The current number of bytes read in the current scanline
/// </summary>
private int currentRowBytesRead;

Expand Down Expand Up @@ -551,13 +546,15 @@ private void DecodePixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel>
{
int pass = 0;
int width = this.header.Width;
while (true)
{
int numColumns = Adam7.ComputeColumns(this.header.Width, this.pass);
int numColumns = Adam7.ComputeColumns(width, pass);

if (numColumns == 0)
{
this.pass++;
pass++;

// This pass contains no data; skip to next pass
continue;
Expand Down Expand Up @@ -605,23 +602,23 @@ private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFra
}

Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]);

this.SwapBuffers();

this.currentRow += Adam7.RowIncrement[this.pass];
this.currentRow += Adam7.RowIncrement[pass];
}

this.pass++;
pass++;
this.previousScanline.Clear();

if (this.pass < 7)
if (pass < 7)
{
this.currentRow = Adam7.FirstRow[this.pass];
this.currentRow = Adam7.FirstRow[pass];
}
else
{
this.pass = 0;
pass = 0;
break;
}
}
Expand Down Expand Up @@ -859,6 +856,7 @@ private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> data)

pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth;
pngMetadata.ColorType = this.header.ColorType;
pngMetadata.InterlaceMethod = this.header.InterlaceMethod;

this.pngColorType = this.header.ColorType;
}
Expand Down Expand Up @@ -1202,7 +1200,6 @@ private bool TryReadChunkLength(out int result)
}

result = default;

return false;
}

Expand Down
16 changes: 11 additions & 5 deletions src/ImageSharp/Formats/Png/PngEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
public int CompressionLevel { get; set; } = 6;

/// <summary>
/// Gets or sets the threshold of characters in text metadata, when compression should be used. Defaults to 1024.
/// Gets or sets the threshold of characters in text metadata, when compression should be used.
/// Defaults to 1024.
/// </summary>
public int CompressTextThreshold { get; set; } = 1024;
public int TextCompressionThreshold { get; set; } = 1024;

/// <summary>
/// Gets or sets the gamma value, that will be written the image.
Expand All @@ -47,14 +48,19 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions

/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// Defaults to the <see cref="WuQuantizer"/>
/// Defaults to the <see cref="WuQuantizer"/>.
/// </summary>
public IQuantizer Quantizer { get; set; }

/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 255;
public byte Threshold { get; set; } = byte.MaxValue;

/// <summary>
/// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
public PngInterlaceMode? InterlaceMethod { get; set; }

/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
Expand All @@ -65,7 +71,7 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), this))
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
{
encoder.Encode(image, stream);
}
Expand Down
Loading