Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 5 additions & 0 deletions src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,10 @@ internal interface IPngEncoderOptions
/// Gets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
PngInterlaceMode? InterlaceMethod { get; }

/// <summary>
/// Gets the optimize method.
/// </summary>
PngOptimizeMethod? OptimizeMethod { get; }
}
}
5 changes: 5 additions & 0 deletions src/ImageSharp/Formats/Png/PngEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
/// </summary>
public PngInterlaceMode? InterlaceMethod { get; set; }

/// <summary>
/// Gets or sets the optimize method.
/// </summary>
public PngOptimizeMethod? OptimizeMethod { get; set; }

/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
Expand Down
59 changes: 53 additions & 6 deletions src/ImageSharp/Formats/Png/PngEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,65 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IQuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);

IQuantizedFrame<TPixel> quantized;

if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.MakeTransparentBlack) == PngOptimizeMethod.MakeTransparentBlack)
{
using (Image<TPixel> tempImage = image.Clone())
{
Span<TPixel> span = tempImage.GetPixelSpan();
for (int i = 0; i < span.Length; i++)
{
Rgba32 rgba32 = default;
span[i].ToRgba32(ref rgba32);

if (rgba32.A == 0)
{
rgba32.R = 0;
rgba32.G = 0;
rgba32.B = 0;
}

span[i].FromRgba32(rgba32);
}

quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, tempImage);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, tempImage, quantized);
}
}
else
{
quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
}

stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

this.WriteHeaderChunk(stream);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
this.WritePhysicalChunk(stream, metadata);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, metadata);
this.WriteTextChunks(stream, pngMetadata);

if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressPhysicalChunk) != PngOptimizeMethod.SuppressPhysicalChunk)
{
this.WritePhysicalChunk(stream, metadata);
}

if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressGammaChunk) != PngOptimizeMethod.SuppressGammaChunk)
{
this.WriteGammaChunk(stream);
}

if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressExifChunk) != PngOptimizeMethod.SuppressExifChunk)
{
this.WriteExifChunk(stream, metadata);
}

if (((this.options.OptimizeMethod ?? PngOptimizeMethod.None) & PngOptimizeMethod.SuppressTextChunks) != PngOptimizeMethod.SuppressTextChunks)
{
this.WriteTextChunks(stream, pngMetadata);
}

this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
this.WriteEndChunk(stream);
stream.Flush();
Expand Down
6 changes: 6 additions & 0 deletions src/ImageSharp/Formats/Png/PngEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public PngEncoderOptions(IPngEncoderOptions source)
this.Quantizer = source.Quantizer;
this.Threshold = source.Threshold;
this.InterlaceMethod = source.InterlaceMethod;
this.OptimizeMethod = source.OptimizeMethod;
}

/// <summary>
Expand Down Expand Up @@ -78,5 +79,10 @@ public PngEncoderOptions(IPngEncoderOptions source)
/// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
public PngInterlaceMode? InterlaceMethod { get; set; }

/// <summary>
/// Gets or sets a the optimize method.
/// </summary>
public PngOptimizeMethod? OptimizeMethod { get; set; }
}
}
49 changes: 49 additions & 0 deletions src/ImageSharp/Formats/Png/PngOptimizeMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 PngOptimizeMethod
Copy link
Member

@antonfirsov antonfirsov Sep 30, 2019

Choose a reason for hiding this comment

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

As I mentioned on gitter, it's better if our naming should reflect functional semantics rather than a specific use case (optimization). In that case "positive" flags may be easier than negative ones expressing the suppression. "positive" flags may be better than negative ones, but I'm not sure about this, need @JimBobSquarePants 's advice.

Suggestion Positive flags:

public enum PngChunkFilter
{
    ExcludeAll = 0,
    IncludePhysicalChunk = 1 << 0,
    IncludeGammaChunk = 1 << 1,
    IncludeExifChunk = 1 << 2,
    IncludeTextChunks = 1 << 3,

    // Default:
    IncludeAll = IncludePhysicalChunk | IncludeGammaChunk | IncludeExifChunk | IncludeTextChunks
}

Negative flags:

public enum PngChunkFilter
{
    // Default:
    IncludeAll = 0,
    ExcludePhysicalChunk = 1 << 0,
    ExcludeGammaChunk = 1 << 1,
    ExcludeExifChunk = 1 << 2,
    ExcludeTextChunks = 1 << 3,
    
    ExcludeAll = ExcludePhysicalChunk | ExcludeGammaChunk | ExcludeExifChunk | ExcludeTextChunks
}

Copy link
Member

Choose a reason for hiding this comment

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

I much prefer the suggested naming definition.

The overall metadata API is designed to preserve as much metadata as possible so I think we should adopt the negative flags. This to me as a consumer would cognitively reflect that intention as I'm building a rule for what to exclude.

Copy link
Collaborator

Choose a reason for hiding this comment

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

this is changed now to negative flags

{
/// <summary>
/// With the None filter, the scanline is transmitted unmodified.
/// </summary>
None = 0,

/// <summary>
/// Suppress the physical dimension information chunk.
/// </summary>
SuppressPhysicalChunk = 1,

/// <summary>
/// Suppress the gamma information chunk.
/// </summary>
SuppressGammaChunk = 2,

/// <summary>
/// Suppress the eXIf chunk.
/// </summary>
SuppressExifChunk = 4,

/// <summary>
/// Suppress the tTXt, iTXt or zTXt chunk.
/// </summary>
SuppressTextChunks = 8,

/// <summary>
/// Make funlly transparent pixels black.
/// </summary>
MakeTransparentBlack = 16,
Copy link
Member

Choose a reason for hiding this comment

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

This one is really different from the other ones, I would rather place this one into a separate property inside PngEncoder.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. It doesn't seem to fit. Is the naming accurate also? It's not black, it's empty.

Copy link
Collaborator

Choose a reason for hiding this comment

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

i think MakeTransparentBlack is clear in what it does, but im open for other suggestions


/// <summary>
/// All possible optimizations.
/// </summary>
All = 31,
Copy link
Member

Choose a reason for hiding this comment

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

Defining the union of the flags manually is more prone to mistakes when changed in future.

Copy link
Member

Choose a reason for hiding this comment

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

All = ~None.

}
}
42 changes: 39 additions & 3 deletions tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,44 @@ public void WorksWithAllBitDepths<TPixel>(TestImageProvider<TPixel> provider, Pn
}
}

[Theory]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
public void WorksWithAllBitDepthsOptimized<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : struct, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
pngBitDepth,
interlaceMode,
appendPngColorType: true,
appendPixelType: true,
appendPngBitDepth: true,
optimizeMethod: PngOptimizeMethod.All);
}
}

[Theory]
[WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)]
public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
where TPixel : struct, IPixel<TPixel>
where TPixel : struct, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
Expand Down Expand Up @@ -356,7 +390,8 @@ private static void TestPngEncoderCore<TPixel>(
bool appendPixelType = false,
bool appendCompressionLevel = false,
bool appendPaletteSize = false,
bool appendPngBitDepth = false)
bool appendPngBitDepth = false,
PngOptimizeMethod optimizeMethod = PngOptimizeMethod.None)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
Expand All @@ -368,7 +403,8 @@ private static void TestPngEncoderCore<TPixel>(
CompressionLevel = compressionLevel,
BitDepth = bitDepth,
Quantizer = new WuQuantizer(paletteSize),
InterlaceMethod = interlaceMode
InterlaceMethod = interlaceMode,
OptimizeMethod = optimizeMethod,
};

string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
Expand Down