Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9d62a2c
Added: ability to skip unneeded chunks for optimization mode
equinox2k Sep 19, 2019
69f01c3
Update: forgot to implement Optimized property
equinox2k Sep 19, 2019
9ee2066
Added: test + fixed optimized property
equinox2k Sep 19, 2019
8d9fbb7
Fixed property documentation + Optimized transparency
equinox2k Sep 19, 2019
81bc719
Update: expanded optimize options
equinox2k Sep 20, 2019
fc8f5e3
Update: transparent pixels to black before quantization
equinox2k Sep 20, 2019
ba08c64
Fixed tests
equinox2k Sep 20, 2019
f8c5277
Fixed transparency update
equinox2k Sep 20, 2019
6e3e4ce
Fixed formatting
equinox2k Sep 20, 2019
6259881
Merge branch 'master' into PNGOptimisation
equinox2k Sep 30, 2019
d68d7bd
Merge remote-tracking branch 'upstream/master' into PNGOptimisation
brianpopow Apr 29, 2020
6765f96
Renamed enum to PngChunkFilter and also renamed enum names
brianpopow Apr 29, 2020
f257ef2
Add tests for exclude filter
brianpopow Apr 29, 2020
f04e836
MakeTransparentBlack is now a png encoder option
brianpopow Apr 29, 2020
624591c
Refactor
brianpopow Apr 29, 2020
a86713f
Add tests for make transparent black option
brianpopow Apr 29, 2020
8ca9b97
MakeTransparentBlack option now work with all png color type
brianpopow Apr 29, 2020
c386f9d
Merge branch 'master' into PNGOptimisation
brianpopow Apr 29, 2020
200ef9f
ExcludeAll = ~None
brianpopow Apr 30, 2020
f9c2f6a
Improve exclude filter test to also check presence of expected chunks
brianpopow Apr 30, 2020
0f581ab
Remove not needed image parameter from CalculateBitDepth
brianpopow May 1, 2020
bc7eb27
Add IgnoreMetadata to the png encoder options
brianpopow May 1, 2020
ec3656f
Add PngTransparentColorBehavior enum
brianpopow May 1, 2020
749f68a
Merge branch 'master' into PNGOptimisation
brianpopow May 1, 2020
129b639
Switched Preserve to be 0 for PngTransparentColorBehavior enum
brianpopow May 1, 2020
b69d772
Rename PngTransparentColorBehavior to PngTransparentColorMode
brianpopow May 1, 2020
b817615
Merge branch 'master' into PNGOptimisation
JimBobSquarePants May 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,15 @@ internal interface IPngEncoderOptions
/// Gets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
PngInterlaceMode? InterlaceMethod { get; }

/// <summary>
/// Gets chunk filter method.
/// </summary>
PngChunkFilter? ChunkFilter { get; }

/// <summary>
/// Gets a value indicating whether fully transparent pixels should be converted to black pixels.
/// </summary>
bool MakeTransparentBlack { get; }
}
}
44 changes: 44 additions & 0 deletions src/ImageSharp/Formats/Png/PngChunkFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;

namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Provides enumeration of available PNG optimization methods.
/// </summary>
[Flags]
public enum PngChunkFilter
{
/// <summary>
/// With the None filter, all chunks will be written.
/// </summary>
None = 0,

/// <summary>
/// Excludes the physical dimension information chunk from encoding.
/// </summary>
ExcludePhysicalChunk = 1 << 0,

/// <summary>
/// Excludes the gamma information chunk from encoding.
/// </summary>
ExcludeGammaChunk = 1 << 1,

/// <summary>
/// Excludes the eXIf chunk from encoding.
/// </summary>
ExcludeExifChunk = 1 << 2,

/// <summary>
/// Excludes the tTXt, iTXt or zTXt chunk from encoding.
/// </summary>
ExcludeTextChunks = 1 << 3,

/// <summary>
/// All possible optimizations.
/// </summary>
ExcludeAll = ~None
}
}
10 changes: 10 additions & 0 deletions src/ImageSharp/Formats/Png/PngEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
/// <inheritdoc/>
public PngInterlaceMode? InterlaceMethod { get; set; }

/// <summary>
/// Gets or sets the chunk filter. This can be used to exclude some ancillary chunks from being written.
/// </summary>
public PngChunkFilter? ChunkFilter { get; set; }

/// <summary>
/// Gets or sets a value indicating whether fully transparent pixels should be converted to black pixels.
/// </summary>
public bool MakeTransparentBlack { get; set; }
Copy link
Member

@JimBobSquarePants JimBobSquarePants Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the use case for this property. Why would we choose Black over Transparent? They should both encode to the same compression result and you get to preserve transparency.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants the intention is to convert fully transparent pixels that may contain r g b values to a transparent black I.e r g b of 0 and thus should six compression

Copy link

@UkooLabs UkooLabs Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So name maybe confusing but something like MakeTransparentIntoTransparentBlack Is long :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UkooLabs That's what I thought it meant to do. However the implementation is converting to Color.Black rather than Color.Transparent. Probably because the property name needs some love. ClearTransparency perhaps or even a two property enum. PngTransparentColorBehavior Clear, Preserve

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i really misunderstood that. I was thinking it should be Color.Black

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming is hard...


