diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs
index 27a0e121b6..81a76e28d1 100644
--- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpCompression.cs
@@ -69,7 +69,7 @@ internal enum BmpCompression : int
/// rather than four or eight bits in size.
///
/// Note: Because compression value of 4 is ambiguous for BI_RGB for windows and RLE24 for OS/2, the enum value is remapped
- /// to a different value.
+ /// to a different value, to be clearly separate from valid windows values.
///
RLE24 = 100,
}
diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs
index 288c3dfa19..c9d631da0e 100644
--- a/src/ImageSharp/Formats/Gif/GifConstants.cs
+++ b/src/ImageSharp/Formats/Gif/GifConstants.cs
@@ -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.Collections.Generic;
@@ -7,7 +7,7 @@
namespace SixLabors.ImageSharp.Formats.Gif
{
///
- /// Constants that define specific points within a gif.
+ /// Constants that define specific points within a Gif.
///
internal static class GifConstants
{
@@ -67,14 +67,9 @@ internal static class GifConstants
public const byte CommentLabel = 0xFE;
///
- /// The name of the property inside the image properties for the comments.
+ /// The maximum length of a comment data sub-block is 255.
///
- public const string Comments = "Comments";
-
- ///
- /// The maximum comment length.
- ///
- public const int MaxCommentLength = 1024 * 8;
+ public const int MaxCommentSubBlockLength = 255;
///
/// The image descriptor label ,.
@@ -102,18 +97,18 @@ internal static class GifConstants
public const byte EndIntroducer = 0x3B;
///
- /// Gets the default encoding to use when reading comments.
+ /// The character encoding to use when reading and writing comments - (ASCII 7bit).
///
- public static readonly Encoding DefaultEncoding = Encoding.ASCII;
+ public static readonly Encoding Encoding = Encoding.ASCII;
///
- /// The list of mimetypes that equate to a gif.
+ /// The collection of mimetypes that equate to a Gif.
///
public static readonly IEnumerable MimeTypes = new[] { "image/gif" };
///
- /// The list of file extensions that equate to a gif.
+ /// The collection of file extensions that equate to a Gif.
///
public static readonly IEnumerable FileExtensions = new[] { "gif" };
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 1addcd0abf..7691ec1aa5 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -1,8 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
-using System.Text;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -18,11 +17,6 @@ public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDe
///
public bool IgnoreMetadata { get; set; } = false;
- ///
- /// Gets or sets the encoding that should be used when reading comments.
- ///
- public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
-
///
/// Gets or sets the decoding mode for multi-frame images
///
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index e16ecb42e3..c11e93a93a 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -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;
@@ -77,7 +77,6 @@ internal sealed class GifDecoderCore
/// The decoder options.
public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
{
- this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.IgnoreMetadata = options.IgnoreMetadata;
this.DecodingMode = options.DecodingMode;
this.configuration = configuration ?? Configuration.Default;
@@ -88,11 +87,6 @@ public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
///
public bool IgnoreMetadata { get; internal set; }
- ///
- /// Gets the text encoding
- ///
- public Encoding TextEncoding { get; }
-
///
/// Gets the decoding mode for multi-frame images
///
@@ -317,11 +311,12 @@ private void ReadComments()
{
int length;
+ var stringBuilder = new StringBuilder();
while ((length = this.stream.ReadByte()) != 0)
{
- if (length > GifConstants.MaxCommentLength)
+ if (length > GifConstants.MaxCommentSubBlockLength)
{
- throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'");
+ throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block");
}
if (this.IgnoreMetadata)
@@ -333,10 +328,15 @@ private void ReadComments()
using (IManagedByteBuffer commentsBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(length))
{
this.stream.Read(commentsBuffer.Array, 0, length);
- string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length);
- this.metadata.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
+ string commentPart = GifConstants.Encoding.GetString(commentsBuffer.Array, 0, length);
+ stringBuilder.Append(commentPart);
}
}
+
+ if (stringBuilder.Length > 0)
+ {
+ this.gifMetadata.Comments.Add(stringBuilder.ToString());
+ }
}
///
@@ -632,4 +632,4 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index 4210b08765..fef311596e 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -1,8 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
-using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -14,11 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
{
- ///
- /// Gets or sets the encoding that should be used when writing comments.
- ///
- public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
-
///
/// Gets or sets the quantizer for reducing the color count.
/// Defaults to the
@@ -38,4 +32,4 @@ public void Encode(Image image, Stream stream)
encoder.Encode(image, stream);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 36e27866e9..98e53e5b4e 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -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;
@@ -37,11 +37,6 @@ internal sealed class GifEncoderCore
///
private readonly byte[] buffer = new byte[20];
- ///
- /// The text encoding used to write comments.
- ///
- private readonly Encoding textEncoding;
-
///
/// The quantizer used to generate the color palette.
///
@@ -57,11 +52,6 @@ internal sealed class GifEncoderCore
///
private int bitDepth;
- ///
- /// Gif specific metadata.
- ///
- private GifMetadata gifMetadata;
-
///
/// Initializes a new instance of the class.
///
@@ -70,7 +60,6 @@ internal sealed class GifEncoderCore
public GifEncoderCore(MemoryAllocator memoryAllocator, IGifEncoderOptions options)
{
this.memoryAllocator = memoryAllocator;
- this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer;
this.colorTableMode = options.ColorTableMode;
}
@@ -90,8 +79,8 @@ public void Encode(Image image, Stream stream)
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
- this.gifMetadata = metadata.GetFormatMetadata(GifFormat.Instance);
- this.colorTableMode = this.colorTableMode ?? this.gifMetadata.ColorTableMode;
+ GifMetadata gifMetadata = metadata.GetFormatMetadata(GifFormat.Instance);
+ this.colorTableMode = this.colorTableMode ?? gifMetadata.ColorTableMode;
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
// Quantize the image returning a palette.
@@ -117,12 +106,12 @@ public void Encode(Image image, Stream stream)
}
// Write the comments.
- this.WriteComments(metadata, stream);
+ this.WriteComments(gifMetadata, stream);
// Write application extension to allow additional frames.
if (image.Frames.Count > 1)
{
- this.WriteApplicationExtension(stream, this.gifMetadata.RepeatCount);
+ this.WriteApplicationExtension(stream, gifMetadata.RepeatCount);
}
if (useGlobalTable)
@@ -333,25 +322,51 @@ private void WriteApplicationExtension(Stream stream, ushort repeatCount)
///
/// The metadata to be extract the comment data.
/// The stream to write to.
- private void WriteComments(ImageMetadata metadata, Stream stream)
+ private void WriteComments(GifMetadata metadata, Stream stream)
{
- if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property)
- || string.IsNullOrEmpty(property.Value))
+ if (metadata.Comments.Count == 0)
{
return;
}
- byte[] comments = this.textEncoding.GetBytes(property.Value);
+ foreach (string comment in metadata.Comments)
+ {
+ this.buffer[0] = GifConstants.ExtensionIntroducer;
+ this.buffer[1] = GifConstants.CommentLabel;
+ stream.Write(this.buffer, 0, 2);
+
+ // Comment will be stored in chunks of 255 bytes, if it exceeds this size.
+ ReadOnlySpan commentSpan = comment.AsSpan();
+ int idx = 0;
+ for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength)
+ {
+ WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength);
+ }
- int count = Math.Min(comments.Length, 255);
+ // Write the length bytes, if any, to another sub block.
+ if (idx < comment.Length)
+ {
+ int remaining = comment.Length - idx;
+ WriteCommentSubBlock(stream, commentSpan, idx, remaining);
+ }
- this.buffer[0] = GifConstants.ExtensionIntroducer;
- this.buffer[1] = GifConstants.CommentLabel;
- this.buffer[2] = (byte)count;
+ stream.WriteByte(GifConstants.Terminator);
+ }
+ }
- stream.Write(this.buffer, 0, 3);
- stream.Write(comments, 0, count);
- stream.WriteByte(GifConstants.Terminator);
+ ///
+ /// Writes a comment sub-block to the stream.
+ ///
+ /// The stream to write to.
+ /// Comment as a Span.
+ /// Current start index.
+ /// The length of the string to write. Should not exceed 255 bytes.
+ private static void WriteCommentSubBlock(Stream stream, ReadOnlySpan commentSpan, int idx, int length)
+ {
+ string subComment = commentSpan.Slice(idx, length).ToString();
+ byte[] subCommentBytes = GifConstants.Encoding.GetBytes(subComment);
+ stream.WriteByte((byte)length);
+ stream.Write(subCommentBytes, 0, length);
}
///
@@ -458,4 +473,4 @@ private void WriteImageData(IQuantizedFrame image, Stream stream
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
index 613825ad63..dfc96af5a6 100644
--- a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
+++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Gif
@@ -51,4 +51,4 @@ private GifFrameMetadata(GifFrameMetadata other)
///
public IDeepCloneable DeepClone() => new GifFrameMetadata(this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs
index 0b6566fbfe..b00db6752b 100644
--- a/src/ImageSharp/Formats/Gif/GifMetaData.cs
+++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs
@@ -1,6 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
+
namespace SixLabors.ImageSharp.Formats.Gif
{
///
@@ -24,6 +26,11 @@ private GifMetadata(GifMetadata other)
this.RepeatCount = other.RepeatCount;
this.ColorTableMode = other.ColorTableMode;
this.GlobalColorTableLength = other.GlobalColorTableLength;
+
+ for (int i = 0; i < other.Comments.Count; i++)
+ {
+ this.Comments.Add(other.Comments[i]);
+ }
}
///
@@ -44,7 +51,13 @@ private GifMetadata(GifMetadata other)
///
public int GlobalColorTableLength { get; set; }
+ ///
+ /// Gets or sets the the collection of comments about the graphics, credits, descriptions or any
+ /// other type of non-control and non-graphic data.
+ ///
+ public IList Comments { get; set; } = new List();
+
///
public IDeepCloneable DeepClone() => new GifMetadata(this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
index 871b511a0d..050ab170b2 100644
--- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
@@ -1,7 +1,6 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Text;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp.Formats.Gif
@@ -16,11 +15,6 @@ internal interface IGifDecoderOptions
///
bool IgnoreMetadata { get; }
- ///
- /// Gets the text encoding that should be used when reading comments.
- ///
- Encoding TextEncoding { get; }
-
///
/// Gets the decoding mode for multi-frame images.
///
diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
index 4b3c28a92c..5936d30cba 100644
--- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
@@ -1,7 +1,6 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Text;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif
@@ -11,11 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
internal interface IGifEncoderOptions
{
- ///
- /// Gets the text encoding used to write comments.
- ///
- Encoding TextEncoding { get; }
-
///
/// Gets the quantizer used to generate the color palette.
///
@@ -26,4 +20,4 @@ internal interface IGifEncoderOptions
///
GifColorTableMode? ColorTableMode { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
index 5b650ac2a0..44cb837a6a 100644
--- a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
@@ -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.Text;
@@ -14,10 +14,5 @@ internal interface IPngDecoderOptions
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
///
bool IgnoreMetadata { get; }
-
- ///
- /// Gets the encoding that should be used when reading text chunks.
- ///
- Encoding TextEncoding { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
index 7e5a9fa6b8..ee1a823fd2 100644
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
@@ -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 SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -6,7 +6,7 @@
namespace SixLabors.ImageSharp.Formats.Png
{
///
- /// The options available for manipulating the encoder pipeline
+ /// The options available for manipulating the encoder pipeline.
///
internal interface IPngEncoderOptions
{
@@ -17,7 +17,7 @@ internal interface IPngEncoderOptions
PngBitDepth? BitDepth { get; }
///
- /// Gets the color type
+ /// Gets the color type.
///
PngColorType? ColorType { get; }
@@ -33,7 +33,12 @@ internal interface IPngEncoderOptions
int CompressionLevel { get; }
///
- /// Gets the gamma value, that will be written the the image.
+ /// Gets the threshold of characters in text metadata, when compression should be used.
+ ///
+ int CompressTextThreshold { get; }
+
+ ///
+ /// Gets the gamma value, that will be written the image.
///
/// The gamma value of the image.
float? Gamma { get; }
@@ -48,4 +53,4 @@ internal interface IPngEncoderOptions
///
byte Threshold { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs
index 1b251a5748..e41b49066a 100644
--- a/src/ImageSharp/Formats/Png/PngChunkType.cs
+++ b/src/ImageSharp/Formats/Png/PngChunkType.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Png
@@ -55,6 +55,19 @@ internal enum PngChunkType : uint
///
Text = 0x74455874U,
+ ///
+ /// Textual information that the encoder wishes to record with the image. The zTXt and tEXt chunks are semantically equivalent,
+ /// but the zTXt chunk is recommended for storing large blocks of text. Each zTXt chunk contains a (uncompressed) keyword and
+ /// a compressed text string.
+ ///
+ CompressedText = 0x7A545874U,
+
+ ///
+ /// The iTXt chunk contains International textual data. It contains a keyword, an optional language tag, an optional translated keyword
+ /// and the actual text string, which can be compressed or uncompressed.
+ ///
+ InternationalText = 0x69545874U,
+
///
/// The tRNS chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images)
@@ -62,4 +75,4 @@ internal enum PngChunkType : uint
///
Transparency = 0x74524E53U
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs
index e1f978e1ac..d54a53c1c3 100644
--- a/src/ImageSharp/Formats/Png/PngConstants.cs
+++ b/src/ImageSharp/Formats/Png/PngConstants.cs
@@ -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.Collections.Generic;
@@ -7,25 +7,38 @@
namespace SixLabors.ImageSharp.Formats.Png
{
///
- /// Defines png constants defined in the specification.
+ /// Defines Png constants defined in the specification.
///
internal static class PngConstants
{
///
- /// The default encoding for text metadata.
+ /// The character encoding to use when reading and writing textual data keywords and text - (Latin-1 ISO-8859-1).
///
- public static readonly Encoding DefaultEncoding = Encoding.ASCII;
+ public static readonly Encoding Encoding = Encoding.GetEncoding("ISO-8859-1");
///
- /// The list of mimetypes that equate to a png.
+ /// The character encoding to use when reading and writing language tags within iTXt chunks - (ASCII 7bit).
+ ///
+ public static readonly Encoding LanguageEncoding = Encoding.ASCII;
+
+ ///
+ /// The character encoding to use when reading and writing translated textual data keywords and text - (UTF8).
+ ///
+ public static readonly Encoding TranslatedEncoding = Encoding.UTF8;
+
+ ///
+ /// The list of mimetypes that equate to a Png.
///
public static readonly IEnumerable MimeTypes = new[] { "image/png" };
///
- /// The list of file extensions that equate to a png.
+ /// The list of file extensions that equate to a Png.
///
public static readonly IEnumerable FileExtensions = new[] { "png" };
+ ///
+ /// The header bytes identifying a Png.
+ ///
public static readonly byte[] HeaderBytes =
{
0x89, // Set the high bit.
@@ -54,5 +67,15 @@ internal static class PngConstants
[PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 },
[PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
};
+
+ ///
+ /// The maximum length of keyword in a text chunk is 79 bytes.
+ ///
+ public const int MaxTextKeywordLength = 79;
+
+ ///
+ /// The minimum length of a keyword in a text chunk is 1 byte.
+ ///
+ public const int MinTextKeywordLength = 1;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index 040da94737..19e5e848d0 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -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.IO;
@@ -34,11 +34,6 @@ public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDe
///
public bool IgnoreMetadata { get; set; }
- ///
- /// Gets or sets the encoding that should be used when reading text chunks.
- ///
- public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding;
-
///
/// Decodes the image from the specified stream to the .
///
@@ -63,4 +58,4 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
///
public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 5e9d1440ac..74ead3938a 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -1,8 +1,9 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
+using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -39,11 +40,6 @@ internal sealed class PngDecoderCore
///
private readonly Configuration configuration;
- ///
- /// Gets the encoding to use
- ///
- private readonly Encoding textEncoding;
-
///
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
///
@@ -70,22 +66,22 @@ internal sealed class PngDecoderCore
private int bytesPerPixel;
///
- /// The number of bytes per sample
+ /// The number of bytes per sample.
///
private int bytesPerSample;
///
- /// The number of bytes per scanline
+ /// The number of bytes per scanline.
///
private int bytesPerScanline;
///
- /// The palette containing color information for indexed png's
+ /// The palette containing color information for indexed png's.
///
private byte[] palette;
///
- /// The palette containing alpha channel color information for indexed png's
+ /// The palette containing alpha channel color information for indexed png's.
///
private byte[] paletteAlpha;
@@ -95,37 +91,37 @@ internal sealed class PngDecoderCore
private bool isEndChunkReached;
///
- /// Previous scanline processed
+ /// Previous scanline processed.
///
private IManagedByteBuffer previousScanline;
///
- /// The current scanline that is being processed
+ /// The current scanline that is being processed.
///
private IManagedByteBuffer scanline;
///
- /// The index of the current scanline being processed
+ /// The index of the current scanline being processed.
///
private int currentRow = Adam7.FirstRow[0];
///
- /// The current pass for an interlaced PNG
+ /// The current pass for an interlaced PNG.
///
private int pass;
///
- /// The current number of bytes read in the current scanline
+ /// The current number of bytes read in the current scanline.
///
private int currentRowBytesRead;
///
- /// Gets or sets the png color type
+ /// Gets or sets the png color type.
///
private PngColorType pngColorType;
///
- /// The next chunk of data to return
+ /// The next chunk of data to return.
///
private PngChunk? nextChunk;
@@ -138,7 +134,6 @@ public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
{
this.configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.configuration.MemoryAllocator;
- this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding;
this.ignoreMetadata = options.IgnoreMetadata;
}
@@ -204,7 +199,13 @@ public Image Decode(Stream stream)
this.AssignTransparentMarkers(alpha, pngMetadata);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array.AsSpan(0, chunk.Length));
+ this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
+ break;
+ case PngChunkType.CompressedText:
+ this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
+ break;
+ case PngChunkType.InternationalText:
+ this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
@@ -271,7 +272,7 @@ public IImageInfo Identify(Stream stream)
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array.AsSpan(0, chunk.Length));
+ this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@@ -653,7 +654,7 @@ private void ProcessDefilteredScanline(ReadOnlySpan defilteredScan
this.header,
scanlineSpan,
rowSpan,
- pngMetadata.HasTrans,
+ pngMetadata.HasTransparency,
pngMetadata.TransparentGray16.GetValueOrDefault(),
pngMetadata.TransparentGray8.GetValueOrDefault());
@@ -687,7 +688,7 @@ private void ProcessDefilteredScanline(ReadOnlySpan defilteredScan
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
- pngMetadata.HasTrans,
+ pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
@@ -737,7 +738,7 @@ private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defi
rowSpan,
pixelOffset,
increment,
- pngMetadata.HasTrans,
+ pngMetadata.HasTransparency,
pngMetadata.TransparentGray16.GetValueOrDefault(),
pngMetadata.TransparentGray8.GetValueOrDefault());
@@ -776,7 +777,7 @@ private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defi
increment,
this.bytesPerPixel,
this.bytesPerSample,
- pngMetadata.HasTrans,
+ pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
@@ -816,7 +817,7 @@ private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngM
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc);
- pngMetadata.HasTrans = true;
+ pngMetadata.HasTransparency = true;
return;
}
@@ -824,7 +825,7 @@ private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngM
byte g = ReadByteLittleEndian(alpha, 2);
byte b = ReadByteLittleEndian(alpha, 4);
pngMetadata.TransparentRgb24 = new Rgb24(r, g, b);
- pngMetadata.HasTrans = true;
+ pngMetadata.HasTransparency = true;
}
}
else if (this.pngColorType == PngColorType.Grayscale)
@@ -840,7 +841,7 @@ private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngM
pngMetadata.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0));
}
- pngMetadata.HasTrans = true;
+ pngMetadata.HasTransparency = true;
}
}
}
@@ -867,7 +868,7 @@ private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data)
///
/// The metadata to decode to.
/// The containing the data.
- private void ReadTextChunk(ImageMetadata metadata, ReadOnlySpan data)
+ private void ReadTextChunk(PngMetadata metadata, ReadOnlySpan data)
{
if (this.ignoreMetadata)
{
@@ -876,10 +877,151 @@ private void ReadTextChunk(ImageMetadata metadata, ReadOnlySpan data)
int zeroIndex = data.IndexOf((byte)0);
- string name = this.textEncoding.GetString(data.Slice(0, zeroIndex));
- string value = this.textEncoding.GetString(data.Slice(zeroIndex + 1));
+ // Keywords are restricted to 1 to 79 bytes in length.
+ if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength)
+ {
+ return;
+ }
+
+ ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex);
+ if (!this.TryReadTextKeyword(keywordBytes, out string name))
+ {
+ return;
+ }
+
+ string value = PngConstants.Encoding.GetString(data.Slice(zeroIndex + 1));
- metadata.Properties.Add(new ImageProperty(name, value));
+ metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty));
+ }
+
+ ///
+ /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string.
+ ///
+ /// The metadata to decode to.
+ /// The containing the data.
+ private void ReadCompressedTextChunk(PngMetadata metadata, ReadOnlySpan data)
+ {
+ if (this.ignoreMetadata)
+ {
+ return;
+ }
+
+ int zeroIndex = data.IndexOf((byte)0);
+ if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength)
+ {
+ return;
+ }
+
+ byte compressionMethod = data[zeroIndex + 1];
+ if (compressionMethod != 0)
+ {
+ // Only compression method 0 is supported (zlib datastream with deflate compression).
+ return;
+ }
+
+ ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex);
+ if (!this.TryReadTextKeyword(keywordBytes, out string name))
+ {
+ return;
+ }
+
+ ReadOnlySpan compressedData = data.Slice(zeroIndex + 2);
+ metadata.TextData.Add(new PngTextData(name, this.UncompressTextData(compressedData, PngConstants.Encoding), string.Empty, string.Empty));
+ }
+
+ ///
+ /// Reads a iTXt chunk, which contains international text data. It contains:
+ /// - A uncompressed keyword.
+ /// - Compression flag, indicating if a compression is used.
+ /// - Compression method.
+ /// - Language tag (optional).
+ /// - A translated keyword (optional).
+ /// - Text data, which is either compressed or uncompressed.
+ ///
+ /// The metadata to decode to.
+ /// The containing the data.
+ private void ReadInternationalTextChunk(PngMetadata metadata, ReadOnlySpan data)
+ {
+ if (this.ignoreMetadata)
+ {
+ return;
+ }
+
+ int zeroIndexKeyword = data.IndexOf((byte)0);
+ if (zeroIndexKeyword < PngConstants.MinTextKeywordLength || zeroIndexKeyword > PngConstants.MaxTextKeywordLength)
+ {
+ return;
+ }
+
+ byte compressionFlag = data[zeroIndexKeyword + 1];
+ if (!(compressionFlag == 0 || compressionFlag == 1))
+ {
+ return;
+ }
+
+ byte compressionMethod = data[zeroIndexKeyword + 2];
+ if (compressionMethod != 0)
+ {
+ // Only compression method 0 is supported (zlib datastream with deflate compression).
+ return;
+ }
+
+ int langStartIdx = zeroIndexKeyword + 3;
+ int languageLength = data.Slice(langStartIdx).IndexOf((byte)0);
+ if (languageLength < 0)
+ {
+ return;
+ }
+
+ string language = PngConstants.LanguageEncoding.GetString(data.Slice(langStartIdx, languageLength));
+
+ int translatedKeywordStartIdx = langStartIdx + languageLength + 1;
+ int translatedKeywordLength = data.Slice(translatedKeywordStartIdx).IndexOf((byte)0);
+ string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength));
+
+ ReadOnlySpan keywordBytes = data.Slice(0, zeroIndexKeyword);
+ if (!this.TryReadTextKeyword(keywordBytes, out string keyword))
+ {
+ return;
+ }
+
+ int dataStartIdx = translatedKeywordStartIdx + translatedKeywordLength + 1;
+ if (compressionFlag == 1)
+ {
+ ReadOnlySpan compressedData = data.Slice(dataStartIdx);
+ metadata.TextData.Add(new PngTextData(keyword, this.UncompressTextData(compressedData, PngConstants.TranslatedEncoding), language, translatedKeyword));
+ }
+ else
+ {
+ string value = PngConstants.TranslatedEncoding.GetString(data.Slice(dataStartIdx));
+ metadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword));
+ }
+ }
+
+ ///
+ /// Decompresses a byte array with zlib compressed text data.
+ ///
+ /// Compressed text data bytes.
+ /// The string encoding to use.
+ /// A string.
+ private string UncompressTextData(ReadOnlySpan compressedData, Encoding encoding)
+ {
+ using (var memoryStream = new MemoryStream(compressedData.ToArray()))
+ using (var inflateStream = new ZlibInflateStream(memoryStream, () => 0))
+ {
+ inflateStream.AllocateNewBytes(compressedData.Length);
+ var uncompressedBytes = new List();
+
+ // Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
+ int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
+ while (bytesRead != 0)
+ {
+ uncompressedBytes.AddRange(this.buffer.AsSpan().Slice(0, bytesRead).ToArray());
+ bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
+ }
+
+ return encoding.GetString(uncompressedBytes.ToArray());
+ }
}
///
@@ -1048,7 +1190,7 @@ private PngChunkType ReadChunkType()
/// Attempts to read the length of the next chunk.
///
///
- /// Whether the the length was read.
+ /// Whether the length was read.
///
private bool TryReadChunkLength(out int result)
{
@@ -1064,6 +1206,37 @@ private bool TryReadChunkLength(out int result)
return false;
}
+ ///
+ /// Tries to reads a text chunk keyword, which have some restrictions to be valid:
+ /// Keywords shall contain only printable Latin-1 characters and should not have leading or trailing whitespace.
+ /// See: https://www.w3.org/TR/PNG/#11zTXt
+ ///
+ /// The keyword bytes.
+ /// The name.
+ /// True, if the keyword could be read and is valid.
+ private bool TryReadTextKeyword(ReadOnlySpan keywordBytes, out string name)
+ {
+ name = string.Empty;
+
+ // Keywords shall contain only printable Latin-1.
+ foreach (byte c in keywordBytes)
+ {
+ if (!((c >= 32 && c <= 126) || (c >= 161 && c <= 255)))
+ {
+ return false;
+ }
+ }
+
+ // Keywords should not be empty or have leading or trailing whitespace.
+ name = PngConstants.Encoding.GetString(keywordBytes);
+ if (string.IsNullOrWhiteSpace(name) || name.StartsWith(" ") || name.EndsWith(" "))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
private void SwapBuffers()
{
IManagedByteBuffer temp = this.previousScanline;
@@ -1071,4 +1244,4 @@ private void SwapBuffers()
this.scanline = temp;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index 96e97a305f..7ef465a485 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -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.IO;
@@ -36,7 +36,12 @@ public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
public int CompressionLevel { get; set; } = 6;
///
- /// Gets or sets the gamma value, that will be written the the image.
+ /// Gets or sets the threshold of characters in text metadata, when compression should be used. Defaults to 1024.
+ ///
+ public int CompressTextThreshold { get; set; } = 1024;
+
+ ///
+ /// Gets or sets the gamma value, that will be written the image.
///
public float? Gamma { get; set; }
@@ -66,4 +71,4 @@ public void Encode(Image image, Stream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index def57c3b0e..695c5c9f57 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -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;
@@ -6,8 +6,11 @@
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text;
+
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
@@ -43,7 +46,7 @@ internal sealed class PngEncoderCore : IDisposable
private readonly MemoryAllocator memoryAllocator;
///
- /// The configuration instance for the decoding operation
+ /// The configuration instance for the decoding operation.
///
private Configuration configuration;
@@ -73,10 +76,15 @@ internal sealed class PngEncoderCore : IDisposable
private readonly PngFilterMethod pngFilterMethod;
///
- /// Gets or sets the CompressionLevel value
+ /// Gets or sets the CompressionLevel value.
///
private readonly int compressionLevel;
+ ///
+ /// The threshold of characters in text metadata, when compression should be used.
+ ///
+ private readonly int compressTextThreshold;
+
///
/// Gets or sets the alpha threshold value
///
@@ -88,12 +96,12 @@ internal sealed class PngEncoderCore : IDisposable
private IQuantizer quantizer;
///
- /// Gets or sets a value indicating whether to write the gamma chunk
+ /// Gets or sets a value indicating whether to write the gamma chunk.
///
private bool writeGamma;
///
- /// The png bit depth
+ /// The png bit depth.
///
private PngBitDepth? pngBitDepth;
@@ -191,6 +199,7 @@ public PngEncoderCore(MemoryAllocator memoryAllocator, IPngEncoderOptions option
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
this.threshold = options.Threshold;
+ this.compressTextThreshold = options.CompressTextThreshold;
}
///
@@ -292,7 +301,7 @@ public void Encode(Image image, Stream stream)
this.WritePaletteChunk(stream, quantized);
}
- if (pngMetadata.HasTrans)
+ if (pngMetadata.HasTransparency)
{
this.WriteTransparencyChunk(stream, pngMetadata);
}
@@ -300,6 +309,7 @@ public void Encode(Image image, Stream stream)
this.WritePhysicalChunk(stream, metadata);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, metadata);
+ this.WriteTextChunks(stream, pngMetadata);
this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
this.WriteEndChunk(stream);
stream.Flush();
@@ -433,71 +443,71 @@ private void CollectTPixelBytes(ReadOnlySpan rowSpan)
switch (this.bytesPerPixel)
{
case 4:
- {
- // 8 bit Rgba
- PixelOperations.Instance.ToRgba32Bytes(
- this.configuration,
- rowSpan,
- rawScanlineSpan,
- this.width);
- break;
- }
+ {
+ // 8 bit Rgba
+ PixelOperations.Instance.ToRgba32Bytes(
+ this.configuration,
+ rowSpan,
+ rawScanlineSpan,
+ this.width);
+ break;
+ }
case 3:
- {
- // 8 bit Rgb
- PixelOperations.Instance.ToRgb24Bytes(
- this.configuration,
- rowSpan,
- rawScanlineSpan,
- this.width);
- break;
- }
+ {
+ // 8 bit Rgb
+ PixelOperations.Instance.ToRgb24Bytes(
+ this.configuration,
+ rowSpan,
+ rawScanlineSpan,
+ this.width);
+ break;
+ }
case 8:
+ {
+ // 16 bit Rgba
+ using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length))
{
- // 16 bit Rgba
- using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length))
+ Span rgbaSpan = rgbaBuffer.GetSpan();
+ ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan);
+ PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan);
+
+ // Can't map directly to byte array as it's big endian.
+ for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8)
{
- Span rgbaSpan = rgbaBuffer.GetSpan();
- ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan);
- PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan);
-
- // Can't map directly to byte array as it's big endian.
- for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8)
- {
- Rgba64 rgba = Unsafe.Add(ref rgbaRef, x);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A);
- }
+ Rgba64 rgba = Unsafe.Add(ref rgbaRef, x);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A);
}
-
- break;
}
+ break;
+ }
+
default:
+ {
+ // 16 bit Rgb
+ using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length))
{
- // 16 bit Rgb
- using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length))
+ Span rgbSpan = rgbBuffer.GetSpan();
+ ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan);
+ PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan);
+
+ // Can't map directly to byte array as it's big endian.
+ for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6)
{
- Span rgbSpan = rgbBuffer.GetSpan();
- ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan);
- PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan);
-
- // Can't map directly to byte array as it's big endian.
- for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6)
- {
- Rgb48 rgb = Unsafe.Add(ref rgbRef, x);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G);
- BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B);
- }
+ Rgb48 rgb = Unsafe.Add(ref rgbRef, x);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G);
+ BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B);
}
-
- break;
}
+
+ break;
+ }
}
}
@@ -738,6 +748,85 @@ private void WriteExifChunk(Stream stream, ImageMetadata meta)
}
}
+ ///
+ /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk,
+ /// depending whether the text contains any latin characters or should be compressed.
+ ///
+ /// The containing image data.
+ /// The image metadata.
+ private void WriteTextChunks(Stream stream, PngMetadata meta)
+ {
+ const int MaxLatinCode = 255;
+ foreach (PngTextData textData in meta.TextData)
+ {
+ bool hasUnicodeCharacters = textData.Value.Any(c => c > MaxLatinCode);
+ if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
+ {
+ // Write iTXt chunk.
+ byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
+ byte[] textBytes = textData.Value.Length > this.compressTextThreshold
+ ? this.GetCompressedTextBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
+ : PngConstants.TranslatedEncoding.GetBytes(textData.Value);
+
+ byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword);
+ byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag);
+
+ Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5];
+ keywordBytes.CopyTo(outputBytes);
+ if (textData.Value.Length > this.compressTextThreshold)
+ {
+ // Indicate that the text is compressed.
+ outputBytes[keywordBytes.Length + 1] = 1;
+ }
+
+ int keywordStart = keywordBytes.Length + 3;
+ languageTag.CopyTo(outputBytes.Slice(keywordStart));
+ int translatedKeywordStart = keywordStart + languageTag.Length + 1;
+ translatedKeyword.CopyTo(outputBytes.Slice(translatedKeywordStart));
+ textBytes.CopyTo(outputBytes.Slice(translatedKeywordStart + translatedKeyword.Length + 1));
+ this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes.ToArray());
+ }
+ else
+ {
+ if (textData.Value.Length > this.compressTextThreshold)
+ {
+ // Write zTXt chunk.
+ byte[] compressedData = this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
+ Span outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2];
+ PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
+ compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2));
+ this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray());
+ }
+ else
+ {
+ // Write tEXt chunk.
+ Span outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1];
+ PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
+ PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes.Slice(textData.Keyword.Length + 1));
+ this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Compresses a given text using Zlib compression.
+ ///
+ /// The text bytes to compress.
+ /// The compressed text byte array.
+ private byte[] GetCompressedTextBytes(byte[] textBytes)
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ using (var deflateStream = new ZlibDeflateStream(memoryStream, this.compressionLevel))
+ {
+ deflateStream.Write(textBytes);
+ }
+
+ return memoryStream.ToArray();
+ }
+ }
+
///
/// Writes the gamma information to the stream.
///
diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs
index dd951763f7..8111382639 100644
--- a/src/ImageSharp/Formats/Png/PngMetaData.cs
+++ b/src/ImageSharp/Formats/Png/PngMetaData.cs
@@ -1,6 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@@ -26,11 +27,16 @@ private PngMetadata(PngMetadata other)
this.BitDepth = other.BitDepth;
this.ColorType = other.ColorType;
this.Gamma = other.Gamma;
- this.HasTrans = other.HasTrans;
+ this.HasTransparency = other.HasTransparency;
this.TransparentGray8 = other.TransparentGray8;
this.TransparentGray16 = other.TransparentGray16;
this.TransparentRgb24 = other.TransparentRgb24;
this.TransparentRgb48 = other.TransparentRgb48;
+
+ for (int i = 0; i < other.TextData.Count; i++)
+ {
+ this.TextData.Add(other.TextData[i]);
+ }
}
///
@@ -70,11 +76,39 @@ private PngMetadata(PngMetadata other)
public Gray16? TransparentGray16 { get; set; }
///
- /// Gets or sets a value indicating whether the image has transparency chunk and markers were decoded
+ /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded.
+ ///
+ public bool HasTransparency { get; set; }
+
+ ///
+ /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks.
+ /// Used for conveying textual information associated with the image.
+ ///
+ public IList TextData { get; set; } = new List();
+
+ ///
+ /// Gets the list of png text properties for storing meta information about this image.
///
- public bool HasTrans { get; set; }
+ public IList PngTextProperties { get; } = new List();
///
public IDeepCloneable DeepClone() => new PngMetadata(this);
+
+ internal bool TryGetPngTextProperty(string keyword, out PngTextData result)
+ {
+ for (int i = 0; i < this.TextData.Count; i++)
+ {
+ if (this.TextData[i].Keyword == keyword)
+ {
+ result = this.TextData[i];
+
+ return true;
+ }
+ }
+
+ result = default;
+
+ return false;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngTextData.cs b/src/ImageSharp/Formats/Png/PngTextData.cs
new file mode 100644
index 0000000000..21171487ea
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/PngTextData.cs
@@ -0,0 +1,143 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// Stores text data contained in the iTXt, tEXt, and zTXt chunks.
+ /// Used for conveying textual information associated with the image, like the name of the author,
+ /// the copyright information, the date, where the image was created, or some other information.
+ ///
+ public readonly struct PngTextData : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The keyword of the property.
+ /// The value of the property.
+ /// An optional language tag.
+ /// A optional translated keyword.
+ public PngTextData(string keyword, string value, string languageTag, string translatedKeyword)
+ {
+ Guard.NotNullOrWhiteSpace(keyword, nameof(keyword));
+
+ // No leading or trailing whitespace is allowed in keywords.
+ this.Keyword = keyword.Trim();
+ this.Value = value;
+ this.LanguageTag = languageTag;
+ this.TranslatedKeyword = translatedKeyword;
+ }
+
+ ///
+ /// Gets the keyword of this which indicates
+ /// the type of information represented by the text string as described in https://www.w3.org/TR/PNG/#11keywords.
+ ///
+ ///
+ /// Typical properties are the author, copyright information or other meta information.
+ ///
+ public string Keyword { get; }
+
+ ///
+ /// Gets the value of this .
+ ///
+ public string Value { get; }
+
+ ///
+ /// Gets an optional language tag defined in https://www.w3.org/TR/PNG/#2-RFC-3066 indicates the human language used by the translated keyword and the text.
+ /// If the first word is two or three letters long, it is an ISO language code https://www.w3.org/TR/PNG/#2-ISO-639.
+ ///
+ ///
+ /// Examples: cn, en-uk, no-bok, x-klingon, x-KlInGoN.
+ ///
+ public string LanguageTag { get; }
+
+ ///
+ /// Gets an optional translated keyword, should contain a translation of the keyword into the language indicated by the language tag.
+ ///
+ public string TranslatedKeyword { get; }
+
+ ///
+ /// Compares two objects. The result specifies whether the values
+ /// of the properties of the two objects are equal.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(PngTextData left, PngTextData right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects. The result specifies whether the values
+ /// of the properties of the two objects are unequal.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(PngTextData left, PngTextData right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Indicates whether this instance and a specified object are equal.
+ ///
+ ///
+ /// The object to compare with the current instance.
+ ///
+ ///
+ /// true if and this instance are the same type and represent the
+ /// same value; otherwise, false.
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is PngTextData other && this.Equals(other);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ public override int GetHashCode() => HashCode.Combine(this.Keyword, this.Value, this.LanguageTag, this.TranslatedKeyword);
+
+ ///
+ /// Returns the fully qualified type name of this instance.
+ ///
+ ///
+ /// A containing a fully qualified type name.
+ ///
+ public override string ToString() => $"PngTextData [ Name={this.Keyword}, Value={this.Value} ]";
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ ///
+ /// True if the current object is equal to the parameter; otherwise, false.
+ ///
+ /// An object to compare with this object.
+ public bool Equals(PngTextData other)
+ {
+ return this.Keyword.Equals(other.Keyword)
+ && this.Value.Equals(other.Value)
+ && this.LanguageTag.Equals(other.LanguageTag)
+ && this.TranslatedKeyword.Equals(other.TranslatedKeyword);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs
index b9efca4fee..b3751bfbdc 100644
--- a/src/ImageSharp/MetaData/ImageMetaData.cs
+++ b/src/ImageSharp/MetaData/ImageMetaData.cs
@@ -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.Collections.Generic;
@@ -63,11 +63,6 @@ private ImageMetadata(ImageMetadata other)
this.formatMetadata.Add(meta.Key, meta.Value.DeepClone());
}
- foreach (ImageProperty property in other.Properties)
- {
- this.Properties.Add(property);
- }
-
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
}
@@ -127,11 +122,6 @@ public double VerticalResolution
///
public IccProfile IccProfile { get; set; }
- ///
- /// Gets the list of properties for storing meta information about this image.
- ///
- public IList Properties { get; } = new List();
-
///
/// Gets the metadata value associated with the specified key.
///
@@ -156,29 +146,6 @@ public TFormatMetadata GetFormatMetadata(IImageFormat
public ImageMetadata DeepClone() => new ImageMetadata(this);
- ///
- /// Looks up a property with the provided name.
- ///
- /// The name of the property to lookup.
- /// The property, if found, with the provided name.
- /// Whether the property was found.
- internal bool TryGetProperty(string name, out ImageProperty result)
- {
- foreach (ImageProperty property in this.Properties)
- {
- if (property.Name == name)
- {
- result = property;
-
- return true;
- }
- }
-
- result = default;
-
- return false;
- }
-
///
/// Synchronizes the profiles with the current metadata.
///
diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs
deleted file mode 100644
index 905e42dab1..0000000000
--- a/src/ImageSharp/MetaData/ImageProperty.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-
-namespace SixLabors.ImageSharp.Metadata
-{
- ///
- /// Stores meta information about a image, like the name of the author,
- /// the copyright information, the date, where the image was created
- /// or some other information.
- ///
- public readonly struct ImageProperty : IEquatable
- {
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The name of the property.
- /// The value of the property.
- public ImageProperty(string name, string value)
- {
- Guard.NotNullOrWhiteSpace(name, nameof(name));
-
- this.Name = name;
- this.Value = value;
- }
-
- ///
- /// Gets the name of this indicating which kind of
- /// information this property stores.
- ///
- ///
- /// Typical properties are the author, copyright
- /// information or other meta information.
- ///
- public string Name { get; }
-
- ///
- /// Gets the value of this .
- ///
- public string Value { get; }
-
- ///
- /// Compares two objects. The result specifies whether the values
- /// of the or properties of the two
- /// objects are equal.
- ///
- ///
- /// The on the left side of the operand.
- ///
- ///
- /// The on the right side of the operand.
- ///
- ///
- /// True if the current left is equal to the parameter; otherwise, false.
- ///
- public static bool operator ==(ImageProperty left, ImageProperty right)
- {
- return left.Equals(right);
- }
-
- ///
- /// Compares two objects. The result specifies whether the values
- /// of the or properties of the two
- /// objects are unequal.
- ///
- ///
- /// The on the left side of the operand.
- ///
- ///
- /// The on the right side of the operand.
- ///
- ///
- /// True if the current left is unequal to the parameter; otherwise, false.
- ///
- public static bool operator !=(ImageProperty left, ImageProperty right)
- {
- return !(left == right);
- }
-
- ///
- /// Indicates whether this instance and a specified object are equal.
- ///
- ///
- /// The object to compare with the current instance.
- ///
- ///
- /// true if and this instance are the same type and represent the
- /// same value; otherwise, false.
- ///
- public override bool Equals(object obj)
- {
- return obj is ImageProperty other && this.Equals(other);
- }
-
- ///
- /// Returns the hash code for this instance.
- ///
- ///
- /// A 32-bit signed integer that is the hash code for this instance.
- ///
- public override int GetHashCode() => HashCode.Combine(this.Name, this.Value);
-
- ///
- /// Returns the fully qualified type name of this instance.
- ///
- ///
- /// A containing a fully qualified type name.
- ///
- public override string ToString() => $"ImageProperty [ Name={this.Name}, Value={this.Value} ]";
-
- ///
- /// Indicates whether the current object is equal to another object of the same type.
- ///
- ///
- /// True if the current object is equal to the parameter; otherwise, false.
- ///
- /// An object to compare with this object.
- public bool Equals(ImageProperty other)
- {
- return this.Name.Equals(other.Name) && Equals(this.Value, other.Value);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs
index afe7eb04ff..4f9d22ec2a 100644
--- a/tests/ImageSharp.Sandbox46/Program.cs
+++ b/tests/ImageSharp.Sandbox46/Program.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index 784f7ce703..1f49b67131 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
@@ -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;
@@ -37,14 +37,6 @@ public class GifDecoderTests
TestImages.Gif.Issues.BadDescriptorWidth
};
- public static readonly TheoryData RatioFiles =
- new TheoryData
- {
- { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch},
- { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
- { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
- };
-
private static readonly Dictionary BasicVerificationFrameCount =
new Dictionary
{
@@ -91,40 +83,6 @@ public unsafe void Decode_NonTerminatedFinalFrame()
}
}
- [Theory]
- [MemberData(nameof(RatioFiles))]
- public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- var decoder = new GifDecoder();
- using (Image image = decoder.Decode(Configuration.Default, stream))
- {
- ImageMetadata meta = image.Metadata;
- Assert.Equal(xResolution, meta.HorizontalResolution);
- Assert.Equal(yResolution, meta.VerticalResolution);
- Assert.Equal(resolutionUnit, meta.ResolutionUnits);
- }
- }
- }
-
- [Theory]
- [MemberData(nameof(RatioFiles))]
- public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- var decoder = new GifDecoder();
- IImageInfo image = decoder.Identify(Configuration.Default, stream);
- ImageMetadata meta = image.Metadata;
- Assert.Equal(xResolution, meta.HorizontalResolution);
- Assert.Equal(yResolution, meta.VerticalResolution);
- Assert.Equal(resolutionUnit, meta.ResolutionUnits);
- }
- }
-
[Theory]
[WithFile(TestImages.Gif.Trans, TestPixelTypes)]
public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider)
@@ -155,57 +113,6 @@ public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(1, image.Metadata.Properties.Count);
- Assert.Equal("Comments", image.Metadata.Properties[0].Name);
- Assert.Equal("ImageSharp", image.Metadata.Properties[0].Value);
- }
- }
-
- [Fact]
- public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
- {
- var options = new GifDecoder
- {
- IgnoreMetadata = true
- };
-
- var testFile = TestFile.Create(TestImages.Gif.Rings);
-
- using (Image image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(0, image.Metadata.Properties.Count);
- }
- }
-
- [Fact]
- public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
- {
- var options = new GifDecoder
- {
- TextEncoding = Encoding.Unicode
- };
-
- var testFile = TestFile.Create(TestImages.Gif.Rings);
-
- using (Image image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(1, image.Metadata.Properties.Count);
- Assert.Equal("浉条卥慨灲", image.Metadata.Properties[0].Value);
- }
- }
-
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void CanDecodeJustOneFrame(TestImageProvider provider)
@@ -258,4 +165,4 @@ public void CanDecodeIntermingledImages()
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index eab30944e9..9424278190 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
@@ -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.IO;
@@ -92,55 +92,9 @@ public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten()
memStream.Position = 0;
using (var output = Image.Load(memStream))
{
- Assert.Equal(1, output.Metadata.Properties.Count);
- Assert.Equal("Comments", output.Metadata.Properties[0].Name);
- Assert.Equal("ImageSharp", output.Metadata.Properties[0].Value);
- }
- }
- }
- }
-
- [Fact]
- public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten()
- {
- var options = new GifEncoder();
-
- var testFile = TestFile.Create(TestImages.Gif.Rings);
-
- using (Image input = testFile.CreateRgba32Image())
- {
- input.Metadata.Properties.Clear();
- using (var memStream = new MemoryStream())
- {
- input.SaveAsGif(memStream, options);
-
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- Assert.Equal(0, output.Metadata.Properties.Count);
- }
- }
- }
- }
-
- [Fact]
- public void Encode_WhenCommentIsTooLong_CommentIsTrimmed()
- {
- using (var input = new Image(1, 1))
- {
- string comments = new string('c', 256);
- input.Metadata.Properties.Add(new ImageProperty("Comments", comments));
-
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, new GifEncoder());
-
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- Assert.Equal(1, output.Metadata.Properties.Count);
- Assert.Equal("Comments", output.Metadata.Properties[0].Name);
- Assert.Equal(255, output.Metadata.Properties[0].Value.Length);
+ GifMetadata metadata = output.Metadata.GetFormatMetadata(GifFormat.Instance);
+ Assert.Equal(1, metadata.Comments.Count);
+ Assert.Equal("ImageSharp", metadata.Comments[0]);
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs
index 8510a3461c..2d554eb620 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs
@@ -1,13 +1,29 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
public class GifMetaDataTests
{
+ public static readonly TheoryData RatioFiles =
+ new TheoryData
+ {
+ { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch},
+ { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
+ { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
+ };
+
[Fact]
public void CloneIsDeep()
{
@@ -15,7 +31,9 @@ public void CloneIsDeep()
{
RepeatCount = 1,
ColorTableMode = GifColorTableMode.Global,
- GlobalColorTableLength = 2
+ GlobalColorTableLength = 2,
+ Comments = new List() { "Foo" }
+
};
var clone = (GifMetadata)meta.DeepClone();
@@ -27,6 +45,114 @@ public void CloneIsDeep()
Assert.False(meta.RepeatCount.Equals(clone.RepeatCount));
Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode));
Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength));
+ Assert.False(meta.Comments.Equals(clone.Comments));
+ Assert.True(meta.Comments.SequenceEqual(clone.Comments));
+ }
+
+ [Fact]
+ public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
+ {
+ var options = new GifDecoder
+ {
+ IgnoreMetadata = false
+ };
+
+ var testFile = TestFile.Create(TestImages.Gif.Rings);
+
+ using (Image image = testFile.CreateRgba32Image(options))
+ {
+ GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance);
+ Assert.Equal(1, metadata.Comments.Count);
+ Assert.Equal("ImageSharp", metadata.Comments[0]);
+ }
+ }
+
+ [Fact]
+ public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
+ {
+ var options = new GifDecoder
+ {
+ IgnoreMetadata = true
+ };
+
+ var testFile = TestFile.Create(TestImages.Gif.Rings);
+
+ using (Image image = testFile.CreateRgba32Image(options))
+ {
+ GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance);
+ Assert.Equal(0, metadata.Comments.Count);
+ }
+ }
+
+ [Fact]
+ public void Decode_CanDecodeLargeTextComment()
+ {
+ var options = new GifDecoder();
+ var testFile = TestFile.Create(TestImages.Gif.LargeComment);
+
+ using (Image image = testFile.CreateRgba32Image(options))
+ {
+ GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance);
+ Assert.Equal(2, metadata.Comments.Count);
+ Assert.Equal(new string('c', 349), metadata.Comments[0]);
+ Assert.Equal("ImageSharp", metadata.Comments[1]);
+ }
+ }
+
+ [Fact]
+ public void Encode_PreservesTextData()
+ {
+ var decoder = new GifDecoder();
+ var testFile = TestFile.Create(TestImages.Gif.LargeComment);
+
+ using (Image input = testFile.CreateRgba32Image(decoder))
+ using (var memoryStream = new MemoryStream())
+ {
+ input.Save(memoryStream, new GifEncoder());
+ memoryStream.Position = 0;
+
+ using (Image image = decoder.Decode(Configuration.Default, memoryStream))
+ {
+ GifMetadata metadata = image.Metadata.GetFormatMetadata(GifFormat.Instance);
+ Assert.Equal(2, metadata.Comments.Count);
+ Assert.Equal(new string('c', 349), metadata.Comments[0]);
+ Assert.Equal("ImageSharp", metadata.Comments[1]);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new GifDecoder();
+ IImageInfo image = decoder.Identify(Configuration.Default, stream);
+ ImageMetadata meta = image.Metadata;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new GifDecoder();
+ using (Image image = decoder.Decode(Configuration.Default, stream))
+ {
+ ImageMetadata meta = image.Metadata;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 5bb2db7848..2e9fd7481e 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -3,12 +3,9 @@
// ReSharper disable InconsistentNaming
-using System.Buffers.Binary;
using System.IO;
-using System.Text;
using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -77,14 +74,6 @@ public partial class PngDecoderTests
TestImages.Png.GrayAlpha8BitInterlaced
};
- public static readonly TheoryData RatioFiles =
- new TheoryData
- {
- { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter},
- { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
- { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
- };
-
[Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
public void Decode(TestImageProvider provider)
@@ -193,57 +182,6 @@ public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(1, image.Metadata.Properties.Count);
- Assert.Equal("Software", image.Metadata.Properties[0].Name);
- Assert.Equal("paint.net 4.0.6", image.Metadata.Properties[0].Value);
- }
- }
-
- [Fact]
- public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored()
- {
- var options = new PngDecoder()
- {
- IgnoreMetadata = true
- };
-
- var testFile = TestFile.Create(TestImages.Png.Blur);
-
- using (Image image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(0, image.Metadata.Properties.Count);
- }
- }
-
- [Fact]
- public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
- {
- var options = new PngDecoder()
- {
- TextEncoding = Encoding.Unicode
- };
-
- var testFile = TestFile.Create(TestImages.Png.Blur);
-
- using (Image image = testFile.CreateRgba32Image(options))
- {
- Assert.Equal(1, image.Metadata.Properties.Count);
- Assert.Equal("潓瑦慷敲", image.Metadata.Properties[0].Name);
- }
- }
-
[Theory]
[InlineData(TestImages.Png.Bpp1, 1)]
[InlineData(TestImages.Png.Gray4Bpp, 4)]
@@ -260,39 +198,5 @@ public void Identify(string imagePath, int expectedPixelSize)
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
}
-
- [Theory]
- [MemberData(nameof(RatioFiles))]
- public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- var decoder = new PngDecoder();
- using (Image image = decoder.Decode(Configuration.Default, stream))
- {
- ImageMetadata meta = image.Metadata;
- Assert.Equal(xResolution, meta.HorizontalResolution);
- Assert.Equal(yResolution, meta.VerticalResolution);
- Assert.Equal(resolutionUnit, meta.ResolutionUnits);
- }
- }
- }
-
- [Theory]
- [MemberData(nameof(RatioFiles))]
- public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- var decoder = new PngDecoder();
- IImageInfo image = decoder.Identify(Configuration.Default, stream);
- ImageMetadata meta = image.Metadata;
- Assert.Equal(xResolution, meta.HorizontalResolution);
- Assert.Equal(yResolution, meta.VerticalResolution);
- Assert.Equal(resolutionUnit, meta.ResolutionUnits);
- }
- }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index b8178fd4f3..3f36513ef9 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -271,7 +271,7 @@ public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngCo
using (Image input = testFile.CreateRgba32Image())
{
PngMetadata inMeta = input.Metadata.GetFormatMetadata(PngFormat.Instance);
- Assert.True(inMeta.HasTrans);
+ Assert.True(inMeta.HasTransparency);
using (var memStream = new MemoryStream())
{
@@ -280,7 +280,7 @@ public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngCo
using (var output = Image.Load(memStream))
{
PngMetadata outMeta = output.Metadata.GetFormatMetadata(PngFormat.Instance);
- Assert.True(outMeta.HasTrans);
+ Assert.True(outMeta.HasTransparency);
switch (pngColorType)
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs
index 72fc2f8656..db4d7d69d4 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs
@@ -1,13 +1,26 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class PngMetaDataTests
{
+ public static readonly TheoryData RatioFiles =
+ new TheoryData
+ {
+ { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter},
+ { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
+ { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
+ };
+
[Fact]
public void CloneIsDeep()
{
@@ -15,8 +28,10 @@ public void CloneIsDeep()
{
BitDepth = PngBitDepth.Bit16,
ColorType = PngColorType.GrayscaleWithAlpha,
- Gamma = 2
+ Gamma = 2,
+ TextData = new List() { new PngTextData("name", "value", "foo", "bar") }
};
+
var clone = (PngMetadata)meta.DeepClone();
clone.BitDepth = PngBitDepth.Bit2;
@@ -26,6 +41,180 @@ public void CloneIsDeep()
Assert.False(meta.BitDepth.Equals(clone.BitDepth));
Assert.False(meta.ColorType.Equals(clone.ColorType));
Assert.False(meta.Gamma.Equals(clone.Gamma));
+ Assert.False(meta.TextData.Equals(clone.TextData));
+ Assert.True(meta.TextData.SequenceEqual(clone.TextData));
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)]
+ public void Decoder_CanReadTextData(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new PngDecoder()))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort"));
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)]
+ public void Encoder_PreservesTextData(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ var decoder = new PngDecoder();
+ using (Image input = provider.GetImage(decoder))
+ using (var memoryStream = new MemoryStream())
+ {
+ input.Save(memoryStream, new PngEncoder());
+
+ memoryStream.Position = 0;
+ using (Image image = decoder.Decode(Configuration.Default, memoryStream))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag"));
+ Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort"));
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)]
+ public void Decoder_IgnoresInvalidTextData(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new PngDecoder()))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("leading space"));
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("trailing space"));
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("space"));
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("empty"));
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("invalid characters"));
+ Assert.DoesNotContain(meta.TextData, m => m.Value.Equals("too large"));
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.PngWithMetaData, PixelTypes.Rgba32)]
+ public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ var decoder = new PngDecoder();
+ using (Image input = provider.GetImage(decoder))
+ using (var memoryStream = new MemoryStream())
+ {
+ // this will be a zTXt chunk.
+ var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty);
+ // this will be a iTXt chunk.
+ var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword");
+ PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance);
+ inputMetadata.TextData.Add(expectedText);
+ inputMetadata.TextData.Add(expectedTextNoneLatin);
+ input.Save(memoryStream, new PngEncoder()
+ {
+ CompressTextThreshold = 50
+ });
+
+ memoryStream.Position = 0;
+ using (Image image = decoder.Decode(Configuration.Default, memoryStream))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.Contains(meta.TextData, m => m.Equals(expectedText));
+ Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin));
+ }
+ }
+ }
+
+ [Fact]
+ public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead()
+ {
+ var options = new PngDecoder()
+ {
+ IgnoreMetadata = false
+ };
+
+ var testFile = TestFile.Create(TestImages.Png.Blur);
+
+ using (Image image = testFile.CreateRgba32Image(options))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+
+ Assert.Equal(1, meta.TextData.Count);
+ Assert.Equal("Software", meta.TextData[0].Keyword);
+ Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value);
+ Assert.Equal(0.4545d, meta.Gamma, precision: 4);
+ }
+ }
+
+ [Fact]
+ public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored()
+ {
+ var options = new PngDecoder()
+ {
+ IgnoreMetadata = true
+ };
+
+ var testFile = TestFile.Create(TestImages.Png.Blur);
+
+ using (Image image = testFile.CreateRgba32Image(options))
+ {
+ PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
+ Assert.Equal(0, meta.TextData.Count);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new PngDecoder();
+ using (Image image = decoder.Decode(Configuration.Default, stream))
+ {
+ ImageMetadata meta = image.Metadata;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(RatioFiles))]
+ public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new PngDecoder();
+ IImageInfo image = decoder.Identify(Configuration.Default, stream);
+ ImageMetadata meta = image.Metadata;
+ Assert.Equal(xResolution, meta.HorizontalResolution);
+ Assert.Equal(yResolution, meta.VerticalResolution);
+ Assert.Equal(resolutionUnit, meta.ResolutionUnits);
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs
new file mode 100644
index 0000000000..72c0fd7ab0
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Formats.Png;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Png
+{
+ ///
+ /// Tests the class.
+ ///
+ public class PngTextDataTests
+ {
+ ///
+ /// Tests the equality operators for inequality.
+ ///
+ [Fact]
+ public void AreEqual()
+ {
+ var property1 = new PngTextData("Foo", "Bar", "foo", "bar");
+ var property2 = new PngTextData("Foo", "Bar", "foo", "bar");
+
+ Assert.Equal(property1, property2);
+ Assert.True(property1 == property2);
+ }
+
+ ///
+ /// Tests the equality operators for equality.
+ ///
+ [Fact]
+ public void AreNotEqual()
+ {
+ var property1 = new PngTextData("Foo", "Bar", "foo", "bar");
+ var property2 = new PngTextData("Foo", "Foo", string.Empty, string.Empty);
+ var property3 = new PngTextData("Bar", "Bar", "unit", "test");
+ var property4 = new PngTextData("Foo", null, "test", "case");
+
+ Assert.NotEqual(property1, property2);
+ Assert.True(property1 != property2);
+
+ Assert.NotEqual(property1, property3);
+ Assert.NotEqual(property1, property4);
+ }
+
+ ///
+ /// Tests whether the constructor throws an exception when the property keyword is null or empty.
+ ///
+ [Fact]
+ public void ConstructorThrowsWhenKeywordIsNullOrEmpty()
+ {
+ Assert.Throws(() => new PngTextData(null, "Foo", "foo", "bar"));
+
+ Assert.Throws(() => new PngTextData(string.Empty, "Foo", "foo", "bar"));
+ }
+
+ ///
+ /// Tests whether the constructor correctly assigns properties.
+ ///
+ [Fact]
+ public void ConstructorAssignsProperties()
+ {
+ var property = new PngTextData("Foo", null, "unit", "test");
+ Assert.Equal("Foo", property.Keyword);
+ Assert.Null(property.Value);
+ Assert.Equal("unit", property.LanguageTag);
+ Assert.Equal("test", property.TranslatedKeyword);
+
+ property = new PngTextData("Foo", string.Empty, string.Empty, null);
+ Assert.Equal("Foo", property.Keyword);
+ Assert.Equal(string.Empty, property.Value);
+ Assert.Equal(string.Empty, property.LanguageTag);
+ Assert.Null(property.TranslatedKeyword);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
index 5f02ce7aeb..6730605e98 100644
--- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
@@ -1,6 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
+
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -9,7 +11,7 @@
using Xunit;
-namespace SixLabors.ImageSharp.Tests
+namespace SixLabors.ImageSharp.Tests.MetaData
{
///
/// Tests the class.
@@ -22,33 +24,27 @@ public void ConstructorImageMetaData()
var metaData = new ImageMetadata();
var exifProfile = new ExifProfile();
- var imageProperty = new ImageProperty("name", "value");
metaData.ExifProfile = exifProfile;
metaData.HorizontalResolution = 4;
metaData.VerticalResolution = 2;
- metaData.Properties.Add(imageProperty);
ImageMetadata clone = metaData.DeepClone();
Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray());
Assert.Equal(4, clone.HorizontalResolution);
Assert.Equal(2, clone.VerticalResolution);
- Assert.Equal(imageProperty, clone.Properties[0]);
}
[Fact]
public void CloneIsDeep()
{
- var metaData = new ImageMetadata();
-
- var exifProfile = new ExifProfile();
- var imageProperty = new ImageProperty("name", "value");
-
- metaData.ExifProfile = exifProfile;
- metaData.HorizontalResolution = 4;
- metaData.VerticalResolution = 2;
- metaData.Properties.Add(imageProperty);
+ var metaData = new ImageMetadata
+ {
+ ExifProfile = new ExifProfile(),
+ HorizontalResolution = 4,
+ VerticalResolution = 2
+ };
ImageMetadata clone = metaData.DeepClone();
clone.HorizontalResolution = 2;
@@ -57,8 +53,6 @@ public void CloneIsDeep()
Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile));
Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution));
Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution));
- Assert.False(metaData.Properties.Equals(clone.Properties));
- Assert.False(metaData.GetFormatMetadata(GifFormat.Instance).Equals(clone.GetFormatMetadata(GifFormat.Instance)));
}
[Fact]
@@ -100,15 +94,17 @@ public void SyncProfiles()
exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
- var image = new Image(1, 1);
- image.Metadata.ExifProfile = exifProfile;
- image.Metadata.HorizontalResolution = 400;
- image.Metadata.VerticalResolution = 500;
+ using (var image = new Image(1, 1))
+ {
+ image.Metadata.ExifProfile = exifProfile;
+ image.Metadata.HorizontalResolution = 400;
+ image.Metadata.VerticalResolution = 500;
- image.Metadata.SyncProfiles();
+ image.Metadata.SyncProfiles();
- Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble());
- Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble());
+ Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble());
+ Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble());
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs
deleted file mode 100644
index 8cce5ba414..0000000000
--- a/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using SixLabors.ImageSharp.Metadata;
-using Xunit;
-
-namespace SixLabors.ImageSharp.Tests
-{
- ///
- /// Tests the class.
- ///
- public class ImagePropertyTests
- {
- ///
- /// Tests the equality operators for inequality.
- ///
- [Fact]
- public void AreEqual()
- {
- var property1 = new ImageProperty("Foo", "Bar");
- var property2 = new ImageProperty("Foo", "Bar");
-
- Assert.Equal(property1, property2);
- Assert.True(property1 == property2);
- }
-
- ///
- /// Tests the equality operators for equality.
- ///
- [Fact]
- public void AreNotEqual()
- {
- var property1 = new ImageProperty("Foo", "Bar");
- var property2 = new ImageProperty("Foo", "Foo");
- var property3 = new ImageProperty("Bar", "Bar");
- var property4 = new ImageProperty("Foo", null);
-
- Assert.False(property1.Equals("Foo"));
-
- Assert.NotEqual(property1, property2);
- Assert.True(property1 != property2);
-
- Assert.NotEqual(property1, property3);
- Assert.NotEqual(property1, property4);
- }
-
- ///
- /// Tests whether the constructor throws an exception when the property name is null or empty.
- ///
- [Fact]
- public void ConstructorThrowsWhenNameIsNullOrEmpty()
- {
- Assert.Throws(() => new ImageProperty(null, "Foo"));
-
- Assert.Throws(() => new ImageProperty(string.Empty, "Foo"));
- }
-
- ///
- /// Tests whether the constructor correctly assigns properties.
- ///
- [Fact]
- public void ConstructorAssignsProperties()
- {
- var property = new ImageProperty("Foo", null);
- Assert.Equal("Foo", property.Name);
- Assert.Null(property.Value);
-
- property = new ImageProperty("Foo", string.Empty);
- Assert.Equal(string.Empty, property.Value);
- }
- }
-}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 754ce20ca9..e95ce09073 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -54,6 +54,8 @@ public static class Png
public const string Gray4BitTrans = "Png/gray-4-tRNS.png";
public const string Gray8BitTrans = "Png/gray-8-tRNS.png";
public const string LowColorVariance = "Png/low-variance.png";
+ public const string PngWithMetaData = "Png/PngWithMetaData.png";
+ public const string InvalidTextData = "Png/InvalidTextData.png";
// Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
public const string Filter0 = "Png/filter0.png";
@@ -343,6 +345,7 @@ public static class Gif
public const string Leo = "Gif/leo.gif";
public const string Ratio4x1 = "Gif/base_4x1.gif";
public const string Ratio1x4 = "Gif/base_1x4.gif";
+ public const string LargeComment = "Gif/large_comment.gif";
public static class Issues
{
diff --git a/tests/Images/Input/Gif/large_comment.gif b/tests/Images/Input/Gif/large_comment.gif
new file mode 100644
index 0000000000..1d378fbf88
Binary files /dev/null and b/tests/Images/Input/Gif/large_comment.gif differ
diff --git a/tests/Images/Input/Png/InvalidTextData.png b/tests/Images/Input/Png/InvalidTextData.png
new file mode 100644
index 0000000000..59f8a97562
Binary files /dev/null and b/tests/Images/Input/Png/InvalidTextData.png differ
diff --git a/tests/Images/Input/Png/PngWithMetaData.png b/tests/Images/Input/Png/PngWithMetaData.png
new file mode 100644
index 0000000000..af417b1f30
Binary files /dev/null and b/tests/Images/Input/Png/PngWithMetaData.png differ
diff --git a/tests/Images/Input/Png/versioning-1_1.png b/tests/Images/Input/Png/versioning-1_1.png
index c13f98fd16..96fb7b078d 100644
Binary files a/tests/Images/Input/Png/versioning-1_1.png and b/tests/Images/Input/Png/versioning-1_1.png differ