Skip to content

Commit

Permalink
Add PngWriteDefines to Magick.NET (#1661)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenscordeirobr authored Jul 16, 2024
1 parent b6210a6 commit 31acab3
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 0 deletions.
80 changes: 80 additions & 0 deletions src/Magick.NET/Formats/Png/PngChunkFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using System;

namespace ImageMagick.Formats;

/// <summary>
/// Specifies the chunks to be included or excluded in the PNG image.
/// This is a flags enumeration, allowing a bitwise combination of its member values.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "The lowercase names are consistent with the naming conventions of PNG chunk types as defined in the PNG specification.")]
[Flags]
public enum PngChunkFlags
{
/// <summary>
/// No chunks specified.
/// </summary>
None = 0,

/// <summary>
/// Include or exclude all chunks.
/// </summary>
All = bKGD | cHRM | EXIF | gAMA | iCCP | iTXt | sRGB | tEXt | zCCP | zTXt | date,

/// <summary>
/// Include or exclude bKGD chunk.
/// </summary>
bKGD = 1,

/// <summary>
/// Include or exclude cHRM chunk.
/// </summary>
cHRM = 2,

/// <summary>
/// Include or exclude EXIF chunk.
/// </summary>
EXIF = 4,

/// <summary>
/// Include or exclude gAMA chunk.
/// </summary>
gAMA = 8,

/// <summary>
/// Include or exclude iCCP chunk.
/// </summary>
iCCP = 16,

/// <summary>
/// Include or exclude iTXt chunk.
/// </summary>
iTXt = 32,

/// <summary>
/// Include or exclude sRGB chunk.
/// </summary>
sRGB = 64,

/// <summary>
/// Include or exclude tEXt chunk.
/// </summary>
tEXt = 128,

/// <summary>
/// Include or exclude zCCP chunk.
/// </summary>
zCCP = 256,

/// <summary>
/// Include or exclude zTXt chunk.
/// </summary>
zTXt = 512,

/// <summary>
/// Include or exclude date chunk.
/// </summary>
date = 1024,
}
35 changes: 35 additions & 0 deletions src/Magick.NET/Formats/Png/PngCompressionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

namespace ImageMagick.Formats;

/// <summary>
/// Specifies the PNG compression filter.
/// </summary>
public enum PngCompressionFilter
{
/// <summary>
/// 0 - None: No filter.
/// </summary>
None,

/// <summary>
/// Sub: Subtracts the value of the pixel to the left.
/// </summary>
Sub,

/// <summary>
/// Up: Subtracts the value of the pixel above.
/// </summary>
Up,

/// <summary>
/// Average: Uses the average of the left and above pixels.
/// </summary>
Average,

/// <summary>
/// Paeth: A predictive filter using the Paeth algorithm.
/// </summary>
Paeth,
}
60 changes: 60 additions & 0 deletions src/Magick.NET/Formats/Png/PngCompressionStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

namespace ImageMagick.Formats;

/// <summary>
/// Specifies the PNG compression strategy.
/// </summary>
public enum PngCompressionStrategy
{
/// <summary>
/// Use the Huffman compression.
/// </summary>
HuffmanOnly,

/// <summary>
/// Compression algorithm with filtering.
/// </summary>
Filtered,

/// <summary>
/// Use the Run-Length Encoding compression.
/// </summary>
RLE,

/// <summary>
/// Use a fixed strategy for compression.
/// </summary>
Fixed,

/// <summary>
/// Use the default compression strategy.
/// </summary>
Default,

/// <summary>
/// Adaptive filtering is used when quality is greater than 50 and the image does not have a color map; otherwise, no filtering is used.
/// </summary>
Adaptive,

/// <summary>
/// Adaptive filtering with minimum-sum-of-absolute-values is used.
/// </summary>
AdaptiveMinimumSum,

/// <summary>
/// LOCO color transformation (intrapixel differencing) and adaptive filtering with minimum-sum-of-absolute-values are used. Only applicable if the output is MNG.
/// </summary>
LOCO,

/// <summary>
/// The zlib Z_RLE compression strategy (or the Z_HUFFMAN_ONLY strategy when compression level is 0) is used with adaptive PNG filtering.
/// </summary>
ZRLEAdaptive,

/// <summary>
/// The zlib Z_RLE compression strategy (or the Z_HUFFMAN_ONLY strategy when compression level is 0) is used with no PNG filtering.
/// </summary>
ZRLENoFilter,
}
127 changes: 127 additions & 0 deletions src/Magick.NET/Formats/Png/PngWriteDefines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;

namespace ImageMagick.Formats;

/// <summary>
/// Class for defines that are used when a <see cref="MagickFormat.Png"/> image is written.
/// </summary>
public sealed class PngWriteDefines : IWriteDefines
{
/// <summary>
/// Gets or sets the bit depth for the PNG image.
/// Valid values: 1, 2, 4, 8, 16.
/// </summary>
/// <exception cref="ArgumentException"> Thrown when the bit depth is invalid for the given color type.</exception>
public uint? BitDepth { get; set; }

/// <summary>
/// Gets or sets the color type of the image.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the color type is invalid or unsupported.</exception>
public ColorType? ColorType { get; set; }

/// <summary>
/// Gets or sets the compression filter for the PNG image.
/// For compression level 0 (quality value less than 10), the Huffman-only strategy is used, which is fastest but not necessarily the worst compression.
/// </summary>
public PngCompressionFilter? CompressionFilter { get; set; }

/// <summary>
/// Gets or sets the compression level for the PNG image.
/// The compression level ranges from 0 to 9, where 0 indicates no compression and 9 indicates maximum compression.
/// For compression level 0 (quality value less than 10), the Huffman-only strategy is used, which is the fastest but not necessarily the worst compression.
/// </summary>
public uint? CompressionLevel { get; set; }

/// <summary>
/// Gets or sets the compression strategy for the PNG image.
/// </summary>
public PngCompressionStrategy? CompressionStrategy { get; set; }

/// <summary>
/// Gets or sets the chunks to be excluded.
/// </summary>
public PngChunkFlags? ExcludeChunks { get; set; }

/// <summary>
/// Gets or sets the chunks to be included.
/// </summary>
public PngChunkFlags? IncludeChunks { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the iCCP chunk should be preserved when writing the image.
/// </summary>
public bool PreserveiCCP { get; set; }

/// <summary>
/// Gets or sets a value indicating whether ColorMap should be preserve when writing the image.
/// </summary>
public bool PreserveColorMap { get; set; }

/// <summary>
/// Gets the format where the defines are for.
/// </summary>
public MagickFormat Format
=> MagickFormat.Png;

/// <summary>
/// Gets the defines that should be set as a define on an image.
/// </summary>
public IEnumerable<IDefine> Defines
{
get
{
if (BitDepth.HasValue)
yield return new MagickDefine(Format, "bit-depth", BitDepth.Value);

if (ColorType.HasValue)
{
var colorTypeValue = GetPngColorTypeValue(ColorType.Value);
yield return new MagickDefine(Format, "color-type", (int)colorTypeValue);
}

if (CompressionFilter.HasValue)
yield return new MagickDefine(Format, "compression-filter", (int)CompressionFilter.Value);

if (CompressionLevel.HasValue)
yield return new MagickDefine(Format, "compression-level", CompressionLevel.Value);

if (CompressionStrategy.HasValue)
yield return new MagickDefine(Format, "compression-strategy", (int)CompressionStrategy.Value);

if (ExcludeChunks.HasValue)
yield return new MagickDefine(Format, "exclude-chunks", EnumHelper.ConvertFlags(ExcludeChunks.Value));

if (IncludeChunks.HasValue)
yield return new MagickDefine(Format, "include-chunks", EnumHelper.ConvertFlags(IncludeChunks.Value));

if (PreserveiCCP)
yield return new MagickDefine(Format, "preserve-iCCP", PreserveiCCP);

if (PreserveColorMap)
yield return new MagickDefine(Format, "preserve-colormap", PreserveColorMap);
}
}

private int GetPngColorTypeValue(ColorType colorType)
{
// 0 - Grayscale
// 2 - RGB (TrueColor)
// 3 - Indexed color type (Palette or PaletteAlpha)
// 4 - Grayscale with alpha (GrayscaleAlpha)
// 6 - RGB with alpha (TrueColorAlpha)
return colorType switch
{
ImageMagick.ColorType.Grayscale => 0,
ImageMagick.ColorType.TrueColor => 2,
ImageMagick.ColorType.Palette or ImageMagick.ColorType.PaletteAlpha => 3,
ImageMagick.ColorType.GrayscaleAlpha => 4,
ImageMagick.ColorType.TrueColorAlpha => 6,
_ => throw new ArgumentException($"Unsupported color type: {colorType}"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
// Licensed under the Apache License, Version 2.0.

using ImageMagick;
using ImageMagick.Formats;
using Xunit;

namespace Magick.NET.Tests;

public partial class PngWriteDefinesTests
{
public class TheBitDepthProperty
{
[Fact]
public void ShouldSetTheDefine()
{
using var image = new MagickImage();
image.Settings.SetDefines(new PngWriteDefines
{
BitDepth = 8U,
});

Assert.Equal("8", image.Settings.GetDefine(MagickFormat.Png, "bit-depth"));
}

[Fact]
public void ShouldNotSetTheDefineWhenNull()
{
using var image = new MagickImage();
image.Settings.SetDefines(new PngWriteDefines
{
BitDepth = null,
});

Assert.Null(image.Settings.GetDefine(MagickFormat.Png, "bit-depth"));
}
}
}
Loading

0 comments on commit 31acab3

Please sign in to comment.