/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
Expand Down
96 changes: 87 additions & 9 deletions src/ImageSharp/Formats/Png/PngEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;

using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
Expand Down Expand Up @@ -141,10 +141,17 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
this.height = image.Height;

ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetPngMetadata();

PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IndexedImageFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
Image<TPixel> clonedImage = null;
if (this.options.MakeTransparentBlack)
{
clonedImage = image.Clone();
MakeTransparentPixelsBlack(clonedImage);
}

IndexedImageFrame<TPixel> quantized = this.CreateQuantizedImage(image, clonedImage);

stream.Write(PngConstants.HeaderBytes);

Expand All @@ -155,11 +162,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
this.WritePhysicalChunk(stream, metadata);
this.WriteExifChunk(stream, metadata);
this.WriteTextChunks(stream, pngMetadata);
this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
this.WriteDataChunks(this.options.MakeTransparentBlack ? clonedImage : image, quantized, stream);
this.WriteEndChunk(stream);

stream.Flush();

quantized?.Dispose();
clonedImage?.Dispose();
}

/// <inheritdoc />
Expand All @@ -180,6 +189,55 @@ public void Dispose()
this.filterBuffer = null;
}

/// <summary>
/// Makes transparent pixels black.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The cloned image where the transparent pixels will be changed.</param>
private static void MakeTransparentPixelsBlack<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba32 = default;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> span = image.GetPixelRowSpan(y);
for (int x = 0; x < image.Width; x++)
{
span[x].ToRgba32(ref rgba32);

if (rgba32.A == 0)
{
span[x].FromRgba32(Color.Black);
}
}
}
}

/// <summary>
/// Creates the quantized image and sets calculates and sets the bit depth.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The image to quantize.</param>
/// <param name="clonedImage">Cloned image with transparent pixels are changed to black.</param>
/// <returns>The quantized image.</returns>
private IndexedImageFrame<TPixel> CreateQuantizedImage<TPixel>(Image<TPixel> image, Image<TPixel> clonedImage)
where TPixel : unmanaged, IPixel<TPixel>
{
IndexedImageFrame<TPixel> quantized;
if (this.options.MakeTransparentBlack)
{
quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, clonedImage, quantized);
}
else
{
quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
}

return quantized;
}

/// <summary>Collects a row of grayscale pixels.</summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The image row span.</param>
Expand Down Expand Up @@ -602,6 +660,11 @@ private void WritePaletteChunk<TPixel>(Stream stream, IndexedImageFrame<TPixel>
/// <param name="meta">The image metadata.</param>
private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
{
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk)
{
return;
}

PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);

this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
Expand All @@ -614,6 +677,11 @@ private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
/// <param name="meta">The image metadata.</param>
private void WriteExifChunk(Stream stream, ImageMetadata meta)
{
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk)
{
return;
}

if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0)
{
return;
Expand All @@ -631,6 +699,11 @@ private void WriteExifChunk(Stream stream, ImageMetadata meta)
/// <param name="meta">The image metadata.</param>
private void WriteTextChunks(Stream stream, PngMetadata meta)
{
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
{
return;
}

const int MaxLatinCode = 255;
for (int i = 0; i < meta.TextData.Count; i++)
{
Expand Down Expand Up @@ -723,6 +796,11 @@ private byte[] GetCompressedTextBytes(byte[] textBytes)
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream)
{
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk)
{
return;
}

if (this.options.Gamma > 0)
{
// 4-byte unsigned integer of gamma * 100,000.
Expand Down Expand Up @@ -792,7 +870,7 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
/// <param name="pixels">The image.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
private void WriteDataChunks<TPixel>(Image<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
byte[] buffer;
Expand Down Expand Up @@ -890,8 +968,8 @@ private void AllocateExtBuffers()
/// <param name="pixels">The pixels.</param>
/// <param name="quantized">The quantized pixels span.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
private void EncodePixels<TPixel>(Image<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1;
Expand All @@ -914,7 +992,7 @@ private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<T
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="pixels">The pixels.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7Pixels<TPixel>(ImageFrame<TPixel> pixels, ZlibDeflateStream deflateStream)
private void EncodeAdam7Pixels<TPixel>(Image<TPixel> pixels, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
Expand Down
10 changes: 10 additions & 0 deletions src/ImageSharp/Formats/Png/PngEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public PngEncoderOptions(IPngEncoderOptions source)
this.Quantizer = source.Quantizer;
this.Threshold = source.Threshold;
this.InterlaceMethod = source.InterlaceMethod;
this.ChunkFilter = source.ChunkFilter;
this.MakeTransparentBlack = source.MakeTransparentBlack;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -57,5 +59,13 @@ public PngEncoderOptions(IPngEncoderOptions source)

/// <inheritdoc/>
public PngInterlaceMode? InterlaceMethod { get; set; }

/// <summary>
/// Gets or sets a the optimize method.
/// </summary>
public PngChunkFilter? ChunkFilter { get; set; }

/// <inheritdoc/>
public bool MakeTransparentBlack { get; set; }
}
}
Loading