Skip to content

Commit

Permalink
#244 Add support for interlaced PNG encoding (#955)
Browse files Browse the repository at this point in the history
* #244 Implement interlaced PNG encoding

* #244 Update documentations

* #244 Remove comment

* Cleanup

* Update PngEncoderCore.cs
  • Loading branch information
IldarKhayrutdinov authored and JimBobSquarePants committed Aug 8, 2019
1 parent ff7d876 commit 812ef6e
Show file tree
Hide file tree
Showing 13 changed files with 746 additions and 395 deletions.
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

0 comments on commit 812ef6e

Please sign in to comment.