diff --git a/ImageSharp.sln b/ImageSharp.sln index 83208994c..555576435 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28902.138 diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index ea4cd1c8c..be2e964fc 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Advanced /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! /// [Preserve] - private static void SeedEverything() + private static void SeedPixelFormats() { try { @@ -199,6 +200,7 @@ namespace SixLabors.ImageSharp.Advanced default(JpegEncoderCore).Encode(default, default, default); default(PngEncoderCore).Encode(default, default, default); default(TgaEncoderCore).Encode(default, default, default); + default(TiffEncoderCore).Encode(default, default, default); } /// @@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Advanced default(JpegDecoderCore).Decode(default, default, default); default(PngDecoderCore).Decode(default, default, default); default(TgaDecoderCore).Decode(default, default, default); + default(TiffDecoderCore).Decode(default, default, default); } /// @@ -229,6 +232,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); } /// @@ -244,6 +248,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); } /// diff --git a/src/ImageSharp/Common/ByteOrder.cs b/src/ImageSharp/Common/ByteOrder.cs new file mode 100644 index 000000000..cc38f1cde --- /dev/null +++ b/src/ImageSharp/Common/ByteOrder.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// The byte order of the data stream. + /// + public enum ByteOrder + { + /// + /// The big-endian byte order (Motorola). + /// Most-significant byte comes first, and ends with the least-significant byte. + /// + BigEndian, + + /// + /// The little-endian byte order (Intel). + /// Least-significant byte comes first and ends with the most-significant byte. + /// + LittleEndian + } +} diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 4a6e6abcb..efc0e0e15 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Common.Helpers /// private const double InchesInMeter = 1 / 0.0254D; + /// + /// The default resolution unit value. + /// + private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch; + /// /// Scales the value from centimeters to meters. /// @@ -89,7 +94,50 @@ namespace SixLabors.ImageSharp.Common.Helpers IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit); // EXIF is 1, 2, 3 so we minus "1" off the result. - return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1); + return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1); + } + + /// + /// Sets the exif profile resolution values. + /// + /// The exif profile. + /// The resolution unit. + /// The horizontal resolution value. + /// The vertical resolution value. + [MethodImpl(InliningOptions.ShortMethod)] + public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical) + { + switch (unit) + { + case PixelResolutionUnit.AspectRatio: + case PixelResolutionUnit.PixelsPerInch: + case PixelResolutionUnit.PixelsPerCentimeter: + break; + case PixelResolutionUnit.PixelsPerMeter: + { + unit = PixelResolutionUnit.PixelsPerCentimeter; + horizontal = UnitConverter.MeterToCm(horizontal); + vertical = UnitConverter.MeterToCm(vertical); + } + + break; + default: + unit = PixelResolutionUnit.PixelsPerInch; + break; + } + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1)); + + if (unit == PixelResolutionUnit.AspectRatio) + { + exifProfile.RemoveValue(ExifTag.XResolution); + exifProfile.RemoveValue(ExifTag.YResolution); + } + else + { + exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical)); + } } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/Adler32.cs rename to src/ImageSharp/Compression/Zlib/Adler32.cs index 534aba8f5..9b3abd298 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Compression/Zlib/Adler32.cs @@ -9,7 +9,7 @@ using System.Runtime.Intrinsics.X86; #endif #pragma warning disable IDE0007 // Use implicit type -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Calculates the 32 bit Adler checksum of a given buffer according to diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs similarity index 98% rename from src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs rename to src/ImageSharp/Compression/Zlib/Crc32.Lut.cs index 500783353..059bd9f31 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs +++ b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Contains precalulated tables for scalar calculations. diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Compression/Zlib/Crc32.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/Crc32.cs rename to src/ImageSharp/Compression/Zlib/Crc32.cs index 6b19987cb..0ba368df6 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Compression/Zlib/Crc32.cs @@ -9,7 +9,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer diff --git a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs new file mode 100644 index 000000000..2edf76e7d --- /dev/null +++ b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Compression.Zlib +{ + /// + /// Provides enumeration of available deflate compression levels. + /// + public enum DeflateCompressionLevel + { + /// + /// Level 0. Equivalent to . + /// + Level0 = 0, + + /// + /// No compression. Equivalent to . + /// + NoCompression = Level0, + + /// + /// Level 1. Equivalent to . + /// + Level1 = 1, + + /// + /// Best speed compression level. + /// + BestSpeed = Level1, + + /// + /// Level 2. + /// + Level2 = 2, + + /// + /// Level 3. + /// + Level3 = 3, + + /// + /// Level 4. + /// + Level4 = 4, + + /// + /// Level 5. + /// + Level5 = 5, + + /// + /// Level 6. Equivalent to . + /// + Level6 = 6, + + /// + /// The default compression level. Equivalent to . + /// + DefaultCompression = Level6, + + /// + /// Level 7. + /// + Level7 = 7, + + /// + /// Level 8. + /// + Level8 = 8, + + /// + /// Level 9. Equivalent to . + /// + Level9 = 9, + + /// + /// Best compression level. Equivalent to . + /// + BestCompression = Level9, + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs similarity index 96% rename from src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs rename to src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs index a5d129c92..02590ca25 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs +++ b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { internal static class DeflateThrowHelper { diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/Deflater.cs rename to src/ImageSharp/Compression/Zlib/Deflater.cs index 838921581..800c96703 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs +++ b/src/ImageSharp/Compression/Zlib/Deflater.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// This class compresses input with the deflate algorithm described in RFC 1951. diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs similarity index 98% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs rename to src/ImageSharp/Compression/Zlib/DeflaterConstants.cs index ec224d748..30bd75ffc 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs @@ -4,7 +4,7 @@ // using System; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// This class contains constants used for deflation. diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs rename to src/ImageSharp/Compression/Zlib/DeflaterEngine.cs index 797f5d210..d3cfa7c3d 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Strategies for deflater diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs rename to src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs index 96b47fb24..d6892dfd2 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Performs Deflate Huffman encoding. diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs similarity index 98% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs rename to src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs index 5c5651996..cbbf7ea79 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs @@ -5,7 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// A special stream deflating or compressing the bytes that are diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs rename to src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs index f702a7ead..36dfd92da 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Stores pending data for writing data to the Deflater. diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Compression/Zlib/README.md similarity index 100% rename from src/ImageSharp/Formats/Png/Zlib/README.md rename to src/ImageSharp/Compression/Zlib/README.md diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs similarity index 89% rename from src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs rename to src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs index 06c6e3dea..44883665a 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs @@ -4,9 +4,10 @@ using System; using System.IO; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. @@ -39,9 +40,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The stream responsible for compressing the input stream. /// - // private DeflateStream deflateStream; private DeflaterOutputStream deflateStream; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level) + : this(memoryAllocator, stream, (PngCompressionLevel)level) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs similarity index 99% rename from src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs rename to src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs index 52ef0e85b..f4b0543b8 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs @@ -6,7 +6,7 @@ using System.IO; using System.IO.Compression; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Provides methods and properties for deframing streams from PNGs. diff --git a/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf b/src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf similarity index 100% rename from src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf rename to src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 062bcb229..49b7aa79b 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class. /// - /// A collection of configuration modules to register + /// A collection of configuration modules to register. public Configuration(params IConfigurationModule[] configurationModules) { if (configurationModules != null) @@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the size of the buffer to use when working with streams. - /// Intitialized with by default. + /// Initialized with by default. /// public int StreamProcessingBufferSize { @@ -94,9 +95,9 @@ namespace SixLabors.ImageSharp } /// - /// Gets a set of properties for the Congiguration. + /// Gets a set of properties for the Configuration. /// - /// This can be used for storing global settings and defaults to be accessable to processors. + /// This can be used for storing global settings and defaults to be accessible to processors. public IDictionary Properties { get; } = new ConcurrentDictionary(); /// @@ -180,6 +181,7 @@ namespace SixLabors.ImageSharp /// /// . /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() @@ -189,7 +191,8 @@ namespace SixLabors.ImageSharp new JpegConfigurationModule(), new GifConfigurationModule(), new BmpConfigurationModule(), - new TgaConfigurationModule()); + new TgaConfigurationModule(), + new TiffConfigurationModule()); } } } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 075c708b6..0f8b1e16d 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.Tiff; namespace SixLabors.ImageSharp { @@ -535,5 +536,108 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), cancellationToken); + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsTiffAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsTiff(this Image source, Stream stream) + => SaveAsTiff(source, stream, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsTiffAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), + cancellationToken); + } } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index 63b404cc4..af9531225 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -1,4 +1,4 @@ -<#@ template language="C#" #> +<#@ template language="C#" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // Copyright (c) Six Labors. @@ -17,6 +17,7 @@ using SixLabors.ImageSharp.Advanced; "Jpeg", "Png", "Tga", + "Tiff", }; foreach (string fmt in formats) diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs index 7516e0987..961f9b05b 100644 --- a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs +++ b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs @@ -1,11 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.ComponentModel; + namespace SixLabors.ImageSharp.Formats.Png { /// /// Provides enumeration of available PNG compression levels. /// + [EditorBrowsable(EditorBrowsableState.Never)] public enum PngCompressionLevel { /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 3fa0e3f58..c2c336c03 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -10,10 +10,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; -using System.Threading.Tasks; +using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 5d2af8ec6..7a285eb70 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -8,11 +8,9 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/README.md b/src/ImageSharp/Formats/Tga/README.md similarity index 78% rename from src/ImageSharp/Formats/README.md rename to src/ImageSharp/Formats/Tga/README.md index 4a2b401b1..219f111b9 100644 --- a/src/ImageSharp/Formats/README.md +++ b/src/ImageSharp/Formats/Tga/README.md @@ -1,4 +1,4 @@ -# Encoder/Decoder for true vision targa files +# Encoder/Decoder for true vision targa files Useful links for reference: diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs new file mode 100644 index 000000000..08d147526 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + internal static class BitWriterUtils + { + public static void WriteBits(Span buffer, int pos, uint count, byte value) + { + int bitPos = pos % 8; + int bufferPos = pos / 8; + int startIdx = bufferPos + bitPos; + int endIdx = (int)(startIdx + count); + + if (value == 1) + { + for (int i = startIdx; i < endIdx; i++) + { + WriteBit(buffer, bufferPos, bitPos); + + bitPos++; + if (bitPos >= 8) + { + bitPos = 0; + bufferPos++; + } + } + } + else + { + for (int i = startIdx; i < endIdx; i++) + { + WriteZeroBit(buffer, bufferPos, bitPos); + + bitPos++; + if (bitPos >= 8) + { + bitPos = 0; + bufferPos++; + } + } + } + } + + public static void WriteBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos)); + + public static void WriteZeroBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos))); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs new file mode 100644 index 000000000..225036f90 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + internal sealed class DeflateCompressor : TiffBaseCompressor + { + private readonly DeflateCompressionLevel compressionLevel; + + private readonly MemoryStream memoryStream = new MemoryStream(); + + public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) + : base(output, allocator, width, bitsPerPixel, predictor) + => this.compressionLevel = compressionLevel; + + /// + public override TiffCompression Method => TiffCompression.Deflate; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) + { + this.memoryStream.Seek(0, SeekOrigin.Begin); + using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel)) + { + if (this.Predictor == TiffPredictor.Horizontal) + { + HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); + } + + stream.Write(rows); + stream.Flush(); + } + + int size = (int)this.memoryStream.Position; + +#if !NETSTANDARD1_3 + byte[] buffer = this.memoryStream.GetBuffer(); + this.Output.Write(buffer, 0, size); +#else + this.memoryStream.SetLength(size); + this.memoryStream.Position = 0; + this.memoryStream.CopyTo(this.Output); +#endif + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs new file mode 100644 index 000000000..d2ae9096e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + internal sealed class LzwCompressor : TiffBaseCompressor + { + private TiffLzwEncoder lzwEncoder; + + public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) + : base(output, allocator, width, bitsPerPixel, predictor) + { + } + + /// + public override TiffCompression Method => TiffCompression.Lzw; + + /// + public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); + + /// + public override void CompressStrip(Span rows, int height) + { + if (this.Predictor == TiffPredictor.Horizontal) + { + HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); + } + + this.lzwEncoder.Encode(rows, this.Output); + } + + /// + protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs new file mode 100644 index 000000000..79bb2e98f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + internal sealed class NoCompressor : TiffBaseCompressor + { + public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(output, memoryAllocator, width, bitsPerPixel) + { + } + + /// + public override TiffCompression Method => TiffCompression.None; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs new file mode 100644 index 000000000..5a2383187 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + internal sealed class PackBitsCompressor : TiffBaseCompressor + { + private IManagedByteBuffer pixelData; + + public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + } + + /// + public override TiffCompression Method => TiffCompression.PackBits; + + /// + public override void Initialize(int rowsPerStrip) + { + int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; + this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes); + } + + /// + public override void CompressStrip(Span rows, int height) + { + DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); + DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); + + Span span = this.pixelData.GetSpan(); + for (int i = 0; i < height; i++) + { + Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); + int size = PackBitsWriter.PackBits(row, span); + this.Output.Write(span.Slice(0, size)); + } + } + + /// + protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs new file mode 100644 index 000000000..30d21e54c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + /// + /// Pack Bits compression for tiff images. See Tiff Spec v6, section 9. + /// + internal static class PackBitsWriter + { + public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan) + { + int maxRunLength = 127; + int posInRowSpan = 0; + int bytesWritten = 0; + int literalRunLength = 0; + + while (posInRowSpan < rowSpan.Length) + { + bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan); + if (useReplicateRun) + { + if (literalRunLength > 0) + { + WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); + bytesWritten += literalRunLength + 1; + } + + // Write a run with the same bytes. + int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength); + WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten); + + bytesWritten += 2; + literalRunLength = 0; + posInRowSpan += runLength; + continue; + } + + literalRunLength++; + posInRowSpan++; + + if (literalRunLength >= maxRunLength) + { + WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); + bytesWritten += literalRunLength + 1; + literalRunLength = 0; + } + } + + if (literalRunLength > 0) + { + WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); + bytesWritten += literalRunLength + 1; + } + + return bytesWritten; + } + + private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) + { + DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); + + int literalRunStart = end - literalRunLength; + sbyte runLength = (sbyte)(literalRunLength - 1); + compressedRowSpan[compressedRowPos] = (byte)runLength; + rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1)); + } + + private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) + { + DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); + + sbyte headerByte = (sbyte)(-runLength + 1); + compressedRowSpan[compressedRowPos] = (byte)headerByte; + compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; + } + + private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos) + { + // We consider run which has at least 3 same consecutive bytes a candidate for a run. + var startByte = rowSpan[startPos]; + int count = 0; + for (int i = startPos + 1; i < rowSpan.Length; i++) + { + if (rowSpan[i] == startByte) + { + count++; + if (count >= 2) + { + return true; + } + } + else + { + break; + } + } + + return false; + } + + private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength) + { + var startByte = rowSpan[startPos]; + int count = 1; + for (int i = startPos + 1; i < rowSpan.Length; i++) + { + if (rowSpan[i] == startByte) + { + count++; + } + else + { + break; + } + + if (count == maxRunLength) + { + break; + } + } + + return count; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs new file mode 100644 index 000000000..3e9b7f4e6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -0,0 +1,592 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + /// + /// Bitwriter for writing compressed CCITT T4 1D data. + /// + internal sealed class T4BitCompressor : TiffBaseCompressor + { + private const uint WhiteZeroRunTermCode = 0x35; + + private const uint BlackZeroRunTermCode = 0x37; + + private static readonly uint[] MakeupRunLength = + { + 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 + }; + + private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() + { + { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } + }; + + private static readonly Dictionary WhiteLen5TermCodes = new Dictionary() + { + { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } + }; + + private static readonly Dictionary WhiteLen6TermCodes = new Dictionary() + { + { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } + }; + + private static readonly Dictionary WhiteLen7TermCodes = new Dictionary() + { + { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, + { 27, 0x24 }, { 28, 0x18 } + }; + + private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() + { + { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, + { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, + { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, + { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, + { 63, 0x34 } + }; + + private static readonly Dictionary BlackLen2TermCodes = new Dictionary() + { + { 2, 0x3 }, { 3, 0x2 } + }; + + private static readonly Dictionary BlackLen3TermCodes = new Dictionary() + { + { 1, 0x2 }, { 4, 0x3 } + }; + + private static readonly Dictionary BlackLen4TermCodes = new Dictionary() + { + { 5, 0x3 }, { 6, 0x2 } + }; + + private static readonly Dictionary BlackLen5TermCodes = new Dictionary() + { + { 7, 0x3 } + }; + + private static readonly Dictionary BlackLen6TermCodes = new Dictionary() + { + { 8, 0x5 }, { 9, 0x4 } + }; + + private static readonly Dictionary BlackLen7TermCodes = new Dictionary() + { + { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } + }; + + private static readonly Dictionary BlackLen8TermCodes = new Dictionary() + { + { 13, 0x4 }, { 14, 0x7 } + }; + + private static readonly Dictionary BlackLen9TermCodes = new Dictionary() + { + { 15, 0x18 } + }; + + private static readonly Dictionary BlackLen10TermCodes = new Dictionary() + { + { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } + }; + + private static readonly Dictionary BlackLen11TermCodes = new Dictionary() + { + { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } + }; + + private static readonly Dictionary BlackLen12TermCodes = new Dictionary() + { + { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, + { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, + { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, + { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, + { 62, 0x66 }, { 63, 0x67 } + }; + + private static readonly Dictionary WhiteLen5MakeupCodes = new Dictionary() + { + { 64, 0x1B }, { 128, 0x12 } + }; + + private static readonly Dictionary WhiteLen6MakeupCodes = new Dictionary() + { + { 192, 0x17 }, { 1664, 0x18 } + }; + + private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() + { + { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } + }; + + private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() + { + { 256, 0x37 } + }; + + private static readonly Dictionary WhiteLen9MakeupCodes = new Dictionary() + { + { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, + { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, + { 1600, 0x9A }, { 1728, 0x9B } + }; + + private static readonly Dictionary WhiteLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() + { + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() + { + { 64, 0xF } + }; + + private static readonly Dictionary BlackLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() + { + { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() + { + { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, + { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, + { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } + }; + + /// + /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. + /// + private readonly bool useModifiedHuffman; + + private IMemoryOwner compressedDataBuffer; + + private int bytePosition; + + private byte bitPosition; + + /// + /// Initializes a new instance of the class. + /// + /// The output. + /// The allocator. + /// The width. + /// The bits per pixel. + /// Indicates if the modified huffman RLE should be used. + public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) + : base(output, allocator, width, bitsPerPixel) + { + this.bytePosition = 0; + this.bitPosition = 0; + this.useModifiedHuffman = useModifiedHuffman; + } + + /// + public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; + + /// + public override void Initialize(int rowsPerStrip) + { + // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. + int maxNeededBytes = this.Width * rowsPerStrip; + this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); + } + + /// Writes a image compressed with CCITT T4 to the stream. + /// The pixels as 8-bit gray array. + /// The strip height. + public override void CompressStrip(Span pixelsAsGray, int height) + { + DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); + DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); + + this.compressedDataBuffer.Clear(); + Span compressedData = this.compressedDataBuffer.GetSpan(); + + this.bytePosition = 0; + this.bitPosition = 0; + + if (!this.useModifiedHuffman) + { + // An EOL code is expected at the start of the data. + this.WriteCode(12, 1, compressedData); + } + + for (int y = 0; y < height; y++) + { + bool isWhiteRun = true; + bool isStartOrRow = true; + int x = 0; + + Span row = pixelsAsGray.Slice(y * this.Width, this.Width); + while (x < this.Width) + { + uint runLength = 0; + for (int i = x; i < this.Width; i++) + { + if (isWhiteRun && row[i] != 255) + { + break; + } + + if (isWhiteRun && row[i] == 255) + { + runLength++; + continue; + } + + if (!isWhiteRun && row[i] != 0) + { + break; + } + + if (!isWhiteRun && row[i] == 0) + { + runLength++; + } + } + + if (isStartOrRow && runLength == 0) + { + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); + + isWhiteRun = false; + isStartOrRow = false; + continue; + } + + uint code; + uint codeLength; + if (runLength <= 63) + { + code = this.GetTermCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + } + else + { + runLength = this.GetBestFittingMakeupRunLength(runLength); + code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + + // If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero. + if (x == this.Width) + { + if (isWhiteRun) + { + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); + } + else + { + this.WriteCode(10, BlackZeroRunTermCode, compressedData); + } + } + + continue; + } + + isStartOrRow = false; + isWhiteRun = !isWhiteRun; + } + + this.WriteEndOfLine(compressedData); + } + + // Write the compressed data to the stream. + int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; + this.Output.Write(compressedData.Slice(0, bytesToWrite)); + } + + protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); + + private void WriteEndOfLine(Span compressedData) + { + if (this.useModifiedHuffman) + { + // Check if padding is necessary. + if (this.bitPosition % 8 != 0) + { + // Skip padding bits, move to next byte. + this.bytePosition++; + this.bitPosition = 0; + } + } + else + { + // Write EOL. + this.WriteCode(12, 1, compressedData); + } + } + + private void WriteCode(uint codeLength, uint code, Span compressedData) + { + while (codeLength > 0) + { + int bitNumber = (int)codeLength; + bool bit = (code & (1 << (bitNumber - 1))) != 0; + if (bit) + { + BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); + } + else + { + BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); + } + + this.bitPosition++; + if (this.bitPosition == 8) + { + this.bytePosition++; + this.bitPosition = 0; + } + + codeLength--; + } + } + + private uint GetBestFittingMakeupRunLength(uint runLength) + { + for (int i = 0; i < MakeupRunLength.Length - 1; i++) + { + if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) + { + return MakeupRunLength[i]; + } + } + + return MakeupRunLength[MakeupRunLength.Length - 1]; + } + + private uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return this.GetWhiteTermCode(runLength, out codeLength); + } + + return this.GetBlackTermCode(runLength, out codeLength); + } + + private uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return this.GetWhiteMakeupCode(runLength, out codeLength); + } + + return this.GetBlackMakeupCode(runLength, out codeLength); + } + + private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (WhiteLen5MakeupCodes.ContainsKey(runLength)) + { + codeLength = 5; + return WhiteLen5MakeupCodes[runLength]; + } + + if (WhiteLen6MakeupCodes.ContainsKey(runLength)) + { + codeLength = 6; + return WhiteLen6MakeupCodes[runLength]; + } + + if (WhiteLen7MakeupCodes.ContainsKey(runLength)) + { + codeLength = 7; + return WhiteLen7MakeupCodes[runLength]; + } + + if (WhiteLen8MakeupCodes.ContainsKey(runLength)) + { + codeLength = 8; + return WhiteLen8MakeupCodes[runLength]; + } + + if (WhiteLen9MakeupCodes.ContainsKey(runLength)) + { + codeLength = 9; + return WhiteLen9MakeupCodes[runLength]; + } + + if (WhiteLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return WhiteLen11MakeupCodes[runLength]; + } + + if (WhiteLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return WhiteLen12MakeupCodes[runLength]; + } + + return 0; + } + + private uint GetBlackMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (BlackLen10MakeupCodes.ContainsKey(runLength)) + { + codeLength = 10; + return BlackLen10MakeupCodes[runLength]; + } + + if (BlackLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return BlackLen11MakeupCodes[runLength]; + } + + if (BlackLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return BlackLen12MakeupCodes[runLength]; + } + + if (BlackLen13MakeupCodes.ContainsKey(runLength)) + { + codeLength = 13; + return BlackLen13MakeupCodes[runLength]; + } + + return 0; + } + + private uint GetWhiteTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (WhiteLen4TermCodes.ContainsKey(runLength)) + { + codeLength = 4; + return WhiteLen4TermCodes[runLength]; + } + + if (WhiteLen5TermCodes.ContainsKey(runLength)) + { + codeLength = 5; + return WhiteLen5TermCodes[runLength]; + } + + if (WhiteLen6TermCodes.ContainsKey(runLength)) + { + codeLength = 6; + return WhiteLen6TermCodes[runLength]; + } + + if (WhiteLen7TermCodes.ContainsKey(runLength)) + { + codeLength = 7; + return WhiteLen7TermCodes[runLength]; + } + + if (WhiteLen8TermCodes.ContainsKey(runLength)) + { + codeLength = 8; + return WhiteLen8TermCodes[runLength]; + } + + return 0; + } + + private uint GetBlackTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (BlackLen2TermCodes.ContainsKey(runLength)) + { + codeLength = 2; + return BlackLen2TermCodes[runLength]; + } + + if (BlackLen3TermCodes.ContainsKey(runLength)) + { + codeLength = 3; + return BlackLen3TermCodes[runLength]; + } + + if (BlackLen4TermCodes.ContainsKey(runLength)) + { + codeLength = 4; + return BlackLen4TermCodes[runLength]; + } + + if (BlackLen5TermCodes.ContainsKey(runLength)) + { + codeLength = 5; + return BlackLen5TermCodes[runLength]; + } + + if (BlackLen6TermCodes.ContainsKey(runLength)) + { + codeLength = 6; + return BlackLen6TermCodes[runLength]; + } + + if (BlackLen7TermCodes.ContainsKey(runLength)) + { + codeLength = 7; + return BlackLen7TermCodes[runLength]; + } + + if (BlackLen8TermCodes.ContainsKey(runLength)) + { + codeLength = 8; + return BlackLen8TermCodes[runLength]; + } + + if (BlackLen9TermCodes.ContainsKey(runLength)) + { + codeLength = 9; + return BlackLen9TermCodes[runLength]; + } + + if (BlackLen10TermCodes.ContainsKey(runLength)) + { + codeLength = 10; + return BlackLen10TermCodes[runLength]; + } + + if (BlackLen11TermCodes.ContainsKey(runLength)) + { + codeLength = 11; + return BlackLen11TermCodes[runLength]; + } + + if (BlackLen12TermCodes.ContainsKey(runLength)) + { + codeLength = 12; + return BlackLen12TermCodes[runLength]; + } + + return 0; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs new file mode 100644 index 000000000..baeabdbb2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + /* + This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + /// + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. + /// + /// + /// + /// This code is based on the used for GIF encoding. There is potential + /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW + /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is + /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial + /// byte indicating the length of the sub-block. In TIFF the data is written as a single block + /// with no length indicator (this can be determined from the 'StripByteCounts' entry). + /// + /// + internal sealed class TiffLzwEncoder : IDisposable + { + // Clear: Re-initialize tables. + private static readonly int ClearCode = 256; + + // End of Information. + private static readonly int EoiCode = 257; + + private static readonly int MinBits = 9; + private static readonly int MaxBits = 12; + + private static readonly int TableSize = 1 << MaxBits; + + // A child is made up of a parent (or prefix) code plus a suffix byte + // and siblings are strings with a common parent(or prefix) and different suffix bytes. + private readonly IMemoryOwner children; + + private readonly IMemoryOwner siblings; + + private readonly IMemoryOwner suffixes; + + // Initial setup + private int parent; + private int bitsPerCode; + private int nextValidCode; + private int maxCode; + + // Buffer for partial codes + private int bits; + private int bitPos; + private int bufferPosition; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + public TiffLzwEncoder(MemoryAllocator memoryAllocator) + { + this.children = memoryAllocator.Allocate(TableSize); + this.siblings = memoryAllocator.Allocate(TableSize); + this.suffixes = memoryAllocator.Allocate(TableSize); + } + + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The data to compress. + /// The stream to write to. + public void Encode(Span data, Stream stream) + { + this.Reset(); + + Span childrenSpan = this.children.GetSpan(); + Span suffixesSpan = this.suffixes.GetSpan(); + Span siblingsSpan = this.siblings.GetSpan(); + int length = data.Length; + + if (length == 0) + { + return; + } + + if (this.parent == -1) + { + // Init stream. + this.WriteCode(stream, ClearCode); + this.parent = this.ReadNextByte(data); + } + + while (this.bufferPosition < data.Length) + { + int value = this.ReadNextByte(data); + int child = childrenSpan[this.parent]; + + if (child > 0) + { + if (suffixesSpan[child] == value) + { + this.parent = child; + } + else + { + int sibling = child; + + while (true) + { + if (siblingsSpan[sibling] > 0) + { + sibling = siblingsSpan[sibling]; + + if (suffixesSpan[sibling] == value) + { + this.parent = sibling; + break; + } + } + else + { + siblingsSpan[sibling] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); + + break; + } + } + } + } + else + { + childrenSpan[this.parent] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); + } + } + + // Write EOI when we are done. + this.WriteCode(stream, this.parent); + this.WriteCode(stream, EoiCode); + + // Flush partial codes by writing 0 pad. + if (this.bitPos > 0) + { + this.WriteCode(stream, 0); + } + } + + /// + public void Dispose() + { + this.children.Dispose(); + this.siblings.Dispose(); + this.suffixes.Dispose(); + } + + private void Reset() + { + this.children.Clear(); + this.siblings.Clear(); + this.suffixes.Clear(); + + this.parent = -1; + this.bitsPerCode = MinBits; + this.nextValidCode = EoiCode + 1; + this.maxCode = (1 << this.bitsPerCode) - 1; + + this.bits = 0; + this.bitPos = 0; + this.bufferPosition = 0; + } + + private byte ReadNextByte(Span data) => data[this.bufferPosition++]; + + private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) + { + if (this.nextValidCode > this.maxCode) + { + if (this.bitsPerCode == MaxBits) + { + // Reset stream by writing Clear code. + this.WriteCode(stream, ClearCode); + + // Reset tables. + this.ResetTables(); + } + else + { + // Increase code size. + this.bitsPerCode++; + this.maxCode = MaxValue(this.bitsPerCode); + } + } + } + + private void WriteCode(Stream stream, int code) + { + this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); + this.bitPos += this.bitsPerCode; + + while (this.bitPos >= 8) + { + int b = (this.bits >> (this.bitPos - 8)) & 0xff; + stream.WriteByte((byte)b); + this.bitPos -= 8; + } + + this.bits &= BitmaskFor(this.bitPos); + } + + private void ResetTables() + { + this.children.GetSpan().Fill(0); + this.siblings.GetSpan().Fill(0); + this.bitsPerCode = MinBits; + this.maxCode = MaxValue(this.bitsPerCode); + this.nextValidCode = EoiCode + 1; + } + + private static int MaxValue(int codeLen) => (1 << codeLen) - 1; + + private static int BitmaskFor(int bits) => MaxValue(bits); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs new file mode 100644 index 000000000..67af4ff6c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO.Compression; + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using Deflate compression. + /// + /// + /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. + /// + internal class DeflateTiffCompression : TiffBaseDecompressor + { + /// + /// Initializes a new instance of the class. + /// + /// The memoryAllocator to use for buffer allocations. + /// The image width. + /// The bits used per pixel. + /// The tiff predictor used. + public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) + : base(memoryAllocator, width, bitsPerPixel, predictor) + { + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + { + long pos = stream.Position; + using (var deframeStream = new ZlibInflateStream( + stream, + () => + { + int left = (int)(byteCount - (stream.Position - pos)); + return left > 0 ? left : 0; + })) + { + deframeStream.AllocateNewBytes(byteCount, true); + DeflateStream dataStream = deframeStream.CompressedStream; + dataStream.Read(buffer, 0, buffer.Length); + } + + if (this.Predictor == TiffPredictor.Horizontal) + { + HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); + } + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs new file mode 100644 index 000000000..0f4fb9c9e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Represents a lzw string with a code word and a code length. + /// + public class LzwString + { + private static readonly LzwString Empty = new LzwString(0, 0, 0, null); + + private readonly LzwString previous; + private readonly byte value; + + /// + /// Initializes a new instance of the class. + /// + /// The code word. + public LzwString(byte code) + : this(code, code, 1, null) + { + } + + private LzwString(byte value, byte firstChar, int length, LzwString previous) + { + this.value = value; + this.FirstChar = firstChar; + this.Length = length; + this.previous = previous; + } + + /// + /// Gets the code length; + /// + public int Length { get; } + + /// + /// Gets the first character of the codeword. + /// + public byte FirstChar { get; } + + /// + /// Concatenates two code words. + /// + /// The code word to concatenate. + /// A concatenated lzw string. + public LzwString Concatenate(byte other) + { + if (this == Empty) + { + return new LzwString(other); + } + + return new LzwString(other, this.FirstChar, this.Length + 1, this); + } + + /// + /// Writes decoded pixel to buffer at a given position. + /// + /// The buffer to write to. + /// The position to write to. + /// The number of bytes written. + public int WriteTo(Span buffer, int offset) + { + if (this.Length == 0) + { + return 0; + } + + if (this.Length == 1) + { + buffer[offset] = this.value; + return 1; + } + + LzwString e = this; + int endIdx = this.Length - 1; + if (endIdx >= buffer.Length) + { + TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); + } + + for (int i = endIdx; i >= 0; i--) + { + buffer[offset + i] = e.value; + e = e.previous; + } + + return this.Length; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs new file mode 100644 index 000000000..7e75dd4f0 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using LZW compression. + /// + internal class LzwTiffCompression : TiffBaseDecompressor + { + /// + /// Initializes a new instance of the class. + /// + /// The memoryAllocator to use for buffer allocations. + /// The image width. + /// The bits used per pixel. + /// The tiff predictor used. + public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) + : base(memoryAllocator, width, bitsPerPixel, predictor) + { + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + { + var decoder = new TiffLzwDecoder(stream); + decoder.DecodePixels(buffer); + + if (this.Predictor == TiffPredictor.Horizontal) + { + HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); + } + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs new file mode 100644 index 000000000..017591e53 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. + /// + internal class ModifiedHuffmanTiffCompression : T4TiffCompression + { + private readonly byte whiteValue; + + private readonly byte blackValue; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The image width. + /// The number of bits per pixel. + /// The photometric interpretation. + public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) + { + bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.whiteValue = (byte)(isWhiteZero ? 0 : 1); + this.blackValue = (byte)(isWhiteZero ? 1 : 0); + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + { + using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); + + buffer.Clear(); + uint bitsWritten = 0; + uint pixelsWritten = 0; + while (bitReader.HasMoreData) + { + bitReader.ReadNextRun(); + + if (bitReader.RunLength > 0) + { + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); + bitsWritten += bitReader.RunLength; + pixelsWritten += bitReader.RunLength; + } + else + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); + bitsWritten += bitReader.RunLength; + pixelsWritten += bitReader.RunLength; + } + } + + if (pixelsWritten % this.Width == 0) + { + bitReader.StartNewRow(); + + // Write padding bits, if necessary. + uint pad = 8 - (bitsWritten % 8); + if (pad != 8) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); + bitsWritten += pad; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs new file mode 100644 index 000000000..58a1c9878 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is not compressed. + /// + internal class NoneTiffCompression : TiffBaseDecompressor + { + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) + { + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs new file mode 100644 index 000000000..e14736b73 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using PackBits compression. + /// + internal class PackBitsTiffCompression : TiffBaseDecompressor + { + private IMemoryOwner compressedDataMemory; + + /// + /// Initializes a new instance of the class. + /// + /// The memoryAllocator to use for buffer allocations. + /// The width of the image. + /// The number of bits per pixel. + public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) + { + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + { + if (this.compressedDataMemory == null) + { + this.compressedDataMemory = this.Allocator.Allocate(byteCount); + } + else if (this.compressedDataMemory.Length() < byteCount) + { + this.compressedDataMemory.Dispose(); + this.compressedDataMemory = this.Allocator.Allocate(byteCount); + } + + Span compressedData = this.compressedDataMemory.GetSpan(); + + stream.Read(compressedData, 0, byteCount); + int compressedOffset = 0; + int decompressedOffset = 0; + + while (compressedOffset < byteCount) + { + byte headerByte = compressedData[compressedOffset]; + + if (headerByte <= 127) + { + int literalOffset = compressedOffset + 1; + int literalLength = compressedData[compressedOffset] + 1; + + if ((literalOffset + literalLength) > compressedData.Length) + { + TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); + } + + compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset)); + + compressedOffset += literalLength + 1; + decompressedOffset += literalLength; + } + else if (headerByte == 0x80) + { + compressedOffset += 1; + } + else + { + byte repeatData = compressedData[compressedOffset + 1]; + int repeatLength = 257 - headerByte; + + ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); + + compressedOffset += 2; + decompressedOffset += repeatLength; + } + } + } + + private static void ArrayCopyRepeat(byte value, Span destinationArray, int destinationIndex, int length) + { + for (int i = 0; i < length; i++) + { + destinationArray[i + destinationIndex] = value; + } + } + + /// + protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs new file mode 100644 index 000000000..09f8c71f7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -0,0 +1,842 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Bitreader for reading compressed CCITT T4 1D data. + /// + internal class T4BitReader : IDisposable + { + /// + /// Number of bits read. + /// + private int bitsRead; + + /// + /// Current value. + /// + private uint value; + + /// + /// Number of bits read for the current run value. + /// + private int curValueBitsRead; + + /// + /// Byte position in the buffer. + /// + private ulong position; + + /// + /// Indicates whether its the first line of data which is read from the image. + /// + private bool isFirstScanLine; + + /// + /// Indicates whether we have found a termination code which signals the end of a run. + /// + private bool terminationCodeFound; + + /// + /// We keep track if its the start of the row, because each run is expected to start with a white run. + /// If the image row itself starts with black, a white run of zero is expected. + /// + private bool isStartOfRow; + + /// + /// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used. + /// + private readonly bool isModifiedHuffmanRle; + + /// + /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. + /// + private readonly bool eolPadding; + + private readonly int dataLength; + + private const int MinCodeLength = 2; + + private readonly int maxCodeLength = 13; + + private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() + { + { 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 } + }; + + private static readonly Dictionary WhiteLen5TermCodes = new Dictionary() + { + { 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 } + }; + + private static readonly Dictionary WhiteLen6TermCodes = new Dictionary() + { + { 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 } + }; + + private static readonly Dictionary WhiteLen7TermCodes = new Dictionary() + { + { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, + { 0x24, 27 }, { 0x18, 28 } + }; + + private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() + { + { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, + { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, + { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, + { 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } + }; + + private static readonly Dictionary BlackLen2TermCodes = new Dictionary() + { + { 0x3, 2 }, { 0x2, 3 } + }; + + private static readonly Dictionary BlackLen3TermCodes = new Dictionary() + { + { 0x2, 1 }, { 0x3, 4 } + }; + + private static readonly Dictionary BlackLen4TermCodes = new Dictionary() + { + { 0x3, 5 }, { 0x2, 6 } + }; + + private static readonly Dictionary BlackLen5TermCodes = new Dictionary() + { + { 0x3, 7 } + }; + + private static readonly Dictionary BlackLen6TermCodes = new Dictionary() + { + { 0x5, 8 }, { 0x4, 9 } + }; + + private static readonly Dictionary BlackLen7TermCodes = new Dictionary() + { + { 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 } + }; + + private static readonly Dictionary BlackLen8TermCodes = new Dictionary() + { + { 0x4, 13 }, { 0x7, 14 } + }; + + private static readonly Dictionary BlackLen9TermCodes = new Dictionary() + { + { 0x18, 15 } + }; + + private static readonly Dictionary BlackLen10TermCodes = new Dictionary() + { + { 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 } + }; + + private static readonly Dictionary BlackLen11TermCodes = new Dictionary() + { + { 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 } + }; + + private static readonly Dictionary BlackLen12TermCodes = new Dictionary() + { + { 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 }, + { 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 }, + { 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 }, + { 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 }, + { 0x66, 62 }, { 0x67, 63 } + }; + + private static readonly Dictionary WhiteLen5MakeupCodes = new Dictionary() + { + { 0x1B, 64 }, { 0x12, 128 } + }; + + private static readonly Dictionary WhiteLen6MakeupCodes = new Dictionary() + { + { 0x17, 192 }, { 0x18, 1664 } + }; + + private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() + { + { 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } + }; + + private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() + { + { 0x37, 256 } + }; + + private static readonly Dictionary WhiteLen9MakeupCodes = new Dictionary() + { + { 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 }, + { 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 }, + { 0x9A, 1600 }, { 0x9B, 1728 } + }; + + private static readonly Dictionary WhiteLen11MakeupCodes = new Dictionary() + { + { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } + }; + + private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() + { + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + }; + + private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() + { + { 0xF, 64 } + }; + + private static readonly Dictionary BlackLen11MakeupCodes = new Dictionary() + { + { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } + }; + + private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() + { + { 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + }; + + private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() + { + { 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 }, + { 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 }, + { 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 } + }; + + /// + /// Initializes a new instance of the class. + /// + /// The compressed input stream. + /// The number of bytes to read from the stream. + /// The memory allocator. + /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. + /// Indicates, if its the modified huffman code variation. Defaults to false. + public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) + { + this.Data = allocator.Allocate(bytesToRead); + this.ReadImageDataFromStream(input, bytesToRead); + + this.isModifiedHuffmanRle = isModifiedHuffman; + this.dataLength = bytesToRead; + this.bitsRead = 0; + this.value = 0; + this.curValueBitsRead = 0; + this.position = 0; + this.IsWhiteRun = true; + this.isFirstScanLine = true; + this.isStartOfRow = true; + this.terminationCodeFound = false; + this.RunLength = 0; + this.eolPadding = eolPadding; + + if (this.eolPadding) + { + this.maxCodeLength = 24; + } + } + + /// + /// Gets the compressed image data. + /// + public IMemoryOwner Data { get; } + + /// + /// Gets a value indicating whether there is more data to read left. + /// + public bool HasMoreData + { + get + { + if (this.isModifiedHuffmanRle) + { + return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7); + } + + return this.position < (ulong)this.dataLength - 1; + } + } + + /// + /// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. + /// + public bool IsWhiteRun { get; private set; } + + /// + /// Gets the number of pixels in the current run. + /// + public uint RunLength { get; private set; } + + /// + /// Gets a value indicating whether the end of a pixel row has been reached. + /// + public bool IsEndOfScanLine + { + get + { + if (this.eolPadding) + { + return this.curValueBitsRead >= 12 && this.value == 1; + } + + return this.curValueBitsRead == 12 && this.value == 1; + } + } + + /// + /// Read the next run of pixels. + /// + public void ReadNextRun() + { + if (this.terminationCodeFound) + { + this.IsWhiteRun = !this.IsWhiteRun; + this.terminationCodeFound = false; + } + + this.Reset(); + + if (this.isFirstScanLine && !this.isModifiedHuffmanRle) + { + // We expect an EOL before the first data. + this.value = this.ReadValue(this.eolPadding ? 16 : 12); + + if (!this.IsEndOfScanLine) + { + TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found"); + } + + this.Reset(); + } + + // A code word must have at least 2 bits. + this.value = this.ReadValue(MinCodeLength); + + do + { + if (this.curValueBitsRead > this.maxCodeLength) + { + TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); + } + + bool isMakeupCode = this.IsMakeupCode(); + if (isMakeupCode) + { + if (this.IsWhiteRun) + { + this.RunLength += this.WhiteMakeupCodeRunLength(); + } + else + { + this.RunLength += this.BlackMakeupCodeRunLength(); + } + + this.isStartOfRow = false; + this.Reset(resetRunLength: false); + continue; + } + + bool isTerminatingCode = this.IsTerminatingCode(); + if (isTerminatingCode) + { + // Each line starts with a white run. If the image starts with black, a white run with length zero is written. + if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) + { + this.IsWhiteRun = !this.IsWhiteRun; + this.Reset(); + this.isStartOfRow = false; + continue; + } + + if (this.IsWhiteRun) + { + this.RunLength += this.WhiteTerminatingCodeRunLength(); + } + else + { + this.RunLength += this.BlackTerminatingCodeRunLength(); + } + + this.terminationCodeFound = true; + this.isStartOfRow = false; + break; + } + + var currBit = this.ReadValue(1); + this.value = (this.value << 1) | currBit; + + if (this.IsEndOfScanLine) + { + this.StartNewRow(); + } + } + while (!this.IsEndOfScanLine); + + this.isFirstScanLine = false; + } + + public void StartNewRow() + { + // Each new row starts with a white run. + this.IsWhiteRun = true; + this.isStartOfRow = true; + this.terminationCodeFound = false; + + if (this.isModifiedHuffmanRle) + { + int pad = 8 - (this.bitsRead % 8); + if (pad != 8) + { + // Skip padding bits, move to next byte. + this.position++; + this.bitsRead = 0; + } + } + } + + /// + public void Dispose() => this.Data.Dispose(); + + private uint WhiteTerminatingCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 4: + { + return WhiteLen4TermCodes[this.value]; + } + + case 5: + { + return WhiteLen5TermCodes[this.value]; + } + + case 6: + { + return WhiteLen6TermCodes[this.value]; + } + + case 7: + { + return WhiteLen7TermCodes[this.value]; + } + + case 8: + { + return WhiteLen8TermCodes[this.value]; + } + } + + return 0; + } + + private uint BlackTerminatingCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 2: + { + return BlackLen2TermCodes[this.value]; + } + + case 3: + { + return BlackLen3TermCodes[this.value]; + } + + case 4: + { + return BlackLen4TermCodes[this.value]; + } + + case 5: + { + return BlackLen5TermCodes[this.value]; + } + + case 6: + { + return BlackLen6TermCodes[this.value]; + } + + case 7: + { + return BlackLen7TermCodes[this.value]; + } + + case 8: + { + return BlackLen8TermCodes[this.value]; + } + + case 9: + { + return BlackLen9TermCodes[this.value]; + } + + case 10: + { + return BlackLen10TermCodes[this.value]; + } + + case 11: + { + return BlackLen11TermCodes[this.value]; + } + + case 12: + { + return BlackLen12TermCodes[this.value]; + } + } + + return 0; + } + + private uint WhiteMakeupCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 5: + { + return WhiteLen5MakeupCodes[this.value]; + } + + case 6: + { + return WhiteLen6MakeupCodes[this.value]; + } + + case 7: + { + return WhiteLen7MakeupCodes[this.value]; + } + + case 8: + { + return WhiteLen8MakeupCodes[this.value]; + } + + case 9: + { + return WhiteLen9MakeupCodes[this.value]; + } + + case 11: + { + return WhiteLen11MakeupCodes[this.value]; + } + + case 12: + { + return WhiteLen12MakeupCodes[this.value]; + } + } + + return 0; + } + + private uint BlackMakeupCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 10: + { + return BlackLen10MakeupCodes[this.value]; + } + + case 11: + { + return BlackLen11MakeupCodes[this.value]; + } + + case 12: + { + return BlackLen12MakeupCodes[this.value]; + } + + case 13: + { + return BlackLen13MakeupCodes[this.value]; + } + } + + return 0; + } + + private bool IsMakeupCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteMakeupCode(); + } + + return this.IsBlackMakeupCode(); + } + + private bool IsWhiteMakeupCode() + { + switch (this.curValueBitsRead) + { + case 5: + { + return WhiteLen5MakeupCodes.ContainsKey(this.value); + } + + case 6: + { + return WhiteLen6MakeupCodes.ContainsKey(this.value); + } + + case 7: + { + return WhiteLen7MakeupCodes.ContainsKey(this.value); + } + + case 8: + { + return WhiteLen8MakeupCodes.ContainsKey(this.value); + } + + case 9: + { + return WhiteLen9MakeupCodes.ContainsKey(this.value); + } + + case 11: + { + return WhiteLen11MakeupCodes.ContainsKey(this.value); + } + + case 12: + { + if (this.isModifiedHuffmanRle) + { + if (this.value == 1) + { + return true; + } + } + + return WhiteLen12MakeupCodes.ContainsKey(this.value); + } + } + + return false; + } + + private bool IsBlackMakeupCode() + { + switch (this.curValueBitsRead) + { + case 10: + { + return BlackLen10MakeupCodes.ContainsKey(this.value); + } + + case 11: + { + if (this.isModifiedHuffmanRle) + { + if (this.value == 0) + { + return true; + } + } + + return BlackLen11MakeupCodes.ContainsKey(this.value); + } + + case 12: + { + return BlackLen12MakeupCodes.ContainsKey(this.value); + } + + case 13: + { + return BlackLen13MakeupCodes.ContainsKey(this.value); + } + } + + return false; + } + + private bool IsTerminatingCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteTerminatingCode(); + } + + return this.IsBlackTerminatingCode(); + } + + private bool IsWhiteTerminatingCode() + { + switch (this.curValueBitsRead) + { + case 4: + { + return WhiteLen4TermCodes.ContainsKey(this.value); + } + + case 5: + { + return WhiteLen5TermCodes.ContainsKey(this.value); + } + + case 6: + { + return WhiteLen6TermCodes.ContainsKey(this.value); + } + + case 7: + { + return WhiteLen7TermCodes.ContainsKey(this.value); + } + + case 8: + { + return WhiteLen8TermCodes.ContainsKey(this.value); + } + } + + return false; + } + + private bool IsBlackTerminatingCode() + { + switch (this.curValueBitsRead) + { + case 2: + { + return BlackLen2TermCodes.ContainsKey(this.value); + } + + case 3: + { + return BlackLen3TermCodes.ContainsKey(this.value); + } + + case 4: + { + return BlackLen4TermCodes.ContainsKey(this.value); + } + + case 5: + { + return BlackLen5TermCodes.ContainsKey(this.value); + } + + case 6: + { + return BlackLen6TermCodes.ContainsKey(this.value); + } + + case 7: + { + return BlackLen7TermCodes.ContainsKey(this.value); + } + + case 8: + { + return BlackLen8TermCodes.ContainsKey(this.value); + } + + case 9: + { + return BlackLen9TermCodes.ContainsKey(this.value); + } + + case 10: + { + return BlackLen10TermCodes.ContainsKey(this.value); + } + + case 11: + { + return BlackLen11TermCodes.ContainsKey(this.value); + } + + case 12: + { + return BlackLen12TermCodes.ContainsKey(this.value); + } + } + + return false; + } + + private void Reset(bool resetRunLength = true) + { + this.value = 0; + this.curValueBitsRead = 0; + + if (resetRunLength) + { + this.RunLength = 0; + } + } + + private uint ReadValue(int nBits) + { + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + + uint v = 0; + int shift = nBits; + while (shift-- > 0) + { + uint bit = this.GetBit(); + v |= bit << shift; + this.curValueBitsRead++; + } + + return v; + } + + private uint GetBit() + { + if (this.bitsRead >= 8) + { + this.LoadNewByte(); + } + + Span dataSpan = this.Data.GetSpan(); + int shift = 8 - this.bitsRead - 1; + var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); + this.bitsRead++; + + return bit; + } + + private void LoadNewByte() + { + this.position++; + this.bitsRead = 0; + + if (this.position >= (ulong)this.dataLength) + { + TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data"); + } + } + + private void ReadImageDataFromStream(Stream input, int bytesToRead) + { + Span dataSpan = this.Data.GetSpan(); + input.Read(dataSpan, 0, bytesToRead); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs new file mode 100644 index 000000000..76f088364 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. + /// + internal class T4TiffCompression : TiffBaseDecompressor + { + private readonly FaxCompressionOptions faxCompressionOptions; + + private readonly byte whiteValue; + + private readonly byte blackValue; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The image width. + /// The number of bits per pixel. + /// Fax compression options. + /// The photometric interpretation. + public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) + { + this.faxCompressionOptions = faxOptions; + + bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.whiteValue = (byte)(isWhiteZero ? 0 : 1); + this.blackValue = (byte)(isWhiteZero ? 1 : 0); + } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + { + if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) + { + TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); + } + + var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); + using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); + + buffer.Clear(); + uint bitsWritten = 0; + while (bitReader.HasMoreData) + { + bitReader.ReadNextRun(); + + if (bitReader.RunLength > 0) + { + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); + bitsWritten += bitReader.RunLength; + } + else + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); + bitsWritten += bitReader.RunLength; + } + } + + if (bitReader.IsEndOfScanLine) + { + // Write padding bytes, if necessary. + uint pad = 8 - (bitsWritten % 8); + if (pad != 8) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); + bitsWritten += pad; + } + } + } + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs new file mode 100644 index 000000000..68d3a7f2a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs @@ -0,0 +1,257 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /* + This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + /// + /// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13. + /// + internal sealed class TiffLzwDecoder + { + /// + /// The stream to decode. + /// + private readonly Stream stream; + + /// + /// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode. + /// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again. + /// + private const int ClearCode = 256; + + /// + /// End of Information. + /// + private const int EoiCode = 257; + + /// + /// Minimum code length of 9 bits. + /// + private const int MinBits = 9; + + /// + /// Maximum code length of 12 bits. + /// + private const int MaxBits = 12; + + /// + /// Maximum table size of 4096. + /// + private const int TableSize = 1 << MaxBits; + + private readonly LzwString[] table; + + private int tableLength; + private int bitsPerCode; + private int oldCode = ClearCode; + private int maxCode; + private int bitMask; + private int maxString; + private bool eofReached; + private int nextData; + private int nextBits; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream to read from. + /// is null. + public TiffLzwDecoder(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + this.stream = stream; + + // TODO: Investigate a manner by which we can avoid this allocation. + this.table = new LzwString[TableSize]; + for (int i = 0; i < 256; i++) + { + this.table[i] = new LzwString((byte)i); + } + + this.Init(); + } + + private void Init() + { + // Table length is 256 + 2, because of special clear code and end of information code. + this.tableLength = 258; + this.bitsPerCode = MinBits; + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + this.maxString = 1; + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// The pixel array to decode to. + public void DecodePixels(Span pixels) + { + // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. + // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ + int code; + int offset = 0; + + while ((code = this.GetNextCode()) != EoiCode) + { + if (code == ClearCode) + { + this.Init(); + code = this.GetNextCode(); + + if (code == EoiCode) + { + break; + } + + if (this.table[code] == null) + { + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); + } + + offset += this.table[code].WriteTo(pixels, offset); + } + else + { + if (this.table[this.oldCode] == null) + { + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); + } + + if (this.IsInTable(code)) + { + offset += this.table[code].WriteTo(pixels, offset); + + this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); + } + else + { + LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); + + offset += outString.WriteTo(pixels, offset); + this.AddStringToTable(outString); + } + } + + this.oldCode = code; + + if (offset >= pixels.Length) + { + break; + } + } + } + + private void AddStringToTable(LzwString lzwString) + { + if (this.tableLength > this.table.Length) + { + TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); + } + + this.table[this.tableLength++] = lzwString; + + if (this.tableLength > this.maxCode) + { + this.bitsPerCode++; + + if (this.bitsPerCode > MaxBits) + { + // Continue reading MaxBits (12 bit) length codes. + this.bitsPerCode = MaxBits; + } + + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + } + + if (lzwString.Length > this.maxString) + { + this.maxString = lzwString.Length; + } + } + + private int GetNextCode() + { + if (this.eofReached) + { + return EoiCode; + } + + int read = this.stream.ReadByte(); + if (read < 0) + { + this.eofReached = true; + return EoiCode; + } + + this.nextData = (this.nextData << 8) | read; + this.nextBits += 8; + + if (this.nextBits < this.bitsPerCode) + { + read = this.stream.ReadByte(); + if (read < 0) + { + this.eofReached = true; + return EoiCode; + } + + this.nextData = (this.nextData << 8) | read; + this.nextBits += 8; + } + + int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; + this.nextBits -= this.bitsPerCode; + + return code; + } + + private bool IsInTable(int code) => code < this.tableLength; + + private int MaxCode() => this.bitMask - 1; + + private static int BitmaskFor(int bits) => (1 << bits) - 1; + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs new file mode 100644 index 000000000..19103de92 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// Fax compression options, see TIFF spec page 51f (T4Options). + /// + [Flags] + public enum FaxCompressionOptions : uint + { + /// + /// No options. + /// + None = 0, + + /// + /// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed). + /// + TwoDimensionalCoding = 1, + + /// + /// If set, uncompressed mode is used. + /// + UncompressedMode = 2, + + /// + /// If set, fill bits have been added as necessary before EOL codes such that + /// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte + /// preceded by a zero nibble: xxxx-0000 0000-0001. + /// + EolPadding = 4 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs new file mode 100644 index 000000000..ae2f17dbb --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -0,0 +1,138 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images. + /// + internal static class HorizontalPredictor + { + /// + /// Inverts the horizontal prediction. + /// + /// Buffer with decompressed pixel data. + /// The width of the image or strip. + /// Bits per pixel. + public static void Undo(Span pixelBytes, int width, int bitsPerPixel) + { + if (bitsPerPixel == 8) + { + Undo8Bit(pixelBytes, width); + } + else if (bitsPerPixel == 24) + { + Undo24Bit(pixelBytes, width); + } + } + + public static void ApplyHorizontalPrediction(Span rows, int width, int bitsPerPixel) + { + if (bitsPerPixel == 8) + { + ApplyHorizontalPrediction8Bit(rows, width); + } + else if (bitsPerPixel == 24) + { + ApplyHorizontalPrediction24Bit(rows, width); + } + } + + /// + /// Applies a horizontal predictor to the rgb row. + /// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next. + /// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus + /// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly. + /// + /// The rgb pixel rows. + /// The width. + [MethodImpl(InliningOptions.ShortMethod)] + private static void ApplyHorizontalPrediction24Bit(Span rows, int width) + { + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) + { + Span rowSpan = rows.Slice(y * width, width); + Span rowRgb = MemoryMarshal.Cast(rowSpan); + + for (int x = rowRgb.Length - 1; x >= 1; x--) + { + byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); + byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); + byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); + var rgb = new Rgb24(r, g, b); + rowRgb[x].FromRgb24(rgb); + } + } + } + + /// + /// Applies a horizontal predictor to a gray pixel row. + /// + /// The gray pixel rows. + /// The width. + [MethodImpl(InliningOptions.ShortMethod)] + private static void ApplyHorizontalPrediction8Bit(Span rows, int width) + { + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) + { + Span rowSpan = rows.Slice(y * width, width); + for (int x = rowSpan.Length - 1; x >= 1; x--) + { + rowSpan[x] -= rowSpan[x - 1]; + } + } + } + + private static void Undo8Bit(Span pixelBytes, int width) + { + int rowBytesCount = width; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) + { + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + + byte pixelValue = rowBytes[0]; + for (int x = 1; x < width; x++) + { + pixelValue += rowBytes[x]; + rowBytes[x] = pixelValue; + } + } + } + + private static void Undo24Bit(Span pixelBytes, int width) + { + int rowBytesCount = width * 3; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) + { + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + Span rowRgb = MemoryMarshal.Cast(rowBytes).Slice(0, width); + ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); + byte r = rowRgbBase.R; + byte g = rowRgbBase.G; + byte b = rowRgbBase.B; + + for (int x = 1; x < rowRgb.Length; x++) + { + ref Rgb24 pixel = ref rowRgb[x]; + r += pixel.R; + g += pixel.G; + b += pixel.B; + var rgb = new Rgb24(r, g, b); + pixel.FromRgb24(rgb); + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs new file mode 100644 index 000000000..5bd4cd1f1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + internal abstract class TiffBaseCompression : IDisposable + { + private bool isDisposed; + + protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + { + this.Allocator = allocator; + this.Width = width; + this.BitsPerPixel = bitsPerPixel; + this.Predictor = predictor; + this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; + } + + /// + /// Gets the image width. + /// + public int Width { get; } + + /// + /// Gets the bits per pixel. + /// + public int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow { get; } + + /// + /// Gets the predictor to use. Should only be used with deflate or lzw compression. + /// + public TiffPredictor Predictor { get; } + + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.Dispose(true); + } + + protected abstract void Dispose(bool disposing); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs new file mode 100644 index 000000000..c5c5c466d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + internal abstract class TiffBaseCompressor : TiffBaseCompression + { + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image to. + /// The memory allocator. + /// The image width. + /// Bits per pixel. + /// The predictor to use (should only be used with deflate or lzw compression). Defaults to none. + protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(allocator, width, bitsPerPixel, predictor) + => this.Output = output; + + /// + /// Gets the compression method to use. + /// + public abstract TiffCompression Method { get; } + + /// + /// Gets the output stream to write the compressed image to. + /// + public Stream Output { get; } + + /// + /// Does any initialization required for the compression. + /// + /// The number of rows per strip. + public abstract void Initialize(int rowsPerStrip); + + /// + /// Compresses a strip of the image. + /// + /// Image rows to compress. + /// Image height. + public abstract void CompressStrip(Span rows, int height); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs new file mode 100644 index 000000000..a289e306a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// The base tiff decompressor class. + /// + internal abstract class TiffBaseDecompressor : TiffBaseCompression + { + protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(allocator, width, bitsPerPixel, predictor) + { + } + + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The strip offset of stream. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, Span buffer) + { + if (stripByteCount > int.MaxValue) + { + TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big."); + } + + stream.Seek(stripOffset, SeekOrigin.Begin); + this.Decompress(stream, (int)stripByteCount, buffer); + + if (stripOffset + stripByteCount < stream.Position) + { + TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); + } + } + + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span buffer); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs new file mode 100644 index 000000000..14a0c6e9d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + internal static class TiffCompressorFactory + { + public static TiffBaseCompressor Create( + TiffCompression method, + Stream output, + MemoryAllocator allocator, + int width, + int bitsPerPixel, + DeflateCompressionLevel compressionLevel, + TiffPredictor predictor) + { + switch (method) + { + // The following compression types are not implemented in the encoder and will default to no compression instead. + case TiffCompression.ItuTRecT43: + case TiffCompression.ItuTRecT82: + case TiffCompression.Jpeg: + case TiffCompression.OldJpeg: + case TiffCompression.OldDeflate: + case TiffCompression.None: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + + return new NoCompressor(output, allocator, width, bitsPerPixel); + + case TiffCompression.PackBits: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new PackBitsCompressor(output, allocator, width, bitsPerPixel); + + case TiffCompression.Deflate: + return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); + + case TiffCompression.Lzw: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); + + case TiffCompression.CcittGroup3Fax: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); + + case TiffCompression.Ccitt1D: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); + + default: + throw TiffThrowHelper.NotSupportedCompressor(method.ToString()); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs new file mode 100644 index 000000000..80bc0af5a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// Provides enumeration of the various TIFF compression types the decoder can handle. + /// + internal enum TiffDecoderCompressionType + { + /// + /// Image data is stored uncompressed in the TIFF file. + /// + None = 0, + + /// + /// Image data is compressed using PackBits compression. + /// + PackBits = 1, + + /// + /// Image data is compressed using Deflate compression. + /// + Deflate = 2, + + /// + /// Image data is compressed using LZW compression. + /// + Lzw = 3, + + /// + /// Image data is compressed using T4-encoding: CCITT T.4. + /// + T4 = 4, + + /// + /// Image data is compressed using modified huffman compression. + /// + HuffmanRle = 5, + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs new file mode 100644 index 000000000..a6d44f4d3 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + internal static class TiffDecompressorsFactory + { + public static TiffBaseDecompressor Create( + TiffDecoderCompressionType method, + MemoryAllocator allocator, + TiffPhotometricInterpretation photometricInterpretation, + int width, + int bitsPerPixel, + TiffPredictor predictor, + FaxCompressionOptions faxOptions) + { + switch (method) + { + case TiffDecoderCompressionType.None: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new NoneTiffCompression(allocator, width, bitsPerPixel); + + case TiffDecoderCompressionType.PackBits: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new PackBitsTiffCompression(allocator, width, bitsPerPixel); + + case TiffDecoderCompressionType.Deflate: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor); + + case TiffDecoderCompressionType.Lzw: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor); + + case TiffDecoderCompressionType.T4: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation); + + case TiffDecoderCompressionType.HuffmanRle: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation); + + default: + throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs new file mode 100644 index 000000000..031494fc5 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the compression formats defined by the Tiff file-format. + /// + public enum TiffCompression : ushort + { + /// + /// A invalid compression value. + /// + Invalid = 0, + + /// + /// No compression. + /// + None = 1, + + /// + /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding. + /// + Ccitt1D = 2, + + /// + /// PackBits compression + /// + PackBits = 32773, + + /// + /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + CcittGroup3Fax = 3, + + /// + /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is choosen. + /// + CcittGroup4Fax = 4, + + /// + /// LZW compression (see Section 13 of the TIFF 6.0 specification). + /// + Lzw = 5, + + /// + /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification). + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + OldJpeg = 6, + + /// + /// JPEG compression (see TIFF Specification, supplement 2). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + Jpeg = 7, + + /// + /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2). + /// + Deflate = 8, + + /// + /// Deflate compression - old. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + OldDeflate = 32946, + + /// + /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + ItuTRecT82 = 9, + + /// + /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + ItuTRecT43 = 10 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs new file mode 100644 index 000000000..a30890a69 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Defines constants defined in the TIFF specification. + /// + internal static class TiffConstants + { + /// + /// Byte order markers for indicating little endian encoding. + /// + public const byte ByteOrderLittleEndian = 0x49; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const byte ByteOrderBigEndian = 0x4D; + + /// + /// Byte order markers for indicating little endian encoding. + /// + public const ushort ByteOrderLittleEndianShort = 0x4949; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const ushort ByteOrderBigEndianShort = 0x4D4D; + + /// + /// Magic number used within the image file header to identify a TIFF format file. + /// + public const ushort HeaderMagicNumber = 42; + + /// + /// RowsPerStrip default value, which is effectively infinity. + /// + public const int RowsPerStripInfinity = 2147483647; + + /// + /// Size (in bytes) of the TIFF file header. + /// + public const int SizeOfTiffHeader = 8; + + /// + /// Size (in bytes) of each individual TIFF IFD entry + /// + public const int SizeOfIfdEntry = 12; + + /// + /// Size (in bytes) of the Short and SShort data types + /// + public const int SizeOfShort = 2; + + /// + /// Size (in bytes) of the Long and SLong data types + /// + public const int SizeOfLong = 4; + + /// + /// Size (in bytes) of the Rational and SRational data types + /// + public const int SizeOfRational = 8; + + /// + /// Size (in bytes) of the Float data type + /// + public const int SizeOfFloat = 4; + + /// + /// Size (in bytes) of the Double data type + /// + public const int SizeOfDouble = 8; + + /// + /// The default strip size is 8k. + /// + public const int DefaultStripSize = 8 * 1024; + + /// + /// The bits per sample for 1 bit bicolor images. + /// + public static readonly ushort[] BitsPerSample1Bit = { 1 }; + + /// + /// The bits per sample for images with a 4 color palette. + /// + public static readonly ushort[] BitsPerSample4Bit = { 4 }; + + /// + /// The bits per sample for 8 bit images. + /// + public static readonly ushort[] BitsPerSample8Bit = { 8 }; + + /// + /// The bits per sample for images with 8 bits for each color channel. + /// + public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 }; + + /// + /// The list of mimetypes that equate to a tiff. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; + + /// + /// The list of file extensions that equate to a tiff. + /// + public static readonly IEnumerable FileExtensions = new[] { "tiff", "tif" }; + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs new file mode 100644 index 000000000..c10167d25 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the possible uses of extra components in TIFF format files. + /// + internal enum TiffExtraSamples + { + /// + /// Unspecified data. + /// + Unspecified = 0, + + /// + /// Associated alpha data (with pre-multiplied color). + /// + AssociatedAlpha = 1, + + /// + /// Unassociated alpha data. + /// + UnassociatedAlpha = 2 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs new file mode 100644 index 000000000..1bb75f836 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the fill orders defined by the Tiff file-format. + /// + internal enum TiffFillOrder : ushort + { + /// + /// Pixels with lower column values are stored in the higher-order bits of the byte. + /// + MostSignificantBitFirst = 1, + + /// + /// Pixels with lower column values are stored in the lower-order bits of the byte. + /// + LeastSignificantBitFirst = 2 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs new file mode 100644 index 000000000..4ed6aafbb --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// + [Flags] + public enum TiffNewSubfileType : uint + { + /// + /// A full-resolution image. + /// + FullImage = 0, + + /// + /// Reduced-resolution version of another image in this TIFF file. + /// + Preview = 1, + + /// + /// A single page of a multi-page image. + /// + SinglePage = 2, + + /// + /// A transparency mask for another image in this TIFF file. + /// + TransparencyMask = 4, + + /// + /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification). + /// + AlternativePreview = 65536, + + /// + /// Mixed raster content (see RFC2301). + /// + MixedRasterContent = 8 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs new file mode 100644 index 000000000..a5305d482 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the image orientations defined by the Tiff file-format. + /// + internal enum TiffOrientation + { + /// + /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively. + /// + TopLeft = 1, + + /// + /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively. + /// + TopRight = 2, + + /// + /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively. + /// + BottomRight = 3, + + /// + /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively. + /// + BottomLeft = 4, + + /// + /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively. + /// + LeftTop = 5, + + /// + /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively. + /// + RightTop = 6, + + /// + /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively. + /// + RightBottom = 7, + + /// + /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively. + /// + LeftBottom = 8 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs new file mode 100644 index 000000000..6dab7de6e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the photometric interpretation formats defined by the Tiff file-format. + /// + public enum TiffPhotometricInterpretation : ushort + { + /// + /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + /// Not supported by the TiffEncoder. + /// + WhiteIsZero = 0, + + /// + /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero = 1, + + /// + /// RGB image. + /// + Rgb = 2, + + /// + /// Palette Color. + /// + PaletteColor = 3, + + /// + /// A transparency mask. + /// + /// Not supported by the TiffEncoder. + /// + TransparencyMask = 4, + + /// + /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + Separated = 5, + + /// + /// YCbCr (see Section 21 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + YCbCr = 6, + + /// + /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + CieLab = 8, + + /// + /// ICC L*a*b* (see TIFF Specification, supplement 1). + /// + /// Not supported by the TiffEncoder. + /// + IccLab = 9, + + /// + /// ITU L*a*b* (see RFC2301). + /// + /// Not supported by the TiffEncoder. + /// + ItuLab = 10, + + /// + /// Color Filter Array (see the DNG specification). + /// + /// Not supported by the TiffEncoder. + /// + ColorFilterArray = 32803, + + /// + /// Linear Raw (see the DNG specification). + /// + /// Not supported by the TiffEncoder. + /// + LinearRaw = 34892 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs new file mode 100644 index 000000000..ea526ede5 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing how the components of each pixel are stored the Tiff file-format. + /// + public enum TiffPlanarConfiguration : ushort + { + /// + /// Chunky format. + /// The component values for each pixel are stored contiguously. + /// The order of the components within the pixel is specified by + /// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB. + /// + Chunky = 1, + + /// + /// Planar format. + /// The components are stored in separate “component planes.” The + /// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional + /// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns + /// for row 0 are stored first, followed by the columns of row 1, and so on.) + /// PhotometricInterpretation describes the type of data stored in each component + /// plane. For example, RGB data is stored with the Red components in one component + /// plane, the Green in another, and the Blue in another. + /// + Planar = 2 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs new file mode 100644 index 000000000..6bde23cac --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// A mathematical operator that is applied to the image data before an encoding scheme is applied. + /// + public enum TiffPredictor : ushort + { + /// + /// No prediction. + /// + None = 1, + + /// + /// Horizontal differencing. + /// + Horizontal = 2, + + /// + /// Floating point horizontal differencing. + /// + /// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none. + /// + FloatingPoint = 3 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs new file mode 100644 index 000000000..81899c5fd --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Specifies how to interpret each data sample in a pixel. + /// + public enum TiffSampleFormat : ushort + { + /// + /// Unsigned integer data. Default value. + /// + UnsignedInteger = 1, + + /// + /// Signed integer data. + /// + SignedInteger = 2, + + /// + /// IEEE floating point data. + /// + Float = 3, + + /// + /// Undefined data format. + /// + Undefined = 4, + + /// + /// The complex int. + /// + ComplexInt = 5, + + /// + /// The complex float. + /// + ComplexFloat = 6 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs new file mode 100644 index 000000000..ff735de86 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// + public enum TiffSubfileType : ushort + { + /// + /// Full-resolution image data. + /// + FullImage = 1, + + /// + /// Reduced-resolution image data. + /// + Preview = 2, + + /// + /// A single page of a multi-page image. + /// + SinglePage = 3 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs new file mode 100644 index 000000000..fce0b175c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants +{ + /// + /// Enumeration representing the thresholding applied to image data defined by the Tiff file-format. + /// + internal enum TiffThresholding + { + /// + /// No dithering or halftoning. + /// + None = 1, + + /// + /// An ordered dither or halftone technique. + /// + Ordered = 2, + + /// + /// A randomized process such as error diffusion. + /// + Random = 3 + } +} diff --git a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs new file mode 100644 index 000000000..537238439 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Encapsulates the options for the . + /// + internal interface ITiffDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs new file mode 100644 index 000000000..d56a587df --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Encapsulates the options for the . + /// + internal interface ITiffEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + TiffBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets the compression type to use. + /// + TiffCompression? Compression { get; } + + /// + /// Gets the compression level 1-9 for the deflate compression mode. + /// Defaults to . + /// + DeflateCompressionLevel? CompressionLevel { get; } + + /// + /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. + /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. + /// + TiffPhotometricInterpretation? PhotometricInterpretation { get; } + + /// + /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. + /// + TiffPredictor? HorizontalPredictor { get; } + + /// + /// Gets the quantizer for creating a color palette image. + /// + IQuantizer Quantizer { get; } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs new file mode 100644 index 000000000..8b2c6bd3a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The TIFF IFD reader class. + /// + internal class DirectoryReader + { + private readonly Stream stream; + + private uint nextIfdOffset; + + // used for sequential read big values (actual for multiframe big files) + // todo: different tags can link to the same data (stream offset) - investigate + private readonly SortedList lazyLoaders = new SortedList(new DuplicateKeyComparer()); + + public DirectoryReader(Stream stream) => this.stream = stream; + + /// + /// Gets the byte order. + /// + public ByteOrder ByteOrder { get; private set; } + + /// + /// Reads image file directories. + /// + /// Image file directories. + public IEnumerable Read() + { + this.ByteOrder = ReadByteOrder(this.stream); + this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader(); + return this.ReadIfds(); + } + + private static ByteOrder ReadByteOrder(Stream stream) + { + var headerBytes = new byte[2]; + stream.Read(headerBytes, 0, 2); + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) + { + return ByteOrder.LittleEndian; + } + else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) + { + return ByteOrder.BigEndian; + } + + throw TiffThrowHelper.ThrowInvalidHeader(); + } + + private IEnumerable ReadIfds() + { + var readers = new List(); + while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length) + { + var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders); + reader.ReadTags(); + + this.nextIfdOffset = reader.NextIfdOffset; + + readers.Add(reader); + } + + // Sequential reading big values. + foreach (Action loader in this.lazyLoaders.Values) + { + loader(); + } + + var list = new List(); + foreach (EntryReader reader in readers) + { + var profile = new ExifProfile(reader.Values, reader.InvalidTags); + list.Add(profile); + } + + return list; + } + + /// + /// used for possibility add a duplicate offsets (but tags don't duplicate). + /// + /// The type of the key. + private class DuplicateKeyComparer : IComparer + where TKey : IComparable + { + public int Compare(TKey x, TKey y) + { + int result = x.CompareTo(y); + + // Handle equality as being greater. + return (result == 0) ? 1 : result; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs new file mode 100644 index 000000000..123a64cc1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + internal class EntryReader : BaseExifReader + { + private readonly uint startOffset; + + private readonly SortedList lazyLoaders; + + public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList lazyLoaders) + : base(stream) + { + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; + this.startOffset = ifdOffset; + this.lazyLoaders = lazyLoaders; + } + + public List Values { get; } = new List(); + + public uint NextIfdOffset { get; private set; } + + public void ReadTags() + { + this.ReadValues(this.Values, this.startOffset); + this.NextIfdOffset = this.ReadUInt32(); + + this.ReadSubIfd(this.Values); + } + + protected override void RegisterExtLoader(uint offset, Action reader) => + this.lazyLoaders.Add(offset, reader); + } + + internal class HeaderReader : BaseExifReader + { + public HeaderReader(Stream stream, ByteOrder byteOrder) + : base(stream) => + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; + + public uint FirstIfdOffset { get; private set; } + + public uint ReadFileHeader() + { + ushort magic = this.ReadUInt16(); + if (magic != TiffConstants.HeaderMagicNumber) + { + TiffThrowHelper.ThrowInvalidHeader(); + } + + this.FirstIfdOffset = this.ReadUInt32(); + return this.FirstIfdOffset; + } + + protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs b/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs new file mode 100644 index 000000000..b9da86fc4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the tiff format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); + + /// + /// Gets the tiff format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs new file mode 100644 index 000000000..6724adec0 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images). + /// + /// The pixel format. + internal class BlackIsZero1TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + Color black = Color.Black; + Color white = Color.White; + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x += 8) + { + byte b = data[offset++]; + int maxShift = Math.Min(left + width - x, 8); + + for (int shift = 0; shift < maxShift; shift++) + { + int bit = (b >> (7 - shift)) & 1; + + color.FromRgba32(bit == 0 ? black : white); + + pixels[x + shift, y] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs new file mode 100644 index 000000000..cf59c1222 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images). + /// + internal class BlackIsZero4TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + bool isOddWidth = (width & 1) == 1; + + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width - 1;) + { + byte byteData = data[offset++]; + + byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); + + pixels[x++, y] = color; + + byte intensity2 = (byte)((byteData & 0x0F) * 17); + l8.PackedValue = intensity2; + color.FromL8(l8); + + pixels[x++, y] = color; + } + + if (isOddWidth) + { + byte byteData = data[offset++]; + + byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); + + pixels[left + width - 1, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs new file mode 100644 index 000000000..096f0449b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images). + /// + internal class BlackIsZero8TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + byte intensity = data[offset++]; + + l8.PackedValue = intensity; + color.FromL8(l8); + + pixels[x, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs new file mode 100644 index 000000000..83cef8e75 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (for all bit depths). + /// + internal class BlackIsZeroTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly ushort bitsPerSample0; + + private readonly float factor; + + public BlackIsZeroTiffColor(ushort[] bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample[0]; + this.factor = (1 << this.bitsPerSample0) - 1.0f; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + int value = bitReader.ReadBits(this.bitsPerSample0); + float intensity = value / this.factor; + + color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + pixels[x, y] = color; + } + + bitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs new file mode 100644 index 000000000..7ed25f822 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). + /// + internal class PaletteTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly ushort bitsPerSample0; + + private readonly TPixel[] palette; + + /// The number of bits per sample for each pixel. + /// The RGB color lookup table to use for decoding the image. + public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap) + { + this.bitsPerSample0 = bitsPerSample[0]; + int colorCount = 1 << this.bitsPerSample0; + this.palette = GeneratePalette(colorMap, colorCount); + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + int index = bitReader.ReadBits(this.bitsPerSample0); + pixels[x, y] = this.palette[index]; + } + + bitReader.NextRow(); + } + } + + private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) + { + var palette = new TPixel[colorCount]; + + const int rOffset = 0; + int gOffset = colorCount; + int bOffset = colorCount * 2; + + for (int i = 0; i < palette.Length; i++) + { + float r = colorMap[rOffset + i] / 65535F; + float g = colorMap[gOffset + i] / 65535F; + float b = colorMap[bOffset + i] / 65535F; + palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); + } + + return palette; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs new file mode 100644 index 000000000..e45863a57 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images). + /// + internal class Rgb888TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var rgba = default(Rgba32); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + + for (int x = left; x < left + width; x++) + { + byte r = data[offset++]; + byte g = data[offset++]; + byte b = data[offset++]; + + rgba.PackedValue = (uint)(r | (g << 8) | (b << 16) | (0xff << 24)); + color.FromRgba32(rgba); + + pixelRow[x] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs new file mode 100644 index 000000000..e45dd44bd --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). + /// + internal class RgbPlanarTiffColor + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + public RgbPlanarTiffColor(ushort[] bitsPerSample) + /* : base(bitsPerSample, null) */ + { + this.bitsPerSampleR = bitsPerSample[0]; + this.bitsPerSampleG = bitsPerSample[1]; + this.bitsPerSampleB = bitsPerSample[2]; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + } + + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public void Decode(IManagedByteBuffer[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var rBitReader = new BitReader(data[0].GetSpan()); + var gBitReader = new BitReader(data[1].GetSpan()); + var bBitReader = new BitReader(data[2].GetSpan()); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + + color.FromVector4(new Vector4(r, g, b, 1.0f)); + pixels[x, y] = color; + } + + rBitReader.NextRow(); + gBitReader.NextRow(); + bBitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs new file mode 100644 index 000000000..816ba67b7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation (for all bit depths). + /// + internal class RgbTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + public RgbTiffColor(ushort[] bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample[0]; + this.bitsPerSampleG = bitsPerSample[1]; + this.bitsPerSampleB = bitsPerSample[2]; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + + color.FromVector4(new Vector4(r, g, b, 1.0f)); + pixels[x, y] = color; + } + + bitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs new file mode 100644 index 000000000..c08b26ef1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// The base class for photometric interpretation decoders. + /// + /// The pixel format. + internal abstract class TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + /// Decodes source raw pixel data using the current photometric interpretation. + /// + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public abstract void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height); + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs new file mode 100644 index 000000000..0a7941dfb --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + internal static class TiffColorDecoderFactory + where TPixel : unmanaged, IPixel + { + public static TiffBaseColorDecoder Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) + { + switch (colorType) + { + case TiffColorType.WhiteIsZero: + DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZeroTiffColor(bitsPerSample); + + case TiffColorType.WhiteIsZero1: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero1TiffColor(); + + case TiffColorType.WhiteIsZero4: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero4TiffColor(); + + case TiffColorType.WhiteIsZero8: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero8TiffColor(); + + case TiffColorType.BlackIsZero: + DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZeroTiffColor(bitsPerSample); + + case TiffColorType.BlackIsZero1: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero1TiffColor(); + + case TiffColorType.BlackIsZero4: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero4TiffColor(); + + case TiffColorType.BlackIsZero8: + DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero8TiffColor(); + + case TiffColorType.Rgb: + DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb888: + DebugGuard.IsTrue( + bitsPerSample.Length == 3 + && bitsPerSample[0] == 8 + && bitsPerSample[1] == 8 + && bitsPerSample[2] == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb888TiffColor(); + + case TiffColorType.PaletteColor: + DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); + DebugGuard.NotNull(colorMap, "colorMap"); + return new PaletteTiffColor(bitsPerSample, colorMap); + + default: + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); + } + } + + public static RgbPlanarTiffColor CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) + { + switch (colorType) + { + case TiffColorType.RgbPlanar: + DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbPlanarTiffColor(bitsPerSample); + + default: + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs new file mode 100644 index 000000000..484d23163 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Provides enumeration of the various TIFF photometric interpretation implementation types. + /// + internal enum TiffColorType + { + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images. + /// + BlackIsZero1, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images. + /// + BlackIsZero4, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images. + /// + BlackIsZero8, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + WhiteIsZero, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images. + /// + WhiteIsZero1, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images. + /// + WhiteIsZero4, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images. + /// + WhiteIsZero8, + + /// + /// Palette-color. + /// + PaletteColor, + + /// + /// RGB Full Color. + /// + Rgb, + + /// + /// RGB Full Color. Optimized implementation for 8-bit images. + /// + Rgb888, + + /// + /// RGB Full Color. Planar configuration of data. + /// + RgbPlanar, + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs new file mode 100644 index 000000000..465414257 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images). + /// + internal class WhiteIsZero1TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + Color black = Color.Black; + Color white = Color.White; + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x += 8) + { + byte b = data[offset++]; + int maxShift = Math.Min(left + width - x, 8); + + for (int shift = 0; shift < maxShift; shift++) + { + int bit = (b >> (7 - shift)) & 1; + + color.FromRgba32(bit == 0 ? white : black); + + pixels[x + shift, y] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs new file mode 100644 index 000000000..dae89db28 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images). + /// + internal class WhiteIsZero4TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + bool isOddWidth = (width & 1) == 1; + + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width - 1;) + { + byte byteData = data[offset++]; + + byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); + + pixels[x++, y] = color; + + byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); + l8.PackedValue = intensity2; + color.FromL8(l8); + + pixels[x++, y] = color; + } + + if (isOddWidth) + { + byte byteData = data[offset++]; + + byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); + + pixels[left + width - 1, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs new file mode 100644 index 000000000..1b141f9f6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images). + /// + internal class WhiteIsZero8TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + byte intensity = (byte)(255 - data[offset++]); + + l8.PackedValue = intensity; + color.FromL8(l8); + + pixels[x, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs new file mode 100644 index 000000000..697fe2f07 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths). + /// + internal class WhiteIsZeroTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly ushort bitsPerSample0; + + private readonly float factor; + + public WhiteIsZeroTiffColor(ushort[] bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample[0]; + this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + int value = bitReader.ReadBits(this.bitsPerSample0); + float intensity = 1.0f - (value / this.factor); + + color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + pixels[x, y] = color; + } + + bitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md new file mode 100644 index 000000000..5b116b819 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -0,0 +1,253 @@ +# ImageSharp TIFF codec + +## References +- TIFF + - [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf) + - [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf) + - [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf) + - [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf) + - [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt) + - [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP) + - [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html) + - [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html) + - [CCITT T.4 Compression](https://www.itu.int/rec/T-REC-T.4-198811-S/_page.print) + - [CCITT T.6 Compression](https://www.itu.int/rec/T-REC-T.6/en) + +- DNG + - [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html) + +- Metadata (EXIF) + - [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) + +- Metadata (XMP) + - [Adobe XMP Pages](http://www.adobe.com/products/xmp.html) + - [Adobe XMP Developer Center](http://www.adobe.com/devnet/xmp.html) + +## Implementation Status + +- The Decoder and Encoder currently only supports a single frame per image. +- Some compression formats are not yet supported. See the list below. + +### Deviations from the TIFF spec (to be fixed) + +- Decoder + - A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels) + - NB: Need to handle this for both planar and chunky data + - If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this + - Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows) + - Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB? + +### Compression Formats + +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|--------------------------| +|None | Y | Y | | +|Ccitt1D | Y | Y | | +|PackBits | Y | Y | | +|CcittGroup3Fax | Y | Y | | +|CcittGroup4Fax | | | | +|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | +|Old Jpeg | | | We should not even try to support this | +|Jpeg (Technote 2) | | | | +|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. | +|Old Deflate (Technote 2) | | Y | | + +### Photometric Interpretation Formats + +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|--------------------------| +|WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations | +|BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations | +|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation | +|Rgb (Planar) | | Y | General implementation only | +|PaletteColor | Y | Y | General implementation only | +|TransparencyMask | | | | +|Separated (TIFF Extension) | | | | +|YCbCr (TIFF Extension) | | | | +|CieLab (TIFF Extension) | | | | +|IccLab (TechNote 1) | | | | + +### Baseline TIFF Tags + +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|--------------------------| +|NewSubfileType | | | | +|SubfileType | | | | +|ImageWidth | Y | Y | | +|ImageLength | Y | Y | | +|BitsPerSample | Y | Y | | +|Compression | Y | Y | | +|PhotometricInterpretation | Y | Y | | +|Thresholding | | | | +|CellWidth | | | | +|CellLength | | | | +|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | +|ImageDescription | Y | Y | | +|Make | Y | Y | | +|Model | Y | Y | | +|StripOffsets | Y | Y | | +|Orientation | | - | Ignore. Many readers ignore this tag. | +|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | +|RowsPerStrip | Y | Y | | +|StripByteCounts | Y | Y | | +|MinSampleValue | | | | +|MaxSampleValue | | | | +|XResolution | Y | Y | | +|YResolution | Y | Y | | +|PlanarConfiguration | | Y | Encoding support only chunky. | +|FreeOffsets | | | | +|FreeByteCounts | | | | +|GrayResponseUnit | | | | +|GrayResponseCurve | | | | +|ResolutionUnit | Y | Y | | +|Software | Y | Y | | +|DateTime | Y | Y | | +|Artist | Y | Y | | +|HostComputer | Y | Y | | +|ColorMap | Y | Y | | +|ExtraSamples | | - | | +|Copyright | Y | Y | | + +### Extension TIFF Tags + +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|--------------------------| +|NewSubfileType | | | | +|DocumentName | Y | Y | | +|PageName | | | | +|XPosition | | | | +|YPosition | | | | +|T4Options | | Y | | +|T6Options | | | | +|PageNumber | | | | +|TransferFunction | | | | +|Predictor | Y | Y | only Horizontal | +|WhitePoint | | | | +|PrimaryChromaticities | | | | +|HalftoneHints | | | | +|TileWidth | | - | | +|TileLength | | - | | +|TileOffsets | | - | | +|TileByteCounts | | - | | +|BadFaxLines | | | | +|CleanFaxData | | | | +|ConsecutiveBadFaxLines | | | | +|SubIFDs | | - | | +|InkSet | | | | +|InkNames | | | | +|NumberOfInks | | | | +|DotRange | | | | +|TargetPrinter | | | | +|SampleFormat | | - | | +|SMinSampleValue | | | | +|SMaxSampleValue | | | | +|TransferRange | | | | +|ClipPath | | | | +|XClipPathUnits | | | | +|YClipPathUnits | | | | +|Indexed | | | | +|JPEGTables | | | | +|OPIProxy | | | | +|GlobalParametersIFD | | | | +|ProfileType | | | | +|FaxProfile | | | | +|CodingMethods | | | | +|VersionYear | | | | +|ModeNumber | | | | +|Decode | | | | +|DefaultImageColor | | | | +|JPEGProc | | | | +|JPEGInterchangeFormat | | | | +|JPEGInterchangeFormatLength| | | | +|JPEGRestartInterval | | | | +|JPEGLosslessPredictors | | | | +|JPEGPointTransforms | | | | +|JPEGQTables | | | | +|JPEGDCTables | | | | +|JPEGACTables | | | | +|YCbCrCoefficients | | | | +|YCbCrSubSampling | | | | +|YCbCrPositioning | | | | +|ReferenceBlackWhite | | | | +|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). | +|XMP | Y | Y | | +|ImageID | | | | +|ImageLayer | | | | + +### Private TIFF Tags + +| |Encoder|Decoder|Comments | +|---------------------------|:-----:|:-----:|--------------------------| +|Wang Annotation | | | | +|MD FileTag | | | | +|MD ScalePixel | | | | +|MD ColorTable | | | | +|MD LabName | | | | +|MD SampleInfo | | | | +|MD PrepDate | | | | +|MD PrepTime | | | | +|MD FileUnits | | | | +|ModelPixelScaleTag | | | | +|IPTC | Y | Y | | +|INGR Packet Data Tag | | | | +|INGR Flag Registers | | | | +|IrasB Transformation Matrix| | | | +|ModelTiepointTag | | | | +|ModelTransformationTag | | | | +|Photoshop | | | | +|Exif IFD | | - | 0x8769 SubExif | +|ICC Profile | Y | Y | | +|GeoKeyDirectoryTag | | | | +|GeoDoubleParamsTag | | | | +|GeoAsciiParamsTag | | | | +|GPS IFD | | | | +|HylaFAX FaxRecvParams | | | | +|HylaFAX FaxSubAddress | | | | +|HylaFAX FaxRecvTime | | | | +|ImageSourceData | | | | +|Interoperability IFD | | | | +|GDAL_METADATA | | | | +|GDAL_NODATA | | | | +|Oce Scanjob Description | | | | +|Oce Application Selector | | | | +|Oce Identification Number | | | | +|Oce ImageLogic Characteristics| | | | +|DNGVersion | | | | +|DNGBackwardVersion | | | | +|UniqueCameraModel | | | | +|LocalizedCameraModel | | | | +|CFAPlaneColor | | | | +|CFALayout | | | | +|LinearizationTable | | | | +|BlackLevelRepeatDim | | | | +|BlackLevel | | | | +|BlackLevelDeltaH | | | | +|BlackLevelDeltaV | | | | +|WhiteLevel | | | | +|DefaultScale | | | | +|DefaultCropOrigin | | | | +|DefaultCropSize | | | | +|ColorMatrix1 | | | | +|ColorMatrix2 | | | | +|CameraCalibration1 | | | | +|CameraCalibration2 | | | | +|ReductionMatrix1 | | | | +|ReductionMatrix2 | | | | +|AnalogBalance | | | | +|AsShotNeutral | | | | +|AsShotWhiteXY | | | | +|BaselineExposure | | | | +|BaselineNoise | | | | +|BaselineSharpness | | | | +|BayerGreenSplit | | | | +|LinearResponseLimit | | | | +|CameraSerialNumber | | | | +|LensInfo | | | | +|ChromaBlurRadius | | | | +|AntiAliasStrength | | | | +|DNGPrivateData | | | | +|MakerNoteSafety | | | | +|CalibrationIlluminant1 | | | | +|CalibrationIlluminant2 | | | | +|BestQualityScale | | | | +|Alias Layer Metadata | | | | diff --git a/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf b/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf new file mode 100644 index 000000000..40724dd1e Binary files /dev/null and b/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf differ diff --git a/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf b/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf new file mode 100644 index 000000000..32fa877b1 Binary files /dev/null and b/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf differ diff --git a/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf b/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf new file mode 100644 index 000000000..99117063a Binary files /dev/null and b/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf differ diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs new file mode 100644 index 000000000..35a9a590b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumerates the available bits per pixel for the tiff format. + /// + public enum TiffBitsPerPixel + { + /// + /// 1 bit per pixel, for bi-color image. + /// + Bit1 = 1, + + /// + /// 4 bits per pixel, for images with a color palette. + /// + Bit4 = 4, + + /// + /// 8 bits per pixel, grayscale or color palette images. + /// + Bit8 = 8, + + /// + /// 24 bits per pixel. One byte for each color channel. + /// + Bit24 = 24, + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs new file mode 100644 index 000000000..bc74cbc5f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The number of bits per component. + /// + public enum TiffBitsPerSample + { + /// + /// The Bits per samples is not known. + /// + Unknown = 0, + + /// + /// One bit per sample for bicolor images. + /// + Bit1 = 1, + + /// + /// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. + /// + Bit4 = 4, + + /// + /// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. + /// + Bit8 = 8, + + /// + /// 24 bits per sample, each color channel has 8 Bits. + /// + Bit24 = 24, + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs new file mode 100644 index 000000000..5c4c374be --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + internal static class TiffBitsPerSampleExtensions + { + /// + /// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8] + /// + /// The tiff bits per sample. + /// Bits per sample array. + public static ushort[] Bits(this TiffBitsPerSample tiffBitsPerSample) + { + switch (tiffBitsPerSample) + { + case TiffBitsPerSample.Bit1: + return TiffConstants.BitsPerSample1Bit; + case TiffBitsPerSample.Bit4: + return TiffConstants.BitsPerSample4Bit; + case TiffBitsPerSample.Bit8: + return TiffConstants.BitsPerSample8Bit; + case TiffBitsPerSample.Bit24: + return TiffConstants.BitsPerSampleRgb8Bit; + + default: + return Array.Empty(); + } + } + + /// + /// Maps an array of bits per sample to a concrete enum value. + /// + /// The bits per sample array. + /// TiffBitsPerSample enum value. + public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) + { + switch (bitsPerSample.Length) + { + case 3: + if (bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && + bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2]) + { + return TiffBitsPerSample.Bit24; + } + + break; + + case 1: + if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) + { + return TiffBitsPerSample.Bit1; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) + { + return TiffBitsPerSample.Bit4; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) + { + return TiffBitsPerSample.Bit8; + } + + break; + } + + return TiffBitsPerSample.Unknown; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs new file mode 100644 index 000000000..e96dba207 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the TIFF format. + /// + public sealed class TiffConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); + configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs new file mode 100644 index 000000000..9d52e34df --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Image decoder for generating an image out of a TIFF stream. + /// + public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector + { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, "stream"); + + var decoder = new TiffDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TiffDecoderCore(configuration, this); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TiffDecoderCore(configuration, this); + return decoder.Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TiffDecoderCore(configuration, this); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs new file mode 100644 index 000000000..50882c007 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -0,0 +1,339 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Performs the tiff decoding operation. + /// + internal class TiffDecoderCore : IImageDecoderInternals + { + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool ignoreMetadata; + + /// + /// The stream to decode from. + /// + private BufferedReadStream inputStream; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The decoder options. + public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) + { + options ??= new TiffDecoder(); + + this.Configuration = configuration ?? Configuration.Default; + this.ignoreMetadata = options.IgnoreMetadata; + this.memoryAllocator = this.Configuration.MemoryAllocator; + } + + /// + /// Gets or sets the number of bits per component of the pixel format used to decode the image. + /// + public TiffBitsPerSample BitsPerSample { get; set; } + + /// + /// Gets or sets the bits per pixel. + /// + public int BitsPerPixel { get; set; } + + /// + /// Gets or sets the lookup table for RGB palette colored images. + /// + public ushort[] ColorMap { get; set; } + + /// + /// Gets or sets the photometric interpretation implementation to use when decoding the image. + /// + public TiffColorType ColorType { get; set; } + + /// + /// Gets or sets the compression used, when the image was encoded. + /// + public TiffDecoderCompressionType CompressionType { get; set; } + + /// + /// Gets or sets the Fax specific compression options. + /// + public FaxCompressionOptions FaxCompressionOptions { get; set; } + + /// + /// Gets or sets the planar configuration type to use when decoding the image. + /// + public TiffPlanarConfiguration PlanarConfiguration { get; set; } + + /// + /// Gets or sets the photometric interpretation. + /// + public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } + + /// + /// Gets or sets the horizontal predictor. + /// + public TiffPredictor Predictor { get; set; } + + /// + public Configuration Configuration { get; } + + /// + public Size Dimensions { get; private set; } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.inputStream = stream; + var reader = new DirectoryReader(stream); + + IEnumerable directories = reader.Read(); + + var frames = new List>(); + foreach (ExifProfile ifd in directories) + { + ImageFrame frame = this.DecodeFrame(ifd); + frames.Add(frame); + } + + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder); + + // TODO: Tiff frames can have different sizes + ImageFrame root = frames[0]; + this.Dimensions = root.Size(); + foreach (ImageFrame frame in frames) + { + if (frame.Size() != root.Size()) + { + TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); + } + } + + return new Image(this.Configuration, metadata, frames); + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.inputStream = stream; + var reader = new DirectoryReader(stream); + IEnumerable directories = reader.Read(); + + ExifProfile rootFrameExifProfile = directories.First(); + var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); + + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, rootFrameExifProfile); + int width = GetImageWidth(rootFrameExifProfile); + int height = GetImageHeight(rootFrameExifProfile); + + return new ImageInfo(new PixelTypeInfo((int)rootMetadata.BitsPerPixel), width, height, metadata); + } + + /// + /// Decodes the image data from a specified IFD. + /// + /// The pixel format. + /// The IFD tags. + /// + /// The tiff frame. + /// + private ImageFrame DecodeFrame(ExifProfile tags) + where TPixel : unmanaged, IPixel + { + ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ? + new ImageFrameMetadata() : + new ImageFrameMetadata { ExifProfile = tags, XmpProfile = tags.GetValue(ExifTag.XMP)?.Value }; + + TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); + TiffFrameMetadata.Parse(tiffFrameMetaData, tags); + + this.VerifyAndParse(tags, tiffFrameMetaData); + + int width = GetImageWidth(tags); + int height = GetImageHeight(tags); + var frame = new ImageFrame(this.Configuration, width, height, imageFrameMetaData); + + int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + Number[] stripOffsets = tags.GetValue(ExifTag.StripOffsets)?.Value; + Number[] stripByteCounts = tags.GetValue(ExifTag.StripByteCounts)?.Value; + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) + { + this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts); + } + else + { + this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts); + } + + return frame; + } + + /// + /// Calculates the size (in bytes) for a pixel buffer using the determined color format. + /// + /// The width for the desired pixel buffer. + /// The height for the desired pixel buffer. + /// The index of the plane for planar image configuration (or zero for chunky). + /// The size (in bytes) of the required pixel buffer. + private int CalculateStripBufferSize(int width, int height, int plane = -1) + { + int bitsPerPixel; + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); + bitsPerPixel = this.BitsPerPixel; + } + else + { + bitsPerPixel = this.BitsPerSample.Bits()[plane]; + } + + int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; + return bytesPerRow * height; + } + + /// + /// Decodes the image data for strip encoded data. + /// + /// The pixel format. + /// The image frame to decode data into. + /// The number of rows per strip of data. + /// An array of byte offsets to each strip in the image. + /// An array of the size of each strip (in bytes). + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) + where TPixel : unmanaged, IPixel + { + int stripsPerPixel = this.BitsPerSample.Bits().Length; + int stripsPerPlane = stripOffsets.Length / stripsPerPixel; + int bitsPerPixel = this.BitsPerPixel; + + Buffer2D pixels = frame.PixelBuffer; + + var stripBuffers = new IManagedByteBuffer[stripsPerPixel]; + + try + { + for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) + { + int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); + stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); + } + + using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + + RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); + + for (int i = 0; i < stripsPerPlane; i++) + { + int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; + + for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) + { + int stripIndex = (i * stripsPerPixel) + planeIndex; + + decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); + } + + colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); + } + } + finally + { + foreach (IManagedByteBuffer buf in stripBuffers) + { + buf?.Dispose(); + } + } + } + + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) + where TPixel : unmanaged, IPixel + { + // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. + if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) + { + rowsPerStrip = frame.Height; + } + + int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); + int bitsPerPixel = this.BitsPerPixel; + + using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean); + + Buffer2D pixels = frame.PixelBuffer; + + using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); + + for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) + { + int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; + int top = rowsPerStrip * stripIndex; + if (top + stripHeight > frame.Height) + { + // Make sure we ignore any strips that are not needed for the image (if too many are present) + break; + } + + decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffer.GetSpan()); + + colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, top, frame.Width, stripHeight); + } + } + + /// + /// Gets the width of the image frame. + /// + /// The image frame exif profile. + /// The image width. + private static int GetImageWidth(ExifProfile exifProfile) + { + IExifValue width = exifProfile.GetValue(ExifTag.ImageWidth); + if (width == null) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); + } + + return (int)width.Value; + } + + /// + /// Gets the height of the image frame. + /// + /// The image frame exif profile. + /// The image height. + private static int GetImageHeight(ExifProfile exifProfile) + { + IExifValue height = exifProfile.GetValue(ExifTag.ImageLength); + if (height == null) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); + } + + return (int)height.Value; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs new file mode 100644 index 000000000..6f8a81a82 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The decoder metadata creator. + /// + internal static class TiffDecoderMetadataCreator + { + public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder) + where TPixel : unmanaged, IPixel + { + if (frames.Count < 1) + { + TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); + } + + var imageMetaData = new ImageMetadata(); + ExifProfile exifProfileRootFrame = frames[0].Metadata.ExifProfile; + + SetResolution(imageMetaData, exifProfileRootFrame); + + TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); + tiffMetadata.ByteOrder = byteOrder; + + if (!ignoreMetadata) + { + for (int i = 0; i < frames.Count; i++) + { + ImageFrame frame = frames[i]; + ImageFrameMetadata frameMetaData = frame.Metadata; + if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) + { + frameMetaData.IptcProfile = new IptcProfile(iptcBytes); + } + + IExifValue iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); + if (iccProfileBytes != null) + { + frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); + } + } + } + + return imageMetaData; + } + + public static ImageMetadata Create(ByteOrder byteOrder, ExifProfile exifProfile) + { + var imageMetaData = new ImageMetadata(); + SetResolution(imageMetaData, exifProfile); + + TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); + tiffMetadata.ByteOrder = byteOrder; + + return imageMetaData; + } + + private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) + { + imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; + double? horizontalResolution = exifProfile?.GetValue(ExifTag.XResolution)?.Value.ToDouble(); + if (horizontalResolution != null) + { + imageMetaData.HorizontalResolution = horizontalResolution.Value; + } + + double? verticalResolution = exifProfile?.GetValue(ExifTag.YResolution)?.Value.ToDouble(); + if (verticalResolution != null) + { + imageMetaData.VerticalResolution = verticalResolution.Value; + } + } + + private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] iptcBytes) + { + iptcBytes = null; + IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); + + if (iptc != null) + { + if (iptc.DataType == ExifDataType.Byte || iptc.DataType == ExifDataType.Undefined) + { + iptcBytes = (byte[])iptc.GetValue(); + return true; + } + + // Some Encoders write the data type of IPTC as long. + if (iptc.DataType == ExifDataType.Long) + { + uint[] iptcValues = (uint[])iptc.GetValue(); + iptcBytes = new byte[iptcValues.Length * 4]; + Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); + if (iptcBytes[0] == 0x1c) + { + return true; + } + else if (iptcBytes[3] != 0x1c) + { + return false; + } + + // Probably wrong endianess, swap byte order. + Span iptcBytesSpan = iptcBytes.AsSpan(); + Span buffer = stackalloc byte[4]; + for (int i = 0; i < iptcBytes.Length; i += 4) + { + iptcBytesSpan.Slice(i, 4).CopyTo(buffer); + iptcBytes[i] = buffer[3]; + iptcBytes[i + 1] = buffer[2]; + iptcBytes[i + 2] = buffer[1]; + iptcBytes[i + 3] = buffer[0]; + } + + return true; + } + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs new file mode 100644 index 000000000..b5f3e7cf1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -0,0 +1,281 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Linq; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The decoder options parser. + /// + internal static class TiffDecoderOptionsParser + { + private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; + + /// + /// Determines the TIFF compression and color types, and reads any associated parameters. + /// + /// The options. + /// The exif profile of the frame to decode. + /// The IFD entries container to read the image format information for current frame. + public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + { + if (exifProfile.GetValue(ExifTag.TileOffsets)?.Value != null) + { + TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); + } + + if (exifProfile.GetValue(ExifTag.ExtraSamples)?.Value != null) + { + TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); + } + + TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; + if (fillOrder != TiffFillOrder.MostSignificantBitFirst) + { + TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); + } + + if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) + { + TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); + } + + TiffSampleFormat[] sampleFormat = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); + if (sampleFormat != null) + { + foreach (TiffSampleFormat format in sampleFormat) + { + if (format != TiffSampleFormat.UnsignedInteger) + { + TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger SampleFormat."); + } + } + } + + if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null) + { + TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); + } + + VerifyRequiredFieldsArePresent(exifProfile, frameMetadata); + + options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; + options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; + options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; + options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; + options.BitsPerSample = GetBitsPerSample(frameMetadata.BitsPerPixel); + + options.ParseColorType(exifProfile); + options.ParseCompression(frameMetadata.Compression, exifProfile); + } + + private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + { + if (exifProfile.GetValue(ExifTag.StripOffsets) == null) + { + TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); + } + + if (exifProfile.GetValue(ExifTag.StripByteCounts) == null) + { + TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); + } + + if (frameMetadata.BitsPerPixel == null) + { + TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!"); + } + } + + private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) + { + switch (options.PhotometricInterpretation) + { + case TiffPhotometricInterpretation.WhiteIsZero: + { + if (options.BitsPerSample.Bits().Length != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + switch (options.BitsPerSample) + { + case TiffBitsPerSample.Bit8: + { + options.ColorType = TiffColorType.WhiteIsZero8; + break; + } + + case TiffBitsPerSample.Bit4: + { + options.ColorType = TiffColorType.WhiteIsZero4; + break; + } + + case TiffBitsPerSample.Bit1: + { + options.ColorType = TiffColorType.WhiteIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.WhiteIsZero; + break; + } + } + + break; + } + + case TiffPhotometricInterpretation.BlackIsZero: + { + if (options.BitsPerSample.Bits().Length != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + switch (options.BitsPerSample) + { + case TiffBitsPerSample.Bit8: + { + options.ColorType = TiffColorType.BlackIsZero8; + break; + } + + case TiffBitsPerSample.Bit4: + { + options.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case TiffBitsPerSample.Bit1: + { + options.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.BlackIsZero; + break; + } + } + + break; + } + + case TiffPhotometricInterpretation.Rgb: + { + if (options.BitsPerSample.Bits().Length != 3) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + options.ColorType = options.BitsPerSample == TiffBitsPerSample.Bit24 ? TiffColorType.Rgb888 : TiffColorType.Rgb; + } + else + { + options.ColorType = TiffColorType.RgbPlanar; + } + + break; + } + + case TiffPhotometricInterpretation.PaletteColor: + { + options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + if (options.ColorMap != null) + { + if (options.BitsPerSample.Bits().Length != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + options.ColorType = TiffColorType.PaletteColor; + } + else + { + TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); + } + + break; + } + + default: + { + TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); + } + + break; + } + } + + private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) + { + switch (compression) + { + case TiffCompression.None: + { + options.CompressionType = TiffDecoderCompressionType.None; + break; + } + + case TiffCompression.PackBits: + { + options.CompressionType = TiffDecoderCompressionType.PackBits; + break; + } + + case TiffCompression.Deflate: + case TiffCompression.OldDeflate: + { + options.CompressionType = TiffDecoderCompressionType.Deflate; + break; + } + + case TiffCompression.Lzw: + { + options.CompressionType = TiffDecoderCompressionType.Lzw; + break; + } + + case TiffCompression.CcittGroup3Fax: + { + options.CompressionType = TiffDecoderCompressionType.T4; + options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; + + break; + } + + case TiffCompression.Ccitt1D: + { + options.CompressionType = TiffDecoderCompressionType.HuffmanRle; + break; + } + + default: + { + TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); + break; + } + } + } + + private static TiffBitsPerSample GetBitsPerSample(TiffBitsPerPixel? bitsPerPixel) => bitsPerPixel switch + { + TiffBitsPerPixel.Bit1 => TiffBitsPerSample.Bit1, + TiffBitsPerPixel.Bit4 => TiffBitsPerSample.Bit4, + TiffBitsPerPixel.Bit8 => TiffBitsPerSample.Bit8, + TiffBitsPerPixel.Bit24 => TiffBitsPerSample.Bit24, + _ => TiffBitsPerSample.Bit24, + }; + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs new file mode 100644 index 000000000..7d5ccdb94 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Encoder for writing the data image to a stream in TIFF format. + /// + public class TiffEncoder : IImageEncoder, ITiffEncoderOptions + { + /// + public TiffBitsPerPixel? BitsPerPixel { get; set; } + + /// + public TiffCompression? Compression { get; set; } + + /// + public DeflateCompressionLevel? CompressionLevel { get; set; } + + /// + public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } + + /// + public TiffPredictor? HorizontalPredictor { get; set; } + + /// + public IQuantizer Quantizer { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encode = new TiffEncoderCore(this, image.GetMemoryAllocator()); + encode.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs new file mode 100644 index 000000000..74c516f63 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -0,0 +1,381 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.Writers; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Performs the TIFF encoding operation. + /// + internal sealed class TiffEncoderCore : IImageEncoderInternals + { + private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian + ? TiffConstants.ByteOrderLittleEndianShort + : TiffConstants.ByteOrderBigEndianShort; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The quantizer for creating color palette image. + /// + private readonly IQuantizer quantizer; + + /// + /// Sets the deflate compression level. + /// + private readonly DeflateCompressionLevel compressionLevel; + + /// + /// The default predictor is None. + /// + private const TiffPredictor DefaultPredictor = TiffPredictor.None; + + /// + /// The default bits per pixel is Bit24. + /// + private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24; + + /// + /// The default compression is None. + /// + private const TiffCompression DefaultCompression = TiffCompression.None; + + /// + /// The default photometric interpretation is Rgb. + /// + private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + /// The memory allocator. + public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.PhotometricInterpretation = options.PhotometricInterpretation; + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.BitsPerPixel = options.BitsPerPixel; + this.HorizontalPredictor = options.HorizontalPredictor; + this.CompressionType = options.Compression; + this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; + } + + /// + /// Gets the photometric interpretation implementation to use when encoding the image. + /// + internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } + + /// + /// Gets or sets the compression implementation to use when encoding the image. + /// + internal TiffCompression? CompressionType { get; set; } + + /// + /// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. + /// + internal TiffPredictor? HorizontalPredictor { get; set; } + + /// + /// Gets the bits per pixel. + /// + internal TiffBitsPerPixel? BitsPerPixel { get; private set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; + TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); + + // Determine the correct values to encode with. + // EncoderOptions > Metadata > Default. + TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel; + + TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation; + + TiffPredictor predictor = + this.HorizontalPredictor + ?? rootFrameTiffMetaData.Predictor + ?? DefaultPredictor; + + TiffCompression compression = + this.CompressionType + ?? rootFrameTiffMetaData.Compression + ?? DefaultCompression; + + // Make sure, the Encoder options makes sense in combination with each other. + this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor); + + using (var writer = new TiffStreamWriter(stream)) + { + long firstIfdMarker = this.WriteHeader(writer); + + // TODO: multiframing is not supported + this.WriteImage(writer, image, firstIfdMarker); + } + } + + /// + /// Writes the TIFF file header. + /// + /// The to write data to. + /// + /// The marker to write the first IFD offset. + /// + public long WriteHeader(TiffStreamWriter writer) + { + writer.Write(ByteOrderMarker); + writer.Write(TiffConstants.HeaderMagicNumber); + return writer.PlaceMarker(); + } + + /// + /// Writes all data required to define an image. + /// + /// The pixel format. + /// The to write data to. + /// The to encode from. + /// The marker to write this IFD offset. + private void WriteImage(TiffStreamWriter writer, Image image, long ifdOffset) + where TPixel : unmanaged, IPixel + { + var entriesCollector = new TiffEncoderEntriesCollector(); + + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( + this.CompressionType ?? TiffCompression.None, + writer.BaseStream, + this.memoryAllocator, + image.Width, + (int)this.BitsPerPixel, + this.compressionLevel, + this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); + + using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( + this.PhotometricInterpretation, + image.Frames.RootFrame, + this.quantizer, + this.memoryAllocator, + this.configuration, + entriesCollector, + (int)this.BitsPerPixel); + + int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); + + colorWriter.Write(compressor, rowsPerStrip); + + entriesCollector.ProcessImageFormat(this); + entriesCollector.ProcessGeneral(image); + + writer.WriteMarker(ifdOffset, (uint)writer.Position); + long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); + } + + /// + /// Calculates the number of rows written per strip. + /// + /// The height of the image. + /// The number of bytes per row. + /// Number of rows per strip. + private int CalcRowsPerStrip(int height, int bytesPerRow) + { + DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); + DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); + + int rowsPerStrip = TiffConstants.DefaultStripSize / bytesPerRow; + + if (rowsPerStrip > 0) + { + if (rowsPerStrip < height) + { + return rowsPerStrip; + } + + return height; + } + + return 1; + } + + /// + /// Writes a TIFF IFD block. + /// + /// The to write data to. + /// The IFD entries to write to the file. + /// The marker to write the next IFD offset (if present). + private long WriteIfd(TiffStreamWriter writer, List entries) + { + if (entries.Count == 0) + { + TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); + } + + uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); + var largeDataBlocks = new List(); + + entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); + + writer.Write((ushort)entries.Count); + + foreach (IExifValue entry in entries) + { + writer.Write((ushort)entry.Tag); + writer.Write((ushort)entry.DataType); + writer.Write(ExifWriter.GetNumberOfComponents(entry)); + + uint length = ExifWriter.GetLength(entry); + if (length <= 4) + { + int sz = ExifWriter.WriteValue(entry, this.buffer, 0); + DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); + writer.WritePadded(this.buffer.AsSpan(0, sz)); + } + else + { + var raw = new byte[length]; + int sz = ExifWriter.WriteValue(entry, raw, 0); + DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); + largeDataBlocks.Add(raw); + writer.Write(dataOffset); + dataOffset += (uint)(raw.Length + (raw.Length % 2)); + } + } + + long nextIfdMarker = writer.PlaceMarker(); + + foreach (byte[] dataBlock in largeDataBlocks) + { + writer.Write(dataBlock); + + if (dataBlock.Length % 2 == 1) + { + writer.Write(0); + } + } + + return nextIfdMarker; + } + + private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + { + // BitsPerPixel should be the primary source of truth for the encoder options. + if (bitsPerPixel.HasValue) + { + switch (bitsPerPixel) + { + case TiffBitsPerPixel.Bit1: + if (compression == TiffCompression.Ccitt1D || compression == TiffCompression.CcittGroup3Fax || compression == TiffCompression.CcittGroup4Fax) + { + // The normal PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); + break; + } + + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None); + break; + case TiffBitsPerPixel.Bit4: + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None); + break; + case TiffBitsPerPixel.Bit8: + this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); + break; + default: + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); + break; + } + + return; + } + + // If no photometric interpretation was chosen, the input image bit per pixel should be preserved. + if (!photometricInterpretation.HasValue) + { + // At the moment only 8 and 32 bits per pixel can be preserved by the tiff encoder. + if (inputBitsPerPixel == 8) + { + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); + return; + } + + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor); + return; + } + + switch (photometricInterpretation) + { + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (this.CompressionType == TiffCompression.Ccitt1D || + this.CompressionType == TiffCompression.CcittGroup3Fax || + this.CompressionType == TiffCompression.CcittGroup4Fax) + { + this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None); + return; + } + else + { + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); + return; + } + + case TiffPhotometricInterpretation.PaletteColor: + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); + return; + + case TiffPhotometricInterpretation.Rgb: + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor); + return; + } + + this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor); + } + + private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + { + this.BitsPerPixel = bitsPerPixel; + this.PhotometricInterpretation = photometricInterpretation; + this.CompressionType = compression; + this.HorizontalPredictor = predictor; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs new file mode 100644 index 000000000..09605bc69 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -0,0 +1,378 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + internal class TiffEncoderEntriesCollector + { + private const string SoftwareValue = "ImageSharp"; + + public List Entries { get; } = new List(); + + public void ProcessGeneral(Image image) + where TPixel : unmanaged, IPixel + => new GeneralProcessor(this).Process(image); + + public void ProcessImageFormat(TiffEncoderCore encoder) + => new ImageFormatProcessor(this).Process(encoder); + + public void AddOrReplace(IExifValue entry) + { + int index = this.Entries.FindIndex(t => t.Tag == entry.Tag); + if (index >= 0) + { + this.Entries[index] = entry; + } + else + { + this.Entries.Add(entry); + } + } + + private void Add(IExifValue entry) => this.Entries.Add(entry); + + private class GeneralProcessor + { + private readonly TiffEncoderEntriesCollector collector; + + public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; + + public void Process(Image image) + where TPixel : unmanaged, IPixel + { + ImageFrame rootFrame = image.Frames.RootFrame; + ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile(); + byte[] rootFrameXmpBytes = rootFrame.Metadata.XmpProfile; + + var width = new ExifLong(ExifTagValue.ImageWidth) + { + Value = (uint)image.Width + }; + + var height = new ExifLong(ExifTagValue.ImageLength) + { + Value = (uint)image.Height + }; + + var software = new ExifString(ExifTagValue.Software) + { + Value = SoftwareValue + }; + + this.collector.Add(width); + this.collector.Add(height); + + this.ProcessResolution(image.Metadata, rootFrameExifProfile); + this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpBytes); + this.ProcessMetadata(rootFrameExifProfile); + + if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software)) + { + this.collector.Add(software); + } + } + + private static bool IsPureMetadata(ExifTag tag) + { + switch ((ExifTagValue)(ushort)tag) + { + case ExifTagValue.DocumentName: + case ExifTagValue.ImageDescription: + case ExifTagValue.Make: + case ExifTagValue.Model: + case ExifTagValue.Software: + case ExifTagValue.DateTime: + case ExifTagValue.Artist: + case ExifTagValue.HostComputer: + case ExifTagValue.TargetPrinter: + case ExifTagValue.XMP: + case ExifTagValue.Rating: + case ExifTagValue.RatingPercent: + case ExifTagValue.ImageID: + case ExifTagValue.Copyright: + case ExifTagValue.MDLabName: + case ExifTagValue.MDSampleInfo: + case ExifTagValue.MDPrepDate: + case ExifTagValue.MDPrepTime: + case ExifTagValue.MDFileUnits: + case ExifTagValue.SEMInfo: + case ExifTagValue.XPTitle: + case ExifTagValue.XPComment: + case ExifTagValue.XPAuthor: + case ExifTagValue.XPKeywords: + case ExifTagValue.XPSubject: + return true; + default: + return false; + } + } + + private void ProcessResolution(ImageMetadata imageMetadata, ExifProfile exifProfile) + { + UnitConverter.SetResolutionValues( + exifProfile, + imageMetadata.ResolutionUnits, + imageMetadata.HorizontalResolution, + imageMetadata.VerticalResolution); + + this.collector.Add(exifProfile.GetValue(ExifTag.ResolutionUnit).DeepClone()); + + IExifValue xResolution = exifProfile.GetValue(ExifTag.XResolution)?.DeepClone(); + IExifValue yResolution = exifProfile.GetValue(ExifTag.YResolution)?.DeepClone(); + + if (xResolution != null && yResolution != null) + { + this.collector.Add(xResolution); + this.collector.Add(yResolution); + } + } + + private void ProcessMetadata(ExifProfile exifProfile) + { + foreach (IExifValue entry in exifProfile.Values) + { + // todo: skip subIfd + if (entry.DataType == ExifDataType.Ifd) + { + continue; + } + + switch ((ExifTagValue)(ushort)entry.Tag) + { + case ExifTagValue.SubIFDOffset: + case ExifTagValue.GPSIFDOffset: + case ExifTagValue.SubIFDs: + case ExifTagValue.XMP: + case ExifTagValue.IPTC: + case ExifTagValue.IccProfile: + continue; + } + + switch (ExifTags.GetPart(entry.Tag)) + { + case ExifParts.ExifTags: + case ExifParts.GpsTags: + break; + + case ExifParts.IfdTags: + if (!IsPureMetadata(entry.Tag)) + { + continue; + } + + break; + } + + if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag)) + { + this.collector.AddOrReplace(entry.DeepClone()); + } + } + } + + private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, byte[] xmpProfile) + { + if (exifProfile != null && exifProfile.Parts != ExifParts.None) + { + foreach (IExifValue entry in exifProfile.Values) + { + if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) + { + ExifParts entryPart = ExifTags.GetPart(entry.Tag); + if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) + { + this.collector.AddOrReplace(entry.DeepClone()); + } + } + } + } + else + { + exifProfile.RemoveValue(ExifTag.SubIFDOffset); + } + + if (imageMetadata.IptcProfile != null) + { + imageMetadata.IptcProfile.UpdateData(); + var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte) + { + Value = imageMetadata.IptcProfile.Data + }; + + this.collector.Add(iptc); + } + else + { + exifProfile.RemoveValue(ExifTag.IPTC); + } + + if (imageMetadata.IccProfile != null) + { + var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined) + { + Value = imageMetadata.IccProfile.ToByteArray() + }; + + this.collector.Add(icc); + } + else + { + exifProfile.RemoveValue(ExifTag.IccProfile); + } + + TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata(); + if (xmpProfile != null) + { + var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) + { + Value = xmpProfile + }; + + this.collector.Add(xmp); + } + else + { + exifProfile.RemoveValue(ExifTag.XMP); + } + } + } + + private class ImageFormatProcessor + { + private readonly TiffEncoderEntriesCollector collector; + + public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; + + public void Process(TiffEncoderCore encoder) + { + var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel) + { + Value = GetSamplesPerPixel(encoder) + }; + + ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); + var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample) + { + Value = bitsPerSampleValue + }; + + ushort compressionType = GetCompressionType(encoder); + var compression = new ExifShort(ExifTagValue.Compression) + { + Value = compressionType + }; + + var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) + { + Value = (ushort)encoder.PhotometricInterpretation + }; + + this.collector.AddOrReplace(samplesPerPixel); + this.collector.AddOrReplace(bitPerSample); + this.collector.AddOrReplace(compression); + this.collector.AddOrReplace(photometricInterpretation); + + if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) + { + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; + + this.collector.AddOrReplace(predictor); + } + } + } + + private static uint GetSamplesPerPixel(TiffEncoderCore encoder) + { + switch (encoder.PhotometricInterpretation) + { + case TiffPhotometricInterpretation.PaletteColor: + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + return 1; + case TiffPhotometricInterpretation.Rgb: + default: + return 3; + } + } + + private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) + { + switch (encoder.PhotometricInterpretation) + { + case TiffPhotometricInterpretation.PaletteColor: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) + { + return TiffConstants.BitsPerSample4Bit; + } + else + { + return TiffConstants.BitsPerSample8Bit; + } + + case TiffPhotometricInterpretation.Rgb: + return TiffConstants.BitsPerSampleRgb8Bit; + + case TiffPhotometricInterpretation.WhiteIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit; + } + + return TiffConstants.BitsPerSample8Bit; + + case TiffPhotometricInterpretation.BlackIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit; + } + + return TiffConstants.BitsPerSample8Bit; + + default: + return TiffConstants.BitsPerSampleRgb8Bit; + } + } + + private static ushort GetCompressionType(TiffEncoderCore encoder) + { + switch (encoder.CompressionType) + { + case TiffCompression.Deflate: + // Deflate is allowed for all modes. + return (ushort)TiffCompression.Deflate; + case TiffCompression.PackBits: + // PackBits is allowed for all modes. + return (ushort)TiffCompression.PackBits; + case TiffCompression.Lzw: + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + return (ushort)TiffCompression.Lzw; + } + + break; + + case TiffCompression.CcittGroup3Fax: + return (ushort)TiffCompression.CcittGroup3Fax; + + case TiffCompression.Ccitt1D: + return (ushort)TiffCompression.Ccitt1D; + } + + return (ushort)TiffCompression.None; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs new file mode 100644 index 000000000..2217ffb7f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffFormat.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Encapsulates the means to encode and decode Tiff images. + /// + public sealed class TiffFormat : IImageFormat + { + private TiffFormat() + { + } + + /// + /// Gets the current instance. + /// + public static TiffFormat Instance { get; } = new TiffFormat(); + + /// + public string Name => "TIFF"; + + /// + public string DefaultMimeType => "image/tiff"; + + /// + public IEnumerable MimeTypes => TiffConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => TiffConstants.FileExtensions; + + /// + public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata(); + + /// + public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs new file mode 100644 index 000000000..25a0578e9 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Provides Tiff specific metadata information for the frame. + /// + public class TiffFrameMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public TiffFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other tiff frame metadata. + private TiffFrameMetadata(TiffFrameMetadata other) + { + this.BitsPerPixel = other.BitsPerPixel; + this.Compression = other.Compression; + this.PhotometricInterpretation = other.PhotometricInterpretation; + this.Predictor = other.Predictor; + } + + /// + /// Gets or sets the bits per pixel. + /// + public TiffBitsPerPixel? BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression scheme used on the image data. + /// + public TiffCompression? Compression { get; set; } + + /// + /// Gets or sets the color space of the image data. + /// + public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } + + /// + /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. + /// + public TiffPredictor? Predictor { get; set; } + + /// + /// Returns a new instance parsed from the given Exif profile. + /// + /// The Exif profile containing tiff frame directory tags to parse. + /// If null, a new instance is created and parsed instead. + /// The . + internal static TiffFrameMetadata Parse(ExifProfile profile) + { + var meta = new TiffFrameMetadata(); + Parse(meta, profile); + return meta; + } + + /// + /// Parses the given Exif profile to populate the properties of the tiff frame meta data.. + /// + /// The tiff frame meta data. + /// The Exif profile containing tiff frame directory tags. + internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) + { + if (profile != null) + { + ushort[] bitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; + meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(bitsPerSample); + meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; + meta.PhotometricInterpretation = + (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; + meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; + + profile.RemoveValue(ExifTag.BitsPerSample); + profile.RemoveValue(ExifTag.Compression); + profile.RemoveValue(ExifTag.PhotometricInterpretation); + profile.RemoveValue(ExifTag.Predictor); + } + } + + /// + /// Gets the bits per pixel for the given bits per sample. + /// + /// The tiff bits per sample. + /// Bits per pixel. + private static TiffBitsPerPixel? BitsPerPixelFromBitsPerSample(ushort[] bitsPerSample) + { + if (bitsPerSample == null) + { + return null; + } + + int bitsPerPixel = 0; + foreach (ushort bits in bitsPerSample) + { + bitsPerPixel += bits; + } + + return (TiffBitsPerPixel)bitsPerPixel; + } + + /// + public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs new file mode 100644 index 000000000..f7e6f7a99 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Detects tiff file headers + /// + public sealed class TiffImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 4; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return TiffFormat.Instance; + } + + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && + ((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian + (header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs new file mode 100644 index 000000000..cf1ab3754 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Provides Tiff specific metadata information for the image. + /// + public class TiffMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public TiffMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private TiffMetadata(TiffMetadata other) => this.ByteOrder = other.ByteOrder; + + /// + /// Gets or sets the byte order. + /// + public ByteOrder ByteOrder { get; set; } + + /// + public IDeepCloneable DeepClone() => new TiffMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs new file mode 100644 index 000000000..3c541a786 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Cold path optimizations for throwing tiff format based exceptions. + /// + internal static class TiffThrowHelper + { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}"); + + [MethodImpl(InliningOptions.ColdPath)] + public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowArgumentException(string message) => throw new ArgumentException(message); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs new file mode 100644 index 000000000..40e67c1b0 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Utils +{ + /// + /// Utility class to read a sequence of bits from an array + /// + internal ref struct BitReader + { + private readonly ReadOnlySpan array; + private int offset; + private int bitOffset; + + /// + /// Initializes a new instance of the struct. + /// + /// The array to read data from. + public BitReader(ReadOnlySpan array) + { + this.array = array; + this.offset = 0; + this.bitOffset = 0; + } + + /// + /// Reads the specified number of bits from the array. + /// + /// The number of bits to read. + /// The value read from the array. + public int ReadBits(uint bits) + { + int value = 0; + + for (uint i = 0; i < bits; i++) + { + int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01; + value = (value << 1) | bit; + + this.bitOffset++; + + if (this.bitOffset == 8) + { + this.bitOffset = 0; + this.offset++; + } + } + + return value; + } + + /// + /// Moves the reader to the next row of byte-aligned data. + /// + public void NextRow() + { + if (this.bitOffset > 0) + { + this.bitOffset = 0; + this.offset++; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs new file mode 100644 index 000000000..232daa18d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal abstract class TiffBaseColorWriter : IDisposable + where TPixel : unmanaged, IPixel + { + private bool isDisposed; + + protected TiffBaseColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + { + this.Image = image; + this.MemoryAllocator = memoryAllocator; + this.Configuration = configuration; + this.EntriesCollector = entriesCollector; + } + + /// + /// Gets the bits per pixel. + /// + public abstract int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow => ((this.Image.Width * this.BitsPerPixel) + 7) / 8; + + protected ImageFrame Image { get; } + + protected MemoryAllocator MemoryAllocator { get; } + + protected Configuration Configuration { get; } + + protected TiffEncoderEntriesCollector EntriesCollector { get; } + + public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) + { + DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); + int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; + + uint[] stripOffsets = new uint[stripsCount]; + uint[] stripByteCounts = new uint[stripsCount]; + + int stripIndex = 0; + compressor.Initialize(rowsPerStrip); + for (int y = 0; y < this.Image.Height; y += rowsPerStrip) + { + long offset = compressor.Output.Position; + + int height = Math.Min(rowsPerStrip, this.Image.Height - y); + this.EncodeStrip(y, height, compressor); + + long endOffset = compressor.Output.Position; + stripOffsets[stripIndex] = (uint)offset; + stripByteCounts[stripIndex] = (uint)(endOffset - offset); + stripIndex++; + } + + DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match"); + this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); + } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.Dispose(true); + } + + protected static Span GetStripPixels(Buffer2D buffer2D, int y, int height) + where T : struct + => buffer2D.GetSingleSpan().Slice(y * buffer2D.Width, height * buffer2D.Width); + + protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); + + /// + /// Adds image format information to the specified IFD. + /// + /// The rows per strip. + /// The strip offsets. + /// The strip byte counts. + private void AddStripTags(int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) + { + this.EntriesCollector.AddOrReplace(new ExifLong(ExifTagValue.RowsPerStrip) + { + Value = (uint)rowsPerStrip + }); + + this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripOffsets) + { + Value = stripOffsets + }); + + this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripByteCounts) + { + Value = stripByteCounts + }); + } + + protected abstract void Dispose(bool disposing); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs new file mode 100644 index 000000000..be5c837ea --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal sealed class TiffBiColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel + { + private readonly Image imageBlackWhite; + + private IMemoryOwner pixelsAsGray; + + private IMemoryOwner bitStrip; + + public TiffBiColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) + { + // Convert image to black and white. + this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); + this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); + } + + /// + public override int BitsPerPixel => 1; + + /// + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(height * this.Image.Width); + + Span pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width); + + Span pixelsBlackWhite = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height); + + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhite, pixelAsGraySpan, pixelsBlackWhite.Length); + + if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) + { + // Special case for T4BitCompressor. + compressor.CompressStrip(pixelAsGraySpan, height); + } + else + { + // Write uncompressed image. + int bytesPerStrip = this.BytesPerRow * height; + this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); + + Span rows = this.bitStrip.Slice(0, bytesPerStrip); + rows.Clear(); + + int grayPixelIndex = 0; + for (int s = 0; s < height; s++) + { + int bitIndex = 0; + int byteIndex = 0; + Span outputRow = rows.Slice(s * this.BytesPerRow); + for (int x = 0; x < this.Image.Width; x++) + { + int shift = 7 - bitIndex; + if (pixelAsGraySpan[grayPixelIndex++] == 255) + { + outputRow[byteIndex] |= (byte)(1 << shift); + } + + bitIndex++; + if (bitIndex == 8) + { + byteIndex++; + bitIndex = 0; + } + } + } + + compressor.CompressStrip(rows, height); + } + } + + /// + protected override void Dispose(bool disposing) + { + this.imageBlackWhite?.Dispose(); + this.pixelsAsGray?.Dispose(); + this.bitStrip?.Dispose(); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs new file mode 100644 index 000000000..e53f4b420 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal static class TiffColorWriterFactory + { + public static TiffBaseColorWriter Create( + TiffPhotometricInterpretation? photometricInterpretation, + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) + where TPixel : unmanaged, IPixel + { + switch (photometricInterpretation) + { + case TiffPhotometricInterpretation.PaletteColor: + return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (bitsPerPixel == 1) + { + return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); + } + + return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); + default: + return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs new file mode 100644 index 000000000..4df57f7e8 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + /// + /// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). + /// + internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel + { + private IManagedByteBuffer rowBuffer; + + protected TiffCompositeColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) + { + } + + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + if (this.rowBuffer == null) + { + this.rowBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.BytesPerRow * height); + } + + this.rowBuffer.Clear(); + + Span rowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); + + Span pixels = GetStripPixels(this.Image.PixelBuffer, y, height); + + this.EncodePixels(pixels, rowSpan); + compressor.CompressStrip(rowSpan, height); + } + + protected abstract void EncodePixels(Span pixels, Span buffer); + + /// + protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs new file mode 100644 index 000000000..117960ba7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal sealed class TiffGrayWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel + { + public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) + { + } + + /// + public override int BitsPerPixel => 8; + + /// + protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs new file mode 100644 index 000000000..d1a3dd1ea --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -0,0 +1,142 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal sealed class TiffPaletteWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel + { + private readonly int maxColors; + private readonly int colorPaletteSize; + private readonly int colorPaletteBytes; + private readonly IndexedImageFrame quantizedImage; + + public TiffPaletteWriter( + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) + : base(image, memoryAllocator, configuration, entriesCollector) + { + DebugGuard.NotNull(quantizer, nameof(quantizer)); + DebugGuard.NotNull(configuration, nameof(configuration)); + DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); + DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); + + this.BitsPerPixel = bitsPerPixel; + this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; + this.colorPaletteSize = this.maxColors * 3; + this.colorPaletteBytes = this.colorPaletteSize * 2; + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions() + { + MaxColors = this.maxColors + }); + this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + + this.AddColorMapTag(); + } + + /// + public override int BitsPerPixel { get; } + + /// + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + Span indexedPixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height); + if (this.BitsPerPixel == 4) + { + int width = this.Image.Width; + int halfWidth = width >> 1; + int excess = (width & 1) * height; // (width % 2) * height + int rows4BitBufferLength = (halfWidth * height) + excess; + using IMemoryOwner rows4bitBuffer = this.MemoryAllocator.Allocate(rows4BitBufferLength); + Span rows4bit = rows4bitBuffer.GetSpan(); + int idxPixels = 0; + int idx4bitRows = 0; + for (int row = 0; row < height; row++) + { + for (int x = 0; x < halfWidth; x++) + { + rows4bit[idx4bitRows] = (byte)((indexedPixels[idxPixels] << 4) | (indexedPixels[idxPixels + 1] & 0xF)); + idxPixels += 2; + idx4bitRows++; + } + + // Make sure rows are byte-aligned. + if (width % 2 != 0) + { + rows4bit[idx4bitRows++] = (byte)(indexedPixels[idxPixels++] << 4); + } + } + + compressor.CompressStrip(rows4bit.Slice(0, idx4bitRows), height); + } + else + { + compressor.CompressStrip(indexedPixels, height); + } + } + + /// + protected override void Dispose(bool disposing) => this.quantizedImage?.Dispose(); + + private void AddColorMapTag() + { + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.colorPaletteBytes); + Span colorPalette = colorPaletteBuffer.GetSpan(); + + ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; + int quantizedColorBytes = quantizedColors.Length * 3 * 2; + + // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. + Span quantizedColorRgb48 = MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes)); + PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); + + // It can happen that the quantized colors are less than the expected maximum per channel. + int diffToMaxColors = this.maxColors - quantizedColors.Length; + + // In a TIFF ColorMap, all the Red values come first, followed by the Green values, + // then the Blue values. Convert the quantized palette to this format. + var palette = new ushort[this.colorPaletteSize]; + int paletteIdx = 0; + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].R; + } + + paletteIdx += diffToMaxColors; + + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].G; + } + + paletteIdx += diffToMaxColors; + + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].B; + } + + var colorMap = new ExifShortArray(ExifTagValue.ColorMap) + { + Value = palette + }; + + this.EntriesCollector.AddOrReplace(colorMap); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs new file mode 100644 index 000000000..a3050f8a2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + internal sealed class TiffRgbWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel + { + public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) + { + } + + /// + public override int BitsPerPixel => 24; + + /// + protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs new file mode 100644 index 000000000..05a1ca7a2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.IO; + +namespace SixLabors.ImageSharp.Formats.Tiff.Writers +{ + /// + /// Utility class for writing TIFF data to a . + /// + internal sealed class TiffStreamWriter : IDisposable + { + private static readonly byte[] PaddingBytes = new byte[4]; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// Initializes a new instance of the class. + /// + /// The output stream. + public TiffStreamWriter(Stream output) => this.BaseStream = output; + + /// + /// Gets a value indicating whether the architecture is little-endian. + /// + public bool IsLittleEndian => BitConverter.IsLittleEndian; + + /// + /// Gets the current position within the stream. + /// + public long Position => this.BaseStream.Position; + + /// + /// Gets the base stream. + /// + public Stream BaseStream { get; } + + /// + /// Writes an empty four bytes to the stream, returning the offset to be written later. + /// + /// The offset to be written later. + public long PlaceMarker() + { + long offset = this.BaseStream.Position; + this.Write(0u); + return offset; + } + + /// + /// Writes an array of bytes to the current stream. + /// + /// The bytes to write. + public void Write(byte[] value) => this.BaseStream.Write(value, 0, value.Length); + + /// + /// Writes the specified value. + /// + /// The bytes to write. + public void Write(ReadOnlySpan value) => this.BaseStream.Write(value); + + /// + /// Writes a byte to the current stream. + /// + /// The byte to write. + public void Write(byte value) => this.BaseStream.WriteByte(value); + + /// + /// Writes a two-byte unsigned integer to the current stream. + /// + /// The two-byte unsigned integer to write. + public void Write(ushort value) + { + if (this.IsLittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value); + } + + this.BaseStream.Write(this.buffer.AsSpan(0, 2)); + } + + /// + /// Writes a four-byte unsigned integer to the current stream. + /// + /// The four-byte unsigned integer to write. + public void Write(uint value) + { + if (this.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value); + } + + this.BaseStream.Write(this.buffer.AsSpan(0, 4)); + } + + /// + /// Writes an array of bytes to the current stream, padded to four-bytes. + /// + /// The bytes to write. + public void WritePadded(Span value) + { + this.BaseStream.Write(value); + + if (value.Length % 4 != 0) + { + this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4)); + } + } + + /// + /// Writes a four-byte unsigned integer to the specified marker in the stream. + /// + /// The offset returned when placing the marker + /// The four-byte unsigned integer to write. + public void WriteMarker(long offset, uint value) + { + long currentOffset = this.BaseStream.Position; + this.BaseStream.Seek(offset, SeekOrigin.Begin); + this.Write(value); + this.BaseStream.Seek(currentOffset, SeekOrigin.Begin); + } + + /// + /// Disposes instance, ensuring any unwritten data is flushed. + /// + public void Dispose() => this.BaseStream.Flush(); + } +} diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 2021f1249..1819fd2bc 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -1,8 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; namespace SixLabors.ImageSharp.Metadata { @@ -35,8 +39,34 @@ namespace SixLabors.ImageSharp.Metadata { this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); } + + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + this.IptcProfile = other.IptcProfile?.DeepClone(); + this.XmpProfile = other.XmpProfile != null ? new byte[other.XmpProfile.Length] : null; + other.XmpProfile?.AsSpan().CopyTo(this.XmpProfile.AsSpan()); } + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets or sets the XMP profile. + /// + internal byte[] XmpProfile { get; set; } + + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } + + /// + /// Gets or sets the iptc profile. + /// + public IptcProfile IptcProfile { get; set; } + /// public ImageFrameMetadata DeepClone() => new ImageFrameMetadata(this); @@ -63,4 +93,4 @@ namespace SixLabors.ImageSharp.Metadata return newMeta; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs index 13e67554c..733eb4a79 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs @@ -75,6 +75,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// A 64-bit double precision floating point value. /// - DoubleFloat = 12 + DoubleFloat = 12, + + /// + /// Reference to an IFD (32-bit (4-byte) unsigned integer). + /// + Ifd = 13 } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs index dc12f3819..0a9c879ce 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs @@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// ExifTags /// - ExifTags = 4, + ExifTags = 2, /// /// GPSTags /// - GpsTags = 8, + GpsTags = 4, /// /// All diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index 55af45fb4..9265314ed 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; + using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif @@ -52,6 +53,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.InvalidTags = Array.Empty(); } + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The invalid tags. + internal ExifProfile(List values, IReadOnlyList invalidTags) + { + this.Parts = ExifParts.All; + this.values = values; + this.InvalidTags = invalidTags; + } + /// /// Initializes a new instance of the class /// by making a copy from another EXIF profile. @@ -154,7 +167,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// The tag of the EXIF value. /// - /// The . + /// True, if the value was removed, otherwise false. /// public bool RemoveValue(ExifTag tag) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 749c69186..6e671b3ec 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -5,27 +5,100 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { + internal class ExifReader : BaseExifReader + { + private readonly List loaders = new List(); + + public ExifReader(byte[] exifData) + : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData)))) + { + } + + /// + /// Reads and returns the collection of EXIF values. + /// + /// + /// The . + /// + public List ReadValues() + { + var values = new List(); + + // II == 0x4949 + this.IsBigEndian = this.ReadUInt16() != 0x4949; + + if (this.ReadUInt16() != 0x002A) + { + return values; + } + + uint ifdOffset = this.ReadUInt32(); + this.ReadValues(values, ifdOffset); + + uint thumbnailOffset = this.ReadUInt32(); + this.GetThumbnail(thumbnailOffset); + + this.ReadSubIfd(values); + + foreach (Action loader in this.loaders) + { + loader(); + } + + return values; + } + + protected override void RegisterExtLoader(uint offset, Action loader) => this.loaders.Add(loader); + + private void GetThumbnail(uint offset) + { + if (offset == 0) + { + return; + } + + var values = new List(); + this.ReadValues(values, offset); + + foreach (ExifValue value in values) + { + if (value == ExifTag.JPEGInterchangeFormat) + { + this.ThumbnailOffset = ((ExifLong)value).Value; + } + else if (value == ExifTag.JPEGInterchangeFormatLength) + { + this.ThumbnailLength = ((ExifLong)value).Value; + } + } + } + } + /// - /// Reads and parses EXIF data from a byte array. + /// Reads and parses EXIF data from a stream. /// - internal sealed class ExifReader + internal abstract class BaseExifReader { + private readonly byte[] offsetBuffer = new byte[4]; + private readonly byte[] buf4 = new byte[4]; + private readonly byte[] buf2 = new byte[2]; + + private readonly Stream data; private List invalidTags; - private readonly byte[] exifData; - private int position; - private bool isBigEndian; + private uint exifOffset; + private uint gpsOffset; - public ExifReader(byte[] exifData) - { - this.exifData = exifData ?? throw new ArgumentNullException(nameof(exifData)); - } + protected BaseExifReader(Stream stream) => + this.data = stream ?? throw new ArgumentNullException(nameof(stream)); private delegate TDataType ConverterMethod(ReadOnlySpan data); @@ -35,66 +108,51 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); /// - /// Gets the thumbnail length in the byte stream. + /// Gets or sets the thumbnail length in the byte stream. /// - public uint ThumbnailLength { get; private set; } + public uint ThumbnailLength { get; protected set; } /// - /// Gets the thumbnail offset position in the byte stream. + /// Gets or sets the thumbnail offset position in the byte stream. /// - public uint ThumbnailOffset { get; private set; } + public uint ThumbnailOffset { get; protected set; } - /// - /// Gets the remaining length. - /// - private int RemainingLength - { - get - { - if (this.position >= this.exifData.Length) - { - return 0; - } + public bool IsBigEndian { get; protected set; } - return this.exifData.Length - this.position; - } - } + protected abstract void RegisterExtLoader(uint offset, Action loader); /// - /// Reads and returns the collection of EXIF values. + /// Reads the values to the values collection. /// - /// - /// The . - /// - public List ReadValues() + /// The values. + /// The IFD offset. + protected void ReadValues(List values, uint offset) { - var values = new List(); - - // II == 0x4949 - this.isBigEndian = this.ReadUInt16() != 0x4949; - - if (this.ReadUInt16() != 0x002A) + if (offset > this.data.Length) { - return values; + return; } - uint ifdOffset = this.ReadUInt32(); - this.AddValues(values, ifdOffset); + this.Seek(offset); + int count = this.ReadUInt16(); - uint thumbnailOffset = this.ReadUInt32(); - this.GetThumbnail(thumbnailOffset); + for (int i = 0; i < count; i++) + { + this.ReadValue(values); + } + } + protected void ReadSubIfd(List values) + { if (this.exifOffset != 0) { - this.AddValues(values, this.exifOffset); + this.ReadValues(values, this.exifOffset); } if (this.gpsOffset != 0) { - this.AddValues(values, this.gpsOffset); + this.ReadValues(values, this.gpsOffset); } - - return values; } private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) @@ -128,58 +186,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return Encoding.UTF8.GetString(buffer); } - /// - /// Adds the collection of EXIF values to the reader. - /// - /// The values. - /// The index. - private void AddValues(List values, uint index) - { - if (index > (uint)this.exifData.Length) - { - return; - } - - this.position = (int)index; - int count = this.ReadUInt16(); - - for (int i = 0; i < count; i++) - { - if (!this.TryReadValue(out ExifValue value)) - { - continue; - } - - bool duplicate = false; - foreach (IExifValue val in values) - { - if (val == value) - { - duplicate = true; - break; - } - } - - if (duplicate) - { - continue; - } - - if (value == ExifTag.SubIFDOffset) - { - this.exifOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.GPSIFDOffset) - { - this.gpsOffset = ((ExifLong)value).Value; - } - else - { - values.Add(value); - } - } - } - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) { if (buffer.Length == 0) @@ -275,28 +281,28 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - private bool TryReadValue(out ExifValue exifValue) + private void ReadValue(List values) { - exifValue = default; - // 2 | 2 | 4 | 4 // tag | type | count | value offset - if (this.RemainingLength < 12) + if ((this.data.Length - this.data.Position) < 12) { - return false; + return; } var tag = (ExifTagValue)this.ReadUInt16(); ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + uint numberOfComponents = this.ReadUInt32(); + + this.TryReadSpan(this.offsetBuffer); + // Ensure that the data type is valid if (dataType == ExifDataType.Unknown) { - return false; + return; } - uint numberOfComponents = this.ReadUInt32(); - // Issue #132: ExifDataType == Undefined is treated like a byte array. // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) if (dataType == ExifDataType.Undefined && numberOfComponents == 0) @@ -304,110 +310,103 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif numberOfComponents = 4; } - uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); + ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); - this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; + } - object value; + uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); if (size > 4) { - int oldIndex = this.position; - uint newIndex = this.ConvertToUInt32(offsetBuffer); + uint newOffset = this.ConvertToUInt32(this.offsetBuffer); - // Ensure that the new index does not overrun the data - if (newIndex > int.MaxValue) + // Ensure that the new index does not overrun the data. + if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) { this.AddInvalidTag(new UnkownExifTag(tag)); - return false; + return; } - this.position = (int)newIndex; - - if (this.RemainingLength < size) + this.RegisterExtLoader(newOffset, () => { - this.AddInvalidTag(new UnkownExifTag(tag)); - - this.position = oldIndex; - return false; - } - - this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer); - - value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.position = oldIndex; + byte[] dataBuffer = new byte[size]; + this.Seek(newOffset); + if (this.TryReadSpan(dataBuffer)) + { + object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); + this.Add(values, exifValue, value); + } + }); } else { - value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents); - } + object value = this.ConvertValue(dataType, this.offsetBuffer, numberOfComponents); - exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + this.Add(values, exifValue, value); + } + } - if (exifValue is null) + private void Add(IList values, IExifValue exif, object value) + { + if (!exif.TrySetValue(value)) { - this.AddInvalidTag(new UnkownExifTag(tag)); - return false; + return; } - if (!exifValue.TrySetValue(value)) + foreach (IExifValue val in values) { - return false; + // Sometimes duplicates appear, can compare val.Tag == exif.Tag + if (val == exif) + { + Debug.WriteLine($"Duplicate Exif tag: tag={exif.Tag}, dataType={exif.DataType}"); + return; + } } - return true; + if (exif.Tag == ExifTag.SubIFDOffset) + { + this.exifOffset = (uint)value; + } + else if (exif.Tag == ExifTag.GPSIFDOffset) + { + this.gpsOffset = (uint)value; + } + else + { + values.Add(exif); + } } private void AddInvalidTag(ExifTag tag) - => (this.invalidTags ?? (this.invalidTags = new List())).Add(tag); + => (this.invalidTags ??= new List()).Add(tag); + + private void Seek(long pos) + => this.data.Seek(pos, SeekOrigin.Begin); - private bool TryReadSpan(int length, out ReadOnlySpan span) + private bool TryReadSpan(Span span) { - if (this.RemainingLength < length) + int length = span.Length; + if ((this.data.Length - this.data.Position) < length) { - span = default; - return false; } - span = new ReadOnlySpan(this.exifData, this.position, length); - - this.position += length; - - return true; + int read = this.data.Read(span); + return read == length; } - private uint ReadUInt32() - { - // Known as Long in Exif Specification - return this.TryReadSpan(4, out ReadOnlySpan span) - ? this.ConvertToUInt32(span) + // Known as Long in Exif Specification. + protected uint ReadUInt32() => + this.TryReadSpan(this.buf4) + ? this.ConvertToUInt32(this.buf4) : default; - } - private ushort ReadUInt16() - { - return this.TryReadSpan(2, out ReadOnlySpan span) - ? this.ConvertToShort(span) + protected ushort ReadUInt16() => this.TryReadSpan(this.buf2) + ? this.ConvertToShort(this.buf2) : default; - } - - private void GetThumbnail(uint offset) - { - var values = new List(); - this.AddValues(values, offset); - - foreach (ExifValue value in values) - { - if (value == ExifTag.JPEGInterchangeFormat) - { - this.ThumbnailOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.JPEGInterchangeFormatLength) - { - this.ThumbnailLength = ((ExifLong)value).Value; - } - } - } private double ConvertToDouble(ReadOnlySpan buffer) { @@ -416,7 +415,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return default; } - long intValue = this.isBigEndian + long intValue = this.IsBigEndian ? BinaryPrimitives.ReadInt64BigEndian(buffer) : BinaryPrimitives.ReadInt64LittleEndian(buffer); @@ -425,13 +424,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private uint ConvertToUInt32(ReadOnlySpan buffer) { - // Known as Long in Exif Specification + // Known as Long in Exif Specification. if (buffer.Length < 4) { return default; } - return this.isBigEndian + return this.IsBigEndian ? BinaryPrimitives.ReadUInt32BigEndian(buffer) : BinaryPrimitives.ReadUInt32LittleEndian(buffer); } @@ -443,7 +442,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return default; } - return this.isBigEndian + return this.IsBigEndian ? BinaryPrimitives.ReadUInt16BigEndian(buffer) : BinaryPrimitives.ReadUInt16LittleEndian(buffer); } @@ -455,7 +454,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return default; } - int intValue = this.isBigEndian + int intValue = this.IsBigEndian ? BinaryPrimitives.ReadInt32BigEndian(buffer) : BinaryPrimitives.ReadInt32LittleEndian(buffer); @@ -484,7 +483,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return default; } - return this.isBigEndian + return this.IsBigEndian ? BinaryPrimitives.ReadInt32BigEndian(buffer) : BinaryPrimitives.ReadInt32LittleEndian(buffer); } @@ -509,7 +508,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return default; } - return this.isBigEndian + return this.IsBigEndian ? BinaryPrimitives.ReadInt16BigEndian(buffer) : BinaryPrimitives.ReadInt16LittleEndian(buffer); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index a240c1392..e7a01b070 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -260,9 +260,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return length; } - private static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); + internal static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); - private static uint GetNumberOfComponents(IExifValue exifValue) + internal static uint GetNumberOfComponents(IExifValue exifValue) { object value = exifValue.GetValue(); @@ -279,17 +279,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return 1; } - private int WriteArray(IExifValue value, Span destination, int offset) + private static int WriteArray(IExifValue value, Span destination, int offset) { if (value.DataType == ExifDataType.Ascii) { - return this.WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset); + return WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset); } int newOffset = offset; foreach (object obj in (Array)value.GetValue()) { - newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); + newOffset = WriteValue(value.DataType, obj, destination, newOffset); } return newOffset; @@ -310,7 +310,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (GetLength(value) > 4) { WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); - newOffset = this.WriteValue(value, destination, newOffset); + newOffset = WriteValue(value, destination, newOffset); } } @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else { - this.WriteValue(value, destination, newOffset); + WriteValue(value, destination, newOffset); } newOffset += 4; @@ -362,7 +362,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); } - private int WriteValue(ExifDataType dataType, object value, Span destination, int offset) + private static int WriteValue(ExifDataType dataType, object value, Span destination, int offset) { switch (dataType) { @@ -410,14 +410,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - private int WriteValue(IExifValue value, Span destination, int offset) + internal static int WriteValue(IExifValue value, Span destination, int offset) { if (value.IsArray && value.DataType != ExifDataType.Ascii) { - return this.WriteArray(value, destination, offset); + return WriteArray(value, destination, offset); } - return this.WriteValue(value.DataType, value.GetValue(), destination, offset); + return WriteValue(value.DataType, value.GetValue(), destination, offset); } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs index e20867b43..fdde66c51 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs @@ -21,6 +21,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag XMP => new ExifTag(ExifTagValue.XMP); + /// + /// Gets the IPTC exif tag. + /// + public static ExifTag IPTC => new ExifTag(ExifTagValue.IPTC); + + /// + /// Gets the IccProfile exif tag. + /// + public static ExifTag IccProfile => new ExifTag(ExifTagValue.IccProfile); + /// /// Gets the CFAPattern2 exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs index 7e73b75aa..680136b03 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs @@ -16,6 +16,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); + /// + /// Gets the RowsPerStrip exif tag. + /// + public static ExifTag RowsPerStrip { get; } = new ExifTag(ExifTagValue.RowsPerStrip); + /// /// Gets the TileWidth exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs index e9440119e..e4ea6d0de 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); + /// + /// Gets the StripByteCounts exif tag. + /// + public static ExifTag StripByteCounts { get; } = new ExifTag(ExifTagValue.StripByteCounts); + /// /// Gets the TileByteCounts exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs index f52045531..d27c26ee5 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs @@ -56,6 +56,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); + /// + /// Gets the Predictor exif tag. + /// + public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); + /// /// Gets the GrayResponseUnit exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs index ce0bb36f0..6ed9131c8 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs @@ -46,11 +46,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); - /// - /// Gets the Predictor exif tag. - /// - public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); - /// /// Gets the HalftoneHints exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs index e07a32598..3d13a82dc 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs @@ -24,7 +24,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif GPSIFDOffset = 0x8825, /// - /// SubfileType + /// Indicates the identification of the Interoperability rule. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability/interoperabilityindex.html + /// + [ExifTagDescription("R98", "Indicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.")] + [ExifTagDescription("THM", "Indicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.")] + InteroperabilityIndex = 0x0001, + + /// + /// A general indication of the kind of data contained in this subfile. + /// See Section 8: Baseline Fields. /// [ExifTagDescription(0U, "Full-resolution Image")] [ExifTagDescription(1U, "Reduced-resolution image")] @@ -38,7 +47,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif SubfileType = 0x00FE, /// - /// OldSubfileType + /// A general indication of the kind of data contained in this subfile. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "Full-resolution Image")] [ExifTagDescription((ushort)2, "Reduced-resolution image")] @@ -46,22 +56,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif OldSubfileType = 0x00FF, /// - /// ImageWidth + /// The number of columns in the image, i.e., the number of pixels per row. + /// See Section 8: Baseline Fields. /// ImageWidth = 0x0100, /// - /// ImageLength + /// The number of rows of pixels in the image. + /// See Section 8: Baseline Fields. /// ImageLength = 0x0101, /// - /// BitsPerSample + /// Number of bits per component. + /// See Section 8: Baseline Fields. /// BitsPerSample = 0x0102, /// - /// Compression + /// Compression scheme used on the image data. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "Uncompressed")] [ExifTagDescription((ushort)2, "CCITT 1D")] @@ -107,7 +121,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif Compression = 0x0103, /// - /// PhotometricInterpretation + /// The color space of the image data. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)0, "WhiteIsZero")] [ExifTagDescription((ushort)1, "BlackIsZero")] @@ -126,7 +141,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif PhotometricInterpretation = 0x0106, /// - /// Thresholding + /// For black and white TIFF files that represent shades of gray, the technique used to convert from gray to black and white pixels. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "No dithering or halftoning")] [ExifTagDescription((ushort)2, "Ordered dither or halftone")] @@ -134,49 +150,58 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif Thresholding = 0x0107, /// - /// CellWidth + /// The width of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. + /// See Section 8: Baseline Fields. /// CellWidth = 0x0108, /// - /// CellLength + /// The length of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. + /// See Section 8: Baseline Fields. /// CellLength = 0x0109, /// - /// FillOrder + /// The logical order of bits within a byte. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "Normal")] [ExifTagDescription((ushort)2, "Reversed")] FillOrder = 0x010A, /// - /// DocumentName + /// The name of the document from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. /// DocumentName = 0x010D, /// - /// ImageDescription + /// A string that describes the subject of the image. + /// See Section 8: Baseline Fields. /// ImageDescription = 0x010E, /// - /// Make + /// The scanner manufacturer. + /// See Section 8: Baseline Fields. /// Make = 0x010F, /// - /// Model + /// The scanner model name or number. + /// See Section 8: Baseline Fields. /// Model = 0x0110, /// - /// StripOffsets + /// For each strip, the byte offset of that strip. + /// See Section 8: Baseline Fields. /// StripOffsets = 0x0111, /// - /// Orientation + /// The orientation of the image with respect to the rows and columns. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "Horizontal (normal)")] [ExifTagDescription((ushort)2, "Mirror horizontal")] @@ -189,74 +214,88 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif Orientation = 0x0112, /// - /// SamplesPerPixel + /// The number of components per pixel. + /// See Section 8: Baseline Fields. /// SamplesPerPixel = 0x0115, /// - /// RowsPerStrip + /// The number of rows per strip. + /// See Section 8: Baseline Fields. /// RowsPerStrip = 0x0116, /// - /// StripByteCounts + /// For each strip, the number of bytes in the strip after compression. + /// See Section 8: Baseline Fields. /// StripByteCounts = 0x0117, /// - /// MinSampleValue + /// The minimum component value used. + /// See Section 8: Baseline Fields. /// MinSampleValue = 0x0118, /// - /// MaxSampleValue + /// The maximum component value used. + /// See Section 8: Baseline Fields. /// MaxSampleValue = 0x0119, /// - /// XResolution + /// The number of pixels per ResolutionUnit in the ImageWidth direction. + /// See Section 8: Baseline Fields. /// XResolution = 0x011A, /// - /// YResolution + /// The number of pixels per ResolutionUnit in the direction. + /// See Section 8: Baseline Fields. /// YResolution = 0x011B, /// - /// PlanarConfiguration + /// How the components of each pixel are stored. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "Chunky")] [ExifTagDescription((ushort)2, "Planar")] PlanarConfiguration = 0x011C, /// - /// PageName + /// The name of the page from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. /// PageName = 0x011D, /// - /// XPosition + /// X position of the image. + /// See Section 12: Document Storage and Retrieval. /// XPosition = 0x011E, /// - /// YPosition + /// Y position of the image. + /// See Section 12: Document Storage and Retrieval. /// YPosition = 0x011F, /// - /// FreeOffsets + /// For each string of contiguous unused bytes in a TIFF file, the byte offset of the string. + /// See Section 8: Baseline Fields. /// FreeOffsets = 0x0120, /// - /// FreeByteCounts + /// For each string of contiguous unused bytes in a TIFF file, the number of bytes in the string. + /// See Section 8: Baseline Fields. /// FreeByteCounts = 0x0121, /// - /// GrayResponseUnit + /// The precision of the information contained in the GrayResponseCurve. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "0.1")] [ExifTagDescription((ushort)2, "0.001")] @@ -266,12 +305,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif GrayResponseUnit = 0x0122, /// - /// GrayResponseCurve + /// For grayscale data, the optical density of each possible pixel value. + /// See Section 8: Baseline Fields. /// GrayResponseCurve = 0x0123, /// - /// T4Options + /// Options for Group 3 Fax compression. /// [ExifTagDescription(0U, "2-Dimensional encoding")] [ExifTagDescription(1U, "Uncompressed")] @@ -279,13 +319,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif T4Options = 0x0124, /// - /// T6Options + /// Options for Group 4 Fax compression. /// [ExifTagDescription(1U, "Uncompressed")] T6Options = 0x0125, /// - /// ResolutionUnit + /// The unit of measurement for XResolution and YResolution. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)1, "None")] [ExifTagDescription((ushort)2, "Inches")] @@ -293,7 +334,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ResolutionUnit = 0x0128, /// - /// PageNumber + /// The page number of the page from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. /// PageNumber = 0x0129, @@ -308,22 +350,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif TransferFunction = 0x012D, /// - /// Software + /// Name and version number of the software package(s) used to create the image. + /// See Section 8: Baseline Fields. /// Software = 0x0131, /// - /// DateTime + /// Date and time of image creation. + /// See Section 8: Baseline Fields. /// DateTime = 0x0132, /// - /// Artist + /// Person who created the image. + /// See Section 8: Baseline Fields. /// Artist = 0x013B, /// - /// HostComputer + /// The computer and/or operating system in use at the time of image creation. + /// See Section 8: Baseline Fields. /// HostComputer = 0x013C, @@ -343,7 +389,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif PrimaryChromaticities = 0x013F, /// - /// ColorMap + /// A color map for palette color images. + /// See Section 8: Baseline Fields. /// ColorMap = 0x0140, @@ -390,6 +437,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// ConsecutiveBadFaxLines = 0x0148, + /// + /// Offset to child IFDs. + /// See TIFF Supplement 1: Adobe Pagemaker 6.0. + /// Each value is an offset (from the beginning of the TIFF file, as always) to a child IFD. Child images provide extra information for the parent image - such as a subsampled version of the parent image. + /// TIFF data type is Long or 13, IFD. The IFD type is identical to LONG, except that it is only used to point to other valid IFDs. + /// + SubIFDs = 0x014A, + /// /// InkSet /// @@ -418,7 +473,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif TargetPrinter = 0x0151, /// - /// ExtraSamples + /// Description of extra components. + /// See Section 8: Baseline Fields. /// [ExifTagDescription((ushort)0, "Unspecified")] [ExifTagDescription((ushort)1, "Associated Alpha")] @@ -485,6 +541,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif [ExifTagDescription((ushort)1, "Higher resolution image exists")] OPIProxy = 0x015F, + /// + /// Used in the TIFF-FX standard to point to an IFD containing tags that are globally applicable to the complete TIFF file. + /// See RFC2301: TIFF-F/FX Specification. + /// It is recommended that a TIFF writer place this field in the first IFD, where a TIFF reader would find it quickly. + /// Each field in the GlobalParametersIFD is a TIFF field that is legal in any IFD. Required baseline fields should not be located in the GlobalParametersIFD, but should be in each image IFD. If a conflict exists between fields in the GlobalParametersIFD and in the image IFDs, then the data in the image IFD shall prevail. + /// + GlobalParametersIFD = 0x0190, + /// /// ProfileType /// @@ -637,6 +701,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// ImageID = 0x800D, + /// + /// Annotation data, as used in 'Imaging for Windows'. + /// See Other Private TIFF tags: http://www.awaresystems.be/imaging/tiff/tifftags/private.html + /// + WangAnnotation = 0x80A4, + /// /// CFARepeatPatternDim /// @@ -653,7 +723,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif BatteryLevel = 0x828F, /// - /// Copyright + /// Copyright notice. + /// See Section 8: Baseline Fields. /// Copyright = 0x8298, @@ -668,38 +739,70 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif FNumber = 0x829D, /// - /// MDFileTag + /// Specifies the pixel data format encoding in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html /// + [ExifTagDescription((ushort)2, "Squary root data format")] + [ExifTagDescription((ushort)128, "Linear data format")] MDFileTag = 0x82A5, /// - /// MDScalePixel + /// Specifies a scale factor in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// The scale factor is to be applies to each pixel before presenting it to the user. /// MDScalePixel = 0x82A6, /// - /// MDLabName + /// Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// Since the display is only 9bit, the 16bit data must be converted before display. + /// 8bit value = (16bit value - low range ) * 255 / (high range - low range) + /// Count: n. + /// + [ExifTagDescription((ushort)0, "lowest possible")] + [ExifTagDescription((ushort)1, "low range")] + [ExifTagDescription("n-2", "high range")] + [ExifTagDescription("n-1", "highest possible")] + MDColorTable = 0x82A7, + + /// + /// Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html /// MDLabName = 0x82A8, /// - /// MDSampleInfo + /// Information about the sample, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// This information is entered by the person that scanned the file. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. /// MDSampleInfo = 0x82A9, /// - /// MDPrepDate + /// Date the sample was prepared, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// The format of this data is YY/MM/DD. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. /// MDPrepDate = 0x82AA, /// - /// MDPrepTime + /// Time the sample was prepared, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// Format of this data is HH:MM using the 24-hour clock. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. /// MDPrepTime = 0x82AB, /// - /// MDFileUnits + /// Units for data in this file, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html /// + [ExifTagDescription("O.D.", "Densitometer")] + [ExifTagDescription("Counts", "PhosphorImager")] + [ExifTagDescription("RFU", "FluorImager")] MDFileUnits = 0x82AC, /// @@ -707,6 +810,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// PixelScale = 0x830E, + /// + /// IPTC (International Press Telecommunications Council) metadata. + /// See IPTC 4.1 specification. + /// + IPTC = 0x83BB, + /// /// IntergraphPacketData /// @@ -737,6 +846,40 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// ModelTransform = 0x85D8, + /// + /// Collection of Photoshop 'Image Resource Blocks' (Embedded Metadata). + /// See Extracting the Thumbnail from the PhotoShop private TIFF Tag: https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html + /// + Photoshop = 0x8649, + + /// + /// ICC profile data. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/iccprofile.html + /// + IccProfile = 0x8773, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html + /// This tag is also know as 'ProjectionInfoTag' and 'CoordSystemInfoTag' + /// This tag may be used to store the GeoKey Directory, which defines and references the "GeoKeys". + /// + GeoKeyDirectoryTag = 0x87AF, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html + /// This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE and stored here. + /// + GeoDoubleParamsTag = 0x87B0, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html + /// This tag is used to store all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag. Since keys use offsets into tags, any special comments may be placed at the beginning of this tag. For the most part, the only keys that are ASCII valued are "Citation" keys, giving documentation and references for obscure projections, datums, etc. + /// + GeoAsciiParamsTag = 0x87B1, + /// /// ImageLayer /// @@ -1184,6 +1327,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// RelatedSoundFile = 0xA004, + /// + /// A pointer to the Exif-related Interoperability IFD. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability.html + /// Interoperability IFD is composed of tags which stores the information to ensure the Interoperability. + /// The Interoperability structure of Interoperability IFD is same as TIFF defined IFD structure but does not contain the image data characteristically compared with normal TIFF IFD. + /// + InteroperabilityIFD = 0xA005, + /// /// FlashEnergy /// @@ -1539,5 +1690,41 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// GPSDifferential /// GPSDifferential = 0x001E, + + /// + /// Used in the Oce scanning process. + /// Identifies the scanticket used in the scanning process. + /// Includes a trailing zero. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceScanjobDescription = 0xC427, + + /// + /// Used in the Oce scanning process. + /// Identifies the application to process the TIFF file that results from scanning. + /// Includes a trailing zero. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceApplicationSelector = 0xC428, + + /// + /// Used in the Oce scanning process. + /// This is the user's answer to an optional question embedded in the Oce scanticket, and presented to that user before scanning. It can serve in further determination of the workflow. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceIdentificationNumber = 0xC429, + + /// + /// Used in the Oce scanning process. + /// This tag encodes the imageprocessing done by the Oce ImageLogic module in the scanner to ensure optimal quality for certain workflows. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceImageLogicCharacteristics = 0xC42A, + + /// + /// Alias Sketchbook Pro layer usage description. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/alias.html + /// + AliasLayerMetadata = 0xC660, } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 28002f0b7..2d3a93aed 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -36,6 +36,90 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int val: + return this.SetSingle(val); + case uint val: + return this.SetSingle(val); + case short val: + return this.SetSingle(val); + case ushort val: + return this.SetSingle(val); + case int[] array: + return this.SetArray(array); + case uint[] array: + return this.SetArray(array); + case short[] array: + return this.SetArray(array); + case ushort[] array: + return this.SetArray(array); + } + + return false; + } + public override IExifValue DeepClone() => new ExifNumberArray(this); + + private bool SetSingle(Number value) + { + this.Value = new[] { value }; + return true; + } + + private bool SetArray(int[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(uint[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(short[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(ushort[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index 2d8aa9260..af1eee2dc 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -9,10 +9,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); - public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) - { - bool isArray = numberOfComponents != 1; + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray) + { switch (dataType) { case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); @@ -93,6 +93,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.RowsPerStrip: return new ExifNumber(ExifTag.RowsPerStrip); case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); @@ -100,6 +101,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); + case ExifTagValue.StripByteCounts: return new ExifNumberArray(ExifTag.StripByteCounts); case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); @@ -158,6 +160,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.Predictor: return new ExifShort(ExifTag.Predictor); case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); @@ -203,7 +206,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); - case ExifTagValue.Predictor: return new ExifShortArray(ExifTag.Predictor); case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs index 56593acb8..9b28a8fdd 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs @@ -22,10 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization bool clipHistogram, int clipLimit, int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimit) - { - this.NumberOfTiles = numberOfTiles; - } + : base(luminanceLevels, clipHistogram, clipLimit) => this.NumberOfTiles = numberOfTiles; /// /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. @@ -34,8 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - return new AdaptiveHistogramEqualizationProcessor( + => new AdaptiveHistogramEqualizationProcessor( configuration, this.LuminanceLevels, this.ClipHistogram, @@ -43,6 +39,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.NumberOfTiles, source, sourceRectangle); - } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 14687426d..91ed9f5de 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -459,10 +459,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly Configuration configuration; private readonly MemoryAllocator memoryAllocator; - // Used for storing the minimum value for each CDF entry. + /// + /// Used for storing the minimum value for each CDF entry. + /// private readonly Buffer2D cdfMinBuffer2D; - // Used for storing the LUT for each CDF entry. + /// + /// Used for storing the LUT for each CDF entry. + /// private readonly Buffer2D cdfLutBuffer2D; private readonly int pixelsInTile; private readonly int sourceWidth; @@ -596,6 +600,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int y = this.tileYStartPositions[index].y; int endY = Math.Min(y + this.tileHeight, this.sourceHeight); Span cdfMinSpan = this.cdfMinBuffer2D.GetRowSpan(cdfY); + cdfMinSpan.Clear(); using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); Span histogram = histogramBuffer.GetSpan(); diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 60686f401..f93334beb 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -49,44 +49,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The . /// The . - public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) + public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) => options.Method switch { - HistogramEqualizationProcessor processor; + HistogramEqualizationMethod.Global + => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), - switch (options.Method) - { - case HistogramEqualizationMethod.Global: - processor = new GlobalHistogramEqualizationProcessor( - options.LuminanceLevels, - options.ClipHistogram, - options.ClipLimit); - break; + HistogramEqualizationMethod.AdaptiveTileInterpolation + => new AdaptiveHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), - case HistogramEqualizationMethod.AdaptiveTileInterpolation: - processor = new AdaptiveHistogramEqualizationProcessor( - options.LuminanceLevels, - options.ClipHistogram, - options.ClipLimit, - options.NumberOfTiles); - break; + HistogramEqualizationMethod.AdaptiveSlidingWindow + => new AdaptiveHistogramEqualizationSlidingWindowProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), - case HistogramEqualizationMethod.AdaptiveSlidingWindow: - processor = new AdaptiveHistogramEqualizationSlidingWindowProcessor( - options.LuminanceLevels, - options.ClipHistogram, - options.ClipLimit, - options.NumberOfTiles); - break; - - default: - processor = new GlobalHistogramEqualizationProcessor( - options.LuminanceLevels, - options.ClipHistogram, - options.ClipLimit); - break; - } - - return processor; - } + _ => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), + }; } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index 59df3058d..9227cb0c0 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -142,6 +142,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization [MethodImpl(InliningOptions.ShortMethod)] public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) { + // TODO: We need a bulk per span equivalent. var vector = sourcePixel.ToVector4(); return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs new file mode 100644 index 000000000..e77bb8b3e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// Enable this for using larger Tiff files. Those files are very large (> 700MB) and therefor not part of the git repo. +// Use the scripts gen_big.ps1 and gen_medium.ps1 in tests\Images\Input\Tiff\Benchmarks to generate those images. +//// #define BIG_TESTS + +using System.IO; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +using SDImage = System.Drawing.Image; +using SDSize = System.Drawing.Size; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [MarkdownExporter] + [HtmlExporter] + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeTiff + { + private string prevImage; + + private byte[] data; + + private Configuration configuration; + +#if BIG_TESTS + private static readonly int BufferSize = 1024 * 68; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage)); + + [Params( + TestImages.Tiff.Benchmark_BwFax3, + //// TestImages.Tiff.Benchmark_RgbFax4, // fax4 is not supported yet. + TestImages.Tiff.Benchmark_GrayscaleUncompressed, + TestImages.Tiff.Benchmark_PaletteUncompressed, + TestImages.Tiff.Benchmark_RgbDeflate, + TestImages.Tiff.Benchmark_RgbLzw, + TestImages.Tiff.Benchmark_RgbPackbits, + TestImages.Tiff.Benchmark_RgbUncompressed)] + public string TestImage { get; set; } + +#else + private static readonly int BufferSize = Configuration.Default.StreamProcessingBufferSize; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params( + TestImages.Tiff.CcittFax3AllTermCodes, + TestImages.Tiff.HuffmanRleAllMakeupCodes, + TestImages.Tiff.Calliphora_GrayscaleUncompressed, + TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, + TestImages.Tiff.Calliphora_RgbDeflate_Predictor, + TestImages.Tiff.Calliphora_RgbLzwPredictor, + TestImages.Tiff.Calliphora_RgbPackbits, + TestImages.Tiff.Calliphora_RgbUncompressed)] + public string TestImage { get; set; } +#endif + + [GlobalSetup] + public void Config() + { + if (this.configuration == null) + { + this.configuration = new Configuration(); + this.configuration.StreamProcessingBufferSize = BufferSize; + } + } + + [IterationSetup] + public void ReadImages() + { + if (this.prevImage != this.TestImage) + { + this.data = File.ReadAllBytes(this.TestImageFullPath); + this.prevImage = this.TestImage; + } + } + + [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] + public SDSize TiffSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.data)) + using (var image = SDImage.FromStream(memoryStream)) + { + return image.Size; + } + } + + [Benchmark(Description = "ImageSharp Tiff")] + public Size TiffCore() + { + using (var ms = new MemoryStream(this.data)) + using (var image = Image.Load(this.configuration, ms)) + { + return image.Size(); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs index bcb015e57..0e320e4a7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -40,17 +40,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Baseline = true, Description = "Magick Tga")] - public void BmpImageMagick() + public void TgaMagick() { using var memoryStream = new MemoryStream(); this.tgaMagick.Write(memoryStream, MagickFormat.Tga); } [Benchmark(Description = "ImageSharp Tga")] - public void BmpImageSharp() + public void TgaImageSharp() { using var memoryStream = new MemoryStream(); - this.tgaCore.SaveAsBmp(memoryStream); + this.tgaCore.SaveAsTga(memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs new file mode 100644 index 000000000..39055faf5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing.Imaging; +using System.IO; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [MarkdownExporter] + [HtmlExporter] + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeTiff + { + private System.Drawing.Image drawing; + private Image core; + + private Configuration configuration; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Tiff.Calliphora_RgbUncompressed)] + public string TestImage { get; set; } + + [Params( + TiffCompression.None, + TiffCompression.Deflate, + TiffCompression.Lzw, + TiffCompression.PackBits, + TiffCompression.CcittGroup3Fax, + TiffCompression.Ccitt1D)] + public TiffCompression Compression { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.core == null) + { + this.configuration = new Configuration(); + this.core = Image.Load(this.configuration, this.TestImageFullPath); + this.drawing = System.Drawing.Image.FromFile(this.TestImageFullPath); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.core.Dispose(); + this.drawing.Dispose(); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] + public void SystemDrawing() + { + ImageCodecInfo codec = FindCodecForType("image/tiff"); + using var parameters = new EncoderParameters(1) + { + Param = {[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression))} + }; + + using var memoryStream = new MemoryStream(); + this.drawing.Save(memoryStream, codec, parameters); + } + + [Benchmark(Description = "ImageSharp Tiff")] + public void TiffCore() + { + TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb; + + // Workaround for 1-bit bug + if (this.Compression == TiffCompression.CcittGroup3Fax || this.Compression == TiffCompression.Ccitt1D) + { + photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; + } + + var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; + using var memoryStream = new MemoryStream(); + this.core.SaveAsTiff(memoryStream, encoder); + } + + private static ImageCodecInfo FindCodecForType(string mimeType) + { + ImageCodecInfo[] imgEncoders = ImageCodecInfo.GetImageEncoders(); + + for (int i = 0; i < imgEncoders.GetLength(0); i++) + { + if (imgEncoders[i].MimeType == mimeType) + { + return imgEncoders[i]; + } + } + + return null; + } + + private static EncoderValue Cast(TiffCompression compression) + { + switch (compression) + { + case TiffCompression.None: + return EncoderValue.CompressionNone; + + case TiffCompression.CcittGroup3Fax: + return EncoderValue.CompressionCCITT3; + + case TiffCompression.Ccitt1D: + return EncoderValue.CompressionRle; + + case TiffCompression.Lzw: + return EncoderValue.CompressionLZW; + + default: + throw new System.NotSupportedException(compression.ToString()); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 0c40b482a..63064eec5 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -9,6 +9,7 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Reports; namespace SixLabors.ImageSharp.Benchmarks { @@ -25,6 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks } #endif + this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50); } public class MultiFramework : Config diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs index 2d0fce0a0..416a62833 100644 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs @@ -3,7 +3,7 @@ using System; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.Compression.Zlib; using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; namespace SixLabors.ImageSharp.Benchmarks.General diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs index 0153ca8b1..8a90b44cf 100644 --- a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs @@ -3,7 +3,7 @@ using System; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.Compression.Zlib; using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; namespace SixLabors.ImageSharp.Benchmarks.General diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 915947532..fe3b16450 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -13,6 +13,7 @@ false false Debug;Release;Release-InnerLoop;Debug-InnerLoop + false diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 655e98c7f..3ad8ef2f8 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,11 +21,11 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 5; + private readonly int expectedDefaultConfigurationCount = 6; public ConfigurationTests() { - // the shallow copy of configuration should behave exactly like the default configuration, + // The shallow copy of configuration should behave exactly like the default configuration, // so by using the copy, we test both the default and the copy. this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); this.ConfigurationEmpty = new Configuration(); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 44a6b17a6..ea5e8b087 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -18,12 +18,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Fact] public void CloneIsDeep() { - var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 }; var clone = (BmpMetadata)meta.DeepClone(); clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2; Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + Assert.False(meta.InfoHeaderType.Equals(clone.InfoHeaderType)); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index a171d6d52..c0843a51b 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats public class GeneralFormatTests { /// - /// A collection made up of one file for each image format + /// A collection made up of one file for each image format. /// public static readonly IEnumerable DefaultFiles = new[] @@ -149,6 +149,11 @@ namespace SixLabors.ImageSharp.Tests.Formats { image.SaveAsTga(output); } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) + { + image.SaveAsTga(output); + } } } } @@ -171,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats using (var image2 = Image.Load(serialized)) { - image2.Save($"{path}/{file.FileName}"); + image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); } } } @@ -196,6 +201,10 @@ namespace SixLabors.ImageSharp.Tests.Formats [InlineData(100, 100, "tga")] [InlineData(100, 10, "tga")] [InlineData(10, 100, "tga")] + [InlineData(100, 100, "tiff")] + [InlineData(100, 10, "tiff")] + [InlineData(10, 100, "tiff")] + public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index 6ec1162c4..c4be71d2a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections { public class GifGraphicControlExtensionTests { @@ -18,4 +18,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs index db88cf5b3..41ec1c7e8 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections { public class GifImageDescriptorTests { @@ -21,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs index 9773bcd61..6efa680c8 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections { public class GifLogicalScreenDescriptorTests { @@ -20,4 +20,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index dea8c62e1..1e00bfff8 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -36,12 +37,14 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 3910b2c49..67df6a881 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -117,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public async Task DecodeAsnc_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + public async Task DecodeAsync_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); @@ -127,60 +128,53 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 0)] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 15)] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 30)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 15)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 30)] - public async Task Decode_IsCancellable(string fileName, int cancellationDelayMs) + [InlineData(0)] + [InlineData(0.5)] + [InlineData(0.9)] + public async Task Decode_IsCancellable(int percentageOfStreamReadToCancel) { - // Decoding these huge files took 300ms on i7-8650U in 2020. 30ms should be safe for cancellation delay. - string hugeFile = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - fileName); - - const int NumberOfRuns = 5; - - for (int i = 0; i < NumberOfRuns; i++) + var cts = new CancellationTokenSource(); + var file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using var pausedStream = new PausedStream(file); + pausedStream.OnWaiting(s => { - var cts = new CancellationTokenSource(); - if (cancellationDelayMs == 0) + if (s.Position >= s.Length * percentageOfStreamReadToCancel) { cts.Cancel(); + pausedStream.Release(); } else { - cts.CancelAfter(cancellationDelayMs); - } - - try - { - using var image = await Image.LoadAsync(hugeFile, cts.Token); - } - catch (TaskCanceledException) - { - // Succesfully observed a cancellation - return; + // allows this/next wait to unblock + pausedStream.Next(); } - } + }); - throw new Exception($"No cancellation happened out of {NumberOfRuns} runs!"); + var config = Configuration.CreateDefaultInstance(); + config.FileSystem = new SingleStreamFileSystem(pausedStream); + await Assert.ThrowsAsync(async () => + { + using Image image = await Image.LoadAsync(config, "someFakeFile", cts.Token); + }); } - [Theory(Skip = "Identify is too fast, doesn't work reliably.")] - [InlineData(TestImages.Jpeg.Baseline.Exif)] - [InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)] - public async Task Identify_IsCancellable(string fileName) + [Fact] + public async Task Identify_IsCancellable() { - string file = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - fileName); - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromTicks(1)); - await Assert.ThrowsAsync(() => Image.IdentifyAsync(file, cts.Token)); + + var file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using var pausedStream = new PausedStream(file); + pausedStream.OnWaiting(s => + { + cts.Cancel(); + pausedStream.Release(); + }); + + var config = Configuration.CreateDefaultInstance(); + config.FileSystem = new SingleStreamFileSystem(pausedStream); + + await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token)); } // DEBUG ONLY! diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 9a1d423a6..3c48865c7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -310,28 +311,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [InlineData(JpegSubsample.Ratio420, 0)] - [InlineData(JpegSubsample.Ratio420, 3)] - [InlineData(JpegSubsample.Ratio420, 10)] - [InlineData(JpegSubsample.Ratio444, 0)] - [InlineData(JpegSubsample.Ratio444, 3)] - [InlineData(JpegSubsample.Ratio444, 10)] - public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs) + [InlineData(JpegSubsample.Ratio420)] + [InlineData(JpegSubsample.Ratio444)] + public async Task Encode_IsCancellable(JpegSubsample subsample) { - using var image = new Image(5000, 5000); - using var stream = new MemoryStream(); var cts = new CancellationTokenSource(); - if (cancellationDelayMs == 0) - { - cts.Cancel(); - } - else + using var pausedStream = new PausedStream(new MemoryStream()); + pausedStream.OnWaiting(s => { - cts.CancelAfter(cancellationDelayMs); - } + // after some writing + if (s.Position >= 500) + { + cts.Cancel(); + pausedStream.Release(); + } + else + { + // allows this/next wait to unblock + pausedStream.Next(); + } + }); - var encoder = new JpegEncoder() { Subsample = subsample }; - await Assert.ThrowsAsync(() => image.SaveAsync(stream, encoder, cts.Token)); + using var image = new Image(5000, 5000); + await Assert.ThrowsAsync(async () => + { + var encoder = new JpegEncoder() { Subsample = subsample }; + await image.SaveAsync(pausedStream, encoder, cts.Token); + }); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs index aadd30f2b..0886bd84d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.Compression.Zlib; using Xunit; using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; diff --git a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs index 1ae21e771..6bdad6ed4 100644 --- a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.Compression.Zlib; using Xunit; using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs index 5f7b4f832..80bfd3497 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs @@ -7,7 +7,6 @@ using System; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.Tests.Formats.Png.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -142,7 +141,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png HwIntrinsics.DisableSIMD); } - [Fact] public void UpAvx2() { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index f9ff41df1..b4307af5d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -90,12 +90,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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")); + Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); } } @@ -277,20 +277,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void VerifyTextDataIsPresent(PngMetadata meta) { Assert.NotNull(meta); - 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")); + Assert.Contains(meta.TextData, m => m.Keyword is "Comment" && m.Value is "comment"); + Assert.Contains(meta.TextData, m => m.Keyword is "Author" && m.Value is "ImageSharp"); + Assert.Contains(meta.TextData, m => m.Keyword is "Copyright" && m.Value is "ImageSharp"); + Assert.Contains(meta.TextData, m => m.Keyword is "Title" && m.Value is "unittest"); + Assert.Contains(meta.TextData, m => m.Keyword is "Description" && m.Value is "compressed-text"); + Assert.Contains(meta.TextData, m => m.Keyword is "International" && m.Value is "'e', mu'tlheghvam, ghaH yu'" && m.LanguageTag is "x-klingon" && m.TranslatedKeyword is "warning"); + Assert.Contains(meta.TextData, m => m.Keyword is "International2" && m.Value is "ИМАГЕШАРП" && m.LanguageTag is "rus"); + Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational" && m.Value is "la plume de la mante" && m.LanguageTag is "fra" && m.TranslatedKeyword is "foobar"); + Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational2" && m.Value is "這是一個考驗" && m.LanguageTag is "chinese"); + Assert.Contains(meta.TextData, m => m.Keyword is "NoLang" && m.Value is "this text chunk is missing a language tag"); + Assert.Contains(meta.TextData, m => m.Keyword is "NoTranslatedKeyword" && m.Value is "dieser chunk hat kein übersetztes Schlüßelwort"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs index dd8ecc096..a9b53e16e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Png { /// /// This class contains reference implementations to produce verification data for unit tests diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs new file mode 100644 index 000000000..782a504d5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +{ + [Trait("Format", "Tiff")] + public class DeflateTiffCompressionTests + { + [Theory] + [InlineData(new byte[] { })] + [InlineData(new byte[] { 42 })] // One byte + [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes + [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence + public void Compress_Decompress_Roundtrip_Works(byte[] data) + { + using (BufferedReadStream stream = CreateCompressedStream(data)) + { + var buffer = new byte[data.Length]; + + using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); + + decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); + + Assert.Equal(data, buffer); + } + } + + private static BufferedReadStream CreateCompressedStream(byte[] data) + { + Stream compressedStream = new MemoryStream(); + + using (Stream uncompressedStream = new MemoryStream(data), + deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6)) + { + uncompressedStream.CopyTo(deflateStream); + } + + compressedStream.Seek(0, SeekOrigin.Begin); + return new BufferedReadStream(Configuration.Default, compressedStream); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs new file mode 100644 index 000000000..bf585e9c8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +{ + [Trait("Format", "Tiff")] + public class LzwTiffCompressionTests + { + [Theory] + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 }, new byte[] { 128, 0, 64, 66, 168, 36, 22, 12, 3, 2, 64, 64, 0, 0 })] // Repeated bytes + + public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) + { + var compressedData = new byte[expectedCompressedData.Length]; + Stream streamData = CreateCompressedStream(inputData); + streamData.Read(compressedData, 0, expectedCompressedData.Length); + + Assert.Equal(expectedCompressedData, compressedData); + } + + [Theory] + [InlineData(new byte[] { })] + [InlineData(new byte[] { 42 })] // One byte + [InlineData(new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 })] + [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes + [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence + + public void Compress_Decompress_Roundtrip_Works(byte[] data) + { + using BufferedReadStream stream = CreateCompressedStream(data); + var buffer = new byte[data.Length]; + + using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); + decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); + + Assert.Equal(data, buffer); + } + + private static BufferedReadStream CreateCompressedStream(byte[] inputData) + { + Stream compressedStream = new MemoryStream(); + + using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator)) + { + encoder.Encode(inputData, compressedStream); + } + + compressedStream.Seek(0, SeekOrigin.Begin); + + return new BufferedReadStream(Configuration.Default, compressedStream); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs new file mode 100644 index 000000000..82ecb315b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.IO; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +{ + [Trait("Format", "Tiff")] + public class NoneTiffCompressionTests + { + [Theory] + [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })] + [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] + public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) + { + var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); + var buffer = new byte[expectedResult.Length]; + + new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer); + + Assert.Equal(expectedResult, buffer); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs new file mode 100644 index 000000000..b67cb8325 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +{ + [Trait("Format", "Tiff")] + public class PackBitsTiffCompressionTests + { + [Theory] + [InlineData(new byte[] { }, new byte[] { })] + [InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte + [InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes + [InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes + [InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes + [InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte + [InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes + [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample + public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) + { + var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); + var buffer = new byte[expectedResult.Length]; + + using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); + decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); + + Assert.Equal(expectedResult, buffer); + } + + [Theory] + [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA })] // Apple PackBits sample + public void Compress_Works(byte[] inputData, byte[] expectedResult) + { + // arrange + Span input = inputData.AsSpan(); + var compressed = new byte[expectedResult.Length]; + + // act + PackBitsWriter.PackBits(input, compressed); + + // assert + Assert.Equal(expectedResult, compressed); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs new file mode 100644 index 000000000..3365a1eb3 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs @@ -0,0 +1,156 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class ImageExtensionsTest + { + [Fact] + public void SaveAsTiff_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); + + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTiffAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTiff_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); + + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(file, new TiffEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTiffAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(file, new TiffEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTiff_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTiffAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsTiff_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(memoryStream, new TiffEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsTiffAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs new file mode 100644 index 000000000..579ee0290 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs @@ -0,0 +1,195 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase + { + private static readonly Rgba32 Gray000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Gray128 = new Rgba32(128, 128, 128, 255); + private static readonly Rgba32 Gray255 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Gray0 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Gray8 = new Rgba32(136, 136, 136, 255); + private static readonly Rgba32 GrayF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Bit0 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Bit1 = new Rgba32(255, 255, 255, 255); + + private static readonly byte[] BilevelBytes4X4 = + { + 0b01010000, + 0b11110000, + 0b01110000, + 0b10010000 + }; + + private static readonly Rgba32[][] BilevelResult4X4 = new[] + { + new[] { Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit1 }, + new[] { Bit1, Bit0, Bit0, Bit1 } + }; + + private static readonly byte[] BilevelBytes12X4 = + { + 0b01010101, 0b01010000, + 0b11111111, 0b11111111, + 0b01101001, 0b10100000, + 0b10010000, 0b01100000 + }; + + private static readonly Rgba32[][] BilevelResult12X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, + new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } + }; + + private static readonly byte[] Grayscale4Bytes4X4 = + { + 0x8F, 0x0F, + 0xFF, 0xFF, + 0x08, 0x8F, + 0xF0, 0xF8 + }; + + private static readonly Rgba32[][] Grayscale4Result4X4 = + { + new[] { Gray8, GrayF, Gray0, GrayF }, + new[] { GrayF, GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8, GrayF }, + new[] { GrayF, Gray0, GrayF, Gray8 } + }; + + private static readonly byte[] Grayscale4Bytes3X4 = + { + 0x8F, 0x00, + 0xFF, 0xF0, + 0x08, 0x80, + 0xF0, 0xF0 + }; + + private static readonly Rgba32[][] Grayscale4Result3X4 = + { + new[] { Gray8, GrayF, Gray0 }, + new[] { GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8 }, + new[] { GrayF, Gray0, GrayF } + }; + + private static readonly byte[] Grayscale8Bytes4X4 = + { + 128, 255, 000, 255, + 255, 255, 255, 255, + 000, 128, 128, 255, + 255, 000, 255, 128 + }; + + private static readonly Rgba32[][] Grayscale8Result4X4 = + { + new[] { Gray128, Gray255, Gray000, Gray255 }, + new[] { Gray255, Gray255, Gray255, Gray255 }, + new[] { Gray000, Gray128, Gray128, Gray255 }, + new[] { Gray255, Gray000, Gray255, Gray128 } + }; + + public static IEnumerable BilevelData + { + get + { + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; + + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; + } + } + + public static IEnumerable Grayscale4_Data + { + get + { + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; + } + } + + public static IEnumerable Grayscale8_Data + { + get + { + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(BilevelData))] + [MemberData(nameof(Grayscale4_Data))] + [MemberData(nameof(Grayscale8_Data))] + public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new BlackIsZeroTiffColor(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(BilevelData))] + public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new BlackIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(Grayscale4_Data))] + public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new BlackIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(Grayscale8_Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new BlackIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs new file mode 100644 index 000000000..0da1d8bbd --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public class PaletteTiffColorTests : PhotometricInterpretationTestBase + { + public static uint[][] Palette4ColorPalette => GeneratePalette(16); + + public static ushort[] Palette4ColorMap => GenerateColorMap(Palette4ColorPalette); + + private static readonly byte[] Palette4Bytes4X4 = + { + 0x01, 0x23, 0x4A, 0xD2, 0x12, 0x34, 0xAB, 0xEF + }; + + private static readonly Rgba32[][] Palette4Result4X4 = GenerateResult( + Palette4ColorPalette, + new[] { new[] { 0x00, 0x01, 0x02, 0x03 }, new[] { 0x04, 0x0A, 0x0D, 0x02 }, new[] { 0x01, 0x02, 0x03, 0x04 }, new[] { 0x0A, 0x0B, 0x0E, 0x0F } }); + + private static readonly byte[] Palette4Bytes3X4 = + { + 0x01, 0x20, + 0x4A, 0xD0, + 0x12, 0x30, + 0xAB, 0xE0 + }; + + private static readonly Rgba32[][] Palette4Result3X4 = GenerateResult(Palette4ColorPalette, new[] { new[] { 0x00, 0x01, 0x02 }, new[] { 0x04, 0x0A, 0x0D }, new[] { 0x01, 0x02, 0x03 }, new[] { 0x0A, 0x0B, 0x0E } }); + + public static IEnumerable Palette4Data + { + get + { + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Palette4Result4X4 }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Offset(Palette4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 0, 4, 4, Offset(Palette4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 1, 4, 4, Offset(Palette4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 1, 4, 4, Offset(Palette4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Palette4Result3X4 }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Offset(Palette4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 0, 3, 4, Offset(Palette4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 1, 3, 4, Offset(Palette4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 1, 3, 4, Offset(Palette4Result3X4, 1, 1, 6, 6) }; + } + } + + public static uint[][] Palette8ColorPalette => GeneratePalette(256); + + public static ushort[] Palette8ColorMap => GenerateColorMap(Palette8ColorPalette); + + private static readonly byte[] Palette8Bytes4X4 = + { + 000, 001, 002, 003, + 100, 110, 120, 130, + 000, 255, 128, 255, + 050, 100, 150, 200 + }; + + private static readonly Rgba32[][] Palette8Result4X4 = GenerateResult(Palette8ColorPalette, new[] { new[] { 000, 001, 002, 003 }, new[] { 100, 110, 120, 130 }, new[] { 000, 255, 128, 255 }, new[] { 050, 100, 150, 200 } }); + + public static IEnumerable Palette8Data + { + get + { + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Palette8Result4X4 }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Offset(Palette8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 0, 4, 4, Offset(Palette8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 1, 4, 4, Offset(Palette8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 1, 4, 4, Offset(Palette8Result4X4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(Palette4Data))] + [MemberData(nameof(Palette8Data))] + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) => AssertDecode(expectedResult, pixels => + { + new PaletteTiffColor(new[] { bitsPerSample }, colorMap).Decode(inputData, pixels, left, top, width, height); + }); + + private static uint[][] GeneratePalette(int count) + { + var palette = new uint[count][]; + + for (uint i = 0; i < count; i++) + { + palette[i] = new[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u }; + } + + return palette; + } + + private static ushort[] GenerateColorMap(uint[][] colorPalette) + { + int colorCount = colorPalette.Length; + var colorMap = new ushort[colorCount * 3]; + + for (int i = 0; i < colorCount; i++) + { + colorMap[(colorCount * 0) + i] = (ushort)colorPalette[i][0]; + colorMap[(colorCount * 1) + i] = (ushort)colorPalette[i][1]; + colorMap[(colorCount * 2) + i] = (ushort)colorPalette[i][2]; + } + + return colorMap; + } + + private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) + { + var result = new Rgba32[pixelLookup.Length][]; + + for (int y = 0; y < pixelLookup.Length; y++) + { + result[y] = new Rgba32[pixelLookup[y].Length]; + + for (int x = 0; x < pixelLookup[y].Length; x++) + { + uint[] sourceColor = colorPalette[pixelLookup[y][x]]; + result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); + } + } + + return result; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs new file mode 100644 index 000000000..0bb61ce1c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public abstract class PhotometricInterpretationTestBase + { + public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128); + + public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height) + { + int inputHeight = input.Length; + int inputWidth = input[0].Length; + + var output = new Rgba32[height][]; + + for (int y = 0; y < output.Length; y++) + { + output[y] = new Rgba32[width]; + + for (int x = 0; x < width; x++) + { + output[y][x] = DefaultColor; + } + } + + for (int y = 0; y < inputHeight; y++) + { + for (int x = 0; x < inputWidth; x++) + { + output[y + yOffset][x + xOffset] = input[y][x]; + } + } + + return output; + } + + internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) + { + int resultWidth = expectedResult[0].Length; + int resultHeight = expectedResult.Length; + + using (var image = new Image(resultWidth, resultHeight)) + { + image.Mutate(x => x.BackgroundColor(DefaultColor)); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + decodeAction(pixels); + + for (int y = 0; y < resultHeight; y++) + { + for (int x = 0; x < resultWidth; x++) + { + Assert.True( + expectedResult[y][x] == pixels[x, y], + $"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}"); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs new file mode 100644 index 000000000..abfae6ab4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase + { + private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); + private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); + private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); + private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); + + private static readonly byte[] Rgb4Bytes4X4R = + { + 0x0F, 0x0F, + 0xF0, 0x0F, + 0x48, 0xC4, + 0x04, 0x8C + }; + + private static readonly byte[] Rgb4Bytes4X4G = + { + 0x0F, 0x0F, + 0x0F, 0x00, + 0x00, 0x08, + 0x04, 0x8C + }; + + private static readonly byte[] Rgb4Bytes4X4B = + { + 0x0F, 0x0F, + 0x00, 0xFF, + 0x00, 0x0C, + 0x04, 0x8C + }; + + private static readonly byte[][] Rgb4Bytes4X4 = { Rgb4Bytes4X4R, Rgb4Bytes4X4G, Rgb4Bytes4X4B }; + + private static readonly Rgba32[][] Rgb4Result4X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } + }; + + private static readonly byte[] Rgb4Bytes3X4R = + { + 0x0F, 0x00, + 0xF0, 0x00, + 0x48, 0xC0, + 0x04, 0x80 + }; + + private static readonly byte[] Rgb4Bytes3X4G = + { + 0x0F, 0x00, + 0x0F, 0x00, + 0x00, 0x00, + 0x04, 0x80 + }; + + private static readonly byte[] Rgb4Bytes3X4B = + { + 0x0F, 0x00, + 0x00, 0xF0, + 0x00, 0x00, + 0x04, 0x80 + }; + + private static readonly byte[][] Rgb4Bytes3X4 = { Rgb4Bytes3X4R, Rgb4Bytes3X4G, Rgb4Bytes3X4B }; + + private static readonly Rgba32[][] Rgb4Result3X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888 } + }; + + public static IEnumerable Rgb4Data + { + get + { + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; + } + } + + private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); + private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); + private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); + private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); + private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); + private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); + private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); + + private static readonly byte[] Rgb8Bytes4X4R = + { + 000, 255, 000, 255, + 255, 000, 000, 255, + 064, 128, 192, 064, + 000, 064, 128, 192 + }; + + private static readonly byte[] Rgb8Bytes4X4G = + { + 000, 255, 000, 255, + 000, 255, 000, 000, + 000, 000, 000, 128, + 000, 064, 128, 192 + }; + + private static readonly byte[] Rgb8Bytes4X4B = + { + 000, 255, 000, 255, + 000, 000, 255, 255, + 000, 000, 000, 192, + 000, 064, 128, 192 + }; + + private static readonly byte[][] Rgb8Bytes4X4 = + { + Rgb8Bytes4X4R, Rgb8Bytes4X4G, Rgb8Bytes4X4B + }; + + private static readonly Rgba32[][] Rgb8Result4X4 = + { + new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, + new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, + new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, + new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } + }; + + public static IEnumerable Rgb8Data + { + get + { + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; + } + } + + private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); + private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); + private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); + private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); + + private static readonly byte[] Rgb484Bytes4X4R = + { + 0x0F, 0x0F, + 0xF0, 0x0F, + 0x48, 0xC4, + 0x04, 0x8C + }; + + private static readonly byte[] Rgb484Bytes4X4G = + { + 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x40, 0x80, 0xC0 + }; + + private static readonly byte[] Rgb484Bytes4X4B = + { + 0x0F, 0x0F, + 0x00, 0xFF, + 0x00, 0x0C, + 0x04, 0x8C + }; + + private static readonly Rgba32[][] Rgb484Result4X4 = + { + new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, + new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, + new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, + new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } + }; + + private static readonly byte[][] Rgb484Bytes4X4 = { Rgb484Bytes4X4R, Rgb484Bytes4X4G, Rgb484Bytes4X4B }; + + public static IEnumerable Rgb484_Data + { + get + { + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(Rgb4Data))] + [MemberData(nameof(Rgb8Data))] + [MemberData(nameof(Rgb484_Data))] + public void Decode_WritesPixelData(byte[][] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + var buffers = new IManagedByteBuffer[inputData.Length]; + for (int i = 0; i < buffers.Length; i++) + { + buffers[i] = Configuration.Default.MemoryAllocator.AllocateManagedByteBuffer(inputData[i].Length); + ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); + } + + new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs new file mode 100644 index 000000000..4abde8f17 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public class RgbTiffColorTests : PhotometricInterpretationTestBase + { + private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); + private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); + private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); + private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); + + private static readonly byte[] Rgb4Bytes4X4 = + { + 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F, + 0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C, + 0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC + }; + + private static readonly Rgba32[][] Rgb4Result4X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } + }; + + private static readonly byte[] Rgb4Bytes3X4 = + { + 0x00, 0x0F, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, + 0x40, 0x08, 0x00, 0xC0, 0x00, + 0x00, 0x04, 0x44, 0x88, 0x80 + }; + + private static readonly Rgba32[][] Rgb4Result3X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888 } + }; + + public static IEnumerable Rgb4Data + { + get + { + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; + } + } + + private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); + private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); + private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); + private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); + private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); + private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); + private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); + + private static readonly byte[] Rgb8Bytes4X4 = + { + 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255, + 255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255, + 064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192, + 000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 + }; + + private static readonly Rgba32[][] Rgb8Result4X4 = + { + new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, + new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, + new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, + new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } + }; + + public static IEnumerable Rgb8Data + { + get + { + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; + } + } + + private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); + private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); + private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); + private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); + + private static readonly byte[] Rgb484Bytes4X4 = + { + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, + 0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C, + 0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C + }; + + private static readonly Rgba32[][] Rgb484Result4X4 = + { + new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, + new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, + new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, + new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } + }; + + public static IEnumerable Rgb484Data + { + get + { + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(Rgb4Data))] + [MemberData(nameof(Rgb8Data))] + [MemberData(nameof(Rgb484Data))] + public void Decode_WritesPixelData(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new RgbTiffColor(bitsPerSample).Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(Rgb8Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new Rgb888TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs new file mode 100644 index 000000000..620fddd7d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs @@ -0,0 +1,195 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +{ + [Trait("Format", "Tiff")] + public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase + { + private static readonly Rgba32 Gray000 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Gray128 = new Rgba32(127, 127, 127, 255); + private static readonly Rgba32 Gray255 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Gray0 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Gray8 = new Rgba32(119, 119, 119, 255); + private static readonly Rgba32 GrayF = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Bit0 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Bit1 = new Rgba32(0, 0, 0, 255); + + private static readonly byte[] BilevelBytes4X4 = + { + 0b01010000, + 0b11110000, + 0b01110000, + 0b10010000 + }; + + private static readonly Rgba32[][] BilevelResult4X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit1 }, + new[] { Bit1, Bit0, Bit0, Bit1 } + }; + + private static readonly byte[] BilevelBytes12X4 = + { + 0b01010101, 0b01010000, + 0b11111111, 0b11111111, + 0b01101001, 0b10100000, + 0b10010000, 0b01100000 + }; + + private static readonly Rgba32[][] BilevelResult12X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, + new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } + }; + + private static readonly byte[] Grayscale4Bytes4X4 = + { + 0x8F, 0x0F, + 0xFF, 0xFF, + 0x08, 0x8F, + 0xF0, 0xF8 + }; + + private static readonly Rgba32[][] Grayscale4Result4X4 = + { + new[] { Gray8, GrayF, Gray0, GrayF }, + new[] { GrayF, GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8, GrayF }, + new[] { GrayF, Gray0, GrayF, Gray8 } + }; + + private static readonly byte[] Grayscale4Bytes3X4 = + { + 0x8F, 0x00, + 0xFF, 0xF0, + 0x08, 0x80, + 0xF0, 0xF0 + }; + + private static readonly Rgba32[][] Grayscale4Result3X4 = + { + new[] { Gray8, GrayF, Gray0 }, + new[] { GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8 }, + new[] { GrayF, Gray0, GrayF } + }; + + private static readonly byte[] Grayscale8Bytes4X4 = + { + 128, 255, 000, 255, + 255, 255, 255, 255, + 000, 128, 128, 255, + 255, 000, 255, 128 + }; + + private static readonly Rgba32[][] Grayscale8Result4X4 = + { + new[] { Gray128, Gray255, Gray000, Gray255 }, + new[] { Gray255, Gray255, Gray255, Gray255 }, + new[] { Gray000, Gray128, Gray128, Gray255 }, + new[] { Gray255, Gray000, Gray255, Gray128 } + }; + + public static IEnumerable BilevelData + { + get + { + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; + + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; + } + } + + public static IEnumerable Grayscale4Data + { + get + { + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; + } + } + + public static IEnumerable Grayscale8Data + { + get + { + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(BilevelData))] + [MemberData(nameof(Grayscale4Data))] + [MemberData(nameof(Grayscale8Data))] + public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZeroTiffColor(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(BilevelData))] + public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(Grayscale4Data))] + public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + + [Theory] + [MemberData(nameof(Grayscale8Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs new file mode 100644 index 000000000..1a72046fb --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -0,0 +1,183 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +using System; +using System.IO; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; + +using static SixLabors.ImageSharp.Tests.TestImages.Tiff; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class TiffDecoderTests + { + public static readonly string[] MultiframeTestImages = Multiframes; + + public static readonly string[] NotSupportedImages = NotSupported; + + private static TiffDecoder TiffDecoder => new TiffDecoder(); + + private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + + [Theory] + [WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)] + public void ThrowsNotSupported(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + + [Theory] + [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] + [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] + public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo info = Image.Identify(stream); + + Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); + Assert.Equal(expectedWidth, info.Width); + Assert.Equal(expectedHeight, info.Height); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); + Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); + Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); + } + } + + [Theory] + [InlineData(RgbLzwNoPredictorMultistrip, ImageSharp.ByteOrder.LittleEndian)] + [InlineData(RgbLzwNoPredictorMultistripMotorola, ImageSharp.ByteOrder.BigEndian)] + public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo info = Image.Identify(stream); + + Assert.NotNull(info.Metadata); + Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); + + stream.Seek(0, SeekOrigin.Begin); + + using var img = Image.Load(stream); + Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); + } + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Uncompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(RgbPalette, PixelTypes.Rgba32)] + [WithFile(RgbPaletteDeflate, PixelTypes.Rgba32)] + [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_4Bit_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsWindows) + { + TestTiffDecoder(provider, new SystemDrawingReferenceDecoder(), useExactComparer: false, 0.01f); + } + } + + [Theory] + [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleDeflate, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleDeflate_Predictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbDeflate_Predictor, PixelTypes.Rgba32)] + [WithFile(RgbDeflate, PixelTypes.Rgba32)] + [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32)] + [WithFile(SmallRgbDeflate, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_DeflateCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(RgbLzwPredictor, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictorSinglestripMotorola, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictorMultistripMotorola, PixelTypes.Rgba32)] + [WithFile(RgbLzwMultistripPredictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbPaletteLzw_Predictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbLzwPredictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleLzw_Predictor, PixelTypes.Rgba32)] + [WithFile(SmallRgbLzw, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_LzwCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(HuffmanRleAllTermCodes, PixelTypes.Rgba32)] + [WithFile(HuffmanRleAllMakeupCodes, PixelTypes.Rgba32)] + [WithFile(HuffmanRle_basi3p02, PixelTypes.Rgba32)] + [WithFile(Calliphora_HuffmanCompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(CcittFax3AllTermCodes, PixelTypes.Rgba32)] + [WithFile(CcittFax3AllMakeupCodes, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax3Compressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax3Compressed_WithEolPadding, PixelTypes.Rgba32)] + [WithFile(Fax3Uncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] + [WithFile(RgbPackbits, PixelTypes.Rgba32)] + [WithFile(RgbPackbitsMultistrip, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_PackBitsCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] + public void DecodeMultiframe(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + Assert.True(image.Frames.Count > 1); + + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + + image.DebugSaveMultiFrame(provider); + image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); + } + + private static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + image.CompareToOriginal( + provider, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder ?? ReferenceDecoder); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs new file mode 100644 index 000000000..acbe2b489 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.Writers; +using SixLabors.ImageSharp.Memory; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class TiffEncoderHeaderTests + { + private static readonly MemoryAllocator MemoryAllocator = new ArrayPoolMemoryAllocator(); + private static readonly Configuration Configuration = Configuration.Default; + private static readonly ITiffEncoderOptions Options = new TiffEncoder(); + + [Fact] + public void WriteHeader_WritesValidHeader() + { + using var stream = new MemoryStream(); + var encoder = new TiffEncoderCore(Options, MemoryAllocator); + + using (var writer = new TiffStreamWriter(stream)) + { + long firstIfdMarker = encoder.WriteHeader(writer); + } + + Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); + } + + [Fact] + public void WriteHeader_ReturnsFirstIfdMarker() + { + using var stream = new MemoryStream(); + var encoder = new TiffEncoderCore(Options, MemoryAllocator); + + using (var writer = new TiffStreamWriter(stream)) + { + long firstIfdMarker = encoder.WriteHeader(writer); + Assert.Equal(4, firstIfdMarker); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs new file mode 100644 index 000000000..105514c98 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -0,0 +1,488 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; + +using static SixLabors.ImageSharp.Tests.TestImages.Tiff; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class TiffEncoderTests + { + private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); + + [Theory] + [InlineData(null, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)] + [InlineData(TiffPhotometricInterpretation.WhiteIsZero, TiffBitsPerPixel.Bit8)] + //// Unsupported TiffPhotometricInterpretation should default to 24 bits + [InlineData(TiffPhotometricInterpretation.CieLab, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.ColorFilterArray, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.ItuLab, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.LinearRaw, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.Separated, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.TransparencyMask, TiffBitsPerPixel.Bit24)] + public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel) + { + // arrange + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.None, frameMetaData.Compression); + } + + [Theory] + [InlineData(TiffBitsPerPixel.Bit24)] + [InlineData(TiffBitsPerPixel.Bit8)] + [InlineData(TiffBitsPerPixel.Bit4)] + [InlineData(TiffBitsPerPixel.Bit1)] + public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel) + { + // arrange + var tiffEncoder = new TiffEncoder { BitsPerPixel = bitsPerPixel }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(bitsPerPixel, frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.None, frameMetaData.Compression); + } + + [Theory] + [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] + public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression) + { + // arrange + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel); + Assert.Equal(expectedCompression, rootFrameMetaData.Compression); + } + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit1)] + [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit24)] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit4)] + [WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + public void TiffEncoder_PreservesBitsPerPixel(TestImageProvider provider, TiffBitsPerPixel expectedBitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + } + + [Fact] + public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8() + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + TiffBitsPerPixel expectedBitsPerPixel = TiffBitsPerPixel.Bit8; + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)] + [WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)] + public void TiffEncoder_PreservesCompression(TestImageProvider provider, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + Assert.Equal(expectedCompression, output.Frames.RootFrame.Metadata.GetTiffMetadata().Compression); + } + + [Theory] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, null)] + [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, null)] + [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + public void TiffEncoder_PreservesPredictor(TestImageProvider provider, TiffPredictor? expectedPredictor) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetadata = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedPredictor, frameMetadata.Predictor); + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] + public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffCompression compression, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new TiffEncoder() { Compression = compression, BitsPerPixel = TiffBitsPerPixel.Bit1 }; + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffBitsPerPixel.Bit1, frameMetaData.BitsPerPixel); + Assert.Equal(expectedCompression, frameMetaData.Compression); + } + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + //// Note: The magick reference decoder does not support 4 bit tiff's, so we use our TIFF decoder instead. + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f, imageDecoder: new TiffDecoder()); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithDeflateCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithDeflateCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup3Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Ccitt1D); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D); + + [Theory] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] + [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + public void TiffEncoder_StripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + TestStripLength(provider, photometricInterpretation, compression); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] + public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + //// CcittGroup3Fax compressed data length can be larger than the original length. + Assert.Throws(() => TestStripLength(provider, photometricInterpretation, compression)); + + private static void TestStripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata(); + TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None; + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; + TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + ImageFrame rootFrame = output.Frames.RootFrame; + + Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + Assert.True(output.Height > (int)rowsPerStrip); + Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1); + Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value; + Assert.NotNull(stripByteCounts); + Assert.True(stripByteCounts.Length > 1); + Assert.NotNull(outputMeta.BitsPerPixel); + + foreach (Number sz in stripByteCounts) + { + Assert.True((uint)sz <= TiffConstants.DefaultStripSize); + } + + // For uncompressed more accurate test. + if (compression == TiffCompression.None) + { + for (int i = 0; i < stripByteCounts.Length - 1; i++) + { + // The difference must be less than one row. + int stripBytes = (int)stripByteCounts[i]; + int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width; + + Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes); + } + } + + // Compare with reference. + TestTiffEncoderCore( + provider, + inputMeta.BitsPerPixel, + photometricInterpretation, + inputCompression); + } + + private static void TestTiffEncoderCore( + TestImageProvider provider, + TiffBitsPerPixel? bitsPerPixel, + TiffPhotometricInterpretation photometricInterpretation, + TiffCompression compression = TiffCompression.None, + TiffPredictor predictor = TiffPredictor.None, + bool useExactComparer = true, + float compareTolerance = 0.001f, + IImageDecoder imageDecoder = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + var encoder = new TiffEncoder + { + PhotometricInterpretation = photometricInterpretation, + BitsPerPixel = bitsPerPixel, + Compression = compression, + HorizontalPredictor = predictor + }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder: imageDecoder ?? ReferenceDecoder); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs new file mode 100644 index 000000000..4510bf912 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class TiffFormatTests + { + [Fact] + public void FormatProperties_AreAsExpected() + { + TiffFormat tiffFormat = TiffFormat.Instance; + + Assert.Equal("TIFF", tiffFormat.Name); + Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); + Assert.Contains("image/tiff", tiffFormat.MimeTypes); + Assert.Contains("tif", tiffFormat.FileExtensions); + Assert.Contains("tiff", tiffFormat.FileExtensions); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs new file mode 100644 index 000000000..3aded7b0e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -0,0 +1,297 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +using static SixLabors.ImageSharp.Tests.TestImages.Tiff; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class TiffMetadataTests + { + private static TiffDecoder TiffDecoder => new TiffDecoder(); + + [Fact] + public void TiffMetadata_CloneIsDeep() + { + var meta = new TiffMetadata + { + ByteOrder = ByteOrder.BigEndian, + }; + + var clone = (TiffMetadata)meta.DeepClone(); + + clone.ByteOrder = ByteOrder.LittleEndian; + + Assert.False(meta.ByteOrder == clone.ByteOrder); + } + + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); + var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); + VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); + + var clone = (TiffFrameMetadata)meta.DeepClone(); + + clone.BitsPerPixel = TiffBitsPerPixel.Bit8; + clone.Compression = TiffCompression.None; + clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; + clone.Predictor = TiffPredictor.Horizontal; + + Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); + Assert.False(meta.Compression == clone.Compression); + Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); + Assert.False(meta.Predictor == clone.Predictor); + } + } + + private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) + { + Assert.NotNull(frameMetaData); + Assert.NotNull(frameMetaData.BitsPerPixel); + Assert.Equal(TiffBitsPerSample.Bit4, (TiffBitsPerSample)frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); + Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); + } + + [Theory] + [InlineData(Calliphora_BiColorUncompressed, 1)] + [InlineData(GrayscaleUncompressed, 8)] + [InlineData(RgbUncompressed, 24)] + public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + IImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetadata); + Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); + } + + [Theory] + [InlineData(GrayscaleUncompressed, ByteOrder.BigEndian)] + [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] + public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + IImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetadata); + Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); + } + + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] + [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] + public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata })) + { + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; + Assert.NotNull(meta); + if (ignoreMetadata) + { + Assert.Null(rootFrameMetaData.XmpProfile); + Assert.Null(rootFrameMetaData.ExifProfile); + } + else + { + Assert.NotNull(rootFrameMetaData.XmpProfile); + Assert.NotNull(rootFrameMetaData.ExifProfile); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Length); + Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); + } + } + } + + [Theory] + [WithFile(InvalidIptcData, PixelTypes.Rgba32)] + public void CanDecodeImage_WithIptcDataAsLong(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + + IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; + Assert.NotNull(iptcProfile); + IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); + Assert.NotNull(byline); + Assert.Equal("Studio Mantyniemi", byline.Value); + } + + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void BaselineTags(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + ImageFrame rootFrame = image.Frames.RootFrame; + Assert.Equal(32, rootFrame.Width); + Assert.Equal(32, rootFrame.Height); + Assert.NotNull(rootFrame.Metadata.XmpProfile); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Length); + + ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; + TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); + Assert.NotNull(exifProfile); + + // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData + // and removed from the profile on decode. + Assert.Equal(26, exifProfile.Values.Count); + Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); + Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); + Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); + Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); + Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); + Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); + Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); + Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); + var expectedResolution = new Rational(10000, 1000, simplify: false); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); + Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); + Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); + Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); + Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); + Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); + Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); + ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + Assert.NotNull(colorMap); + Assert.Equal(48, colorMap.Length); + Assert.Equal(10537, colorMap[0]); + Assert.Equal(14392, colorMap[1]); + Assert.Equal(58596, colorMap[46]); + Assert.Equal(3855, colorMap[47]); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); + Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); + + ImageMetadata imageMetaData = image.Metadata; + Assert.NotNull(imageMetaData); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); + Assert.Equal(10, imageMetaData.HorizontalResolution); + Assert.Equal(10, imageMetaData.VerticalResolution); + + TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetaData); + Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); + } + } + + [Theory] + [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] + public void SubfileType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + Assert.NotNull(meta); + + Assert.Equal(2, image.Frames.Count); + + ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[0].Width); + Assert.Equal(255, image.Frames[0].Height); + + ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[1].Width); + Assert.Equal(255, image.Frames[1].Height); + } + } + + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void Encode_PreservesMetadata(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Load Tiff image + using Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = false }); + + ImageMetadata inputMetaData = image.Metadata; + ImageFrame rootFrameInput = image.Frames.RootFrame; + TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); + byte[] xmpProfileInput = rootFrameInput.Metadata.XmpProfile; + ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; + + Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); + Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); + + // Save to Tiff + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; + using var ms = new MemoryStream(); + image.Save(ms, tiffEncoder); + + // Assert + ms.Position = 0; + using var encodedImage = Image.Load(ms); + + ImageMetadata encodedImageMetaData = encodedImage.Metadata; + ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; + TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); + ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; + byte[] encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; + + Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); + + Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); + Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); + Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); + + Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); + Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); + + PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); + PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); + Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); + Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution), encodedImageExifProfile.GetValue(ExifTag.XResolution)); + Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution), encodedImageExifProfile.GetValue(ExifTag.YResolution)); + + Assert.Equal(xmpProfileInput, encodedImageXmpProfile); + + Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); + Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", exifProfileInput.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Авторские права", exifProfileInput.GetValue(ExifTag.Copyright).Value); + + Assert.Equal(exifProfileInput.Values.Count, encodedImageExifProfile.Values.Count); + Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs new file mode 100644 index 000000000..5ac2f475e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; + +using ImageMagick; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + public static class TiffTestUtils + { + public static void CompareWithReferenceDecoder( + string encodedImagePath, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + var testFile = TestFile.Create(encodedImagePath); + Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + if (useExactComparer) + { + ImageComparer.Exact.VerifySimilarity(magickImage, image); + } + else + { + ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); + } + } + + public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + using var magickImage = new MagickImage(fileInfo); + magickImage.AutoOrient(); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + + Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + + using IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe(); + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels, + resultPixels.Length); + + return result; + } + } + + internal class NumberComparer : IEqualityComparer + { + public bool Equals(Number x, Number y) => x.Equals(y); + + public int GetHashCode(Number obj) => obj.GetHashCode(); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs new file mode 100644 index 000000000..6eea0a1a9 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Writers; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils +{ + [Trait("Format", "Tiff")] + public class TiffWriterTests + { + [Fact] + public void IsLittleEndian_IsTrueOnWindows() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + Assert.True(writer.IsLittleEndian); + } + + [Theory] + [InlineData(new byte[] { }, 0)] + [InlineData(new byte[] { 42 }, 1)] + [InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] + public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(data); + Assert.Equal(writer.Position, expectedResult); + } + + [Fact] + public void Write_WritesByte() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(42); + + Assert.Equal(new byte[] { 42 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesByteArray() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(new byte[] { 2, 4, 6, 8 }); + + Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesUInt16() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(1234); + + Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); + } + + [Fact] + public void Write_WritesUInt32() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(12345678U); + + Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); + } + + [Theory] + [InlineData(new byte[] { }, new byte[] { })] + [InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] + [InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] + [InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] + [InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] + [InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12, 0, 0 })] + + public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.WritePadded(bytes); + + Assert.Equal(expectedResult, stream.ToArray()); + } + + [Fact] + public void WriteMarker_WritesToPlacedPosition() + { + using var stream = new MemoryStream(); + + using (var writer = new TiffStreamWriter(stream)) + { + writer.Write(0x11111111); + long marker = writer.PlaceMarker(); + writer.Write(0x33333333); + + writer.WriteMarker(marker, 0x12345678); + + writer.Write(0x44444444); + } + + Assert.Equal( + new byte[] + { + 0x11, 0x11, 0x11, 0x11, + 0x78, 0x56, 0x34, 0x12, + 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44 + }, stream.ToArray()); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index c889fa76b..dbc5af536 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws( () => { - this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + using var frame = new ImageFrame(Configuration.Default, 1, 1); + using ImageFrame addedFrame = this.Collection.AddFrame(frame); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - this.Collection.AddFrame((ImageFrame)null); + using ImageFrame addedFrame = this.Collection.AddFrame((ImageFrame)null); }); Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - this.Collection.AddFrame(data); + using ImageFrame addedFrame = this.Collection.AddFrame(data); }); Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws( () => { - this.Collection.AddFrame(new Rgba32[0]); + using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); }); Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); @@ -78,7 +79,8 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws( () => { - this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); + using var frame = new ImageFrame(Configuration.Default, 1, 1); + using ImageFrame insertedFrame = this.Collection.InsertFrame(1, frame); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - this.Collection.InsertFrame(1, null); + using ImageFrame insertedFrame = this.Collection.InsertFrame(1, null); }); Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); @@ -102,9 +104,11 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws( () => { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 1, 1); new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 1, 1) }); + new[] { imageFrame1, imageFrame2 }); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -113,24 +117,24 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void RemoveAtFrame_ThrowIfRemovingLastFrame() { + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame }); InvalidOperationException ex = Assert.Throws( - () => - { - collection.RemoveFrame(0); - }); + () => collection.RemoveFrame(0)); Assert.Equal("Cannot remove last frame.", ex.Message); } [Fact] public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame1, imageFrame2 }); collection.RemoveFrame(0); Assert.Equal(1, collection.Count); @@ -139,9 +143,11 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void RootFrameIsFrameAtIndexZero() { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame1, imageFrame2 }); Assert.Equal(collection.RootFrame, collection[0]); } @@ -149,9 +155,11 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConstructorPopulatesFrames() { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame1, imageFrame2 }); Assert.Equal(2, collection.Count); } @@ -159,9 +167,11 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DisposeClearsCollection() { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame1, imageFrame2 }); collection.Dispose(); @@ -171,9 +181,11 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Dispose_DisposesAllInnerFrames() { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); var collection = new ImageFrameCollection( this.Image, - new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); + new[] { imageFrame1, imageFrame2 }); IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); collection.Dispose(); @@ -194,7 +206,8 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); @@ -215,7 +228,8 @@ namespace SixLabors.ImageSharp.Tests Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); TPixel[] sourcePixelData = imgSpan.ToArray(); - img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); using (Image cloned = img.Frames.ExportFrame(0)) { Assert.Equal(1, img.Frames.Count); @@ -227,35 +241,37 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_Default() { - this.Image.Frames.CreateFrame(); - - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); + using (this.Image.Frames.CreateFrame()) + { + Assert.Equal(2, this.Image.Frames.Count); + this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); + } } [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Color.HotPink); - - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); + using (this.Image.Frames.CreateFrame(Color.HotPink)) + { + Assert.Equal(2, this.Image.Frames.Count); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); + } } [Fact] public void AddFrameFromPixelData() { Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imgSpan)); - var pixelData = imgSpan.ToArray(); - this.Image.Frames.AddFrame(pixelData); + Rgba32[] pixelData = imgSpan.ToArray(); + using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); Assert.Equal(2, this.Image.Frames.Count); } [Fact] public void AddFrame_clones_sourceFrame() { - var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); + using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); addedFrame.ComparePixelBufferTo(otherFrameSpan); @@ -265,8 +281,8 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void InsertFrame_clones_sourceFrame() { - var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); + using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); addedFrame.ComparePixelBufferTo(otherFrameSpan); @@ -276,51 +292,51 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void MoveFrame_LeavesFrameInCorrectLocation() { - for (var i = 0; i < 9; i++) + for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } - var frame = this.Image.Frames[4]; + ImageFrame frame = this.Image.Frames[4]; this.Image.Frames.MoveFrame(4, 7); - var newIndex = this.Image.Frames.IndexOf(frame); + int newIndex = this.Image.Frames.IndexOf(frame); Assert.Equal(7, newIndex); } [Fact] public void IndexOf_ReturnsCorrectIndex() { - for (var i = 0; i < 9; i++) + for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } - var frame = this.Image.Frames[4]; - var index = this.Image.Frames.IndexOf(frame); + ImageFrame frame = this.Image.Frames[4]; + int index = this.Image.Frames.IndexOf(frame); Assert.Equal(4, index); } [Fact] public void Contains_TrueIfMember() { - for (var i = 0; i < 9; i++) + for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } - var frame = this.Image.Frames[4]; + ImageFrame frame = this.Image.Frames[4]; Assert.True(this.Image.Frames.Contains(frame)); } [Fact] public void Contains_FalseIfNonMember() { - for (var i = 0; i < 9; i++) + for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } - var frame = new ImageFrame(Configuration.Default, 10, 10); + using var frame = new ImageFrame(Configuration.Default, 10, 10); Assert.False(this.Image.Frames.Contains(frame)); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 3fbe1f70d..271aa30cf 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests private static readonly Size ExpectedImageSize = new Size(108, 202); - private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromBytes_GlobalConfiguration() { - IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); + IImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type); Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedGlobalFormat, type); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_GlobalConfiguration() { - using (var stream = new MemoryStream(this.ActualImageBytes)) + using (var stream = new MemoryStream(ActualImageBytes)) { IImageInfo info = Image.Identify(stream, out IImageFormat type); @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_GlobalConfiguration_NoFormat() { - using (var stream = new MemoryStream(this.ActualImageBytes)) + using (var stream = new MemoryStream(ActualImageBytes)) { IImageInfo info = Image.Identify(stream); @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromNonSeekableStream_GlobalConfiguration() { - using var stream = new MemoryStream(this.ActualImageBytes); + using var stream = new MemoryStream(ActualImageBytes); using var nonSeekableStream = new NonSeekableStream(stream); IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type); @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromNonSeekableStream_GlobalConfiguration_NoFormat() { - using var stream = new MemoryStream(this.ActualImageBytes); + using var stream = new MemoryStream(ActualImageBytes); using var nonSeekableStream = new NonSeekableStream(stream); IImageInfo info = Image.Identify(nonSeekableStream); @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromStreamAsync_GlobalConfiguration_NoFormat() { - using (var stream = new MemoryStream(this.ActualImageBytes)) + using (var stream = new MemoryStream(ActualImageBytes)) { var asyncStream = new AsyncStreamWrapper(stream, () => false); IImageInfo info = await Image.IdentifyAsync(asyncStream); @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromStreamAsync_GlobalConfiguration() { - using (var stream = new MemoryStream(this.ActualImageBytes)) + using (var stream = new MemoryStream(ActualImageBytes)) { var asyncStream = new AsyncStreamWrapper(stream, () => false); (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); @@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat() { - using var stream = new MemoryStream(this.ActualImageBytes); + using var stream = new MemoryStream(ActualImageBytes); using var nonSeekableStream = new NonSeekableStream(stream); var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromNonSeekableStreamAsync_GlobalConfiguration() { - using var stream = new MemoryStream(this.ActualImageBytes); + using var stream = new MemoryStream(ActualImageBytes); using var nonSeekableStream = new NonSeekableStream(stream); var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index ee46807e5..fe3df1721 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -3,9 +3,8 @@ using System; using System.IO; - using Moq; - +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,8 +12,6 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using SixLabors.ImageSharp.Formats; - public partial class ImageTests { public class Save @@ -23,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests public void DetectedEncoding() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "DetectedEncoding.png"); + string file = Path.Combine(dir, "DetectedEncoding.png"); using (var image = new Image(10, 10)) { @@ -40,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests public void WhenExtensionIsUnknown_Throws() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); Assert.Throws( () => @@ -56,14 +53,14 @@ namespace SixLabors.ImageSharp.Tests public void SetEncoding() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "SetEncoding.dat"); + string file = Path.Combine(dir, "SetEncoding.dat"); using (var image = new Image(10, 10)) { image.Save(file, new PngEncoder()); } - using (Image.Load(file, out var mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/png", mime.DefaultMimeType); } @@ -72,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ThrowsWhenDisposed() { - var image = new Image(5, 5); + using var image = new Image(5, 5); image.Dispose(); IImageEncoder encoder = Mock.Of(); using (var stream = new MemoryStream()) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 4e6b002d0..8bb121349 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -4,20 +4,18 @@ using System; using System.IO; using System.Threading; +using System.Threading.Tasks; using Moq; -using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.Threading.Tasks; - using SixLabors.ImageSharp.Advanced; - using SixLabors.ImageSharp.Formats; - using SixLabors.ImageSharp.Tests.TestUtilities; - public partial class ImageTests { public class SaveAsync @@ -43,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests public async Task WhenExtensionIsUnknown_Throws() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); await Assert.ThrowsAsync( async () => @@ -59,14 +57,14 @@ namespace SixLabors.ImageSharp.Tests public async Task SetEncoding() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "SetEncoding.dat"); + string file = Path.Combine(dir, "SetEncoding.dat"); using (var image = new Image(10, 10)) { await image.SaveAsync(file, new PngEncoder()); } - using (Image.Load(file, out var mime)) + using (Image.Load(file, out IImageFormat mime)) { Assert.Equal("image/png", mime.DefaultMimeType); } @@ -142,10 +140,15 @@ namespace SixLabors.ImageSharp.Tests using var stream = new MemoryStream(); var asyncStream = new AsyncStreamWrapper(stream, () => false); var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromTicks(1)); - await Assert.ThrowsAnyAsync(() => - image.SaveAsync(asyncStream, encoder, cts.Token)); + var pausedStream = new PausedStream(asyncStream); + pausedStream.OnWaiting(s => + { + cts.Cancel(); + pausedStream.Release(); + }); + + await Assert.ThrowsAsync(async () => await image.SaveAsync(pausedStream, encoder, cts.Token)); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 1124b6439..1cadf1653 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -60,24 +60,24 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static readonly TheoryData LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + public static readonly TheoryData LengthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void HasCorrectLength_byte(int desiredLength) { this.TestHasCorrectLength(desiredLength); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void HasCorrectLength_float(int desiredLength) { this.TestHasCorrectLength(desiredLength); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void HasCorrectLength_CustomStruct(int desiredLength) { this.TestHasCorrectLength(desiredLength); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void CanAllocateCleanBuffer_byte(int desiredLength) { this.TestCanAllocateCleanBuffer(desiredLength, false); @@ -101,14 +101,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void CanAllocateCleanBuffer_double(int desiredLength) { this.TestCanAllocateCleanBuffer(desiredLength); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) { this.TestCanAllocateCleanBuffer(desiredLength); @@ -145,14 +145,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) { this.TestSpanPropertyIsAlwaysTheSame(desiredLength); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) { this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); @@ -174,18 +174,18 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void WriteAndReadElements_float(int desiredLength) { - this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void WriteAndReadElements_byte(int desiredLength) { - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); } private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) { - T[] expectedVals = new T[buffer.Length()]; + var expectedVals = new T[buffer.Length()]; for (int i = 0; i < buffer.Length(); i++) { @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) { this.TestIndexOutOfRangeShouldThrow(desiredLength, false); @@ -219,14 +219,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) { this.TestIndexOutOfRangeShouldThrow(desiredLength); } [Theory] - [MemberData(nameof(LenthValues))] + [MemberData(nameof(LengthValues))] public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) { this.TestIndexOutOfRangeShouldThrow(desiredLength); @@ -316,4 +316,4 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 3f8904904..f1a90d43e 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -1,11 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Linq; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; +using ExifProfile = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifProfile; +using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Metadata { /// /// Tests the class. @@ -36,9 +40,43 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneIsDeep() { - var metaData = new ImageFrameMetadata(); + // arrange + byte[] xmpProfile = { 1, 2, 3 }; + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.Software, "UnitTest"); + exifProfile.SetValue(ExifTag.Artist, "UnitTest"); + var iccProfile = new IccProfile() + { + Header = new IccProfileHeader() + { + CmmType = "Unittest" + } + }; + var iptcProfile = new ImageSharp.Metadata.Profiles.Iptc.IptcProfile(); + var metaData = new ImageFrameMetadata() + { + XmpProfile = xmpProfile, + ExifProfile = exifProfile, + IccProfile = iccProfile, + IptcProfile = iptcProfile + }; + + // act ImageFrameMetadata clone = metaData.DeepClone(); + + // assert + Assert.NotNull(clone); + Assert.NotNull(clone.ExifProfile); + Assert.NotNull(clone.XmpProfile); + Assert.NotNull(clone.IccProfile); + Assert.NotNull(clone.IptcProfile); + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); + Assert.False(metaData.XmpProfile.Equals(clone.XmpProfile)); + Assert.True(metaData.XmpProfile.SequenceEqual(clone.XmpProfile)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); + Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); + Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 466568bfe..1f23838ab 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -12,8 +12,9 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif { + [Trait("Profile", "Exif")] public class ExifProfileTests { public enum TestImageWriteFormat @@ -62,13 +63,14 @@ namespace SixLabors.ImageSharp.Tests Assert.NotNull(value); Assert.Equal(expected, value.Value); + image.Dispose(); } [Fact] public void ConstructorEmpty() { - new ExifProfile((byte[])null); - new ExifProfile(new byte[] { }); + new ExifProfile(null); + new ExifProfile(Array.Empty()); } [Fact] @@ -156,12 +158,19 @@ namespace SixLabors.ImageSharp.Tests IExifValue value2 = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value2); Assert.Equal(new Rational(double.PositiveInfinity), value2.Value); + + image.Dispose(); } [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - public void SetValue(TestImageWriteFormat imageFormat) + /* The original exif profile has 19 values, the written profile should be 3 less. + 1 x due to setting of null "ReferenceBlackWhite" value. + 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere + strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) + https://exiftool.org/TagNames/EXIF.html */ + [InlineData(TestImageWriteFormat.Jpeg, 16)] + [InlineData(TestImageWriteFormat.Png, 16)] + public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); @@ -201,17 +210,15 @@ namespace SixLabors.ImageSharp.Tests IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); Assert.Equal(expectedLatitude, latitude.Value); - int profileCount = image.Metadata.ExifProfile.Values.Count; + // todo: duplicate tags + Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); + image = WriteAndRead(image, imageFormat); Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); - // Should be 3 less. - // 1 x due to setting of null "ReferenceBlackWhite" value. - // 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere - // strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) - // https://exiftool.org/TagNames/EXIF.html - Assert.Equal(profileCount - 3, image.Metadata.ExifProfile.Values.Count); + Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count); software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); Assert.Equal("15", software.Value); @@ -228,19 +235,45 @@ namespace SixLabors.ImageSharp.Tests latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); Assert.Equal(expectedLatitude, latitude.Value); + image.Dispose(); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat) + { + // Arrange + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; + // Act image = WriteAndRead(image, imageFormat); + // Assert Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(8, image.Metadata.ExifProfile.Values.Count); + Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); + foreach (IExifValue exifProfileValue in image.Metadata.ExifProfile.Values) + { + Assert.True(ExifTags.GetPart(exifProfileValue.Tag) == ExifParts.ExifTags); + } + + image.Dispose(); + } + + [Fact] + public void RemoveEntry_Works() + { + // Arrange + using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + int profileCount = image.Metadata.ExifProfile.Values.Count; + // Assert Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - - Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); + Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count); } [Fact] @@ -285,7 +318,7 @@ namespace SixLabors.ImageSharp.Tests TestProfile(profile); - Image thumbnail = profile.CreateThumbnail(); + using Image thumbnail = profile.CreateThumbnail(); Assert.NotNull(thumbnail); Assert.Equal(256, thumbnail.Width); Assert.Equal(170, thumbnail.Height); @@ -301,7 +334,7 @@ namespace SixLabors.ImageSharp.Tests var junk = new StringBuilder(); for (int i = 0; i < 65600; i++) { - junk.Append("a"); + junk.Append('a'); } var image = new Image(100, 100); @@ -311,7 +344,7 @@ namespace SixLabors.ImageSharp.Tests image.Metadata.ExifProfile = expectedProfile; // Act - Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); + using Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); // Assert ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; @@ -335,7 +368,7 @@ namespace SixLabors.ImageSharp.Tests { // This image contains an 802 byte EXIF profile // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) - Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); + using Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); Assert.NotNull(image); ExifProfile profile = image.Metadata.ExifProfile; @@ -355,7 +388,7 @@ namespace SixLabors.ImageSharp.Tests public void TestArrayValueWithUnspecifiedSize() { // This images contains array in the exif profile that has zero components. - Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); + using Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); ExifProfile profile = image.Metadata.ExifProfile; Assert.NotNull(profile); @@ -363,8 +396,14 @@ namespace SixLabors.ImageSharp.Tests // Force parsing of the profile. Assert.Equal(25, profile.Values.Count); + // todo: duplicate tags (from root container and subIfd) + Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); + byte[] bytes = profile.ToByteArray(); Assert.Equal(525, bytes.Length); + + var profile2 = new ExifProfile(bytes); + Assert.Equal(25, profile2.Values.Count); } [Theory] @@ -377,7 +416,7 @@ namespace SixLabors.ImageSharp.Tests image.Metadata.ExifProfile = CreateExifProfile(); // Act - Image reloadedImage = WriteAndRead(image, imageFormat); + using Image reloadedImage = WriteAndRead(image, imageFormat); // Assert ExifProfile actual = reloadedImage.Metadata.ExifProfile; @@ -428,7 +467,7 @@ namespace SixLabors.ImageSharp.Tests internal static ExifProfile GetExifProfile() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); ExifProfile profile = image.Metadata.ExifProfile; Assert.NotNull(profile); @@ -477,6 +516,9 @@ namespace SixLabors.ImageSharp.Tests { Assert.NotNull(profile); + // todo: duplicate tags + Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932)); + Assert.Equal(16, profile.Values.Count); foreach (IExifValue value in profile.Values) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs index 401546e5c..7cd7da44e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs @@ -6,8 +6,9 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif { + [Trait("Profile", "Exif")] public class ExifReaderTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index 2b00cc5b4..2fec828ad 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -4,8 +4,9 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif { + [Trait("Profile", "Exif")] public class ExifTagDescriptionAttributeTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs index 5fe1b51ba..0a816bb21 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs @@ -5,11 +5,12 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif { + [Trait("Profile", "Exif")] public class ExifValueTests { - private ExifProfile profile; + private readonly ExifProfile profile; public ExifValueTests() { diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index 898c69356..3358b1f97 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -6,6 +6,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { + [Trait("Profile", "Exif")] public class ExifValuesTests { public static TheoryData ByteTags => new TheoryData @@ -94,6 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values public static TheoryData NumberArrayTags => new TheoryData { { ExifTag.StripOffsets }, + { ExifTag.StripByteCounts }, { ExifTag.TileByteCounts }, { ExifTag.ImageLayer } }; @@ -160,6 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.Orientation }, { ExifTag.SamplesPerPixel }, { ExifTag.PlanarConfiguration }, + { ExifTag.Predictor }, { ExifTag.GrayResponseUnit }, { ExifTag.ResolutionUnit }, { ExifTag.CleanFaxData }, @@ -208,7 +211,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.ExtraSamples }, { ExifTag.PageNumber }, { ExifTag.TransferFunction }, - { ExifTag.Predictor }, { ExifTag.HalftoneHints }, { ExifTag.SampleFormat }, { ExifTag.TransferRange }, diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs index 5451cbf37..dff370124 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderCurvesTests { [Theory] [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccResponseCurve output = reader.ReadResponseCurve(channelCount); @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccParametricCurve output = reader.ReadParametricCurve(); @@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccCurveSegment output = reader.ReadCurveSegment(); @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); @@ -67,14 +68,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccSampledCurveElement output = reader.ReadSampledCurveElement(); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs index aa24c2673..411738158 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderLutTests { [Theory] [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); @@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void ReadLut8(byte[] data, IccLut expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLut output = reader.ReadLut8(); @@ -67,14 +68,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void ReadLut16(byte[] data, IccLut expected, int count) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLut output = reader.ReadLut16(count); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs index fe31d74ac..49e0ea262 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderMatrixTests { [Theory] [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); @@ -23,14 +24,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float[] output = reader.ReadMatrix(yCount, isSingle); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs index 3fbef46de..5673ba75b 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderMultiProcessElementTests { [Theory] [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccMultiProcessElement output = reader.ReadMultiProcessElement(); @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); @@ -45,14 +46,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs index cf4cf80d1..7d1d07743 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs @@ -6,15 +6,16 @@ using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderNonPrimitivesTests { [Theory] [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadDateTime(byte[] data, DateTime expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); DateTime output = reader.ReadDateTime(); @@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadVersionNumber(byte[] data, IccVersion expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccVersion output = reader.ReadVersionNumber(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadXyzNumber(byte[] data, Vector3 expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); Vector3 output = reader.ReadXyzNumber(); @@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadProfileId(byte[] data, IccProfileId expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccProfileId output = reader.ReadProfileId(); @@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccPositionNumber output = reader.ReadPositionNumber(); @@ -69,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccResponseNumber output = reader.ReadResponseNumber(); @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccNamedColor output = reader.ReadNamedColor(coordinateCount); @@ -91,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccProfileDescription output = reader.ReadProfileDescription(); @@ -102,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccColorantTableEntry output = reader.ReadColorantTableEntry(); @@ -113,14 +114,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccScreeningChannel output = reader.ReadScreeningChannel(); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs index 2b2b564a7..feff5e496 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs @@ -5,15 +5,16 @@ using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderPrimitivesTests { [Theory] [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadAsciiString(byte[] textBytes, int length, string expected) { - IccDataReader reader = this.CreateReader(textBytes); + IccDataReader reader = CreateReader(textBytes); string output = reader.ReadAsciiString(length); @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() { - IccDataReader reader = this.CreateReader(new byte[4]); + IccDataReader reader = CreateReader(new byte[4]); Assert.Throws(() => reader.ReadAsciiString(-1)); } @@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() { - IccDataReader reader = this.CreateReader(new byte[4]); + IccDataReader reader = CreateReader(new byte[4]); Assert.Throws(() => reader.ReadUnicodeString(-1)); } @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadFix16(byte[] data, float expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float output = reader.ReadFix16(); @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadUFix16(byte[] data, float expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float output = reader.ReadUFix16(); @@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadU1Fix15(byte[] data, float expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float output = reader.ReadU1Fix15(); @@ -73,14 +74,14 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadUFix8(byte[] data, float expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); float output = reader.ReadUFix8(); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs index ea77004ed..45ad6ce49 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs @@ -4,8 +4,9 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderTagDataEntryTests { [Theory] @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); @@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); @@ -79,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); @@ -92,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); @@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); @@ -131,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); @@ -144,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); @@ -157,7 +158,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); @@ -170,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); @@ -183,7 +184,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); @@ -196,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); @@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); @@ -222,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); @@ -237,7 +238,7 @@ namespace SixLabors.ImageSharp.Tests.Icc byte[] data, IccProfileSequenceIdentifierTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); @@ -250,7 +251,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); @@ -263,7 +264,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); @@ -276,7 +277,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); @@ -289,7 +290,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); @@ -302,7 +303,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); @@ -315,7 +316,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); @@ -328,7 +329,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); @@ -341,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); @@ -354,7 +355,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); @@ -367,7 +368,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); @@ -380,7 +381,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); @@ -393,7 +394,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); @@ -406,7 +407,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); @@ -419,7 +420,7 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); @@ -432,14 +433,14 @@ namespace SixLabors.ImageSharp.Tests.Icc MemberType = typeof(IccTestDataTagDataEntry))] internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) { - IccDataReader reader = this.CreateReader(data); + IccDataReader reader = CreateReader(data); IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); Assert.Equal(expected, output); } - private IccDataReader CreateReader(byte[] data) + private static IccDataReader CreateReader(byte[] data) { return new IccDataReader(data); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs index 7c5070af1..6e2bd05ee 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -1,12 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader { + [Trait("Profile", "Icc")] public class IccDataReaderTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs index 593eed97c..3bb2ebc41 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterCurvesTests { [Theory] [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteOneDimensionalCurve(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteResponseCurve(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteParametricCurve(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteCurveSegment(data); byte[] output = writer.GetData(); @@ -60,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteFormulaCurveElement(data); byte[] output = writer.GetData(); @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteSampledCurveElement(data); byte[] output = writer.GetData(); @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs index e48d89ddb..23ea921ae 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterLutTests { [Theory] [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut8(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut16(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClutF32(data); byte[] output = writer.GetData(); @@ -60,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut8(byte[] expected, IccLut data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut8(data); byte[] output = writer.GetData(); @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut16(byte[] expected, IccLut data, int count) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut16(data); byte[] output = writer.GetData(); @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs index 711e3426d..463804671 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterLutTests1 { [Theory] [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut8(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut16(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClutF32(data); byte[] output = writer.GetData(); @@ -60,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut8(byte[] expected, IccLut data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut8(data); byte[] output = writer.GetData(); @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut16(byte[] expected, IccLut data, int count) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut16(data); byte[] output = writer.GetData(); @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs index ecfbad395..b81dba24d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterLutTests2 { [Theory] [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut8(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClut16(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClutF32(data); byte[] output = writer.GetData(); @@ -60,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut8(byte[] expected, IccLut data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut8(data); byte[] output = writer.GetData(); @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut16(byte[] expected, IccLut data, int count) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut16(data); byte[] output = writer.GetData(); @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs index 4346265c7..30e8da2da 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs @@ -6,17 +6,16 @@ using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { - using SixLabors.ImageSharp; - + [Trait("Profile", "Icc")] public class IccDataWriterMatrixTests { [Theory] [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -28,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -52,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -64,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -72,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs index bf8b7d069..78826bb4d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterMultiProcessElementTests { [Theory] [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMultiProcessElement(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteCurveSetProcessElement(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMatrixProcessElement(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteClutProcessElement(data); byte[] output = writer.GetData(); @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs index a918adc3f..aa51b149d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs @@ -6,15 +6,16 @@ using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterNonPrimitivesTests { [Theory] [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteDateTime(byte[] expected, DateTime data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteDateTime(data); byte[] output = writer.GetData(); @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteVersionNumber(byte[] expected, IccVersion data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteVersionNumber(data); byte[] output = writer.GetData(); @@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteXyzNumber(byte[] expected, Vector3 data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteXyzNumber(data); byte[] output = writer.GetData(); @@ -50,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteProfileId(byte[] expected, IccProfileId data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteProfileId(data); byte[] output = writer.GetData(); @@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WritePositionNumber(byte[] expected, IccPositionNumber data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WritePositionNumber(data); byte[] output = writer.GetData(); @@ -74,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteResponseNumber(data); byte[] output = writer.GetData(); @@ -86,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteNamedColor(data); byte[] output = writer.GetData(); @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteProfileDescription(data); byte[] output = writer.GetData(); @@ -110,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteScreeningChannel(data); byte[] output = writer.GetData(); @@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs index 1dc37a195..9946d55f9 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -5,15 +5,16 @@ using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterPrimitivesTests { [Theory] [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteAsciiString(byte[] expected, string data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteAsciiString(data); byte[] output = writer.GetData(); @@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteAsciiString(data, length, ensureNullTerminator); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteAsciiStringWithNullWritesEmpty() { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); int count = writer.WriteAsciiString(null); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); } @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteUnicodeStringWithNullWritesEmpty() { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); int count = writer.WriteUnicodeString(null); byte[] output = writer.GetData(); @@ -69,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteFix16(byte[] expected, float data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteFix16(data); byte[] output = writer.GetData(); @@ -81,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteUFix16(byte[] expected, float data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUFix16(data); byte[] output = writer.GetData(); @@ -93,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteU1Fix15(byte[] expected, float data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteU1Fix15(data); byte[] output = writer.GetData(); @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteUFix8(byte[] expected, float data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUFix8(data); byte[] output = writer.GetData(); @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs index 6325f26ce..7fd79994a 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs @@ -4,15 +4,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterTagDataEntryTests { [Theory] [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUnknownTagDataEntry(data); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteChromaticityTagDataEntry(data); byte[] output = writer.GetData(); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteColorantOrderTagDataEntry(data); byte[] output = writer.GetData(); @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteColorantTableTagDataEntry(data); byte[] output = writer.GetData(); @@ -60,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteCurveTagDataEntry(data); byte[] output = writer.GetData(); @@ -72,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteDataTagDataEntry(data); byte[] output = writer.GetData(); @@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteDateTimeTagDataEntry(data); byte[] output = writer.GetData(); @@ -96,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut16TagDataEntry(data); byte[] output = writer.GetData(); @@ -108,7 +109,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLut8TagDataEntry(data); byte[] output = writer.GetData(); @@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLutAtoBTagDataEntry(data); byte[] output = writer.GetData(); @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteLutBtoATagDataEntry(data); byte[] output = writer.GetData(); @@ -144,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMeasurementTagDataEntry(data); byte[] output = writer.GetData(); @@ -156,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMultiLocalizedUnicodeTagDataEntry(data); byte[] output = writer.GetData(); @@ -168,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteMultiProcessElementsTagDataEntry(data); byte[] output = writer.GetData(); @@ -180,7 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteNamedColor2TagDataEntry(data); byte[] output = writer.GetData(); @@ -192,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteParametricCurveTagDataEntry(data); byte[] output = writer.GetData(); @@ -204,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteProfileSequenceDescTagDataEntry(data); byte[] output = writer.GetData(); @@ -216,7 +217,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteProfileSequenceIdentifierTagDataEntry(data); byte[] output = writer.GetData(); @@ -228,7 +229,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteResponseCurveSet16TagDataEntry(data); byte[] output = writer.GetData(); @@ -240,7 +241,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteFix16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -252,7 +253,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteSignatureTagDataEntry(data); byte[] output = writer.GetData(); @@ -264,7 +265,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteTextTagDataEntry(data); byte[] output = writer.GetData(); @@ -276,7 +277,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUFix16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -288,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUInt16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -300,7 +301,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUInt32ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -312,7 +313,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUInt64ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -324,7 +325,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUInt8ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -336,7 +337,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteViewingConditionsTagDataEntry(data); byte[] output = writer.GetData(); @@ -348,7 +349,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteXyzTagDataEntry(data); byte[] output = writer.GetData(); @@ -360,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteTextDescriptionTagDataEntry(data); byte[] output = writer.GetData(); @@ -372,7 +373,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteCrdInfoTagDataEntry(data); byte[] output = writer.GetData(); @@ -384,7 +385,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteScreeningTagDataEntry(data); byte[] output = writer.GetData(); @@ -396,7 +397,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteUcrBgTagDataEntry(data); byte[] output = writer.GetData(); @@ -404,7 +405,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs index 9fa3e644c..325eac146 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -4,14 +4,15 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter { + [Trait("Profile", "Icc")] public class IccDataWriterTests { [Fact] public void WriteEmpty() { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteEmpty(4); byte[] output = writer.GetData(); @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [InlineData(4, 4)] public void WritePadding(int writePosition, int expectedLength) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteEmpty(writePosition); writer.WritePadding(); @@ -37,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt8(byte[] data, byte[] expected) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt16(byte[] expected, ushort[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -61,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayInt16(byte[] expected, short[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt32(byte[] expected, uint[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -85,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayInt32(byte[] expected, int[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt64(byte[] expected, ulong[] data) { - IccDataWriter writer = this.CreateWriter(); + using IccDataWriter writer = CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(expected, output); } - private IccDataWriter CreateWriter() + private static IccDataWriter CreateWriter() { return new IccDataWriter(); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs index a40082f78..ad2619f03 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs @@ -5,8 +5,9 @@ using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc { + [Trait("Profile", "Icc")] public class IccProfileTests { [Theory] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs index e9d960ebb..c2b57d0ba 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs @@ -4,8 +4,9 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc { + [Trait("Profile", "Icc")] public class IccReaderTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs index 0d4495912..bd9f55d3e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs @@ -4,8 +4,9 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc { + [Trait("Profile", "Icc")] public class IccWriterTests { [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs index b06a52964..aa24d191b 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs @@ -1,11 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Xunit; -namespace SixLabors.ImageSharp.Tests.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various { + [Trait("Profile", "Icc")] public class IccProfileIdTests { [Fact] @@ -29,4 +30,4 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(4u, id.Part4); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 9e763536b..3c60f4526 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -16,6 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + public static IEnumerable AllIptcTags() { foreach (object tag in Enum.GetValues(typeof(IptcTag))) @@ -31,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC // arrange var profile = new IptcProfile(); var value = new string('s', tag.MaxLength() + 1); - var expectedLength = tag.MaxLength(); + int expectedLength = tag.MaxLength(); // act profile.SetValue(tag, value); @@ -48,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC // arrange var profile = new IptcProfile(); var value = new string('s', tag.MaxLength() + 1); - var expectedLength = value.Length; + int expectedLength = value.Length; // act profile.SetValue(tag, value, false); @@ -100,34 +103,53 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC [Theory] [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] - public void ReadIptcMetadata_Works(TestImageProvider provider) + public void ReadIptcMetadata_FromJpg_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) { Assert.NotNull(image.Metadata.IptcProfile); var iptcValues = image.Metadata.IptcProfile.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); - ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); - ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); - ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); - ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); - ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); - ContainsIptcValue(iptcValues, IptcTag.Source, "source"); - ContainsIptcValue(iptcValues, IptcTag.Name, "title"); - ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); - ContainsIptcValue(iptcValues, IptcTag.City, "city"); - ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); - ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); - ContainsIptcValue(iptcValues, IptcTag.Country, "country"); - ContainsIptcValue(iptcValues, IptcTag.Category, "category"); - ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); - ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); - ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + IptcProfileContainsExpectedValues(iptcValues); } } + [Theory] + [WithFile(TestImages.Tiff.IptcData, PixelTypes.Rgba32)] + public void ReadIptcMetadata_FromTiff_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + IptcProfile iptc = image.Frames.RootFrame.Metadata.IptcProfile; + Assert.NotNull(iptc); + var iptcValues = iptc.Values.ToList(); + IptcProfileContainsExpectedValues(iptcValues); + } + } + + private static void IptcProfileContainsExpectedValues(List iptcValues) + { + ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); + ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); + ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); + ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); + ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); + ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); + ContainsIptcValue(iptcValues, IptcTag.Source, "source"); + ContainsIptcValue(iptcValues, IptcTag.Name, "title"); + ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); + ContainsIptcValue(iptcValues, IptcTag.City, "city"); + ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); + ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); + ContainsIptcValue(iptcValues, IptcTag.Country, "country"); + ContainsIptcValue(iptcValues, IptcTag.Category, "category"); + ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); + ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); + ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) @@ -206,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); // act - Image reloadedImage = WriteAndReadJpeg(image); + using Image reloadedImage = WriteAndReadJpeg(image); // assert IptcProfile actual = reloadedImage.Metadata.IptcProfile; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index cc7f32bef..a2688359f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void PixelTypeInfoHasCorrectBitsPerPixel() { - var bits = this.Operations.GetPixelTypeInfo().BitsPerPixel; + int bits = this.Operations.GetPixelTypeInfo().BitsPerPixel; Assert.Equal(Unsafe.SizeOf() * 8, bits); } diff --git a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs index ae9befba0..d144c876f 100644 --- a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.ComponentModel.DataAnnotations; +using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -10,7 +10,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing { - public abstract class BaseImageOperationsExtensionTest + public abstract class BaseImageOperationsExtensionTest : IDisposable { protected readonly IImageProcessingContext operations; private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; @@ -59,5 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Processing return Assert.IsType(operation.GenericProcessor); } + + public void Dispose() => this.source?.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs index f4f800107..e6a34d1f0 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -9,13 +9,14 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization { + [Trait("Category", "Processors")] public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest { [Fact] public void AdaptiveThreshold_UsesDefaults_Works() { // arrange - var expectedThresholdLimit = .85f; + float expectedThresholdLimit = .85f; Color expectedUpper = Color.White; Color expectedLower = Color.Black; @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void AdaptiveThreshold_SettingThresholdLimit_Works() { // arrange - var expectedThresholdLimit = .65f; + float expectedThresholdLimit = .65f; // act this.operations.AdaptiveThreshold(expectedThresholdLimit); @@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() { // arrange - var expectedThresholdLimit = .77f; + float expectedThresholdLimit = .77f; Color expectedUpper = Color.HotPink; Color expectedLower = Color.Yellow; @@ -83,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() { // arrange - var expectedThresholdLimit = .77f; + float expectedThresholdLimit = .77f; Color expectedUpper = Color.HotPink; Color expectedLower = Color.Yellow; diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index a2fb9f9ba..e241f729a 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization { + [Trait("Category", "Processors")] public class BinaryThresholdTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs index 0bbb962fc..191b195b4 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization { + [Trait("Category", "Processors")] public class OrderedDitherFactoryTests { #pragma warning disable SA1025 // Code should not contain multiple whitespace in a row diff --git a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs index eb176f5f0..65d956424 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution { + [Trait("Category", "Processors")] public class BoxBlurTest : BaseImageOperationsExtensionTest { [Fact] @@ -36,4 +37,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution Assert.Equal(5, processor.Radius); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 3ffb8f4e3..56a73b3ff 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution { + [Trait("Category", "Processors")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] public class DetectEdgesTest : BaseImageOperationsExtensionTest { diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 26454fcb6..ce21cab5f 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution { + [Trait("Category", "Processors")] public class GaussianBlurTest : BaseImageOperationsExtensionTest { [Fact] @@ -36,4 +37,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution Assert.Equal(.6f, processor.Sigma); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index d264e82e1..e94683092 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution { + [Trait("Category", "Processors")] public class GaussianSharpenTest : BaseImageOperationsExtensionTest { [Fact] @@ -36,4 +37,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution Assert.Equal(.6f, processor.Sigma); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs index 73f6a3f47..31a1fc2d4 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -5,7 +5,7 @@ using System; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors { public class LaplacianKernelFactoryTests { diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 9f0a80453..71cee8f7f 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -6,8 +6,9 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization +namespace SixLabors.ImageSharp.Tests.Processing.Dithering { + [Trait("Category", "Processors")] public class DitherTest : BaseImageOperationsExtensionTest { private class Assert : Xunit.Assert diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs index 5bc6256d9..11c90a507 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -3,11 +3,11 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects { + [Trait("Category", "Processors")] public class BackgroundColorTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs index 2fd7ac7ef..c1c0dc07a 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects { + [Trait("Category", "Processors")] public class OilPaintTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs index 33061e1e4..e13b22a94 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Effects { + [Trait("Category", "Processors")] public class PixelateTest : BaseImageOperationsExtensionTest { [Fact] @@ -36,4 +37,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(23, processor.Size); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs index f87ace189..6eaa9937b 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class BlackWhiteTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 680a6afdc..7f0330148 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -6,8 +6,9 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class BrightnessTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index e65b67815..237f132a4 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -12,6 +12,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class ColorBlindnessTest : BaseImageOperationsExtensionTest { public static IEnumerable TheoryData = new[] diff --git a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs index e181999fa..b968e023f 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -1,13 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Filters; - + [Trait("Category", "Processors")] public class ContrastTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs index 15945e468..3965d10c9 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs @@ -1,13 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Filters; - + [Trait("Category", "Processors")] public class FilterTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index 36c2ff769..2e56331a7 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -12,6 +12,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class GrayscaleTest : BaseImageOperationsExtensionTest { public static IEnumerable ModeTheoryData = new[] diff --git a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs index 9d85af589..04fb3d599 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class HueTest : BaseImageOperationsExtensionTest { [Fact] @@ -28,4 +29,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters Assert.Equal(5f, processor.Degrees); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs index e773a177f..ed1c729e6 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs @@ -5,8 +5,9 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class InvertTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs index 798c0e055..72be60b39 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class KodachromeTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs index cbf44e4c6..2b8a8be88 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs @@ -5,8 +5,9 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class LightnessTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs index e7d289ea5..f28601fe3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -1,15 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Tests.Processing; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Filters; - + [Trait("Category", "Processors")] public class LomographTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs index 8e8b4636c..526fd9a2d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs @@ -1,12 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class OpacityTest : BaseImageOperationsExtensionTest { [Fact] @@ -27,4 +28,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(.6f, processor.Amount); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs index 2cfd8519d..5601920e9 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class PolaroidTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs index b61a12102..e6542e79a 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class SaturateTest : BaseImageOperationsExtensionTest { [Fact] @@ -27,4 +28,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters Assert.Equal(5, processor.Amount); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs index c7f85b732..7a9242cb3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Filters { + [Trait("Category", "Processors")] public class SepiaTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs index cd0a65ad5..d85495b92 100644 --- a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Processing private static void CheckThrowsCorrectObjectDisposedException(Action action) { - var ex = Assert.Throws(action); + ObjectDisposedException ex = Assert.Throws(action); Assert.Equal(ExpectedExceptionMessage, ex.Message); } } diff --git a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs index c206938a2..220bd5f05 100644 --- a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -12,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing /// /// Contains test cases for default implementation. /// - public class ImageProcessingContextTests + public class ImageProcessingContextTests : IDisposable { private readonly Image image = new Image(10, 10); @@ -195,5 +196,7 @@ namespace SixLabors.ImageSharp.Tests.Processing .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) .Returns(this.cloningProcessorImpl.Object); } + + public void Dispose() => this.image?.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs index 481463f47..285535b9f 100644 --- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs +++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing { public class IntegralImageTests : BaseImageOperationsExtensionTest { diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 4460f04fb..85b753024 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -10,6 +10,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Normalization { // ReSharper disable InconsistentNaming + [Trait("Category", "Processors")] public class HistogramEqualizationTests { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); @@ -17,10 +18,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [Theory] [InlineData(256)] [InlineData(65536)] - public void GlobalHistogramEqualization_WithDifferentLumanceLevels(int luminanceLevels) + public void GlobalHistogramEqualization_WithDifferentLuminanceLevels(int luminanceLevels) { // Arrange - var pixels = new byte[] + byte[] pixels = { 52, 55, 61, 59, 70, 61, 76, 61, 62, 59, 55, 104, 94, 85, 59, 71, @@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization } } - var expected = new byte[] + byte[] expected = { 0, 12, 53, 32, 146, 53, 174, 53, 57, 32, 12, 227, 219, 202, 32, 154, @@ -140,6 +141,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization /// See: https://github.com/SixLabors/ImageSharp/pull/984 /// /// The pixel type of the image. + /// The test image provider. [Theory] [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] @@ -149,17 +151,55 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization using (Image image = provider.GetImage()) { var options = new HistogramEqualizationOptions() - { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 256, - ClipHistogram = true, - ClipLimit = 5, - NumberOfTiles = 10 - }; + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 256, + ClipHistogram = true, + ClipLimit = 5, + NumberOfTiles = 10 + }; image.Mutate(x => x.HistogramEqualization(options)); image.DebugSave(provider); image.CompareToReferenceOutput(ValidatorComparer, provider); } } + + [Theory] + [WithTestPatternImages(5120, 9234, PixelTypes.L16)] + public unsafe void Issue1640(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + // https://github.com/SixLabors/ImageSharp/discussions/1640 + // Test using isolated memory to ensure clean buffers for reference + provider.Configuration = Configuration.CreateDefaultInstance(); + var options = new HistogramEqualizationOptions + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 4096, + ClipHistogram = false, + ClipLimit = 350, + NumberOfTiles = 8 + }; + + using Image image = provider.GetImage(); + using Image referenceResult = image.Clone(ctx => + { + ctx.HistogramEqualization(options); + ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); + }); + + using Image processed = image.Clone(ctx => + { + ctx.HistogramEqualization(options); + ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); + }); + + ValidatorComparer.VerifySimilarity(referenceResult, processed); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 8bc0a2c97..a84751f5a 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays { + [Trait("Category", "Processors")] public class GlowTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs index 32e8ba384..3e0c851d2 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -3,11 +3,11 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Overlays { + [Trait("Category", "Processors")] public class VignetteTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 24e52d5d0..0103b138a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -11,6 +11,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { + [Trait("Category", "Processors")] public class BinaryDitherTests { public static readonly string[] CommonTestImages = diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index fd08eb2de..446ac70d4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -4,12 +4,12 @@ using System.Globalization; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { + [Trait("Category", "Processors")] public class BinaryThresholdTest { public static readonly TheoryData BinaryThresholdValues diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index dbf59a29b..2351cbb91 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -16,6 +16,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] public class BokehBlurTest { private static readonly string Components10x2 = @" diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 529a4b49c..eadee0c2e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] [GroupOutput("Convolution")] public class BoxBlurTest : Basic1ParameterConvolutionTests { @@ -13,4 +15,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => ctx.BoxBlur(value, bounds); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index e468778de..cc28bf304 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -11,6 +11,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] [GroupOutput("Convolution")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] public class DetectEdgesTest diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 31b3d20db..44fe673ec 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] [GroupOutput("Convolution")] public class GaussianBlurTest : Basic1ParameterConvolutionTests { @@ -13,4 +15,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => ctx.GaussianBlur(value, bounds); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 7d3e91803..2b4f38e89 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] [GroupOutput("Convolution")] public class GaussianSharpenTest : Basic1ParameterConvolutionTests { @@ -13,4 +15,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => ctx.GaussianSharpen(value, bounds); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 5c1b5da7f..36ce5029c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -8,8 +8,9 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering { + [Trait("Category", "Processors")] public class DitherTests { public const PixelTypes CommonNonDefaultPixelTypes = diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index b29e45221..acf2c0613 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { + [Trait("Category", "Processors")] [GroupOutput("Effects")] public class BackgroundColorTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index 0d68a860d..1dcd8181f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { + [Trait("Category", "Processors")] [GroupOutput("Effects")] public class OilPaintTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs index 919cb3137..6edde73cd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { + [Trait("Category", "Processors")] [GroupOutput("Effects")] public class PixelShaderTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index 2173cbef8..e4de119fc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { + [Trait("Category", "Processors")] [GroupOutput("Effects")] public class PixelateTest { @@ -30,4 +31,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs index fdcc3c6f7..0927a8b81 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class BlackWhiteTest { @@ -21,4 +20,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index d7e5b13cc..97f04440b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -2,14 +2,13 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class BrightnessTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index a007f7194..f86858c84 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class ColorBlindnessTest { @@ -33,4 +32,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs index 25fe9c84c..81a7e24ff 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class ContrastTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 535179cb1..b5c0e583c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -2,14 +2,13 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp; - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class FilterTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs index 279b699ee..c568188fb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class GrayscaleTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs index 3538f0dba..0c2b455e3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class HueTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs index a2e0b0b4b..8c435d23a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class InvertTest { @@ -20,4 +19,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects provider.RunValidatingProcessorTest(x => x.Invert()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs index f21d45836..2fecac32c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class KodachromeTest { @@ -20,4 +19,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunValidatingProcessorTest(x => x.Kodachrome()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs index c924ddc4f..69fa8cdea 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs @@ -2,14 +2,13 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class LightnessTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs index a7ef2f862..e5621b592 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class LomographTest { @@ -20,4 +19,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunValidatingProcessorTest(x => x.Lomograph()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs index 64025a6fb..645746a21 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class OpacityTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs index 8be43efa9..8077051cd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class PolaroidTest { @@ -20,4 +19,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunValidatingProcessorTest(x => x.Polaroid()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs index 91c6e4af8..e10243289 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class SaturateTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs index af2c2136a..86e3050c2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] [GroupOutput("Filters")] public class SepiaTest { @@ -20,4 +19,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunValidatingProcessorTest(x => x.Sepia()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index f0d6b784b..0a2b9921c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { + [Trait("Category", "Processors")] [GroupOutput("Overlays")] public class GlowTest : OverlayTestBase { @@ -15,4 +17,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Glow(rect); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index fa4d422b1..6814a9132 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -9,6 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { + [Trait("Category", "Processors")] [GroupOutput("Overlays")] public abstract class OverlayTestBase { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 6eccde4bc..3a6c8a11a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { + [Trait("Category", "Processors")] [GroupOutput("Overlays")] public class VignetteTest : OverlayTestBase { @@ -15,4 +17,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Vignette(rect); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index 2b4460429..cccb77e86 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { + [Trait("Category", "Processors")] public class OctreeQuantizerTests { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index 0df498cd1..991a2bcb7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -8,9 +8,10 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { + [Trait("Category", "Processors")] public class PaletteQuantizerTests { - private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue }; + private static readonly Color[] Palette = { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index 3f4656d41..af1d7f3f3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { + [Trait("Category", "Processors")] public class QuantizerTests { /// diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 8881aa9ad..639f8fd2d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { + [Trait("Category", "Processors")] public class WuQuantizerTests { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 49a443d92..d2d2fcc1f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -14,6 +14,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class AffineTransformTests { private readonly ITestOutputHelper output; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 44f88c3a2..38fde5060 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class AutoOrientTests { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index 78c35fa9b..52f3b65de 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class CropTest { @@ -29,4 +30,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms comparer: ImageComparer.Exact); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index 16668fb20..4e8a65ddc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class EntropyCropTest { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index c094febc9..b9f0fb9e8 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -9,6 +9,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class FlipTests { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index 2ea833640..b1441d109 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public class PadTest { public static readonly string[] CommonTestImages = diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs index 4691fc82b..43fe196f7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public class ResamplerTests { [Theory] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs index ceee3e7e0..253d29eea 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public class ResizeHelperTests { [Theory] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index da567f18c..dfab25b11 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public partial class ResizeKernelMapTests { /// diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 991bca80e..f15a6242d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -13,6 +13,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public partial class ResizeKernelMapTests { private ITestOutputHelper Output { get; } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 58b7fd12e..42cf1e3c1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -16,6 +15,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] public class ResizeTests { private const PixelTypes CommonNonDefaultPixelTypes = diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs index 398039e43..0648c48b4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -1,14 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - using SixLabors.ImageSharp.Processing; - + [Trait("Category", "Processors")] public class RotateFlipTests { public static readonly string[] FlipFiles = { TestImages.Bmp.F }; @@ -36,4 +35,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index 1e888a51a..61b63d064 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class RotateTests { @@ -43,4 +44,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs index 2fd87de29..05d5095af 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,6 +10,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class SkewTests { @@ -64,4 +65,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms appendPixelTypeToFileName: false); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs index f508744fa..61af13ea3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs @@ -10,6 +10,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [Trait("Category", "Processors")] [GroupOutput("Transforms")] public class SwizzleTests { diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index 50fff725b..1b681a82f 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -1,13 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Transforms; - + [Trait("Category", "Processors")] public class AutoOrientTests : BaseImageOperationsExtensionTest { [Fact] @@ -17,4 +17,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.Verify(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index 9fa75448b..ed56f681c 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,6 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class CropTest : BaseImageOperationsExtensionTest { [Theory] @@ -41,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Throws(() => this.operations.Crop(cropRectangle)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs index f2ca8dee5..53fa02edb 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class EntropyCropTest : BaseImageOperationsExtensionTest { [Theory] @@ -20,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(threshold, processor.Threshold); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index 3f6e26b8e..843cd3040 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -1,13 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Transforms; - + [Trait("Category", "Processors")] public class FlipTests : BaseImageOperationsExtensionTest { [Theory] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 3f49b0f02..227e470d4 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class PadTest : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index d95992d6b..2f0f8f6ac 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -1,12 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using SixLabors.ImageSharp.Processing; +using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class ProjectiveTransformBuilderTests : TransformBuilderTestBase { protected override ProjectiveTransformBuilder CreateBuilder() diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 2fd5f2a7d..ef8e03763 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -14,6 +14,7 @@ using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class ProjectiveTransformTests { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 5f426083c..60f7aaa0b 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -9,6 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class ResizeTests : BaseImageOperationsExtensionTest { [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs index 379d39966..90a96972a 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class RotateFlipTests : BaseImageOperationsExtensionTest { [Theory] @@ -32,4 +33,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(flip, flipProcessor.FlipMode); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs index 6f7dbd9de..b79bb29eb 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class RotateTests : BaseImageOperationsExtensionTest { [Theory] @@ -34,4 +35,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(expectedAngle, processor.Degrees); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs index de276b427..06282494a 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class SkewTest : BaseImageOperationsExtensionTest { [Fact] @@ -20,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(20, processor.DegreesY); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs index cde6aeca3..a6d032335 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs @@ -7,6 +7,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class SwizzleTests : BaseImageOperationsExtensionTest { private struct InvertXAndYSwizzler : ISwizzler diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 2e0dfd59e..d4540e433 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -9,6 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public abstract class TransformBuilderTestBase { private static readonly ApproximateFloatComparer Comparer = new ApproximateFloatComparer(1e-6f); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs index 81c415c06..869162b38 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + [Trait("Category", "Processors")] public class TransformsHelpersTest { [Fact] diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 7273a65f7..c8d0633d7 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests public TestDecoder Decoder { get; } - private byte[] header = Guid.NewGuid().ToByteArray(); + private readonly byte[] header = Guid.NewGuid().ToByteArray(); public MemoryStream CreateStream(byte[] marker = null) { @@ -119,16 +119,16 @@ namespace SixLabors.ImageSharp.Tests public IEnumerable FileExtensions => this.SupportedExtensions; - public bool IsSupportedFileFormat(ReadOnlySpan header) + public bool IsSupportedFileFormat(ReadOnlySpan fileHeader) { - if (header.Length < this.header.Length) + if (fileHeader.Length < this.header.Length) { return false; } for (int i = 0; i < this.header.Length; i++) { - if (header[i] != this.header[i]) + if (fileHeader[i] != this.header[i]) { return false; } @@ -137,11 +137,11 @@ namespace SixLabors.ImageSharp.Tests return true; } - public void Configure(Configuration host) + public void Configure(Configuration configuration) { - host.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); - host.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); - host.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); + configuration.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); + configuration.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); + configuration.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); } public struct DecodeOperation @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Tests public class TestHeader : IImageFormatDetector { - private TestFormat testFormat; + private readonly TestFormat testFormat; public int HeaderSize => this.testFormat.HeaderSize; @@ -199,7 +199,7 @@ namespace SixLabors.ImageSharp.Tests public class TestDecoder : IImageDecoder, IImageInfoDetector { - private TestFormat testFormat; + private readonly TestFormat testFormat; public TestDecoder(TestFormat testFormat) { @@ -212,20 +212,20 @@ namespace SixLabors.ImageSharp.Tests public int HeaderSize => this.testFormat.HeaderSize; - public Image Decode(Configuration config, Stream stream) + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel - => this.DecodeImpl(config, stream, default).GetAwaiter().GetResult(); + => this.DecodeImpl(configuration, stream, default).GetAwaiter().GetResult(); - public Task> DecodeAsync(Configuration config, Stream stream, CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => this.DecodeImpl(config, stream, cancellationToken); + => this.DecodeImpl(configuration, stream, cancellationToken); private async Task> DecodeImpl(Configuration config, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken); - var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); + byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { Marker = marker, @@ -251,9 +251,9 @@ namespace SixLabors.ImageSharp.Tests => await this.DecodeImpl(configuration, stream, cancellationToken); } - public class TestEncoder : ImageSharp.Formats.IImageEncoder + public class TestEncoder : IImageEncoder { - private TestFormat testFormat; + private readonly TestFormat testFormat; public TestEncoder(TestFormat testFormat) { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c15327e0d..09394d4ea 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -497,5 +497,97 @@ namespace SixLabors.ImageSharp.Tests public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; } + + public static class Tiff + { + public const string Benchmark_Path = "Tiff/Benchmarks/"; + public const string Benchmark_BwFax3 = "medium_bw_Fax3.tiff"; + public const string Benchmark_BwFax4 = "medium_bw_Fax4.tiff"; + public const string Benchmark_BwRle = "medium_bw_Rle.tiff"; + public const string Benchmark_GrayscaleUncompressed = "medium_grayscale_uncompressed.tiff"; + public const string Benchmark_PaletteUncompressed = "medium_palette_uncompressed.tiff"; + public const string Benchmark_RgbDeflate = "medium_rgb_deflate.tiff"; + public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; + public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; + public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; + + public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; + public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; + public const string Calliphora_GrayscaleLzw_Predictor = "Tiff/Calliphora_gray_lzw_predictor.tiff"; + public const string Calliphora_GrayscaleDeflate = "Tiff/Calliphora_gray_deflate.tiff"; + public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate_predictor.tiff"; + public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff"; + public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff"; + public const string Calliphora_RgbLzwPredictor = "Tiff/Calliphora_rgb_lzw_predictor.tiff"; + public const string Calliphora_RgbPaletteLzw = "Tiff/Calliphora_rgb_palette_lzw.tiff"; + public const string Calliphora_RgbPaletteLzw_Predictor = "Tiff/Calliphora_rgb_palette_lzw_predictor.tiff"; + public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff"; + public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff"; + public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tiff"; + public const string Fax3Uncompressed = "Tiff/ccitt_fax3_uncompressed.tiff"; + public const string Calliphora_Fax3Compressed_WithEolPadding = "Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff"; + public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; + public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; + public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; + + public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; + public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; + public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; + public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; + + // Test case for an issue, that the last bits in a row got ignored. + public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; + + public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; + public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; + public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff"; + public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff"; + public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; + public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff"; + public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; + public const string RgbJpeg = "Tiff/rgb_jpeg.tiff"; + public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff"; + public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff"; + public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; + public const string RgbLzwNoPredictorMultistripMotorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; + public const string RgbLzwNoPredictorSinglestripMotorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; + public const string RgbLzwMultistripPredictor = "Tiff/rgb_lzw_multistrip.tiff"; + public const string RgbPackbits = "Tiff/rgb_packbits.tiff"; + public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; + public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; + public const string RgbPalette = "Tiff/rgb_palette.tiff"; + public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; + public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; + public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; + public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + + public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; + public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; + + public const string RgbUncompressedTiled = "Tiff/rgb_uncompressed_tiled.tiff"; + public const string MultiframeDifferentSizeTiled = "Tiff/multipage_ withPreview_differentSize_tiled.tiff"; + + public const string MultiframeLzwPredictor = "Tiff/multipage_lzw.tiff"; + public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff"; + public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; + public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; + + public const string FillOrder2 = "Tiff/b0350_fillorder2.tiff"; + public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; + + public const string Fax4_Motorola = "Tiff/moy.tiff"; + + public const string SampleMetadata = "Tiff/metadata_sample.tiff"; + + // Iptc data as long[] instead of byte[] + public const string InvalidIptcData = "Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff"; + public const string IptcData = "Tiff/iptc.tiff"; + + public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; + + public static readonly string[] Metadata = { SampleMetadata }; + + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, FillOrder2, Calliphora_Fax4Compressed, Fax4_Motorola }; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 0cf76a389..12db71e66 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests if (!addedRows.Any()) { - addedRows = new[] { new object[0] }; + addedRows = new[] { Array.Empty() }; } bool firstIsProvider = this.FirstIsProvider(testMethod); diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs new file mode 100644 index 000000000..501651285 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + using System; + + public static class ByteArrayUtility + { + public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + { + var reversedBytes = new byte[bytes.Length]; + Array.Copy(bytes, reversedBytes, bytes.Length); + Array.Reverse(reversedBytes); + return reversedBytes; + } + else + { + return bytes; + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs new file mode 100644 index 000000000..bbb75a9cf --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + using System; + using System.Collections.Generic; + + public class ByteBuffer + { + private readonly List bytes = new List(); + private readonly bool isLittleEndian; + + public ByteBuffer(bool isLittleEndian) + { + this.isLittleEndian = isLittleEndian; + } + + public void AddByte(byte value) + { + this.bytes.Add(value); + } + + public void AddUInt16(ushort value) + { + this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); + } + + public void AddUInt32(uint value) + { + this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); + } + + public byte[] ToArray() + { + return this.bytes.ToArray(); + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index f186ed318..7612b663a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. /// - /// The image to rdaw on. + /// The image to draw on. private static void DrawTestPattern(Image image) { // first lets split the image into 4 quadrants diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 8a038a691..07acabccf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests details = '_' + details; } - return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"); + return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}{Path.DirectorySeparatorChar}{this.TestName}{pixName}{fn}{details}{extension}"); } /// diff --git a/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs b/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs new file mode 100644 index 000000000..4d3646301 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + public class PausedStream : Stream + { + private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0); + + private readonly CancellationTokenSource cancelationTokenSource = new CancellationTokenSource(); + + private readonly Stream innerStream; + private Action onWaitingCallback; + + public void OnWaiting(Action onWaitingCallback) => this.onWaitingCallback = onWaitingCallback; + + public void OnWaiting(Action onWaitingCallback) => this.OnWaiting(_ => onWaitingCallback()); + + public void Release() + { + this.semaphore.Release(); + this.cancelationTokenSource.Cancel(); + } + + public void Next() => this.semaphore.Release(); + + private void Wait() + { + if (this.cancelationTokenSource.IsCancellationRequested) + { + return; + } + + this.onWaitingCallback?.Invoke(this.innerStream); + + try + { + this.semaphore.Wait(this.cancelationTokenSource.Token); + } + catch (OperationCanceledException) + { + // ignore this as its just used to unlock any waits in progress + } + } + + private async Task Await(Func action) + { + await Task.Yield(); + this.Wait(); + await action(); + } + + private async Task Await(Func> action) + { + await Task.Yield(); + this.Wait(); + return await action(); + } + + private T Await(Func action) + { + this.Wait(); + return action(); + } + + private void Await(Action action) + { + this.Wait(); + action(); + } + + public PausedStream(byte[] data) + : this(new MemoryStream(data)) + { + } + + public PausedStream(string filePath) + : this(File.OpenRead(filePath)) + { + } + + public PausedStream(Stream innerStream) => this.innerStream = innerStream; + + public override bool CanTimeout => this.innerStream.CanTimeout; + + public override void Close() => this.Await(() => this.innerStream.Close()); + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => this.Await(() => this.innerStream.CopyToAsync(destination, bufferSize, cancellationToken)); + + public override bool CanRead => this.innerStream.CanRead; + + public override bool CanSeek => this.innerStream.CanSeek; + + public override bool CanWrite => this.innerStream.CanWrite; + + public override long Length => this.Await(() => this.innerStream.Length); + + public override long Position { get => this.Await(() => this.innerStream.Position); set => this.Await(() => this.innerStream.Position = value); } + + public override void Flush() => this.Await(() => this.innerStream.Flush()); + + public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count)); + + public override long Seek(long offset, SeekOrigin origin) => this.Await(() => this.innerStream.Seek(offset, origin)); + + public override void SetLength(long value) => this.Await(() => this.innerStream.SetLength(value)); + + public override void Write(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Write(buffer, offset, count)); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.ReadAsync(buffer, offset, count, cancellationToken)); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.WriteAsync(buffer, offset, count, cancellationToken)); + + public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value)); + + public override int ReadByte() => this.Await(() => this.innerStream.ReadByte()); + + protected override void Dispose(bool disposing) => this.innerStream.Dispose(); + +#if NETCOREAPP + public override void CopyTo(Stream destination, int bufferSize) => this.Await(() => this.innerStream.CopyTo(destination, bufferSize)); + + public override int Read(Span buffer) + { + this.Wait(); + return this.innerStream.Read(buffer); + } + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.ReadAsync(buffer, cancellationToken)); + + public override void Write(ReadOnlySpan buffer) + { + this.Wait(); + this.innerStream.Write(buffer); + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.WriteAsync(buffer, cancellationToken)); +#endif + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 163bb84d5..4e2866be1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; @@ -10,6 +11,7 @@ using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -75,23 +77,27 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var settings = new MagickReadSettings(); settings.SetDefines(bmpReadDefines); - using var magickImage = new MagickImage(stream, settings); - var result = new Image(configuration, magickImage.Width, magickImage.Height); - MemoryGroup resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup; - - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) + using var magickImageCollection = new MagickImageCollection(stream, settings); + var framesList = new List>(); + foreach (IMagickImage magicFrame in magickImageCollection) { - if (magickImage.Depth == 8) + var frame = new ImageFrame(configuration, magicFrame.Width, magicFrame.Height); + framesList.Add(frame); + + MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; + + using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); + if (magicFrame.Depth == 8 || magicFrame.Depth == 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - FromRgba32Bytes(configuration, data, resultPixels); + FromRgba32Bytes(configuration, data, framePixels); } - else if (magickImage.Depth == 16) + else if (magicFrame.Depth == 16) { ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, resultPixels); + FromRgba64Bytes(configuration, bytes, framePixels); } else { @@ -99,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - return result; + return new Image(configuration, new ImageMetadata(), framesList); } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); diff --git a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs new file mode 100644 index 000000000..ddd1ec750 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + internal class SingleStreamFileSystem : IFileSystem + { + private readonly Stream stream; + + public SingleStreamFileSystem(Stream stream) => this.stream = stream; + + Stream IFileSystem.Create(string path) => this.stream; + + Stream IFileSystem.OpenRead(string path) => this.stream; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index ea5b2f665..f68fb4d95 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests @@ -55,7 +56,8 @@ namespace SixLabors.ImageSharp.Tests var cfg = new Configuration( new JpegConfigurationModule(), new GifConfigurationModule(), - new TgaConfigurationModule()); + new TgaConfigurationModule(), + new TiffConfigurationModule()); IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 1c5aedd9b..de365c429 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -514,6 +514,31 @@ namespace SixLabors.ImageSharp.Tests return image; } + public static Image CompareToOriginalMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + IImageDecoder referenceDecoder = null) + where TPixel : unmanaged, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) + { + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } + + var testFile = TestFile.Create(path); + + referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path); + + using (var original = Image.Load(testFile.Bytes, referenceDecoder)) + { + comparer.VerifySimilarity(original, image); + } + + return image; + } + /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 5f41021a0..32b5eaf18 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -7,7 +7,6 @@ using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -354,7 +353,7 @@ namespace SixLabors.ImageSharp.Tests } } - public static string AsInvariantString(this FormattableString formattable) => System.FormattableString.Invariant(formattable); + public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); public static IResampler GetResampler(string name) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs index 87f8cb8c1..92f972941 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -1,13 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { public class SemaphoreReadMemoryStreamTests { diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff new file mode 100644 index 000000000..5574bd58e --- /dev/null +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:622d69dba0a8a67aa3b87e384a2b9ea8d29689eaa5cb5d0eee857f98ed660517 +size 15154924 diff --git a/tests/Images/Input/Tiff/Benchmarks/.gitignore b/tests/Images/Input/Tiff/Benchmarks/.gitignore new file mode 100644 index 000000000..ab0f2bdbc --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/.gitignore @@ -0,0 +1,2 @@ +*.tiff +*.tif diff --git a/tests/Images/Input/Tiff/Benchmarks/gen.bat b/tests/Images/Input/Tiff/Benchmarks/gen.bat new file mode 100644 index 000000000..3a0a032c1 --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/gen.bat @@ -0,0 +1,2 @@ +powershell -executionpolicy RemoteSigned -file gen_big.ps1 +powershell -executionpolicy RemoteSigned -file gen_medium.ps1 diff --git a/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 b/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 new file mode 100644 index 000000000..9d0c137f7 --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 @@ -0,0 +1,14 @@ +$Gm_Exe = "C:\Program Files\ImageMagick-7.0.10-Q16-HDRI\magick.exe" +$Source_Image = ".\jpeg444_big.jpg" +$Output_Prefix = ".\big" + +& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" +& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" +& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" +& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" +& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff" +& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff" + +& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 b/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 new file mode 100644 index 000000000..9bfc65066 --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 @@ -0,0 +1,14 @@ +$Gm_Exe = "C:\Program Files\ImageMagick-7.0.10-Q16-HDRI\magick.exe" +$Source_Image = ".\jpeg444_medium.jpg" +$Output_Prefix = ".\medium" + +& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" +& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" +& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" +& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" +& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff" +& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff" + +& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 b/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 new file mode 100644 index 000000000..6ed0c080c --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 @@ -0,0 +1,12 @@ +$Gm_Exe = "C:\Program Files\GraphicsMagick-1.3.25-Q16\gm.exe" +$Source_Image = "..\Jpg\baseline\Calliphora.jpg" +$Output_Prefix = ".\Calliphora" + +& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" +& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" +& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" +& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" + +& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" +& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg new file mode 100644 index 000000000..1887fa4a5 --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89c632bbc42bc917f81e7c47595c95cb914a619604ac07b8cebf6fd4d1d744ca +size 5667 diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg new file mode 100644 index 000000000..650aff92b --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf48ba72885b98b9d05f1e4bed2e85f5db1db04b0206fc8160a9da2367f4467c +size 1984946 diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg new file mode 100644 index 000000000..0300c67ab --- /dev/null +++ b/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75cdf78efd14c880f26d5009e087df06e772b000edddbb404e7098177f895ac1 +size 525610 diff --git a/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff new file mode 100644 index 000000000..b0dbdde54 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed3b08730e5c34eb8d268f58d1e09efe2605398899bfd726cc3b35de21baa6ff +size 121196 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff new file mode 100644 index 000000000..39852d534 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b9b105857723bca5f478a9ab23c0aeca93abe863781019bbd2da47f18c46f24 +size 125778 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff new file mode 100644 index 000000000..2072b0c47 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15e0f4f04699c7253ce422a7741ada192615182da53e9fd86bdf547cd991b290 +size 126382 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff new file mode 100644 index 000000000..384d00eaa --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a2c95aec08b96bca30af344f7d9952a603a951802ce534a5f2c5f563795cbd2 +size 117704 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff new file mode 100644 index 000000000..621ef158a --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2314b31ca9938fa8b11cbabda0b118a90025a45d2931fca9afa131c0d6919aca +size 557717 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff new file mode 100644 index 000000000..f44a6e934 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9576b3a49b84e26938a7e9ded5f43a1a3c3390bf4824803f5aaab8e00c1afb4 +size 630947 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff new file mode 100644 index 000000000..b14eeba8d --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f24fd8f36a4847fcb84a317de4fd2eacd5eb0c58ef4436d33919f0a6658d0d9 +size 698309 diff --git a/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff new file mode 100644 index 000000000..5db7ef564 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0283f2be39a151ca3ed19be97ebe4a6b17978ed251dd4d0d568895865fec24c7 +size 964588 diff --git a/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff b/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff new file mode 100644 index 000000000..e0a39d248 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a4f687de9925863b1c9f32f53b6c05fb121f9d7b02ff5869113c4745433f10d +size 124644 diff --git a/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff new file mode 100644 index 000000000..1592645c8 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b70500348b1af7828c15e7782eaca105ff749136d7c45eb4cab8c5cd5269c3f6 +size 966134 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff new file mode 100644 index 000000000..c2ebed364 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da6e6a35a0bb0f5f2d49e3c5f0eb2deb7118718dd08844f66a6cb72f48b5c489 +size 1476294 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff new file mode 100644 index 000000000..c9f5fadee --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba79ffac35e16208406e073492d770d3a77e51a47112aa02ab8fd98b5a8487b2 +size 198564 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif b/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif new file mode 100644 index 000000000..745052267 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53006876fcdc655a794462de57eb6b56f4d0cdd3cb8b752c63328db0eb4aa3c1 +size 725085 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff new file mode 100644 index 000000000..99642af52 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecb529e5e3e0eca6f5e407b034fa8ba67bb4b9068af9e9b30425b08d30a249c0 +size 1756355 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff new file mode 100644 index 000000000..862db0b39 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59dbb48f10c40cbbd4f5617a9f57536790ce0b9a4cc241dc8d6257095598cb76 +size 2891292 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff new file mode 100644 index 000000000..be84f0a30 --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29fa2b157c92f6a8bd4036e9d075e24fc451e72ec1a251d97a4b40454e01405c +size 792087 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff new file mode 100644 index 000000000..7ebd74d9d --- /dev/null +++ b/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acb46c990af78fcb0e63849f0f26acffe26833d5cfd2fc77a728baf92166eea3 +size 2893218 diff --git a/tests/Images/Input/Tiff/b0350_fillorder2.tiff b/tests/Images/Input/Tiff/b0350_fillorder2.tiff new file mode 100644 index 000000000..3b7ee6ac3 --- /dev/null +++ b/tests/Images/Input/Tiff/b0350_fillorder2.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37c6a28f460d8781fdc3bcf0cc9bd23f633b03899563546bfc6234a8478f67f0 +size 68637 diff --git a/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff b/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff new file mode 100644 index 000000000..2b290438a --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af20deb1b64cac3272b6560565cb01f28247b9fd8b6d5a86eafbe7b0aea27d48 +size 340 diff --git a/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff b/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff new file mode 100644 index 000000000..d76966336 --- /dev/null +++ b/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc9047bc28ff5256530a7203bf73fa0a7ed1545736cd57fabdaff2d600cfa3f1 +size 132265 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff b/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff new file mode 100644 index 000000000..6a1153bac --- /dev/null +++ b/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60b64bd3c24437eb90c0a17a4328e997702d7e4c0889ec90abde092ab9b490e8 +size 546 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff b/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff new file mode 100644 index 000000000..a2da71cf6 --- /dev/null +++ b/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faa92346ccff9cc7e3275b31cbc4ec054e27d0d0ed20a215a22b6178c2d7adf0 +size 564 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff b/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff new file mode 100644 index 000000000..d1bbbec42 --- /dev/null +++ b/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f60615dea1b417ec125bf365b7c3a7c15ee2381947eb29dae2c34655fd0c2530 +size 821 diff --git a/tests/Images/Input/Tiff/flower-minisblack-04.tiff b/tests/Images/Input/Tiff/flower-minisblack-04.tiff new file mode 100644 index 000000000..e6d1e1336 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-04.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18991fca75a89b3d15c7f93dee0454e3943920b595ba16145ebc1fd8bd45b1f5 +size 1905 diff --git a/tests/Images/Input/Tiff/flower-palette-04.tiff b/tests/Images/Input/Tiff/flower-palette-04.tiff new file mode 100644 index 000000000..8594a0b00 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-palette-04.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:700ec8103b4197c415ba90d983a7d5f471f155fd5b1c952d86ee9becba898a1a +size 2010 diff --git a/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff b/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff new file mode 100644 index 000000000..6e57bb56e --- /dev/null +++ b/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaee95d80f1e9eb9afbb7447da78a685f29359181ce71c045cff3aacda28a916 +size 14530 diff --git a/tests/Images/Input/Tiff/grayscale_uncompressed.tiff b/tests/Images/Input/Tiff/grayscale_uncompressed.tiff new file mode 100644 index 000000000..570edfc6d --- /dev/null +++ b/tests/Images/Input/Tiff/grayscale_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fefe6f6e1daf270546848c23ef437cfd072abb812e539fbab1006d74d416e9a4 +size 65758 diff --git a/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff b/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff new file mode 100644 index 000000000..643805ca7 --- /dev/null +++ b/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa8dfeb96763b2b35b5f06f37021d7e33551485105ad4a3a704d76b3aecf039d +size 518 diff --git a/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff b/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff new file mode 100644 index 000000000..2ab78c71e --- /dev/null +++ b/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99a488957403e3c35f7dfbbcbe7f187a74e6cab61b233c91f4892079c04984fd +size 550 diff --git a/tests/Images/Input/Tiff/iptc.tiff b/tests/Images/Input/Tiff/iptc.tiff new file mode 100644 index 000000000..ed06ff411 --- /dev/null +++ b/tests/Images/Input/Tiff/iptc.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7a8d89df97b35ab3be9745a345687defd427bf4ca519ad3c5a52208d98f7694 +size 15170 diff --git a/tests/Images/Input/Tiff/little_endian.tiff b/tests/Images/Input/Tiff/little_endian.tiff new file mode 100644 index 000000000..64653d620 --- /dev/null +++ b/tests/Images/Input/Tiff/little_endian.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b42205ddeb20f8bdb1182bdf1345e695be4bf9617ba0576bef0d5b76642fa1a +size 191232 diff --git a/tests/Images/Input/Tiff/metadata_sample.tiff b/tests/Images/Input/Tiff/metadata_sample.tiff new file mode 100644 index 000000000..d76735268 --- /dev/null +++ b/tests/Images/Input/Tiff/metadata_sample.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72a1c8022d699e0e7248940f0734d01d6ab9bf4a71022e8b5626b64d66a5f39d +size 8107 diff --git a/tests/Images/Input/Tiff/moy.tiff b/tests/Images/Input/Tiff/moy.tiff new file mode 100644 index 000000000..197aade49 --- /dev/null +++ b/tests/Images/Input/Tiff/moy.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026bb9372882d8fc15540b4f94e23138e75aacb0ebf2f5940b056fc66819ec46 +size 1968862 diff --git a/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff b/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff new file mode 100644 index 000000000..164740c4a --- /dev/null +++ b/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e5ef9ccff292ed33a352cd040326c1ceeefc2cd68aedf0598dbff8326deecf6 +size 113784 diff --git a/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff b/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff new file mode 100644 index 000000000..4a4db4524 --- /dev/null +++ b/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63360eb1e383d0d3830155a269c4b5f4b07f3a2cb386f18427ea1c5ae5f1817a +size 160015 diff --git a/tests/Images/Input/Tiff/multipage_differentSize.tiff b/tests/Images/Input/Tiff/multipage_differentSize.tiff new file mode 100644 index 000000000..7caa44710 --- /dev/null +++ b/tests/Images/Input/Tiff/multipage_differentSize.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8476813a4c68883872a8a196f567a567d2351802f86114aef0c64e9786a7d8b9 +size 210533 diff --git a/tests/Images/Input/Tiff/multipage_differentVariants.tiff b/tests/Images/Input/Tiff/multipage_differentVariants.tiff new file mode 100644 index 000000000..9bbb84d8b --- /dev/null +++ b/tests/Images/Input/Tiff/multipage_differentVariants.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e54681a90093f0f613299eabf01db60ac971c96d26d281db01873b2cb9fb2d09 +size 483062 diff --git a/tests/Images/Input/Tiff/multipage_lzw.tiff b/tests/Images/Input/Tiff/multipage_lzw.tiff new file mode 100644 index 000000000..d2595dcdc --- /dev/null +++ b/tests/Images/Input/Tiff/multipage_lzw.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da86a6d5fa61609edb54ef9118be16d89488f0cfce0acd16990e68b685f76094 +size 43432 diff --git a/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff b/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff new file mode 100644 index 000000000..d68c53483 --- /dev/null +++ b/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6b269e44539ea2e36f6357941c97a158ee288a7b44dab35338c241de69b5d37 +size 16078 diff --git a/tests/Images/Input/Tiff/palette_uncompressed.tiff b/tests/Images/Input/Tiff/palette_uncompressed.tiff new file mode 100644 index 000000000..b282d65b5 --- /dev/null +++ b/tests/Images/Input/Tiff/palette_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75f1bcaff7dc09ddbe6ded7b764b8c0b17bffc3392bafdc7bc7a4c7d616a38e5 +size 67394 diff --git a/tests/Images/Input/Tiff/rgb_deflate.tiff b/tests/Images/Input/Tiff/rgb_deflate.tiff new file mode 100644 index 000000000..7abd84d86 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_deflate.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0af0db6a42424e3db5c6b84be6e253817413b2de68cc91f7288a8434150fe088 +size 67130 diff --git a/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff b/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff new file mode 100644 index 000000000..dc9b36a9f --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb8375584d0ba70626f0026bf91306c423a6c00f511362a3ce523cefb1e65d56 +size 68058 diff --git a/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff b/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff new file mode 100644 index 000000000..97623cd5b --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1db70e0cfb056cfc675db3a2b85a1f226c53cd70275808773ff580c738b3db1 +size 3158 diff --git a/tests/Images/Input/Tiff/rgb_jpeg.tiff b/tests/Images/Input/Tiff/rgb_jpeg.tiff new file mode 100644 index 000000000..b198d1aba --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_jpeg.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa7f77e16bc51a55f5d2cb8d162801ea9edc620e16ec7ab43323f3c994830399 +size 5736 diff --git a/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff b/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff new file mode 100644 index 000000000..6cfc803bb --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55ecb81526b238ca4a43b559a33a3b393b9776fe32fd2f3b78a1b460780f7ba9 +size 26962 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff new file mode 100644 index 000000000..4d8c11afe --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b67399bf019d8a585a6433bcaf6299846b85cb7783cf9350340b900185e645c6 +size 155004 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff new file mode 100644 index 000000000..59290df1c --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3fa8877b14979d2b56c0f0acd18b1c797ffe5d4ab91fce5dad8e1177acf7f4d +size 155004 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff new file mode 100644 index 000000000..557fb4c51 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86cd325c7587d1712c91fed16d18a16dcbbbce97de6f9e3ae782e0e5da1ff541 +size 154735 diff --git a/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff b/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff new file mode 100644 index 000000000..44092f6c7 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:895f7e1fb17e42175e6c0d67fbc08a7c65d7e19a71e67388034cdaecc407407a +size 131092 diff --git a/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff b/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff new file mode 100644 index 000000000..a1d3d77f4 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fe0ad0f383136e32f45c922de530ebcbce055f99ca81ef1d50608e241ea621c +size 25806 diff --git a/tests/Images/Input/Tiff/rgb_packbits.tiff b/tests/Images/Input/Tiff/rgb_packbits.tiff new file mode 100644 index 000000000..28310cade --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_packbits.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c5c7996d78fb97a43bdd6d9fec7c1cb6bdea546d73c21f5a068edc602ff3aa8 +size 198460 diff --git a/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff b/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff new file mode 100644 index 000000000..fa9a8f2ae --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79ea79362ddad668b28cb919c91dc793b4246f33e411e3794cbe587c5461367a +size 198402 diff --git a/tests/Images/Input/Tiff/rgb_palette.tiff b/tests/Images/Input/Tiff/rgb_palette.tiff new file mode 100644 index 000000000..b282d65b5 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_palette.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75f1bcaff7dc09ddbe6ded7b764b8c0b17bffc3392bafdc7bc7a4c7d616a38e5 +size 67394 diff --git a/tests/Images/Input/Tiff/rgb_palette_deflate.tiff b/tests/Images/Input/Tiff/rgb_palette_deflate.tiff new file mode 100644 index 000000000..ef03cdb3e --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_palette_deflate.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:434cc4c212dfa975c130e2acd7c704b9cc6d0bf168336b8f778f811ddaf6a812 +size 24990 diff --git a/tests/Images/Input/Tiff/rgb_small_deflate.tiff b/tests/Images/Input/Tiff/rgb_small_deflate.tiff new file mode 100644 index 000000000..cd78dfc88 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_small_deflate.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09c964c2f11806c2ff1e9fc7b06ddbecbc9e94cb7f4bd2b9841f0a3939d98eef +size 2575 diff --git a/tests/Images/Input/Tiff/rgb_small_lzw.tiff b/tests/Images/Input/Tiff/rgb_small_lzw.tiff new file mode 100644 index 000000000..deaeda645 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_small_lzw.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2630a452dcc86f557594fe29ae4244fbb29a276cdee53835157af17f966e1405 +size 3221 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed.tiff b/tests/Images/Input/Tiff/rgb_uncompressed.tiff new file mode 100644 index 000000000..c9602763d --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87134a685bcd816d77cae664b415f1b6a25b78933953c128a742fba653eca9fa +size 196924 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff b/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff new file mode 100644 index 000000000..0f4912136 --- /dev/null +++ b/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:165d85d8e3be6b44309855474c73ae6c14267393945d287fc20be4fcadc0e3f3 +size 3337