diff --git a/.editorconfig b/.editorconfig index 03036f8a53..33fd0577a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -75,7 +75,7 @@ indent_style = tab [*.{cs,csx,cake,vb,vbx}] # Default Severity for all .NET Code Style rules below -dotnet_analyzer_diagnostic.severity = warning +dotnet_analyzer_diagnostic.category-style.severity = warning ########################################## # Language Rules diff --git a/.gitignore b/.gitignore index 475d6e76b0..769a40c6cc 100644 --- a/.gitignore +++ b/.gitignore @@ -221,4 +221,5 @@ artifacts/ # Tests **/Images/ActualOutput **/Images/ReferenceOutput +**/Images/Input/MemoryStress .DS_Store diff --git a/Directory.Build.props b/Directory.Build.props index 3df93fcd40..b3e18e5a5a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,13 +18,12 @@ - - - false - - + true - + diff --git a/ImageSharp.sln b/ImageSharp.sln index 83208994cb..bf1f3579c0 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 @@ -480,115 +479,43 @@ Global EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU - Debug-InnerLoop|x64 = Debug-InnerLoop|x64 - Debug-InnerLoop|x86 = Debug-InnerLoop|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU - Release-InnerLoop|x64 = Release-InnerLoop|x64 - Release-InnerLoop|x86 = Release-InnerLoop|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.ActiveCfg = Debug|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU - {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|x86 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|x86 {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|x64 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|x86 - {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|x86 {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/shared-infrastructure b/shared-infrastructure index 48e73f455f..9b94ebc4be 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506 +Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332 diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index ea4cd1c8c4..be2e964fc0 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 0000000000..cc38f1cdee --- /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/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index f2367d488a..1193eccee3 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp { @@ -72,12 +71,6 @@ namespace SixLabors.ImageSharp } } - public static void Read(this Stream stream, IManagedByteBuffer buffer) - => stream.Read(buffer.Array, 0, buffer.Length()); - - public static void Write(this Stream stream, IManagedByteBuffer buffer) - => stream.Write(buffer.Array, 0, buffer.Length()); - #if !SUPPORTS_SPAN_STREAM // This is a port of the CoreFX implementation and is MIT Licensed: // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 9ef7c01c61..f56cb37a81 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -37,7 +37,7 @@ namespace SixLabors /// has a different size than /// [Conditional("DEBUG")] - public static void MustBeSameSized(Span target, Span other, string parameterName) + public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) where T : struct { if (target.Length != other.Length) @@ -57,7 +57,7 @@ namespace SixLabors /// has less items than /// [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(Span target, Span minSpan, string parameterName) + public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) where T : struct { if (target.Length < minSpan.Length) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 0581993014..db65b84cca 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -23,6 +23,16 @@ namespace SixLabors.ImageSharp private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif +#if !SUPPORTS_BITOPERATIONS + private static ReadOnlySpan Log2DeBruijn => new byte[32] + { + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + }; +#endif + /// /// Determine the Greatest CommonDivisor (GCD) of two numbers. /// @@ -756,7 +766,7 @@ namespace SixLabors.ImageSharp /// widening them to 32-bit integers and performing four additions. /// /// - /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) /// is widened and added onto as such: /// /// accumulator += i32(1, 2, 3, 4); @@ -825,5 +835,49 @@ namespace SixLabors.ImageSharp return Sse2.ConvertToInt32(vsum); } #endif + + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// + /// The value. + public static int Log2(uint value) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.Log2(value); +#else + return Log2SoftwareFallback(value); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so will work on every platform/runtime. + /// + /// + /// Description of this bit hacking can be found here: + /// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer + /// + /// The value. + private static int Log2SoftwareFallback(uint value) + { + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return Unsafe.AddByteOffset( + ref MemoryMarshal.GetReference(Log2DeBruijn), + (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here + } +#endif } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 4faf577fd9..b530a37e77 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -532,6 +532,7 @@ namespace SixLabors.ImageSharp /// /// Performs a multiplication and an addition of the . /// + /// ret = (vm0 * vm1) + va /// The vector to add to the intermediate result. /// The first vector to multiply. /// The second vector to multiply. @@ -552,6 +553,30 @@ namespace SixLabors.ImageSharp } } + /// + /// Performs a multiplication and a substraction of the . + /// + /// ret = (vm0 * vm1) - vs + /// The vector to substract from the intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector256 MultiplySubstract( + in Vector256 vs, + in Vector256 vm0, + in Vector256 vm1) + { + if (Fma.IsSupported) + { + return Fma.MultiplySubtract(vm1, vm0, vs); + } + else + { + return Avx.Subtract(Avx.Multiply(vm0, vm1), vs); + } + } + /// /// as many elements as possible, slicing them down (keeping the remainder). /// diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 4a6e6abcb6..efc0e0e152 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 534aba8f5a..9b3abd298b 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 5007833539..059bd9f312 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 6b19987cb1..0ba368df64 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 0000000000..2edf76e7d5 --- /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 a5d129c92c..02590ca253 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 98% rename from src/ImageSharp/Formats/Png/Zlib/Deflater.cs rename to src/ImageSharp/Compression/Zlib/Deflater.cs index 8389215813..7ff8342aac 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. @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The number of compressed bytes added to the output, or 0 if either /// or returns true or length is zero. /// - public int Deflate(byte[] output, int offset, int length) + public int Deflate(Span output, int offset, int length) { int origLength = length; 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 ec224d748d..30bd75ffcd 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 94% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs rename to src/ImageSharp/Compression/Zlib/DeflaterEngine.cs index 797f5d2101..506b0f2c1c 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 @@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// This array contains the part of the uncompressed stream that /// is of relevance. The current character is indexed by strstart. /// - private IManagedByteBuffer windowMemoryOwner; + private IMemoryOwner windowMemoryOwner; private MemoryHandle windowMemoryHandle; - private readonly byte[] window; + private readonly Memory window; private readonly byte* pinnedWindowPointer; private int maxChain; @@ -153,19 +153,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Create pinned pointers to the various buffers to allow indexing // without bounds checks. - this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); - this.window = this.windowMemoryOwner.Array; - this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); + this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Memory; + this.windowMemoryHandle = this.window.Pin(); this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); this.head = this.headMemoryOwner.Memory; - this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); + this.headMemoryHandle = this.head.Pin(); this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); this.prev = this.prevMemoryOwner.Memory; - this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin(); + this.prevMemoryHandle = this.prev.Pin(); this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; // We start at index 1, to avoid an implementation deficiency, that @@ -303,7 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib case DeflaterConstants.DEFLATE_STORED: if (this.strstart > this.blockStart) { - this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib case DeflaterConstants.DEFLATE_FAST: if (this.strstart > this.blockStart) { - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (this.strstart > this.blockStart) { - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -362,7 +362,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib more = this.inputEnd - this.inputOff; } - Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more); + Unsafe.CopyBlockUnaligned( + ref this.window.Span[this.strstart + this.lookahead], + ref this.inputBuf[this.inputOff], + unchecked((uint)more)); this.inputOff += more; this.lookahead += more; @@ -426,7 +429,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private void SlideWindow() { - Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE); + Unsafe.CopyBlockUnaligned( + ref this.window.Span[0], + ref this.window.Span[DeflaterConstants.WSIZE], + DeflaterConstants.WSIZE); + this.matchStart -= DeflaterConstants.WSIZE; this.strstart -= DeflaterConstants.WSIZE; this.blockStart -= DeflaterConstants.WSIZE; @@ -663,7 +670,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib lastBlock = false; } - this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock); + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock); this.blockStart += storedLength; return !(lastBlock || storedLength == 0); } @@ -683,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (this.lookahead == 0) { // We are flushing everything - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); this.blockStart = this.strstart; return false; } @@ -743,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (this.huffman.IsFull()) { bool lastBlock = finish && (this.lookahead == 0); - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock); this.blockStart = this.strstart; return !lastBlock; } @@ -771,7 +778,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.prevAvailable = false; // We are flushing everything - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); this.blockStart = this.strstart; return false; } @@ -846,7 +853,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; - this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock); + this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock); this.blockStart += len; return !lastBlock; } diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs similarity index 97% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs rename to src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs index 96b47fb24b..27a8d5671d 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. @@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private Tree blTree; // Buffer for distances - private readonly IMemoryOwner distanceManagedBuffer; + private readonly IMemoryOwner distanceMemoryOwner; private readonly short* pinnedDistanceBuffer; private MemoryHandle distanceBufferHandle; - private readonly IMemoryOwner literalManagedBuffer; + private readonly IMemoryOwner literalMemoryOwner; private readonly short* pinnedLiteralBuffer; private MemoryHandle literalBufferHandle; @@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); - this.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize); - this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); + this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin(); this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; - this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize); - this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin(); + this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin(); this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Count of bytes to write /// True if this is the last block [MethodImpl(InliningOptions.ShortMethod)] - public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) { this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); this.Pending.AlignToByte(); @@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Index of first byte to flush /// Count of bytes to flush /// True if this is the last block - public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) { this.literalTree.Frequencies[EofSymbol]++; @@ -286,13 +286,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib + this.extraBits; int static_len = this.extraBits; - ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); + ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); for (int i = 0; i < LiteralNumber; i++) { static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); } - ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); + ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); for (int i = 0; i < DistanceNumber; i++) { static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); @@ -419,9 +419,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.Pending.Dispose(); this.distanceBufferHandle.Dispose(); - this.distanceManagedBuffer.Dispose(); + this.distanceMemoryOwner.Dispose(); this.literalBufferHandle.Dispose(); - this.literalManagedBuffer.Dispose(); + this.literalMemoryOwner.Dispose(); this.literalTree.Dispose(); this.blTree.Dispose(); @@ -484,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private IMemoryOwner frequenciesMemoryOwner; private MemoryHandle frequenciesMemoryHandle; - private IManagedByteBuffer lengthsMemoryOwner; + private IMemoryOwner lengthsMemoryOwner; private MemoryHandle lengthsMemoryHandle; public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) @@ -498,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; - this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements); + this.lengthsMemoryOwner = memoryAllocator.Allocate(elements); this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); this.Length = (byte*)this.lengthsMemoryHandle.Pointer; diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs similarity index 85% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs rename to src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs index 5c5651996f..d949ddf38c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; 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 @@ -14,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib internal sealed class DeflaterOutputStream : Stream { private const int BufferLength = 512; - private IManagedByteBuffer memoryOwner; - private readonly byte[] buffer; + private IMemoryOwner memoryOwner; + private readonly Memory buffer; private Deflater deflater; private readonly Stream rawStream; private bool isDisposed; @@ -29,8 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) { this.rawStream = rawStream; - this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength); - this.buffer = this.memoryOwner.Array; + this.memoryOwner = memoryAllocator.Allocate(BufferLength); + this.buffer = this.memoryOwner.Memory; this.deflater = new Deflater(memoryAllocator, compressionLevel); } @@ -49,15 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public override long Position { - get - { - return this.rawStream.Position; - } + get => this.rawStream.Position; - set - { - throw new NotSupportedException(); - } + set => throw new NotSupportedException(); } /// @@ -93,14 +88,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { while (flushing || !this.deflater.IsNeedingInput) { - int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength); + int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); if (deflateCount <= 0) { break; } - this.rawStream.Write(this.buffer, 0, deflateCount); + this.rawStream.Write(this.buffer.Span.Slice(0, deflateCount)); } if (!this.deflater.IsNeedingInput) @@ -114,13 +109,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.deflater.Finish(); while (!this.deflater.IsFinished) { - int len = this.deflater.Deflate(this.buffer, 0, BufferLength); + int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); if (len <= 0) { break; } - this.rawStream.Write(this.buffer, 0, len); + this.rawStream.Write(this.buffer.Span.Slice(0, len)); } if (!this.deflater.IsFinished) diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs similarity index 83% rename from src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs rename to src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs index f702a7eada..8f2c8d3987 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs @@ -4,18 +4,19 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Png.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib { /// /// Stores pending data for writing data to the Deflater. /// internal sealed unsafe class DeflaterPendingBuffer : IDisposable { - private readonly byte[] buffer; + private readonly Memory buffer; private readonly byte* pinnedBuffer; - private IManagedByteBuffer bufferMemoryOwner; + private IMemoryOwner bufferMemoryOwner; private MemoryHandle bufferMemoryHandle; private int start; @@ -29,9 +30,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The memory allocator to use for buffer allocations. public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) { - this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); - this.buffer = this.bufferMemoryOwner.Array; - this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin(); + this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE); + this.buffer = this.bufferMemoryOwner.Memory; + this.bufferMemoryHandle = this.buffer.Pin(); this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; } @@ -70,9 +71,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The offset of first byte to write. /// The number of bytes to write. [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBlock(byte[] block, int offset, int length) + public void WriteBlock(ReadOnlySpan block, int offset, int length) { - Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref this.buffer.Span[this.end], + ref MemoryMarshal.GetReference(block.Slice(offset)), + unchecked((uint)length)); + this.end += length; } @@ -136,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// The offset into output array. /// The maximum number of bytes to store. /// The number of bytes flushed. - public int Flush(byte[] output, int offset, int length) + public int Flush(Span output, int offset, int length) { if (this.BitCount >= 8) { @@ -149,13 +154,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { length = this.end - this.start; - Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); this.start = 0; this.end = 0; } else { - Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); this.start += length; } 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 06c6e3dea4..44883665ab 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 52ef0e85ba..f4b0543b84 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 062bcb229c..49b7aa79b0 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/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index f6fefda485..8919befcb2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -817,31 +817,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp padding = 4 - padding; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean)) + using IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean); + TPixel color = default; + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - TPixel color = default; - Span rowSpan = row.GetSpan(); + int newY = Invert(y, height, inverted); + this.stream.Read(rowSpan); + int offset = 0; + Span pixelRow = pixels.GetRowSpan(newY); - for (int y = 0; y < height; y++) + for (int x = 0; x < arrayWidth; x++) { - int newY = Invert(y, height, inverted); - this.stream.Read(row.Array, 0, row.Length()); - int offset = 0; - Span pixelRow = pixels.GetRowSpan(newY); - - for (int x = 0; x < arrayWidth; x++) + int colOffset = x * ppb; + for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) { - int colOffset = x * ppb; - for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) - { - int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - - color.FromBgr24(Unsafe.As(ref colors[colorIndex])); - pixelRow[newX] = color; - } + int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - offset++; + color.FromBgr24(Unsafe.As(ref colors[colorIndex])); + pixelRow[newX] = color; } + + offset++; } } } @@ -873,29 +871,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp int greenMaskBits = CountBits((uint)greenMask); int blueMaskBits = CountBits((uint)blueMask); - using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) + using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); + Span bufferSpan = buffer.GetSpan(); + + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.stream.Read(buffer.Array, 0, stride); - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + this.stream.Read(bufferSpan); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); - int offset = 0; - for (int x = 0; x < width; x++) - { - short temp = BitConverter.ToInt16(buffer.Array, offset); + int offset = 0; + for (int x = 0; x < width; x++) + { + short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan.Slice(offset)); - // Rescale values, so the values range from 0 to 255. - int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); - int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); - int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); - var rgb = new Rgb24((byte)r, (byte)g, (byte)b); + // Rescale values, so the values range from 0 to 255. + int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); + int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); + int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); + var rgb = new Rgb24((byte)r, (byte)g, (byte)b); - color.FromRgb24(rgb); - pixelRow[x] = color; - offset += 2; - } + color.FromRgb24(rgb); + pixelRow[x] = color; + offset += 2; } } } @@ -928,20 +926,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 3); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding); + Span rowSpan = row.GetSpan(); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding)) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - PixelOperations.Instance.FromBgr24Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } + this.stream.Read(rowSpan); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgr24Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); } } @@ -957,20 +954,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + Span rowSpan = row.GetSpan(); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } + this.stream.Read(rowSpan); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); } } @@ -987,87 +983,85 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); - - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) - using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + using IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width); + Span rowSpan = row.GetSpan(); + Span bgraRowSpan = bgraRow.GetSpan(); + long currentPosition = this.stream.Position; + bool hasAlpha = false; + + // Loop though the rows checking each pixel. We start by assuming it's + // an BGR0 image. If we hit a non-zero alpha value, then we know it's + // actually a BGRA image, and change tactics accordingly. + for (int y = 0; y < height; y++) { - Span bgraRowSpan = bgraRow.GetSpan(); - long currentPosition = this.stream.Position; - bool hasAlpha = false; - - // Loop though the rows checking each pixel. We start by assuming it's - // an BGR0 image. If we hit a non-zero alpha value, then we know it's - // actually a BGRA image, and change tactics accordingly. - for (int y = 0; y < height; y++) - { - this.stream.Read(row); + this.stream.Read(rowSpan); - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - bgraRowSpan, - width); - - // Check each pixel in the row to see if it has an alpha value. - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - if (bgra.A > 0) - { - hasAlpha = true; - break; - } - } + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + bgraRowSpan, + width); - if (hasAlpha) + // Check each pixel in the row to see if it has an alpha value. + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + if (bgra.A > 0) { + hasAlpha = true; break; } } - // Reset our stream for a second pass. - this.stream.Position = currentPosition; - - // Process the pixels in bulk taking the raw alpha component value. if (hasAlpha) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } - - return; + break; } + } + + // Reset our stream for a second pass. + this.stream.Position = currentPosition; - // Slow path. We need to set each alpha component value to fully opaque. + // Process the pixels in bulk taking the raw alpha component value. + if (hasAlpha) + { for (int y = 0; y < height; y++) { - this.stream.Read(row); - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - bgraRowSpan, - width); + this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); Span pixelSpan = pixels.GetRowSpan(newY); - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - bgra.A = byte.MaxValue; - ref TPixel pixel = ref pixelSpan[x]; - pixel.FromBgra32(bgra); - } + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); + } + + return; + } + + // Slow path. We need to set each alpha component value to fully opaque. + for (int y = 0; y < height; y++) + { + this.stream.Read(rowSpan); + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + bgraRowSpan, + width); + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + bgra.A = byte.MaxValue; + ref TPixel pixel = ref pixelSpan[x]; + pixel.FromBgra32(bgra); } } } @@ -1108,44 +1102,44 @@ namespace SixLabors.ImageSharp.Formats.Bmp bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; - using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) + using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); + Span bufferSpan = buffer.GetSpan(); + + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + this.stream.Read(bufferSpan); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); + + int offset = 0; + for (int x = 0; x < width; x++) { - this.stream.Read(buffer.Array, 0, stride); - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan.Slice(offset)); - int offset = 0; - for (int x = 0; x < width; x++) + if (unusualBitMask) { - uint temp = BitConverter.ToUInt32(buffer.Array, offset); - - if (unusualBitMask) - { - uint r = (uint)(temp & redMask) >> rightShiftRedMask; - uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; - uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; - float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; - var vector4 = new Vector4( - r * invMaxValueRed, - g * invMaxValueGreen, - b * invMaxValueBlue, - alpha); - color.FromVector4(vector4); - } - else - { - byte r = (byte)((temp & redMask) >> rightShiftRedMask); - byte g = (byte)((temp & greenMask) >> rightShiftGreenMask); - byte b = (byte)((temp & blueMask) >> rightShiftBlueMask); - byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255; - color.FromRgba32(new Rgba32(r, g, b, a)); - } - - pixelRow[x] = color; - offset += 4; + uint r = (uint)(temp & redMask) >> rightShiftRedMask; + uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; + uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; + float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; + var vector4 = new Vector4( + r * invMaxValueRed, + g * invMaxValueGreen, + b * invMaxValueBlue, + alpha); + color.FromVector4(vector4); } + else + { + byte r = (byte)((temp & redMask) >> rightShiftRedMask); + byte g = (byte)((temp & greenMask) >> rightShiftGreenMask); + byte b = (byte)((temp & blueMask) >> rightShiftBlueMask); + byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255; + color.FromRgba32(new Rgba32(r, g, b, a)); + } + + pixelRow[x] = color; + offset += 4; } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 5cf54388d3..c6ca5b09d2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; - this.quantizer = options.Quantizer ?? KnownQuantizers.Wu; + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; } /// @@ -257,7 +257,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } - private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); /// /// Writes the 32bit color palette to the stream. @@ -268,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write32Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -294,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int width = pixels.Width; int rowBytesWithoutPadding = width * 3; - using (IManagedByteBuffer row = this.AllocateRow(width, 3)) + using IMemoryOwner row = this.AllocateRow(width, 3); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - width); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + width); + stream.Write(rowSpan); } } @@ -320,20 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int width = pixels.Width; int rowBytesWithoutPadding = width * 2; - using (IManagedByteBuffer row = this.AllocateRow(width, 2)) + using IMemoryOwner row = this.AllocateRow(width, 2); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - pixelSpan.Length); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + stream.Write(rowSpan); } } @@ -347,17 +348,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { bool isL8 = typeof(TPixel) == typeof(L8); - using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize8Bit, AllocationOptions.Clean); + Span colorPalette = colorPaletteBuffer.GetSpan(); + + if (isL8) { - Span colorPalette = colorPaletteBuffer.GetSpan(); - if (isL8) - { - this.Write8BitGray(stream, image, colorPalette); - } - else - { - this.Write8BitColor(stream, image, colorPalette); - } + this.Write8BitGray(stream, image, colorPalette); + } + else + { + this.Write8BitColor(stream, image, colorPalette); } } @@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp MaxColors = 16 }); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; @@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp MaxColors = 2 }); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 2f6b45aff9..e59dad6826 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -2,12 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; -using System.Threading.Tasks; + using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The global color table. /// - private IManagedByteBuffer globalColorTable; + private IMemoryOwner globalColorTable; /// /// The area to restore. @@ -323,12 +324,12 @@ namespace SixLabors.ImageSharp.Formats.Gif continue; } - using (IManagedByteBuffer commentsBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(length)) - { - this.stream.Read(commentsBuffer.Array, 0, length); - string commentPart = GifConstants.Encoding.GetString(commentsBuffer.Array, 0, length); - stringBuilder.Append(commentPart); - } + using IMemoryOwner commentsBuffer = this.MemoryAllocator.Allocate(length); + Span commentsSpan = commentsBuffer.GetSpan(); + + this.stream.Read(commentsSpan); + string commentPart = GifConstants.Encoding.GetString(commentsSpan); + stringBuilder.Append(commentPart); } if (stringBuilder.Length > 0) @@ -348,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { this.ReadImageDescriptor(); - IManagedByteBuffer localColorTable = null; + IMemoryOwner localColorTable = null; Buffer2D indices = null; try { @@ -356,8 +357,8 @@ namespace SixLabors.ImageSharp.Formats.Gif if (this.imageDescriptor.LocalColorTableFlag) { int length = this.imageDescriptor.LocalColorTableSize * 3; - localColorTable = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); - this.stream.Read(localColorTable.Array, 0, length); + localColorTable = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + this.stream.Read(localColorTable.GetSpan()); } indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); @@ -441,6 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Gif int descriptorRight = descriptorLeft + descriptor.Width; bool transFlag = this.graphicsControlExtension.TransparencyFlag; byte transIndex = this.graphicsControlExtension.TransparencyIndex; + int colorTableMaxIdx = colorTable.Length - 1; for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) { @@ -487,7 +489,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // #403 The left + width value can be larger than the image width for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); + int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); @@ -497,7 +499,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); + int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); if (transIndex != index) { ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); @@ -621,10 +623,10 @@ namespace SixLabors.ImageSharp.Formats.Gif int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; this.gifMetadata.GlobalColorTableLength = globalColorTableLength; - this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); + this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); // Read the global color table data from the stream - stream.Read(this.globalColorTable.Array, 0, globalColorTableLength); + stream.Read(this.globalColorTable.GetSpan()); } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 9c1e95285c..05ea14e9ce 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -7,7 +7,6 @@ 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.Memory; using SixLabors.ImageSharp.Metadata; @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel sampling strategy for global quantization. /// - private IPixelSamplingStrategy pixelSamplingStrategy; + private readonly IPixelSamplingStrategy pixelSamplingStrategy; /// /// Initializes a new instance of the class. @@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif // The palette quantizer can reuse the same pixel map across multiple frames // since the palette is unchanging. This allows a reduction of memory usage across // multi frame gifs using a global palette. - EuclideanPixelMap pixelMap = default; - bool pixelMapSet = false; + PaletteQuantizer paletteFrameQuantizer = default; + bool quantizerInitialized = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; @@ -166,17 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - if (!pixelMapSet) + if (!quantizerInitialized) { - pixelMapSet = true; - pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); + quantizerInitialized = true; + paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); } - using var paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, pixelMap); using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } } + + paletteFrameQuantizer.Dispose(); } private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - ratio = (byte)(((1 / vr) * 64) - 15); + ratio = (byte)((1 / vr * 64) - 15); } } } @@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - for (var i = 0; i < metadata.Comments.Count; i++) + for (int i = 0; i < metadata.Comments.Count; i++) { string comment = metadata.Comments[i]; this.buffer[0] = GifConstants.ExtensionIntroducer; @@ -470,14 +470,16 @@ namespace SixLabors.ImageSharp.Formats.Gif // The maximum number of colors for the bit depth int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); - using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength, AllocationOptions.Clean); + Span colorTableSpan = colorTable.GetSpan(); + PixelOperations.Instance.ToRgb24Bytes( this.configuration, image.Palette.Span, - colorTable.GetSpan(), + colorTableSpan, image.Palette.Length); - stream.Write(colorTable.Array, 0, colorTableLength); + stream.Write(colorTableSpan); } /// diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 075c708b6a..0f8b1e16d9 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 63b404cc44..af95312254 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/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 2d19f5ce26..8ca7b0c801 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Represents a Jpeg block with coefficients. /// - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Explicit)] internal partial struct Block8x8F : IEquatable { /// @@ -27,29 +27,69 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public const int Size = 64; #pragma warning disable SA1600 // ElementsMustBeDocumented + [FieldOffset(0)] public Vector4 V0L; + [FieldOffset(16)] public Vector4 V0R; + [FieldOffset(32)] public Vector4 V1L; + [FieldOffset(48)] public Vector4 V1R; + [FieldOffset(64)] public Vector4 V2L; + [FieldOffset(80)] public Vector4 V2R; + [FieldOffset(96)] public Vector4 V3L; + [FieldOffset(112)] public Vector4 V3R; + [FieldOffset(128)] public Vector4 V4L; + [FieldOffset(144)] public Vector4 V4R; + [FieldOffset(160)] public Vector4 V5L; + [FieldOffset(176)] public Vector4 V5R; + [FieldOffset(192)] public Vector4 V6L; + [FieldOffset(208)] public Vector4 V6R; + [FieldOffset(224)] public Vector4 V7L; + [FieldOffset(240)] public Vector4 V7R; + +#if SUPPORTS_RUNTIME_INTRINSICS + /// + /// A number of rows of 8 scalar coefficients each in + /// + public const int RowCount = 8; + + [FieldOffset(0)] + public Vector256 V0; + [FieldOffset(32)] + public Vector256 V1; + [FieldOffset(64)] + public Vector256 V2; + [FieldOffset(96)] + public Vector256 V3; + [FieldOffset(128)] + public Vector256 V4; + [FieldOffset(160)] + public Vector256 V5; + [FieldOffset(192)] + public Vector256 V6; + [FieldOffset(224)] + public Vector256 V7; +#endif #pragma warning restore SA1600 // ElementsMustBeDocumented /// @@ -278,14 +318,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components if (Avx.IsSupported) { var valueVec = Vector256.Create(value); - Unsafe.As>(ref this.V0L) = Avx.Multiply(Unsafe.As>(ref this.V0L), valueVec); - Unsafe.As>(ref this.V1L) = Avx.Multiply(Unsafe.As>(ref this.V1L), valueVec); - Unsafe.As>(ref this.V2L) = Avx.Multiply(Unsafe.As>(ref this.V2L), valueVec); - Unsafe.As>(ref this.V3L) = Avx.Multiply(Unsafe.As>(ref this.V3L), valueVec); - Unsafe.As>(ref this.V4L) = Avx.Multiply(Unsafe.As>(ref this.V4L), valueVec); - Unsafe.As>(ref this.V5L) = Avx.Multiply(Unsafe.As>(ref this.V5L), valueVec); - Unsafe.As>(ref this.V6L) = Avx.Multiply(Unsafe.As>(ref this.V6L), valueVec); - Unsafe.As>(ref this.V7L) = Avx.Multiply(Unsafe.As>(ref this.V7L), valueVec); + this.V0 = Avx.Multiply(this.V0, valueVec); + this.V1 = Avx.Multiply(this.V1, valueVec); + this.V2 = Avx.Multiply(this.V2, valueVec); + this.V3 = Avx.Multiply(this.V3, valueVec); + this.V4 = Avx.Multiply(this.V4, valueVec); + this.V5 = Avx.Multiply(this.V5, valueVec); + this.V6 = Avx.Multiply(this.V6, valueVec); + this.V7 = Avx.Multiply(this.V7, valueVec); } else #endif @@ -319,45 +359,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components #if SUPPORTS_RUNTIME_INTRINSICS if (Avx.IsSupported) { - Unsafe.As>(ref this.V0L) - = Avx.Multiply( - Unsafe.As>(ref this.V0L), - Unsafe.As>(ref other.V0L)); - - Unsafe.As>(ref this.V1L) - = Avx.Multiply( - Unsafe.As>(ref this.V1L), - Unsafe.As>(ref other.V1L)); - - Unsafe.As>(ref this.V2L) - = Avx.Multiply( - Unsafe.As>(ref this.V2L), - Unsafe.As>(ref other.V2L)); - - Unsafe.As>(ref this.V3L) - = Avx.Multiply( - Unsafe.As>(ref this.V3L), - Unsafe.As>(ref other.V3L)); - - Unsafe.As>(ref this.V4L) - = Avx.Multiply( - Unsafe.As>(ref this.V4L), - Unsafe.As>(ref other.V4L)); - - Unsafe.As>(ref this.V5L) - = Avx.Multiply( - Unsafe.As>(ref this.V5L), - Unsafe.As>(ref other.V5L)); - - Unsafe.As>(ref this.V6L) - = Avx.Multiply( - Unsafe.As>(ref this.V6L), - Unsafe.As>(ref other.V6L)); - - Unsafe.As>(ref this.V7L) - = Avx.Multiply( - Unsafe.As>(ref this.V7L), - Unsafe.As>(ref other.V7L)); + this.V0 = Avx.Multiply(this.V0, other.V0); + this.V1 = Avx.Multiply(this.V1, other.V1); + this.V2 = Avx.Multiply(this.V2, other.V2); + this.V3 = Avx.Multiply(this.V3, other.V3); + this.V4 = Avx.Multiply(this.V4, other.V4); + this.V5 = Avx.Multiply(this.V5, other.V5); + this.V6 = Avx.Multiply(this.V6, other.V6); + this.V7 = Avx.Multiply(this.V7, other.V7); } else #endif @@ -392,14 +401,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components if (Avx.IsSupported) { var valueVec = Vector256.Create(value); - Unsafe.As>(ref this.V0L) = Avx.Add(Unsafe.As>(ref this.V0L), valueVec); - Unsafe.As>(ref this.V1L) = Avx.Add(Unsafe.As>(ref this.V1L), valueVec); - Unsafe.As>(ref this.V2L) = Avx.Add(Unsafe.As>(ref this.V2L), valueVec); - Unsafe.As>(ref this.V3L) = Avx.Add(Unsafe.As>(ref this.V3L), valueVec); - Unsafe.As>(ref this.V4L) = Avx.Add(Unsafe.As>(ref this.V4L), valueVec); - Unsafe.As>(ref this.V5L) = Avx.Add(Unsafe.As>(ref this.V5L), valueVec); - Unsafe.As>(ref this.V6L) = Avx.Add(Unsafe.As>(ref this.V6L), valueVec); - Unsafe.As>(ref this.V7L) = Avx.Add(Unsafe.As>(ref this.V7L), valueVec); + this.V0 = Avx.Add(this.V0, valueVec); + this.V1 = Avx.Add(this.V1, valueVec); + this.V2 = Avx.Add(this.V2, valueVec); + this.V3 = Avx.Add(this.V3, valueVec); + this.V4 = Avx.Add(this.V4, valueVec); + this.V5 = Avx.Add(this.V5, valueVec); + this.V6 = Avx.Add(this.V6, valueVec); + this.V7 = Avx.Add(this.V7, valueVec); } else #endif @@ -468,81 +477,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components DivideRoundAll(ref dest, ref qt); } - /// - /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block. - /// - /// The destination block. - /// The source block. - public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan source) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported) - { - Scale16X16To8X8Vectorized(ref destination, source); - return; - } -#endif - - Scale16X16To8X8Scalar(ref destination, source); - } - - private static void Scale16X16To8X8Vectorized(ref Block8x8F destination, ReadOnlySpan source) - { -#if SUPPORTS_RUNTIME_INTRINSICS - Debug.Assert(Avx2.IsSupported, "AVX2 is required to execute this method"); - - var f2 = Vector256.Create(2f); - var f025 = Vector256.Create(0.25f); - Vector256 switchInnerDoubleWords = Unsafe.As>(ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskSwitchInnerDWords8x32)); - ref Vector256 destRef = ref Unsafe.As>(ref destination); - - for (int i = 0; i < 2; i++) - { - ref Vector256 in1 = ref Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), 2 * i)); - ref Vector256 in2 = ref Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), (2 * i) + 1)); - - for (int j = 0; j < 8; j += 2) - { - Vector256 a = Unsafe.Add(ref in1, j); - Vector256 b = Unsafe.Add(ref in1, j + 1); - Vector256 c = Unsafe.Add(ref in2, j); - Vector256 d = Unsafe.Add(ref in2, j + 1); - - Vector256 calc1 = Avx.Shuffle(a, c, 0b10_00_10_00); - Vector256 calc2 = Avx.Shuffle(a, c, 0b11_01_11_01); - Vector256 calc3 = Avx.Shuffle(b, d, 0b10_00_10_00); - Vector256 calc4 = Avx.Shuffle(b, d, 0b11_01_11_01); - - Vector256 sum = Avx.Add(Avx.Add(calc1, calc2), Avx.Add(calc3, calc4)); - Vector256 add = Avx.Add(sum, f2); - Vector256 res = Avx.Multiply(add, f025); - - destRef = Avx2.PermuteVar8x32(res, switchInnerDoubleWords); - destRef = ref Unsafe.Add(ref destRef, 1); - } - } -#endif - } - - private static unsafe void Scale16X16To8X8Scalar(ref Block8x8F destination, ReadOnlySpan source) - { - for (int i = 0; i < 4; i++) - { - int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - Block8x8F iSource = source[i]; - - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int j = (16 * y) + (2 * x); - float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9]; - destination[(8 * y) + x + dstOff] = (sum + 2) * .25F; - } - } - } - } - [MethodImpl(InliningOptions.ShortMethod)] private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) { @@ -553,19 +487,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components var vadd = Vector256.Create(.5F); var vone = Vector256.Create(1f); - ref Vector256 aBase = ref Unsafe.AsRef(Unsafe.As>(ref a.V0L)); - ref Vector256 bBase = ref Unsafe.AsRef(Unsafe.As>(ref b.V0L)); - ref Vector256 aEnd = ref Unsafe.Add(ref aBase, 8); - - do + for (int i = 0; i < RowCount; i++) { - Vector256 voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd); - Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff); - - aBase = ref Unsafe.Add(ref aBase, 1); - bBase = ref Unsafe.Add(ref bBase, 1); + ref Vector256 aRow = ref Unsafe.Add(ref a.V0, i); + ref Vector256 bRow = ref Unsafe.Add(ref b.V0, i); + Vector256 voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aRow), vone), vadd); + aRow = Avx.Add(Avx.Divide(aRow, bRow), voff); } - while (Unsafe.IsAddressLessThan(ref aBase, ref aEnd)); } else #endif @@ -805,26 +733,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Vector256 t0 = Avx.UnpackLow(r0, r1); Vector256 t2 = Avx.UnpackLow(r2, r3); Vector256 v = Avx.Shuffle(t0, t2, 0x4E); - Unsafe.As>(ref d.V0L) = Avx.Blend(t0, v, 0xCC); - Unsafe.As>(ref d.V1L) = Avx.Blend(t2, v, 0x33); + d.V0 = Avx.Blend(t0, v, 0xCC); + d.V1 = Avx.Blend(t2, v, 0x33); Vector256 t4 = Avx.UnpackLow(r4, r5); Vector256 t6 = Avx.UnpackLow(r6, r7); v = Avx.Shuffle(t4, t6, 0x4E); - Unsafe.As>(ref d.V4L) = Avx.Blend(t4, v, 0xCC); - Unsafe.As>(ref d.V5L) = Avx.Blend(t6, v, 0x33); + d.V4 = Avx.Blend(t4, v, 0xCC); + d.V5 = Avx.Blend(t6, v, 0x33); Vector256 t1 = Avx.UnpackHigh(r0, r1); Vector256 t3 = Avx.UnpackHigh(r2, r3); v = Avx.Shuffle(t1, t3, 0x4E); - Unsafe.As>(ref d.V2L) = Avx.Blend(t1, v, 0xCC); - Unsafe.As>(ref d.V3L) = Avx.Blend(t3, v, 0x33); + d.V2 = Avx.Blend(t1, v, 0xCC); + d.V3 = Avx.Blend(t3, v, 0x33); Vector256 t5 = Avx.UnpackHigh(r4, r5); Vector256 t7 = Avx.UnpackHigh(r6, r7); v = Avx.Shuffle(t5, t7, 0x4E); - Unsafe.As>(ref d.V6L) = Avx.Blend(t5, v, 0xCC); - Unsafe.As>(ref d.V7L) = Avx.Blend(t7, v, 0x33); + d.V6 = Avx.Blend(t5, v, 0xCC); + d.V7 = Avx.Blend(t7, v, 0x33); } else #endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index bc2c7634b5..ec77bf87db 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -1,12 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// /// A compiled look-up table representation of a huffmanSpec. - /// Each value maps to a uint32 of which the 8 most significant bits hold the - /// codeword size in bits and the 24 least significant bits hold the codeword. + /// Each value maps to a int32 of which the 24 most significant bits hold the + /// codeword in bits and the 8 least significant bits hold the codeword size. /// The maximum codeword size is 16 bits. /// internal readonly struct HuffmanLut @@ -44,17 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - this.Values = new uint[maxValue + 1]; + this.Values = new int[maxValue + 1]; int code = 0; int k = 0; for (int i = 0; i < spec.Count.Length; i++) { - int bits = (i + 1) << 24; + int len = i + 1; for (int j = 0; j < spec.Count[i]; j++) { - this.Values[spec.Values[k]] = (uint)(bits | code); + this.Values[spec.Values[k]] = len | (code << 8); code++; k++; } @@ -66,6 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Gets the collection of huffman values. /// - public uint[] Values { get; } + public int[] Values { get; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs new file mode 100644 index 0000000000..331da275cc --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -0,0 +1,524 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using System.Threading; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class HuffmanScanEncoder + { + /// + /// Compiled huffman tree to encode given values. + /// + /// Yields codewords by index consisting of [run length | bitsize]. + private HuffmanLut[] huffmanTables; + + /// + /// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count). + /// + /// + /// This is subject to change, 1024 seems to be the best value in terms of performance. + /// expects it to be at least 8 (see comments in method body). + /// + private const int EmitBufferSizeInBytes = 1024; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. + /// + private readonly byte[] emitBuffer = new byte[EmitBufferSizeInBytes]; + + /// + /// Number of filled bytes in buffer + /// + private int emitLen = 0; + + /// + /// Emmited bits 'micro buffer' before being transfered to the . + /// + private int accumulatedBits; + + /// + /// Number of jagged bits stored in + /// + private int bitCount; + + private Block8x8F temporalBlock1; + private Block8x8F temporalBlock2; + + /// + /// The output stream. All attempted writes after the first error become no-ops. + /// + private readonly Stream target; + + public HuffmanScanEncoder(Stream outputStream) + { + this.target = outputStream; + } + + /// + /// Encodes the image with no subsampling. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + /// Luminance quantization table provided by the callee + /// Chrominance quantization table provided by the callee + /// The token to monitor for cancellation. + public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + + var unzig = ZigZag.CreateUnzigTable(); + + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; + RowOctet currentRows = default; + + var pixelConverter = new YCbCrForwardConverter444(frame); + + for (int y = 0; y < pixels.Height; y += 8) + { + cancellationToken.ThrowIfCancellationRequested(); + currentRows.Update(pixelBuffer, y); + + for (int x = 0; x < pixels.Width; x += 8) + { + pixelConverter.Convert(x, y, ref currentRows); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + ref pixelConverter.Y, + ref luminanceQuantTable, + ref unzig); + + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + ref pixelConverter.Cb, + ref chrominanceQuantTable, + ref unzig); + + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + ref pixelConverter.Cr, + ref chrominanceQuantTable, + ref unzig); + } + } + + this.FlushInternalBuffer(); + } + + /// + /// Encodes the image with subsampling. The Cb and Cr components are each subsampled + /// at a factor of 2 both horizontally and vertically. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + /// Luminance quantization table provided by the callee + /// Chrominance quantization table provided by the callee + /// The token to monitor for cancellation. + public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + + var unzig = ZigZag.CreateUnzigTable(); + + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; + RowOctet currentRows = default; + + var pixelConverter = new YCbCrForwardConverter420(frame); + + for (int y = 0; y < pixels.Height; y += 16) + { + cancellationToken.ThrowIfCancellationRequested(); + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 2; i++) + { + int yOff = i * 8; + currentRows.Update(pixelBuffer, y + yOff); + pixelConverter.Convert(x, y, ref currentRows, i); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + ref pixelConverter.YLeft, + ref luminanceQuantTable, + ref unzig); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + ref pixelConverter.YRight, + ref luminanceQuantTable, + ref unzig); + } + + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + ref pixelConverter.Cb, + ref chrominanceQuantTable, + ref unzig); + + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + ref pixelConverter.Cr, + ref chrominanceQuantTable, + ref unzig); + } + } + + this.FlushInternalBuffer(); + } + + /// + /// Encodes the image with no chroma, just luminance. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + /// Luminance quantization table provided by the callee + /// The token to monitor for cancellation. + public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + + var unzig = ZigZag.CreateUnzigTable(); + + // ReSharper disable once InconsistentNaming + int prevDCY = 0; + + var pixelConverter = LuminanceForwardConverter.Create(); + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; + RowOctet currentRows = default; + + for (int y = 0; y < pixels.Height; y += 8) + { + cancellationToken.ThrowIfCancellationRequested(); + currentRows.Update(pixelBuffer, y); + + for (int x = 0; x < pixels.Width; x += 8) + { + pixelConverter.Convert(frame, x, y, ref currentRows); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + ref pixelConverter.Y, + ref luminanceQuantTable, + ref unzig); + } + } + + this.FlushInternalBuffer(); + } + + /// + /// Writes a block of pixel data using the given quantization table, + /// returning the post-quantized DC value of the DCT-transformed block. + /// The block is in natural (not zig-zag) order. + /// + /// The quantization table index. + /// The previous DC value. + /// Source block + /// Quantization table + /// The 8x8 Unzig block. + /// The . + private int WriteBlock( + QuantIndex index, + int prevDC, + ref Block8x8F src, + ref Block8x8F quant, + ref ZigZag unZig) + { + ref Block8x8F refTemp1 = ref this.temporalBlock1; + ref Block8x8F refTemp2 = ref this.temporalBlock2; + + FastFloatingPointDCT.TransformFDCT(ref src, ref refTemp1, ref refTemp2); + + Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); + + // Emit the DC delta. + int dc = (int)refTemp2[0]; + this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); + + // Emit the AC components. + int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; + + int runLength = 0; + int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2); + for (int zig = 1; zig <= lastValuableIndex; zig++) + { + int ac = (int)refTemp2[zig]; + + if (ac == 0) + { + runLength++; + } + else + { + while (runLength > 15) + { + this.EmitHuff(acHuffTable, 0xf0); + runLength -= 16; + } + + this.EmitHuffRLE(acHuffTable, runLength, ac); + runLength = 0; + } + } + + // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over + // this can be done for any number of trailing zeros, even when all 63 ac values are zero + // (Block8x8F.Size - 1) == 63 - last index of the mcu elements + if (lastValuableIndex != Block8x8F.Size - 1) + { + this.EmitHuff(acHuffTable, 0x00); + } + + return dc; + } + + /// + /// Emits the least significant count of bits to the stream write buffer. + /// The precondition is bits + /// + /// < 1<<nBits && nBits <= 16 + /// + /// . + /// + /// The packed bits. + /// The number of bits + [MethodImpl(InliningOptions.ShortMethod)] + private void Emit(int bits, int count) + { + count += this.bitCount; + bits <<= 32 - count; + bits |= this.accumulatedBits; + + // Only write if more than 8 bits. + if (count >= 8) + { + // Track length + while (count >= 8) + { + byte b = (byte)(bits >> 24); + this.emitBuffer[this.emitLen++] = b; + + // Adding stuff byte + // This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte) + // Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker + if (b == byte.MaxValue) + { + this.emitBuffer[this.emitLen++] = byte.MinValue; + } + + bits <<= 8; + count -= 8; + } + + // This can emit 4 times of: + // 1 byte guaranteed + // 1 extra byte.MinValue byte if previous one was byte.MaxValue + // Thus writing (1 + 1) * 4 = 8 bytes max + // So we must check if emit buffer has extra 8 bytes, if not - call stream.Write + if (this.emitLen > EmitBufferSizeInBytes - 8) + { + this.target.Write(this.emitBuffer, 0, this.emitLen); + this.emitLen = 0; + } + } + + this.accumulatedBits = bits; + this.bitCount = count; + } + + /// + /// Emits the given value with the given Huffman encoder. + /// + /// Compiled Huffman spec values. + /// The value to encode. + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitHuff(int[] table, int value) + { + int x = table[value]; + this.Emit(x >> 8, x & 0xff); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitDirectCurrentTerm(int[] table, int value) + { + int a = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + int bt = GetHuffmanEncodingLength((uint)a); + + this.EmitHuff(table, bt); + if (bt > 0) + { + this.Emit(b & ((1 << bt) - 1), bt); + } + } + + /// + /// Emits a run of runLength copies of value encoded with the given Huffman encoder. + /// + /// Compiled Huffman spec values. + /// The number of copies to encode. + /// The value to encode. + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitHuffRLE(int[] table, int runLength, int value) + { + int a = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + int bt = GetHuffmanEncodingLength((uint)a); + + this.EmitHuff(table, (runLength << 4) | bt); + this.Emit(b & ((1 << bt) - 1), bt); + } + + /// + /// Writes remaining bytes from internal buffer to the target stream. + /// + /// Pads last byte with 1's if necessary + private void FlushInternalBuffer() + { + // pad last byte with 1's + int padBitsCount = 8 - (this.bitCount % 8); + if (padBitsCount != 0) + { + this.Emit((1 << padBitsCount) - 1, padBitsCount); + this.target.Write(this.emitBuffer, 0, this.emitLen); + } + } + + /// + /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. + /// + /// + /// This is an internal operation supposed to be used only in class for jpeg encoding. + /// + /// The value. + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetHuffmanEncodingLength(uint value) + { + DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); +#if SUPPORTS_BITOPERATIONS + // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation + // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) + + // BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0 + // Lzcnt would return 32 for input value of 0 - no need to check that with branching + // Fallback code if Lzcnt is not supported still use if-check + // But most modern CPUs support this instruction so this should not be a problem + return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); +#else + // Ideally: + // if 0 - return 0 in this case + // else - return log2(value) + 1 + // + // Hack based on input value constaint: + // We know that input values are guaranteed to be maximum 16 bit large for huffman encoding + // We can safely shift input value for one bit -> log2(value << 1) + // Because of the 16 bit value constraint it won't overflow + // With that input value change we no longer need to add 1 before returning + // And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to + return Numerics.Log2(value << 1); +#endif + } + + /// + /// Returns index of the last non-zero element in given mcu block. + /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. + /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. + /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// + /// + /// This is an internal operation supposed to be used only in class for jpeg encoding. + /// + /// Mcu block. + /// Index of the last non-zero element. + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetLastValuableElementIndex(ref Block8x8F mcu) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + Vector256 zero8 = Vector256.Zero; + + ref Vector256 mcuStride = ref mcu.V0; + + for (int i = 7; i >= 0; i--) + { + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); + + // we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros + if (areEqual != equalityMask) + { + // last index in the stride, we go from the end to the start of the stride + int startIndex = i * 8; + int index = startIndex + 7; + ref float elemRef = ref Unsafe.As(ref mcu); + while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + // this implementation will return -1 if all ac components are zero and dc are zero + return index; + } + } + + return -1; + } + else +#endif + { + int index = Block8x8F.Size - 1; + ref float elemRef = ref Unsafe.As(ref mcu); + + while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + // this implementation will return 0 if all ac components and dc are zero + return index; + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index f9c16c5be7..51364e3c73 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -24,9 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0, 0, 0 }, new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + + // Luminance AC. new HuffmanSpec( new byte[] { @@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }), + + // Chrominance DC. new HuffmanSpec( new byte[] { @@ -132,4 +136,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Values = values; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs index cc81130dd7..fc5b9a8682 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Block8x8F yBlock = ref this.Y; ref L8 l8Start = ref l8Span[0]; - for (int i = 0; i < 64; i++) + for (int i = 0; i < Block8x8F.Size; i++) { ref L8 c = ref Unsafe.Add(ref l8Start, i); yBlock[i] = c.PackedValue; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs index 3c1a02c5aa..15574a32a2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs @@ -92,48 +92,144 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return tables; } - /// - /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertPixelInto( - int r, - int g, - int b, - ref Block8x8F yResult, - ref Block8x8F cbResult, - ref Block8x8F crResult, - int i) + private float CalculateY(byte r, byte g, byte b) { // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; + return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateCb(byte r, byte g, byte b) + { // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; + return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateCr(byte r, byte g, byte b) + { // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; + return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } - public void Convert(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + /// + /// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma. + /// + /// Span of Rgb24 pixel data + /// Resulting Y values block + /// Resulting Cb values block + /// Resulting Cr values block + public void Convert444(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) { ref Rgb24 rgbStart = ref rgbSpan[0]; - for (int i = 0; i < 64; i++) + for (int i = 0; i < Block8x8F.Size; i++) { - ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); - - this.ConvertPixelInto( - c.R, - c.G, - c.B, - ref yBlock, - ref cbBlock, - ref crBlock, - i); + Rgb24 c = Unsafe.Add(ref rgbStart, i); + + yBlock[i] = this.CalculateY(c.R, c.G, c.B); + cbBlock[i] = this.CalculateCb(c.R, c.G, c.B); + crBlock[i] = this.CalculateCr(c.R, c.G, c.B); } } + /// + /// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma. + /// + /// Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param. + /// Span of Rgb24 pixel data + /// First or "left" resulting Y block + /// Second or "right" resulting Y block + /// Resulting Cb values block + /// Resulting Cr values block + /// Row index of the 16x16 block, 0 or 1 + public void Convert420(Span rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) + { + DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row)); + + ref float yBlockLeftRef = ref Unsafe.As(ref yBlockLeft); + ref float yBlockRightRef = ref Unsafe.As(ref yBlockRight); + + // 0-31 or 32-63 + // upper or lower part + int chromaWriteOffset = row * (Block8x8F.Size / 2); + ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As(ref cbBlock), chromaWriteOffset); + ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As(ref crBlock), chromaWriteOffset); + + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 8; i += 2) + { + int yBlockWriteOffset = i * 8; + ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16); + + int chromaOffset = 8 * (i / 2); + + // left + this.ConvertChunk420( + ref stride, + ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset), + ref Unsafe.Add(ref cbBlockRef, chromaOffset), + ref Unsafe.Add(ref crBlockRef, chromaOffset)); + + // right + this.ConvertChunk420( + ref Unsafe.Add(ref stride, 8), + ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset), + ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4), + ref Unsafe.Add(ref crBlockRef, chromaOffset + 4)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock) + { + // jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons) + // each row is 16 pixels wide thus +16 stride reference offset + // resulting luminance (Y`) are sampled at original resolution thus +8 reference offset + for (int k = 0; k < 8; k += 2) + { + ref float yBlockRef = ref Unsafe.Add(ref yBlock, k); + + // top row + Rgb24 px0 = Unsafe.Add(ref stride, k); + Rgb24 px1 = Unsafe.Add(ref stride, k + 1); + yBlockRef = this.CalculateY(px0.R, px0.G, px0.B); + Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B); + + // bottom row + Rgb24 px2 = Unsafe.Add(ref stride, k + 16); + Rgb24 px3 = Unsafe.Add(ref stride, k + 17); + Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B); + Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B); + + // chroma average for 2x2 pixel block + Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3); + Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) + { + return 0.25f + * (this.CalculateCb(px0.R, px0.G, px0.B) + + this.CalculateCb(px1.R, px1.G, px1.B) + + this.CalculateCb(px2.R, px2.G, px2.B) + + this.CalculateCb(px3.R, px3.G, px3.B)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) + { + return 0.25f + * (this.CalculateCr(px0.R, px0.G, px0.B) + + this.CalculateCr(px1.R, px1.G, px1.B) + + this.CalculateCr(px2.R, px2.G, px2.B) + + this.CalculateCr(px3.R, px3.G, px3.B)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Fix(float x) => (int)((x * (1L << ScaleBits)) + 0.5F); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs index 209cc3c6ab..9566ee862a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -27,19 +27,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + public static int AvxCompatibilityPadding + { + // rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total + // Strides are stored sequentially - one big span of 64 * 3 = 192 bytes + // Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits + // Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride: + // stride 0 0 - 192 -(+64bits)-> 256 + // stride 1 192 - 384 -(+64bits)-> 448 + // stride 2 384 - 576 -(+64bits)-> 640 + // stride 3 576 - 768 -(+64bits)-> 832 + // stride 4 768 - 960 -(+64bits)-> 1024 + // stride 5 960 - 1152 -(+64bits)-> 1216 + // stride 6 1152 - 1344 -(+64bits)-> 1408 + // stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION + // + // Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits + // This is not permitted - we are reading foreign memory + // + // 8 byte padding to rgb byte span will solve this problem without extra code in converters + get + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (IsSupported) + { + return 8; + } +#endif + return 0; + } + } + #if SUPPORTS_RUNTIME_INTRINSICS + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; - private static ReadOnlySpan MoveLast24BytesToSeparateLanes => new byte[] - { - 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, - 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0 - }; - private static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, @@ -47,7 +73,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder }; #endif - public static void Convert(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + /// + /// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling + /// + /// Total size of rgb span must be 200 bytes + /// Span of rgb pixels with size of 64 + /// 8x8 destination matrix of Luminance(Y) converted data + /// 8x8 destination matrix of Chrominance(Cb) converted data + /// 8x8 destination matrix of Chrominance(Cr) converted data + public static void Convert444(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) { Debug.Assert(IsSupported, "AVX2 is required to run this converter"); @@ -63,18 +97,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder var f05 = Vector256.Create(0.5f); var zero = Vector256.Create(0).AsByte(); - ref Vector256 inRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - ref Vector256 destYRef = ref Unsafe.As>(ref yBlock); - ref Vector256 destCbRef = ref Unsafe.As>(ref cbBlock); - ref Vector256 destCrRef = ref Unsafe.As>(ref crBlock); + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); + ref Vector256 destYRef = ref yBlock.V0; + ref Vector256 destCbRef = ref cbBlock.V0; + ref Vector256 destCrRef = ref crBlock.V0; var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); Vector256 rgb, rg, bx; Vector256 r, g, b; - for (int i = 0; i < 7; i++) + + const int bytesPerRgbStride = 24; + for (int i = 0; i < 8; i++) { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte(); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); rgb = Avx2.Shuffle(rgb, extractRgbMask); @@ -94,27 +130,130 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); } +#endif + } + + /// + /// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling + /// + public static void Convert420(ReadOnlySpan rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) + { + Debug.Assert(IsSupported, "AVX2 is required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var f128 = Vector256.Create(128f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + var zero = Vector256.Create(0).AsByte(); + + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); + + int destOffset = row * 4; - extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes)); - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte(); - rgb = Avx2.Shuffle(rgb, extractRgbMask); + ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), destOffset); + ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), destOffset); - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); + var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + + Span> rDataLanes = stackalloc Vector256[4]; + Span> gDataLanes = stackalloc Vector256[4]; + Span> bDataLanes = stackalloc Vector256[4]; + + const int bytesPerRgbStride = 24; + for (int i = 0; i < 4; i++) + { + // 16x2 => 8x1 + // left 8x8 column conversions + for (int j = 0; j < 4; j += 2) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); + + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); + + int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); + + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + + rDataLanes[j] = r; + gDataLanes[j] = g; + bDataLanes[j] = b; + } + + // 16x2 => 8x1 + // right 8x8 column conversions + for (int j = 1; j < 4; j += 2) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + rDataLanes[j] = r; + gDataLanes[j] = g; + bDataLanes[j] = b; + } + + r = Scale16x2_8x1(rDataLanes); + g = Scale16x2_8x1(gDataLanes); + b = Scale16x2_8x1(bDataLanes); + + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + } #endif } + +#if SUPPORTS_RUNTIME_INTRINSICS + /// + /// Scales 16x2 matrix to 8x1 using 2x2 average + /// + /// Input matrix consisting of 4 256bit vectors + /// 256bit vector containing upper and lower scaled parts of the input matrix + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector256 Scale16x2_8x1(ReadOnlySpan> v) + { + Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter"); + DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements"); + + var f025 = Vector256.Create(0.25f); + + Vector256 left = Avx.Add(v[0], v[2]); + Vector256 right = Avx.Add(v[1], v[3]); + Vector256 avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025); + + return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle(); + } +#endif } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs new file mode 100644 index 0000000000..a4abd532b3 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. + /// + /// The pixel type to work on + internal ref struct YCbCrForwardConverter420 + where TPixel : unmanaged, IPixel + { + /// + /// Number of pixels processed per single call + /// + private const int PixelsPerSample = 16 * 8; + + /// + /// Total byte size of processed pixels converted from TPixel to + /// + private const int RgbSpanByteSize = PixelsPerSample * 3; + + /// + /// of sampling area from given frame pixel buffer + /// + private static readonly Size SampleSize = new Size(16, 8); + + /// + /// The left Y component + /// + public Block8x8F YLeft; + + /// + /// The left Y component + /// + public Block8x8F YRight; + + /// + /// The Cb component + /// + public Block8x8F Cb; + + /// + /// The Cr component + /// + public Block8x8F Cr; + + /// + /// The color conversion tables + /// + private RgbToYCbCrConverterLut colorTables; + + /// + /// Temporal 16x8 block to hold TPixel data + /// + private Span pixelSpan; + + /// + /// Temporal RGB block + /// + private Span rgbSpan; + + /// + /// Sampled pixel buffer size + /// + private Size samplingAreaSize; + + /// + /// for internal operations + /// + private Configuration config; + + public YCbCrForwardConverter420(ImageFrame frame) + { + // matrices would be filled during convert calls + this.YLeft = default; + this.YRight = default; + this.Cb = default; + this.Cr = default; + + // temporal pixel buffers + this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); + this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); + + // frame data + this.samplingAreaSize = new Size(frame.Width, frame.Height); + this.config = frame.GetConfiguration(); + + // conversion vector fallback data + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + this.colorTables = RgbToYCbCrConverterLut.Create(); + } + else + { + this.colorTables = default; + } + } + + public void Convert(int x, int y, ref RowOctet currentRows, int idx) + { + YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); + + PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); + + if (RgbToYCbCrConverterVectorized.IsSupported) + { + RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); + } + else + { + this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs new file mode 100644 index 0000000000..ef589272bd --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. + /// + /// The pixel type to work on + internal ref struct YCbCrForwardConverter444 + where TPixel : unmanaged, IPixel + { + /// + /// Number of pixels processed per single call + /// + private const int PixelsPerSample = 8 * 8; + + /// + /// Total byte size of processed pixels converted from TPixel to + /// + private const int RgbSpanByteSize = PixelsPerSample * 3; + + /// + /// of sampling area from given frame pixel buffer + /// + private static readonly Size SampleSize = new Size(8, 8); + + /// + /// The Y component + /// + public Block8x8F Y; + + /// + /// The Cb component + /// + public Block8x8F Cb; + + /// + /// The Cr component + /// + public Block8x8F Cr; + + /// + /// The color conversion tables + /// + private RgbToYCbCrConverterLut colorTables; + + /// + /// Temporal 64-byte span to hold unconverted TPixel data + /// + private Span pixelSpan; + + /// + /// Temporal 64-byte span to hold converted Rgb24 data + /// + private Span rgbSpan; + + /// + /// Sampled pixel buffer size + /// + private Size samplingAreaSize; + + /// + /// for internal operations + /// + private Configuration config; + + public YCbCrForwardConverter444(ImageFrame frame) + { + // matrices would be filled during convert calls + this.Y = default; + this.Cb = default; + this.Cr = default; + + // temporal pixel buffers + this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); + this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); + + // frame data + this.samplingAreaSize = new Size(frame.Width, frame.Height); + this.config = frame.GetConfiguration(); + + // conversion vector fallback data + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + this.colorTables = RgbToYCbCrConverterLut.Create(); + } + else + { + this.colorTables = default; + } + } + + /// + /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) + /// + public void Convert(int x, int y, ref RowOctet currentRows) + { + YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); + + PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); + + ref Block8x8F yBlock = ref this.Y; + ref Block8x8F cbBlock = ref this.Cb; + ref Block8x8F crBlock = ref this.Cr; + + if (RgbToYCbCrConverterVectorized.IsSupported) + { + RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + } + else + { + this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 81e64b277b..6d3620c622 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -2,81 +2,59 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct YCbCrForwardConverter + internal static class YCbCrForwardConverter where TPixel : unmanaged, IPixel { - /// - /// The Y component - /// - public Block8x8F Y; - - /// - /// The Cb component - /// - public Block8x8F Cb; - - /// - /// The Cr component - /// - public Block8x8F Cr; + public static void LoadAndStretchEdges(RowOctet source, Span dest, Point start, Size sampleSize, Size totalSize) + { + DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X)); + DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y)); - /// - /// The color conversion tables - /// - private RgbToYCbCrConverterLut colorTables; + int width = Math.Min(sampleSize.Width, totalSize.Width - start.X); + int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y); - /// - /// Temporal 8x8 block to hold TPixel data - /// - private GenericBlock8x8 pixelBlock; + uint byteWidth = (uint)(width * Unsafe.SizeOf()); + int remainderXCount = sampleSize.Width - width; - /// - /// Temporal RGB block - /// - private GenericBlock8x8 rgbBlock; + ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(dest)); + int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf(); - public static YCbCrForwardConverter Create() - { - var result = default(YCbCrForwardConverter); - if (!RgbToYCbCrConverterVectorized.IsSupported) + for (int y = 0; y < height; y++) { - // Avoid creating lookup tables, when vectorized converter is supported - result.colorTables = RgbToYCbCrConverterLut.Create(); - } + Span row = source[y]; - return result; - } + ref byte s = ref Unsafe.As(ref row[start.X]); + ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes); - /// - /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) - /// - public void Convert(ImageFrame frame, int x, int y, ref RowOctet currentRows) - { - this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows); + Unsafe.CopyBlock(ref d, ref s, byteWidth); + + ref TPixel last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); - Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); - PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); + for (int x = 1; x <= remainderXCount; x++) + { + Unsafe.Add(ref last, x) = last; + } + } - ref Block8x8F yBlock = ref this.Y; - ref Block8x8F cbBlock = ref this.Cb; - ref Block8x8F crBlock = ref this.Cr; + int remainderYCount = sampleSize.Height - height; - if (RgbToYCbCrConverterVectorized.IsSupported) + if (remainderYCount == 0) { - RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + return; } - else + + ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes); + + for (int y = 1; y <= remainderYCount; y++) { - this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y); + Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index a6d0622dd8..0f569b5da1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components @@ -10,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Contains inaccurate, but fast forward and inverse DCT implementations. /// - internal static class FastFloatingPointDCT + internal static partial class FastFloatingPointDCT { #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore private const float C_1_175876 = 1.175875602f; @@ -38,147 +43,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private const float C_0_765367 = 0.765366865f; private const float C_0_125 = 0.1250f; + +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector256 C_V_0_5411 = Vector256.Create(0.541196f); + private static readonly Vector256 C_V_1_3065 = Vector256.Create(1.306563f); + private static readonly Vector256 C_V_1_1758 = Vector256.Create(1.175876f); + private static readonly Vector256 C_V_0_7856 = Vector256.Create(0.785695f); + private static readonly Vector256 C_V_1_3870 = Vector256.Create(1.387040f); + private static readonly Vector256 C_V_0_2758 = Vector256.Create(0.275899f); + + private static readonly Vector256 C_V_n1_9615 = Vector256.Create(-1.961570560f); + private static readonly Vector256 C_V_n0_3901 = Vector256.Create(-0.390180644f); + private static readonly Vector256 C_V_n0_8999 = Vector256.Create(-0.899976223f); + private static readonly Vector256 C_V_n2_5629 = Vector256.Create(-2.562915447f); + private static readonly Vector256 C_V_0_2986 = Vector256.Create(0.298631336f); + private static readonly Vector256 C_V_2_0531 = Vector256.Create(2.053119869f); + private static readonly Vector256 C_V_3_0727 = Vector256.Create(3.072711026f); + private static readonly Vector256 C_V_1_5013 = Vector256.Create(1.501321110f); + private static readonly Vector256 C_V_n1_8477 = Vector256.Create(-1.847759065f); + private static readonly Vector256 C_V_0_7653 = Vector256.Create(0.765366865f); + + private static readonly Vector256 C_V_InvSqrt2 = Vector256.Create(0.707107f); +#endif #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); - /// - /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization). - /// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// - /// Source - /// Destination - /// Temporary block provided by the caller - public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) - { - src.TransposeInto(ref temp); - - IDCT8x4_LeftPart(ref temp, ref dest); - IDCT8x4_RightPart(ref temp, ref dest); - - dest.TransposeInto(ref temp); - - IDCT8x4_LeftPart(ref temp, ref dest); - IDCT8x4_RightPart(ref temp, ref dest); - - // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? - dest.MultiplyInPlace(C_0_125); - } - - /// - /// Do IDCT internal operations on the left part of the block. Original src: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// The source block - /// Destination block - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) - { - Vector4 my1 = s.V1L; - Vector4 my7 = s.V7L; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = s.V3L; - Vector4 mz2 = my3 + my7; - Vector4 my5 = s.V5L; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = s.V2L; - Vector4 my6 = s.V6L; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = s.V0L; - Vector4 my4 = s.V4L; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - d.V0L = my0 + mb0; - d.V7L = my0 - mb0; - d.V1L = my1 + mb1; - d.V6L = my1 - mb1; - d.V2L = my2 + mb2; - d.V5L = my2 - mb2; - d.V3L = my3 + mb3; - d.V4L = my3 - mb3; - } - - /// - /// Do IDCT internal operations on the right part of the block. - /// Original src: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// The source block - /// The destination block - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) - { - Vector4 my1 = s.V1R; - Vector4 my7 = s.V7R; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = s.V3R; - Vector4 mz2 = my3 + my7; - Vector4 my5 = s.V5R; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = s.V2R; - Vector4 my6 = s.V6R; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = s.V0R; - Vector4 my4 = s.V4R; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - d.V0R = my0 + mb0; - d.V7R = my0 - mb0; - d.V1R = my1 + mb1; - d.V6R = my1 - mb1; - d.V2R = my2 + mb2; - d.V5R = my2 - mb2; - d.V3R = my3 + mb3; - d.V4R = my3 - mb3; - } - /// /// Original: /// @@ -309,11 +198,84 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// Combined operation of and + /// using AVX commands. + /// + /// Source + /// Destination + public static void FDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d) + { +#if SUPPORTS_RUNTIME_INTRINSICS + Debug.Assert(Avx.IsSupported, "AVX is required to execute this method"); + + Vector256 t0 = Avx.Add(s.V0, s.V7); + Vector256 t7 = Avx.Subtract(s.V0, s.V7); + Vector256 t1 = Avx.Add(s.V1, s.V6); + Vector256 t6 = Avx.Subtract(s.V1, s.V6); + Vector256 t2 = Avx.Add(s.V2, s.V5); + Vector256 t5 = Avx.Subtract(s.V2, s.V5); + Vector256 t3 = Avx.Add(s.V3, s.V4); + Vector256 t4 = Avx.Subtract(s.V3, s.V4); + + Vector256 c0 = Avx.Add(t0, t3); + Vector256 c1 = Avx.Add(t1, t2); + + // 0 4 + d.V0 = Avx.Add(c0, c1); + d.V4 = Avx.Subtract(c0, c1); + + Vector256 c3 = Avx.Subtract(t0, t3); + Vector256 c2 = Avx.Subtract(t1, t2); + + // 2 6 + d.V2 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(c2, C_V_0_5411), c3, C_V_1_3065); + d.V6 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(c2, C_V_1_3065), c3, C_V_0_5411); + + c3 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(t4, C_V_1_1758), t7, C_V_0_7856); + c0 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(t4, C_V_0_7856), t7, C_V_1_1758); + + c2 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(t5, C_V_1_3870), C_V_0_2758, t6); + c1 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(C_V_0_2758, t5), t6, C_V_1_3870); + + // 3 5 + d.V3 = Avx.Subtract(c0, c2); + d.V5 = Avx.Subtract(c3, c1); + + c0 = Avx.Multiply(Avx.Add(c0, c2), C_V_InvSqrt2); + c3 = Avx.Multiply(Avx.Add(c3, c1), C_V_InvSqrt2); + + // 1 7 + d.V1 = Avx.Add(c0, c3); + d.V7 = Avx.Subtract(c0, c3); +#endif + } + + /// + /// Performs 8x8 matrix Forward Discrete Cosine Transform + /// + /// Source + /// Destination + public static void FDCT8x8(ref Block8x8F s, ref Block8x8F d) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx.IsSupported) + { + FDCT8x8_Avx(ref s, ref d); + } + else +#endif + { + FDCT8x4_LeftPart(ref s, ref d); + FDCT8x4_RightPart(ref s, ref d); + } + } + + /// + /// Apply floating point FDCT from src into dest /// /// Source /// Destination - /// Temporary block provided by the caller + /// Temporary block provided by the caller for optimization /// If true, a constant -128.0 offset is applied for all values before FDCT public static void TransformFDCT( ref Block8x8F src, @@ -327,14 +289,225 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components temp.AddInPlace(-128F); } - FDCT8x4_LeftPart(ref temp, ref dest); - FDCT8x4_RightPart(ref temp, ref dest); + FDCT8x8(ref temp, ref dest); dest.TransposeInto(ref temp); - FDCT8x4_LeftPart(ref temp, ref dest); - FDCT8x4_RightPart(ref temp, ref dest); + FDCT8x8(ref temp, ref dest); + + dest.MultiplyInPlace(C_0_125); + } + + /// + /// Performs 8x8 matrix Inverse Discrete Cosine Transform + /// + /// Source + /// Destination + public static void IDCT8x8(ref Block8x8F s, ref Block8x8F d) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx.IsSupported) + { + IDCT8x8_Avx(ref s, ref d); + } + else +#endif + { + IDCT8x4_LeftPart(ref s, ref d); + IDCT8x4_RightPart(ref s, ref d); + } + } + + /// + /// Do IDCT internal operations on the left part of the block. Original src: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + /// The source block + /// Destination block + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1L; + Vector4 my7 = s.V7L; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3L; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5L; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + Vector4 mz4 = (mz0 + mz1) * C_1_175876; + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2L; + Vector4 my6 = s.V6L; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0L; + Vector4 my4 = s.V4L; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0L = my0 + mb0; + d.V7L = my0 - mb0; + d.V1L = my1 + mb1; + d.V6L = my1 - mb1; + d.V2L = my2 + mb2; + d.V5L = my2 - mb2; + d.V3L = my3 + mb3; + d.V4L = my3 - mb3; + } + + /// + /// Do IDCT internal operations on the right part of the block. + /// Original src: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + /// The source block + /// The destination block + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1R; + Vector4 my7 = s.V7R; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3R; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5R; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = (mz0 + mz1) * C_1_175876; + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2R; + Vector4 my6 = s.V6R; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0R; + Vector4 my4 = s.V4R; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0R = my0 + mb0; + d.V7R = my0 - mb0; + d.V1R = my1 + mb1; + d.V6R = my1 - mb1; + d.V2R = my2 + mb2; + d.V5R = my2 - mb2; + d.V3R = my3 + mb3; + d.V4R = my3 - mb3; + } + + /// + /// Combined operation of and + /// using AVX commands. + /// + /// Source + /// Destination + public static void IDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d) + { +#if SUPPORTS_RUNTIME_INTRINSICS + Debug.Assert(Avx.IsSupported, "AVX is required to execute this method"); + + Vector256 my1 = s.V1; + Vector256 my7 = s.V7; + Vector256 mz0 = Avx.Add(my1, my7); + + Vector256 my3 = s.V3; + Vector256 mz2 = Avx.Add(my3, my7); + Vector256 my5 = s.V5; + Vector256 mz1 = Avx.Add(my3, my5); + Vector256 mz3 = Avx.Add(my1, my5); + + Vector256 mz4 = Avx.Multiply(Avx.Add(mz0, mz1), C_V_1_1758); + + mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, C_V_n1_9615); + mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, C_V_n0_3901); + mz0 = Avx.Multiply(mz0, C_V_n0_8999); + mz1 = Avx.Multiply(mz1, C_V_n2_5629); + + Vector256 mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, C_V_0_2986), mz2); + Vector256 mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, C_V_2_0531), mz3); + Vector256 mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, C_V_3_0727), mz2); + Vector256 mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, C_V_1_5013), mz3); + + Vector256 my2 = s.V2; + Vector256 my6 = s.V6; + mz4 = Avx.Multiply(Avx.Add(my2, my6), C_V_0_5411); + Vector256 my0 = s.V0; + Vector256 my4 = s.V4; + mz0 = Avx.Add(my0, my4); + mz1 = Avx.Subtract(my0, my4); + mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my6, C_V_n1_8477); + mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, C_V_0_7653); + + my0 = Avx.Add(mz0, mz3); + my3 = Avx.Subtract(mz0, mz3); + my1 = Avx.Add(mz1, mz2); + my2 = Avx.Subtract(mz1, mz2); + + d.V0 = Avx.Add(my0, mb0); + d.V7 = Avx.Subtract(my0, mb0); + d.V1 = Avx.Add(my1, mb1); + d.V6 = Avx.Subtract(my1, mb1); + d.V2 = Avx.Add(my2, mb2); + d.V5 = Avx.Subtract(my2, mb2); + d.V3 = Avx.Add(my3, mb3); + d.V4 = Avx.Subtract(my3, mb3); +#endif + } + + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization). + /// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 + /// + /// Source + /// Destination + /// Temporary block provided by the caller + public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) + { + src.TransposeInto(ref temp); + + IDCT8x8(ref temp, ref dest); + dest.TransposeInto(ref temp); + IDCT8x8(ref temp, ref dest); + + // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? dest.MultiplyInPlace(C_0_125); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 8571cf0ec3..9f3966de29 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -928,9 +929,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { int length = remaining; - using (IManagedByteBuffer huffmanData = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) { - ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); + Span huffmanDataSpan = huffmanData.GetSpan(); + ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan); for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -949,11 +951,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index."); } - stream.Read(huffmanData.Array, 0, 16); + stream.Read(huffmanDataSpan, 0, 16); - using (IManagedByteBuffer codeLengths = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) + using (IMemoryOwner codeLengths = this.Configuration.MemoryAllocator.Allocate(17, AllocationOptions.Clean)) { - ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); + Span codeLengthsSpan = codeLengths.GetSpan(); + ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan); int codeLengthSum = 0; for (int j = 1; j < 17; j++) @@ -968,17 +971,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } - using (IManagedByteBuffer huffmanValues = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) { - stream.Read(huffmanValues.Array, 0, codeLengthSum); + Span huffmanValuesSpan = huffmanValues.GetSpan(); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); i += 17 + codeLengthSum; this.BuildHuffmanTable( tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableIndex, - codeLengths.GetSpan(), - huffmanValues.GetSpan()); + codeLengthsSpan, + huffmanValuesSpan); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index f5dc1c79fe..135048aa4e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -5,14 +5,11 @@ using System; using System.Buffers.Binary; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -36,17 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private readonly byte[] buffer = new byte[20]; - /// - /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. - /// - private readonly byte[] emitBuffer = new byte[64]; - - /// - /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + - /// identifier. - /// - private readonly byte[] huffmanBuffer = new byte[179]; - /// /// Gets or sets the subsampling method to use. /// @@ -62,26 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private readonly JpegColorType? colorType; - /// - /// The accumulated bits to write to the stream. - /// - private uint accumulatedBits; - - /// - /// The accumulated bit count. - /// - private uint bitCount; - - /// - /// The scaled chrominance table, in zig-zag order. - /// - private Block8x8F chrominanceQuantTable; - - /// - /// The scaled luminance table, in zig-zag order. - /// - private Block8x8F luminanceQuantTable; - /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -98,29 +64,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.colorType = options.ColorType; } - /// - /// Gets the counts the number of bits needed to hold an integer. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - private static ReadOnlySpan BitCountLut => new byte[] - { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, - }; - /// /// Gets the unscaled quantization tables in zig-zag order. Each /// encoder copies and scales the tables according to its quality parameter. @@ -131,14 +74,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // This is effectively compiled down to: return new ReadOnlySpan(&data, length) // More details can be found: https://github.com/dotnet/roslyn/pull/24621 private static ReadOnlySpan UnscaledQuant_Luminance => new byte[] - { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, - 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, - 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, - 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, - 100, 120, 92, 101, 103, 99, - }; + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }; /// /// Gets the unscaled quantization tables in zig-zag order. Each @@ -150,14 +93,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // This is effectively compiled down to: return new ReadOnlySpan(&data, length) // More details can be found: https://github.com/dotnet/roslyn/pull/24621 private static ReadOnlySpan UnscaledQuant_Chrominance => new byte[] - { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - }; + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }; /// /// Encode writes the image to the jpeg baseline format with the given options. @@ -171,14 +114,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - cancellationToken.ThrowIfCancellationRequested(); - const ushort max = JpegConstants.MaxLength; - if (image.Width >= max || image.Height >= max) + if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength) { - throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); } + cancellationToken.ThrowIfCancellationRequested(); + this.outputStream = stream; ImageMetadata metadata = image.Metadata; @@ -201,10 +144,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } // Initialize the quantization tables. - InitQuantizationTable(0, scale, ref this.luminanceQuantTable); + // TODO: This looks ugly, should we write chrominance table for luminance-only images? + // If not - this can code can be simplified + Block8x8F luminanceQuantTable = default; + Block8x8F chrominanceQuantTable = default; + InitQuantizationTable(0, scale, ref luminanceQuantTable); if (componentCount > 1) { - InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); + InitQuantizationTable(1, scale, ref chrominanceQuantTable); } // Write the Start Of Image marker. @@ -214,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteProfiles(metadata); // Write the quantization tables. - this.WriteDefineQuantizationTables(); + this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, componentCount); @@ -222,13 +169,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the Huffman tables. this.WriteDefineHuffmanTables(componentCount); - // Write the image data. + // Write the scan header. this.WriteStartOfScan(image, componentCount, cancellationToken); + // Write the scan compressed data. + var scanEncoder = new HuffmanScanEncoder(stream); + if (this.colorType == JpegColorType.Luminance) + { + scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); + } + else + { + switch (this.subsample) + { + case JpegSubsample.Ratio444: + scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); + break; + case JpegSubsample.Ratio420: + scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); + break; + } + } + // Write the End Of Image marker. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.EOI; - stream.Write(this.buffer, 0, 2); + this.WriteEndOfImageMarker(); + stream.Flush(); } @@ -248,248 +213,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } - /// - /// Initializes quantization table. - /// - /// The quantization index. - /// The scaling factor. - /// The quantization table. - private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) - { - DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); - ReadOnlySpan unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; - - for (int j = 0; j < Block8x8F.Size; j++) - { - int x = unscaledQuant[j]; - x = ((x * scale) + 50) / 100; - if (x < 1) - { - x = 1; - } - - if (x > 255) - { - x = 255; - } - - quant[j] = x; - } - } - - /// - /// Emits the least significant count of bits of bits to the bit-stream. - /// The precondition is bits - /// - /// < 1<<nBits && nBits <= 16 - /// - /// . - /// - /// The packed bits. - /// The number of bits - /// The reference to the emitBuffer. - [MethodImpl(InliningOptions.ShortMethod)] - private void Emit(uint bits, uint count, ref byte emitBufferBase) - { - count += this.bitCount; - bits <<= (int)(32 - count); - bits |= this.accumulatedBits; - - // Only write if more than 8 bits. - if (count >= 8) - { - // Track length - int len = 0; - while (count >= 8) - { - byte b = (byte)(bits >> 24); - Unsafe.Add(ref emitBufferBase, len++) = b; - if (b == byte.MaxValue) - { - Unsafe.Add(ref emitBufferBase, len++) = byte.MinValue; - } - - bits <<= 8; - count -= 8; - } - - if (len > 0) - { - this.outputStream.Write(this.emitBuffer, 0, len); - } - } - - this.accumulatedBits = bits; - this.bitCount = count; - } - - /// - /// Emits the given value with the given Huffman encoder. - /// - /// The index of the Huffman encoder - /// The value to encode. - /// The reference to the emit buffer. - [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuff(HuffIndex index, int value, ref byte emitBufferBase) - { - uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; - this.Emit(x & ((1 << 24) - 1), x >> 24, ref emitBufferBase); - } - - /// - /// Emits a run of runLength copies of value encoded with the given Huffman encoder. - /// - /// The index of the Huffman encoder - /// The number of copies to encode. - /// The value to encode. - /// The reference to the emit buffer. - [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuffRLE(HuffIndex index, int runLength, int value, ref byte emitBufferBase) - { - int a = value; - int b = value; - if (a < 0) - { - a = -value; - b = value - 1; - } - - uint bt; - if (a < 0x100) - { - bt = BitCountLut[a]; - } - else - { - bt = 8 + (uint)BitCountLut[a >> 8]; - } - - this.EmitHuff(index, (int)((uint)(runLength << 4) | bt), ref emitBufferBase); - if (bt > 0) - { - this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt, ref emitBufferBase); - } - } - - /// - /// Encodes the image with no subsampling. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// The token to monitor for cancellation. - /// The reference to the emit buffer. - private void Encode444(Image pixels, CancellationToken cancellationToken, ref byte emitBufferBase) - where TPixel : unmanaged, IPixel - { - // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) - // (Partially done with YCbCrForwardConverter) - Block8x8F temp1 = default; - Block8x8F temp2 = default; - - Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; - Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - - var unzig = ZigZag.CreateUnzigTable(); - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - var pixelConverter = YCbCrForwardConverter.Create(); - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(frame, x, y, ref currentRows); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref temp1, - ref temp2, - ref onStackLuminanceQuantTable, - ref unzig, - ref emitBufferBase); - - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref pixelConverter.Cb, - ref temp1, - ref temp2, - ref onStackChrominanceQuantTable, - ref unzig, - ref emitBufferBase); - - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref pixelConverter.Cr, - ref temp1, - ref temp2, - ref onStackChrominanceQuantTable, - ref unzig, - ref emitBufferBase); - } - } - } - - /// - /// Encodes the image with no chroma, just luminance. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// The token to monitor for cancellation. - /// The reference to the emit buffer. - private void EncodeGrayscale(Image pixels, CancellationToken cancellationToken, ref byte emitBufferBase) - where TPixel : unmanaged, IPixel - { - // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) - // (Partially done with YCbCrForwardConverter) - Block8x8F temp1 = default; - Block8x8F temp2 = default; - - Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; - - var unzig = ZigZag.CreateUnzigTable(); - - // ReSharper disable once InconsistentNaming - int prevDCY = 0; - - var pixelConverter = LuminanceForwardConverter.Create(); - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(frame, x, y, ref currentRows); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref temp1, - ref temp2, - ref onStackLuminanceQuantTable, - ref unzig, - ref emitBufferBase); - } - } - } - /// /// Writes the application header containing the JFIF identifier plus extra data. /// @@ -539,72 +262,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream.Write(this.buffer, 0, 20); } - /// - /// Writes a block of pixel data using the given quantization table, - /// returning the post-quantized DC value of the DCT-transformed block. - /// The block is in natural (not zig-zag) order. - /// - /// The quantization table index. - /// The previous DC value. - /// Source block - /// Temporal block to be used as FDCT Destination - /// Temporal block 2 - /// Quantization table - /// The 8x8 Unzig block. - /// The reference to the emit buffer. - /// The . - private int WriteBlock( - QuantIndex index, - int prevDC, - ref Block8x8F src, - ref Block8x8F tempDest1, - ref Block8x8F tempDest2, - ref Block8x8F quant, - ref ZigZag unZig, - ref byte emitBufferBase) - { - FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2); - - Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig); - - int dc = (int)tempDest2[0]; - - // Emit the DC delta. - this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC, ref emitBufferBase); - - // Emit the AC components. - var h = (HuffIndex)((2 * (int)index) + 1); - int runLength = 0; - - for (int zig = 1; zig < Block8x8F.Size; zig++) - { - int ac = (int)tempDest2[zig]; - - if (ac == 0) - { - runLength++; - } - else - { - while (runLength > 15) - { - this.EmitHuff(h, 0xf0, ref emitBufferBase); - runLength -= 16; - } - - this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase); - runLength = 0; - } - } - - if (runLength > 0) - { - this.EmitHuff(h, 0x00, ref emitBufferBase); - } - - return dc; - } - /// /// Writes the Define Huffman Table marker and tables. /// @@ -638,34 +295,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); for (int i = 0; i < specs.Length; i++) { - ref HuffmanSpec spec = ref specs[i]; - int len = 0; - - fixed (byte* huffman = this.huffmanBuffer) - fixed (byte* count = spec.Count) - fixed (byte* values = spec.Values) - { - huffman[len++] = headers[i]; - - for (int c = 0; c < spec.Count.Length; c++) - { - huffman[len++] = count[c]; - } - - for (int v = 0; v < spec.Values.Length; v++) - { - huffman[len++] = values[v]; - } - } - - this.outputStream.Write(this.huffmanBuffer, 0, len); + this.outputStream.WriteByte(headers[i]); + this.outputStream.Write(specs[i].Count); + this.outputStream.Write(specs[i].Values); } } /// /// Writes the Define Quantization Marker and tables. /// - private void WriteDefineQuantizationTables() + private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable) { // Marker + quantization table lengths int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); @@ -677,8 +316,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg byte[] dqt = new byte[dqtCount]; int offset = 0; - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable); this.outputStream.Write(dqt, 0, dqtCount); } @@ -982,7 +621,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void WriteStartOfScan(Image image, int componentCount, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Span componentId = stackalloc byte[] { 0x01, @@ -1024,111 +662,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[sosSize] = 0x3f; // Se - End of spectral selection. this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) this.outputStream.Write(this.buffer, 0, sosSize + 2); - - ref byte emitBufferBase = ref MemoryMarshal.GetReference(this.emitBuffer); - if (this.colorType == JpegColorType.Luminance) - { - this.EncodeGrayscale(image, cancellationToken, ref emitBufferBase); - } - else - { - switch (this.subsample) - { - case JpegSubsample.Ratio444: - this.Encode444(image, cancellationToken, ref emitBufferBase); - break; - case JpegSubsample.Ratio420: - this.Encode420(image, cancellationToken, ref emitBufferBase); - break; - } - } - - // Pad the last byte with 1's. - this.Emit(0x7f, 7, ref emitBufferBase); } /// - /// Encodes the image with subsampling. The Cb and Cr components are each subsampled - /// at a factor of 2 both horizontally and vertically. + /// Writes the EndOfImage marker. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// The token to monitor for cancellation. - /// The reference to the emit buffer. - private void Encode420(Image pixels, CancellationToken cancellationToken, ref byte emitBufferBase) - where TPixel : unmanaged, IPixel + private void WriteEndOfImageMarker() { - // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) - Block8x8F b = default; - Span cb = stackalloc Block8x8F[4]; - Span cr = stackalloc Block8x8F[4]; - - Block8x8F temp1 = default; - Block8x8F temp2 = default; - - Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; - Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - - var unzig = ZigZag.CreateUnzigTable(); - - var pixelConverter = YCbCrForwardConverter.Create(); - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - for (int y = 0; y < pixels.Height; y += 16) - { - cancellationToken.ThrowIfCancellationRequested(); - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - currentRows.Update(pixelBuffer, y + yOff); - pixelConverter.Convert(frame, x + xOff, y + yOff, ref currentRows); - - cb[i] = pixelConverter.Cb; - cr[i] = pixelConverter.Cr; - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref temp1, - ref temp2, - ref onStackLuminanceQuantTable, - ref unzig, - ref emitBufferBase); - } - - Block8x8F.Scale16X16To8X8(ref b, cb); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref b, - ref temp1, - ref temp2, - ref onStackChrominanceQuantTable, - ref unzig, - ref emitBufferBase); - - Block8x8F.Scale16X16To8X8(ref b, cr); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref b, - ref temp1, - ref temp2, - ref onStackChrominanceQuantTable, - ref unzig, - ref emitBufferBase); - } - } + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.EOI; + this.outputStream.Write(this.buffer, 0, 2); } /// @@ -1145,5 +688,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[3] = (byte)(length & 0xff); this.outputStream.Write(this.buffer, 0, 4); } + + /// + /// Initializes quantization table. + /// + /// The quantization index. + /// The scaling factor. + /// The quantization table. + private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) + { + DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); + ReadOnlySpan unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; + + for (int j = 0; j < Block8x8F.Size; j++) + { + int x = unscaledQuant[j]; + x = ((x * scale) + 50) / 100; + if (x < 1) + { + x = 1; + } + + if (x > 255) + { + x = 255; + } + + quant[j] = x; + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index fa9eb83917..cc75870e19 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -46,5 +46,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ColdPath)] public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format."); } } diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 0ab1413974..83c6389348 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index e8e0aa7043..6a89a1122a 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 116154836e..c28b877e41 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index e0f35293a4..7e0286991b 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index fd11ba1b6b..7b5f390f12 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Memory; +using System.Buffers; namespace SixLabors.ImageSharp.Formats.Png { @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal readonly struct PngChunk { - public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) + public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) { this.Length = length; this.Type = type; @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets the data bytes appropriate to the chunk type, if any. /// This field can be of zero length or null. /// - public IManagedByteBuffer Data { get; } + public IMemoryOwner Data { get; } /// /// Gets a value indicating whether the given chunk is critical to decoding diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs index 7516e0987d..961f9b05b8 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 3fa0e3f586..de70a9dff6 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; @@ -10,10 +11,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; @@ -85,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Previous scanline processed. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The current scanline that is being processed. /// - private IManagedByteBuffer scanline; + private IMemoryOwner scanline; /// /// The index of the current scanline being processed. @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); @@ -169,29 +169,29 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.Palette: var pal = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(pal); this.palette = pal; break; case PngChunkType.Transparency: var alpha = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); break; case PngChunkType.Text: - this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { var exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); @@ -252,19 +252,19 @@ namespace SixLabors.ImageSharp.Formats.Png this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Text: - this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { var exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The number of bits per value. /// The new array. /// The resulting array. - private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IMemoryOwner buffer) { if (bits >= 8) { @@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Png return false; } - buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); + buffer = this.memoryAllocator.Allocate(bytesPerScanline * 8 / bits, AllocationOptions.Clean); ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref buffer.Array[0]; + ref byte resultRef = ref buffer.GetReference(); int mask = 0xFF >> (8 - bits); int resultOffset = 0; @@ -393,8 +393,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.Configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); } /// @@ -505,7 +505,8 @@ namespace SixLabors.ImageSharp.Formats.Png { while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + Span scanlineSpan = this.scanline.GetSpan(); + int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < this.bytesPerScanline) { @@ -513,7 +514,6 @@ namespace SixLabors.ImageSharp.Formats.Png } this.currentRowBytesRead = 0; - Span scanlineSpan = this.scanline.GetSpan(); switch ((FilterType)scanlineSpan[0]) { @@ -543,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow++; } } @@ -577,7 +577,7 @@ namespace SixLabors.ImageSharp.Formats.Png while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < bytesPerInterlaceScanline) { @@ -618,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan = image.GetPixelRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow += Adam7.RowIncrement[pass]; } @@ -654,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline - 1, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; + + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; + } + } + finally + { + buffer?.Dispose(); } - - buffer?.Dispose(); } /// @@ -736,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; + + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessInterlacedPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessInterlacedPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessInterlacedRgbScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessInterlacedRgbScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessInterlacedRgbaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessInterlacedRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; + } + } + finally + { + buffer?.Dispose(); } - - buffer?.Dispose(); } /// @@ -1190,12 +1210,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The length of the chunk data to read. [MethodImpl(InliningOptions.ShortMethod)] - private IManagedByteBuffer ReadChunkData(int length) + private IMemoryOwner ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() - IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); + IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); - this.currentStream.Read(buffer.Array, 0, length); + this.currentStream.Read(buffer.GetSpan(), 0, length); return buffer; } @@ -1273,9 +1293,9 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } - private void SwapBuffers() + private void SwapScanlineBuffers() { - IManagedByteBuffer temp = this.previousScanline; + IMemoryOwner temp = this.previousScanline; this.previousScanline = this.scanline; this.scanline = temp; } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 5d2af8ec6a..4f6fb73567 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; @@ -82,32 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The raw data of previous scanline. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The raw data of current scanline. /// - private IManagedByteBuffer currentScanline; - - /// - /// The common buffer for the filters. - /// - private IManagedByteBuffer filterBuffer; - - /// - /// The ext buffer for the sub filter, . - /// - private IManagedByteBuffer subFilter; - - /// - /// The ext buffer for the average filter, . - /// - private IManagedByteBuffer averageFilter; - - /// - /// The ext buffer for the Paeth filter, . - /// - private IManagedByteBuffer paethFilter; + private IMemoryOwner currentScanline; /// /// Initializes a new instance of the class. @@ -175,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png { this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = null; this.currentScanline = null; - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.filterBuffer = null; } /// @@ -280,21 +249,17 @@ namespace SixLabors.ImageSharp.Formats.Png else { // 1, 2, and 4 bit grayscale - using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer( - rowSpan.Length, - AllocationOptions.Clean)) - { - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); - Span tempSpan = temp.GetSpan(); - - // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - tempSpan, - rowSpan.Length); - PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); - } + using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + + // We need to first create an array of luminance bytes then scale them down to the correct bit depth. + PixelOperations.Instance.ToL8Bytes( + this.configuration, + rowSpan, + tempSpan, + rowSpan.Length); + PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); } } } @@ -446,6 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.GrayscaleWithAlpha: this.CollectGrayscaleBytes(rowSpan); break; + case PngColorType.Rgb: + case PngColorType.RgbWithAlpha: default: this.CollectTPixelBytes(rowSpan); break; @@ -453,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Apply filter for the raw scanline. + /// Apply the line filter for the raw scanline to enable better compression. /// - private IManagedByteBuffer FilterPixelBytes() + private void FilterPixelBytes(ref Span filter, ref Span attempt) { switch (this.options.FilterMethod) { case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; - + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + break; case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); - return this.filterBuffer; + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); + break; case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; - + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + case PngFilterMethod.Adaptive: default: - return this.GetOptimalFilteredScanline(); + this.ApplyOptimalFilteredScanline(ref filter, ref attempt); + break; } } /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. + /// Collects the pixel data line by line for compressing. + /// Each scanline is filtered in the most optimal manner to improve compression. /// /// The pixel format. /// The row span. - /// The quantized pixels. Can be null. - /// The row. - /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + /// The filtered buffer. + /// Used for attempting optimized filtering. + /// The quantized pixels. Can be . + /// The row number. + private void CollectAndFilterPixelRow( + ReadOnlySpan rowSpan, + ref Span filter, + ref Span attempt, + IndexedImageFrame quantized, + int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// - /// The row span. - private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void EncodeAdam7IndexedPixelRow( + ReadOnlySpan row, + ref Span filter, + ref Span attempt) { // CollectPixelBytes if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); } else { - rowSpan.CopyTo(this.currentScanline.GetSpan()); + row.CopyTo(this.currentScanline.GetSpan()); } - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// - /// The - private IManagedByteBuffer GetOptimalFilteredScanline() + private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) { // Palette images don't compress well with adaptive filtering. - if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) + // Nor do images comprising a single row. + if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) { - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + return; } - this.AllocateExtBuffers(); - Span scanSpan = this.currentScanline.GetSpan(); - Span prevSpan = this.previousScanline.GetSpan(); - - // This order, while different to the enumerated order is more likely to produce a smaller sum - // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum); + Span current = this.currentScanline.GetSpan(); + Span previous = this.previousScanline.GetSpan(); - // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. - // That way the above comment would actually be true. It used to be anyway... - // If we could use SIMD for none branching filters we could really speed it up. - int lowestSum = currentSum; - IManagedByteBuffer actualResult = this.filterBuffer; - - PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + int min = int.MaxValue; + SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.paethFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + UpFilter.Encode(current, previous, attempt, out sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.subFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) { - actualResult = this.averageFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - return actualResult; + PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) + { + SwapSpans(ref filter, ref attempt); + } } /// @@ -614,8 +584,8 @@ namespace SixLabors.ImageSharp.Formats.Png int colorTableLength = paletteLength * Unsafe.SizeOf(); bool hasAlpha = false; - using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); - using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); + using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); @@ -642,12 +612,12 @@ namespace SixLabors.ImageSharp.Formats.Png Unsafe.Add(ref alphaTableRef, i) = alpha; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); // Write the transparency data if (hasAlpha) { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); } } @@ -926,38 +896,13 @@ namespace SixLabors.ImageSharp.Formats.Png /// Allocates the buffers for each scanline. /// /// The bytes per scanline. - /// Length of the result. - private void AllocateBuffers(int bytesPerScanline, int resultLength) + private void AllocateScanlineBuffers(int bytesPerScanline) { // Clean up from any potential previous runs. - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - } - - /// - /// Allocates the ext buffers for adaptive filter. - /// - private void AllocateExtBuffers() - { - if (this.subFilter == null) - { - int resultLength = this.filterBuffer.Length(); - - this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - } + this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); } /// @@ -971,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); - int resultLength = bytesPerScanline + 1; - this.AllocateBuffers(bytesPerScanline, resultLength); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < this.height; y++) { - IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); - deflateStream.Write(r.Array, 0, resultLength); - - IManagedByteBuffer temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; + this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); } } @@ -1006,36 +953,33 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); - this.AllocateBuffers(bytesPerScanline, resultLength); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect pixel data + Span srcRow = pixels.GetPixelRowSpan(row); + for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { - // collect data - Span srcRow = pixels.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - // note: quantized parameter not used - // note: row parameter not used - IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); - deflateStream.Write(r.Array, 0, resultLength); + // Encode data + // Note: quantized parameter not used + // Note: row parameter not used + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + deflateStream.Write(filter); - IManagedByteBuffer temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1061,34 +1005,36 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; - this.AllocateBuffers(bytesPerScanline, resultLength); + this.AllocateScanlineBuffers(bytesPerScanline); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect data + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) { - // collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan); - deflateStream.Write(r.Array, 0, resultLength); + // Encode data + this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); + deflateStream.Write(filter); - IManagedByteBuffer temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1105,7 +1051,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to write to. /// The type of chunk to write. /// The containing data. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); + private void WriteChunk(Stream stream, PngChunkType type, Span data) + => this.WriteChunk(stream, type, data, 0, data.Length); /// /// Writes a chunk of a specified length to the stream at the given offset. @@ -1115,7 +1062,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing data. /// The position to offset the data at. /// The of the data to write. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length) + private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length) { BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); @@ -1128,7 +1075,7 @@ namespace SixLabors.ImageSharp.Formats.Png { stream.Write(data, offset, length); - crc = Crc32.Calculate(crc, data.AsSpan(offset, length)); + crc = Crc32.Calculate(crc, data.Slice(offset, length)); } BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); @@ -1156,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png return scanlineLength / mod; } + + private void SwapScanlineBuffers() + { + IMemoryOwner temp = this.previousScanline; + this.previousScanline = this.currentScanline; + this.currentScanline = temp; + } + + private static void SwapSpans(ref Span a, ref Span b) + { + Span t = b; + b = a; + a = t; + } } } 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 4a2b401b1d..219f111b9d 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/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index eef6e7362b..8f97861400 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -114,9 +114,10 @@ namespace SixLabors.ImageSharp.Formats.Tga int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; - using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean)) + using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) { - this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes); + Span paletteSpan = palette.GetSpan(); + this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) { @@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.fileHeader.Width, this.fileHeader.Height, pixels, - palette.Array, + paletteSpan, colorMapPixelSizeInBytes, origin); } @@ -134,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.fileHeader.Width, this.fileHeader.Height, pixels, - palette.Array, + paletteSpan, colorMapPixelSizeInBytes, origin); } @@ -224,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The color palette. /// Color map size of one entry in bytes. /// The image origin. - private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + private void ReadPaletted(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; @@ -304,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The color palette. /// Color map size of one entry in bytes. /// The image origin. - private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { int bytesPerPixel = 1; @@ -373,22 +374,21 @@ namespace SixLabors.ImageSharp.Formats.Tga return; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); + if (invertY) { - bool invertY = InvertY(origin); - if (invertY) + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadL8Row(width, pixels, row, y); - } + this.ReadL8Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadL8Row(width, pixels, row, y); - } + this.ReadL8Row(width, pixels, rowSpan, y); } } } @@ -406,58 +406,57 @@ namespace SixLabors.ImageSharp.Formats.Tga { TPixel color = default; bool invertX = InvertX(origin); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) - { - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0); + Span rowSpan = row.GetSpan(); - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - this.currentStream.Read(this.scratchBuffer, 0, 2); - if (!this.hasAlpha) - { - this.scratchBuffer[1] |= 1 << 7; - } - - if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) - { - color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); - } - else - { - color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); - } + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); - pixelSpan[x] = color; - } - } - else + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - this.currentStream.Read(row); - Span rowSpan = row.GetSpan(); - + this.currentStream.Read(this.scratchBuffer, 0, 2); if (!this.hasAlpha) { - // We need to set the alpha component value to fully opaque. - for (int x = 1; x < rowSpan.Length; x += 2) - { - rowSpan[x] |= 1 << 7; - } + this.scratchBuffer[1] |= 1 << 7; } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { - PixelOperations.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width); + color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); } else { - PixelOperations.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width); + color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); + } + + pixelSpan[x] = color; + } + } + else + { + this.currentStream.Read(rowSpan); + + if (!this.hasAlpha) + { + // We need to set the alpha component value to fully opaque. + for (int x = 1; x < rowSpan.Length; x += 2) + { + rowSpan[x] |= 1 << 7; } } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + PixelOperations.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width); + } + else + { + PixelOperations.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width); + } } } } @@ -490,23 +489,22 @@ namespace SixLabors.ImageSharp.Formats.Tga return; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) - { - bool invertY = InvertY(origin); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); - if (invertY) + if (invertY) + { + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgr24Row(width, pixels, row, y); - } + this.ReadBgr24Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadBgr24Row(width, pixels, row, y); - } + this.ReadBgr24Row(width, pixels, rowSpan, y); } } } @@ -526,21 +524,21 @@ namespace SixLabors.ImageSharp.Formats.Tga bool invertX = InvertX(origin); if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); + Span rowSpan = row.GetSpan(); + + if (InvertY(origin)) { - if (InvertY(origin)) + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgra32Row(width, pixels, row, y); - } + this.ReadBgra32Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadBgra32Row(width, pixels, row, y); - } + this.ReadBgra32Row(width, pixels, rowSpan, y); } } @@ -652,12 +650,12 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromL8Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -679,12 +677,12 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -698,16 +696,16 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private void ReadPalettedBgra16Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); @@ -716,7 +714,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(byte[] palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) + private void ReadPalettedBgra16Pixel(Span palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) where TPixel : unmanaged, IPixel { Bgra5551 bgra = default; @@ -732,7 +730,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgr24Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private void ReadPalettedBgr24Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); @@ -741,7 +739,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra32Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private void ReadPalettedBgra32Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 1d31ea9f4e..4bf4ca60a1 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; @@ -258,7 +259,8 @@ namespace SixLabors.ImageSharp.Formats.Tga return equalPixelCount; } - private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); /// /// Writes the 8bit pixels uncompressed to the stream. @@ -269,18 +271,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write8Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToL8Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToL8Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -293,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write16Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -317,18 +319,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write24Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -341,18 +343,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write32Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs new file mode 100644 index 0000000000..08d1475268 --- /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 0000000000..225036f909 --- /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 0000000000..d2ae9096e2 --- /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 0000000000..79bb2e98f8 --- /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 0000000000..d06aeb1042 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +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 IMemoryOwner 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.Allocate(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 0000000000..30d21e54ce --- /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 0000000000..30da537eb2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -0,0 +1,594 @@ +// 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 0000000000..baeabdbb20 --- /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 0000000000..67af4ff6cb --- /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 0000000000..0f4fb9c9e2 --- /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 0000000000..7e75dd4f05 --- /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 0000000000..017591e53f --- /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 0000000000..58a1c98781 --- /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 0000000000..e14736b734 --- /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 0000000000..09f8c71f72 --- /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 0000000000..76f0883643 --- /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 0000000000..68d3a7f2a0 --- /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 0000000000..19103de925 --- /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 0000000000..ae2f17dbb5 --- /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 0000000000..5bd4cd1f13 --- /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 0000000000..c5c5c466dc --- /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 0000000000..a289e306a0 --- /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 0000000000..14a0c6e9d7 --- /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 0000000000..80bc0af5ab --- /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 0000000000..a6d44f4d3c --- /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 0000000000..031494fc5d --- /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 0000000000..b545451412 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -0,0 +1,83 @@ +// 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 Rational and SRational data types + /// + public const int SizeOfRational = 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 TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0); + + /// + /// The bits per sample for images with a 4 color palette. + /// + public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0); + + /// + /// The bits per sample for 8 bit images. + /// + public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0); + + /// + /// The bits per sample for color images with 8 bits for each color channel. + /// + public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(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 0000000000..c10167d250 --- /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 0000000000..1bb75f8366 --- /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 0000000000..4ed6aafbb2 --- /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 0000000000..a5305d4824 --- /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 0000000000..6dab7de6ef --- /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 0000000000..ea526ede56 --- /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 0000000000..6bde23cac6 --- /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 0000000000..81899c5fd8 --- /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 0000000000..ff735de86f --- /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 0000000000..fce0b175c8 --- /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 0000000000..5372384397 --- /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 0000000000..d56a587df9 --- /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 0000000000..8b2c6bd3a0 --- /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 0000000000..123a64cc1d --- /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 0000000000..b9da86fc44 --- /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 0000000000..6724adec03 --- /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 0000000000..cf59c1222d --- /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 0000000000..096f0449bf --- /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 0000000000..a4e5e45dfb --- /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(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + 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 0000000000..796227953e --- /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(TiffBitsPerSample bitsPerSample, ushort[] colorMap) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + 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/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs new file mode 100644 index 0000000000..d8c48942f8 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -0,0 +1,60 @@ +// 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 for 4 bits per color channel images. + /// + internal class Rgb444TiffColor : 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 bgra = default(Bgra4444); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + + for (int x = left; x < left + width; x += 2) + { + byte r = (byte)((data[offset] & 0xF0) >> 4); + byte g = (byte)(data[offset] & 0xF); + offset++; + byte b = (byte)((data[offset] & 0xF0) >> 4); + + bgra.PackedValue = ToBgraPackedValue(b, g, r); + color.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x] = color; + if (x + 1 >= pixelRow.Length) + { + offset++; + break; + } + + r = (byte)(data[offset] & 0xF); + offset++; + g = (byte)((data[offset] & 0xF0) >> 4); + b = (byte)(data[offset] & 0xF); + offset++; + + bgra.PackedValue = ToBgraPackedValue(b, g, r); + color.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x + 1] = color; + } + } + } + + private static ushort ToBgraPackedValue(byte b, byte g, byte r) => (ushort)(b | (g << 4) | (r << 8) | (0xF << 12)); + } +} 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 0000000000..e45863a57f --- /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 0000000000..3400bd65d8 --- /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.Buffers; +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(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + + 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(IMemoryOwner[] 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 0000000000..259bb8efa7 --- /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(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + + 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 0000000000..c08b26ef13 --- /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 0000000000..36d2ab746e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -0,0 +1,151 @@ +// 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, TiffBitsPerSample bitsPerSample, ushort[] colorMap) + { + switch (colorType) + { + case TiffColorType.WhiteIsZero: + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZeroTiffColor(bitsPerSample); + + case TiffColorType.WhiteIsZero1: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero1TiffColor(); + + case TiffColorType.WhiteIsZero4: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero4TiffColor(); + + case TiffColorType.WhiteIsZero8: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero8TiffColor(); + + case TiffColorType.BlackIsZero: + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZeroTiffColor(bitsPerSample); + + case TiffColorType.BlackIsZero1: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero1TiffColor(); + + case TiffColorType.BlackIsZero4: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero4TiffColor(); + + case TiffColorType.BlackIsZero8: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero8TiffColor(); + + case TiffColorType.Rgb: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb222: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb444: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb444TiffColor(); + + case TiffColorType.Rgb888: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb888TiffColor(); + + case TiffColorType.Rgb101010: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb121212: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb141414: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb161616: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.PaletteColor: + DebugGuard.NotNull(colorMap, "colorMap"); + return new PaletteTiffColor(bitsPerSample, colorMap); + + default: + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); + } + } + + public static RgbPlanarTiffColor CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) + { + switch (colorType) + { + case TiffColorType.RgbPlanar: + 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 0000000000..517926c239 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -0,0 +1,101 @@ +// 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 color image with 2 bits for each channel. + /// + Rgb222, + + /// + /// RGB color image with 4 bits for each channel. + /// + Rgb444, + + /// + /// RGB Full Color. Optimized implementation for 8-bit images. + /// + Rgb888, + + /// + /// RGB color image with 10 bits for each channel. + /// + Rgb101010, + + /// + /// RGB color image with 12 bits for each channel. + /// + Rgb121212, + + /// + /// RGB color image with 14 bits for each channel. + /// + Rgb141414, + + /// + /// RGB color image with 16 bits for each channel. + /// + Rgb161616, + + /// + /// 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 0000000000..4654142571 --- /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 0000000000..dae89db28d --- /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 0000000000..1b141f9f6a --- /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 0000000000..04b6f98e50 --- /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(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + 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 0000000000..5b116b819a --- /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 0000000000..40724dd1e3 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 0000000000..32fa877b13 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 0000000000..99117063a0 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 0000000000..73f3f4b77e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -0,0 +1,94 @@ +// 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, + + /// + /// 6 bits per pixel. 2 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit6 = 6, + + /// + /// 8 bits per pixel, grayscale or color palette images. + /// + Bit8 = 8, + + /// + /// 10 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. + /// + Bit10 = 10, + + /// + /// 12 bits per pixel. 4 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit12 = 12, + + /// + /// 14 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. + /// + Bit14 = 14, + + /// + /// 16 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit16 = 16, + + /// + /// 24 bits per pixel. One byte for each color channel. + /// + Bit24 = 24, + + /// + /// 30 bits per pixel. 10 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit30 = 30, + + /// + /// 36 bits per pixel. 12 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit36 = 36, + + /// + /// 42 bits per pixel. 14 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit42 = 42, + + /// + /// 48 bits per pixel. 16 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit48 = 48, + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs new file mode 100644 index 0000000000..8fd26ac13d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -0,0 +1,138 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The number of bits per component. + /// + public readonly struct TiffBitsPerSample : IEquatable + { + /// + /// The bits for the channel 0. + /// + public readonly ushort Channel0; + + /// + /// The bits for the channel 1. + /// + public readonly ushort Channel1; + + /// + /// The bits for the channel 2. + /// + public readonly ushort Channel2; + + /// + /// The number of channels. + /// + public readonly byte Channels; + + /// + /// Initializes a new instance of the struct. + /// + /// The bits for the channel 0. + /// The bits for the channel 1. + /// The bits for the channel 2. + public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) + { + this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); + this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); + this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + + this.Channels = 0; + this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); + } + + /// + /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct. + /// + /// The value to parse. + /// The tiff bits per sample. + /// True, if the value could be parsed. + public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) + { + if (value is null || value.Length == 0) + { + sample = default; + return false; + } + + ushort c2; + ushort c1; + ushort c0; + switch (value.Length) + { + case 3: + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + case 2: + c2 = 0; + c1 = value[1]; + c0 = value[0]; + break; + default: + c2 = 0; + c1 = 0; + c0 = value[0]; + break; + } + + sample = new TiffBitsPerSample(c0, c1, c2); + return true; + } + + /// + public override bool Equals(object obj) + => obj is TiffBitsPerSample sample && this.Equals(sample); + + /// + public bool Equals(TiffBitsPerSample other) + => this.Channel0 == other.Channel0 + && this.Channel1 == other.Channel1 + && this.Channel2 == other.Channel2; + + /// + public override int GetHashCode() + => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2); + + /// + /// Converts the bits per sample struct to an ushort array. + /// + /// Bits per sample as ushort array. + public ushort[] ToArray() + { + if (this.Channel1 == 0) + { + return new[] { this.Channel0 }; + } + + if (this.Channel2 == 0) + { + return new[] { this.Channel0, this.Channel1 }; + } + + return new[] { this.Channel0, this.Channel1, this.Channel2 }; + } + + /// + /// Gets the bits per pixel for the given bits per sample. + /// + /// Bits per pixel. + public TiffBitsPerPixel BitsPerPixel() + { + int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2; + return (TiffBitsPerPixel)bitsPerPixel; + } + + /// + public override string ToString() + => $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"; + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs new file mode 100644 index 0000000000..e96dba2077 --- /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 0000000000..9d52e34dfe --- /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 0000000000..3d5bfc7374 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -0,0 +1,366 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +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 bits per sample. + /// + 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) + { + DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); + + int bitsPerPixel = 0; + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); + bitsPerPixel = this.BitsPerPixel; + } + else + { + switch (plane) + { + case 0: + bitsPerPixel = this.BitsPerSample.Channel0; + break; + case 1: + bitsPerPixel = this.BitsPerSample.Channel1; + break; + case 2: + bitsPerPixel = this.BitsPerSample.Channel2; + break; + default: + TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported"); + break; + } + } + + 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.Channels; + int stripsPerPlane = stripOffsets.Length / stripsPerPixel; + int bitsPerPixel = this.BitsPerPixel; + + Buffer2D pixels = frame.PixelBuffer; + + var stripBuffers = new IMemoryOwner[stripsPerPixel]; + + try + { + for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) + { + int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); + stripBuffers[stripIndex] = this.memoryAllocator.Allocate(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, 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 (IMemoryOwner 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 IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); + System.Span stripBufferSpan = stripBuffer.GetSpan(); + 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, 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], stripBufferSpan); + + colorDecoder.Decode(stripBufferSpan, 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 0000000000..6f8a81a827 --- /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 0000000000..288f01cd1c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -0,0 +1,315 @@ +// 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 = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); + + 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.Channels != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel > 16) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + + switch (bitsPerChannel) + { + case 8: + { + options.ColorType = TiffColorType.WhiteIsZero8; + break; + } + + case 4: + { + options.ColorType = TiffColorType.WhiteIsZero4; + break; + } + + case 1: + { + options.ColorType = TiffColorType.WhiteIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.WhiteIsZero; + break; + } + } + + break; + } + + case TiffPhotometricInterpretation.BlackIsZero: + { + if (options.BitsPerSample.Channels != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel > 16) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + + switch (bitsPerChannel) + { + case 8: + { + options.ColorType = TiffColorType.BlackIsZero8; + break; + } + + case 4: + { + options.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case 1: + { + options.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.BlackIsZero; + break; + } + } + + break; + } + + case TiffPhotometricInterpretation.Rgb: + { + if (options.BitsPerSample.Channels != 3) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + ushort bitsPerChannel = options.BitsPerSample.Channel0; + switch (bitsPerChannel) + { + case 16: + options.ColorType = TiffColorType.Rgb161616; + break; + + case 14: + options.ColorType = TiffColorType.Rgb141414; + break; + + case 12: + options.ColorType = TiffColorType.Rgb121212; + break; + + case 10: + options.ColorType = TiffColorType.Rgb101010; + break; + + case 8: + options.ColorType = TiffColorType.Rgb888; + break; + case 4: + options.ColorType = TiffColorType.Rgb444; + break; + case 2: + options.ColorType = TiffColorType.Rgb222; + break; + default: + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + break; + } + } + else + { + options.ColorType = TiffColorType.RgbPlanar; + } + + break; + } + + case TiffPhotometricInterpretation.PaletteColor: + { + options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + if (options.ColorMap != null) + { + if (options.BitsPerSample.Channels != 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs new file mode 100644 index 0000000000..7d5ccdb941 --- /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 0000000000..2273d759f0 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -0,0 +1,393 @@ +// 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; + case TiffBitsPerPixel.Bit6: + case TiffBitsPerPixel.Bit10: + case TiffBitsPerPixel.Bit12: + case TiffBitsPerPixel.Bit14: + case TiffBitsPerPixel.Bit16: + case TiffBitsPerPixel.Bit30: + case TiffBitsPerPixel.Bit36: + case TiffBitsPerPixel.Bit42: + case TiffBitsPerPixel.Bit48: + // Encoding not yet supported bits per pixel will default to 24 bits. + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); + 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 0000000000..43a0868496 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -0,0 +1,383 @@ +// 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.AddOrReplace(width); + this.collector.AddOrReplace(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); + } + + 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 planarConfig = new ExifShort(ExifTagValue.PlanarConfiguration) + { + Value = (ushort)TiffPlanarConfiguration.Chunky + }; + + 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(planarConfig); + 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.ToArray(); + } + else + { + return TiffConstants.BitsPerSample8Bit.ToArray(); + } + + case TiffPhotometricInterpretation.Rgb: + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); + + case TiffPhotometricInterpretation.WhiteIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit.ToArray(); + } + + return TiffConstants.BitsPerSample8Bit.ToArray(); + + case TiffPhotometricInterpretation.BlackIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit.ToArray(); + } + + return TiffConstants.BitsPerSample8Bit.ToArray(); + + default: + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); + } + } + + 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 0000000000..2217ffb7f7 --- /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 0000000000..002dbf039e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -0,0 +1,100 @@ +// 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 number of bits per component. + /// + public TiffBitsPerSample? BitsPerSample { 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) + { + if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample)) + { + meta.BitsPerSample = bitsPerSample; + } + + meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); + 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); + } + } + + /// + 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 0000000000..f7e6f7a997 --- /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 0000000000..cf1ab37545 --- /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 0000000000..3c541a786d --- /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 0000000000..40e67c1b0a --- /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 0000000000..7100fe9fc8 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -0,0 +1,110 @@ +// 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 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 0000000000..6c96e4fc35 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -0,0 +1,110 @@ +// 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) + { + int width = this.Image.Width; + + if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) + { + // Special case for T4BitCompressor. + int stripPixels = width * height; + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + int lastRow = y + height; + int grayRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); + grayRowIdx++; + } + + compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); + } + else + { + // Write uncompressed image. + int bytesPerStrip = this.BytesPerRow * height; + this.bitStrip ??= this.MemoryAllocator.Allocate(bytesPerStrip); + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + + Span rows = this.bitStrip.Slice(0, bytesPerStrip); + rows.Clear(); + + int outputRowIdx = 0; + int lastRow = y + height; + for (int row = y; row < lastRow; row++) + { + int bitIndex = 0; + int byteIndex = 0; + Span outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); + Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); + for (int x = 0; x < this.Image.Width; x++) + { + int shift = 7 - bitIndex; + if (pixelAsGraySpan[x] == 255) + { + outputRow[byteIndex] |= (byte)(1 << shift); + } + + bitIndex++; + if (bitIndex == 8) + { + byteIndex++; + bitIndex = 0; + } + } + + outputRowIdx++; + } + + 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 0000000000..e53f4b420d --- /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 0000000000..88c5f33ddd --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -0,0 +1,57 @@ +// 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.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 IMemoryOwner 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.Allocate(this.BytesPerRow * height); + } + + this.rowBuffer.Clear(); + + Span outputRowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); + + int width = this.Image.Width; + using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); + Span stripPixels = stripPixelBuffer.GetSpan(); + int lastRow = y + height; + int stripPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row); + stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); + stripPixelsRowIdx++; + } + + this.EncodePixels(stripPixels, outputRowSpan); + compressor.CompressStrip(outputRowSpan, 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 0000000000..117960ba7a --- /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 0000000000..e95236fd2b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +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; + private IMemoryOwner indexedPixelsBuffer; + + 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) + { + int width = this.Image.Width; + + if (this.BitsPerPixel == 4) + { + int halfWidth = width >> 1; + int excess = (width & 1) * height; // (width % 2) * height + int rows4BitBufferLength = (halfWidth * height) + excess; + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(rows4BitBufferLength); + Span rows4bit = this.indexedPixelsBuffer.GetSpan(); + int idx4bitRows = 0; + int lastRow = y + height; + for (int row = y; row < lastRow; row++) + { + ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + int idxPixels = 0; + for (int x = 0; x < halfWidth; x++) + { + rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF)); + idxPixels += 2; + idx4bitRows++; + } + + // Make sure rows are byte-aligned. + if (width % 2 != 0) + { + rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4); + } + } + + compressor.CompressStrip(rows4bit.Slice(0, idx4bitRows), height); + } + else + { + int stripPixels = width * height; + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(stripPixels); + Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); + int lastRow = y + height; + int indexedPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); + indexedPixelsRowIdx++; + } + + compressor.CompressStrip(indexedPixels.Slice(0, stripPixels), height); + } + } + + /// + protected override void Dispose(bool disposing) + { + this.quantizedImage?.Dispose(); + this.indexedPixelsBuffer?.Dispose(); + } + + private void AddColorMapTag() + { + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(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 0000000000..a3050f8a2f --- /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 0000000000..05a1ca7a24 --- /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/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index c5fc6b9395..b9220c56ab 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -239,8 +240,8 @@ namespace SixLabors.ImageSharp.IO Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - const string BufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), BufferMessage); + const string bufferMessage = "Offset subtracted from the buffer length is less than count."; + Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); return this.ReadImpl(buffer.AsSpan().Slice(offset, count)); } @@ -266,7 +267,7 @@ namespace SixLabors.ImageSharp.IO this.readOffset = 0; } - Span chunkBuffer = this.readChunk.Buffer.GetSpan(); + IMemoryOwner chunkBuffer = this.readChunk.Buffer; int chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -288,7 +289,7 @@ namespace SixLabors.ImageSharp.IO this.readChunk = this.readChunk.Next; this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer.GetSpan(); + chunkBuffer = this.readChunk.Buffer; chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -324,7 +325,7 @@ namespace SixLabors.ImageSharp.IO this.readOffset = 0; } - byte[] chunkBuffer = this.readChunk.Buffer.Array; + IMemoryOwner chunkBuffer = this.readChunk.Buffer; int chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -341,10 +342,10 @@ namespace SixLabors.ImageSharp.IO this.readChunk = this.readChunk.Next; this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer.Array; + chunkBuffer = this.readChunk.Buffer; } - return chunkBuffer[this.readOffset++]; + return chunkBuffer.GetSpan()[this.readOffset++]; } /// @@ -355,8 +356,8 @@ namespace SixLabors.ImageSharp.IO Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - const string BufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), BufferMessage); + const string bufferMessage = "Offset subtracted from the buffer length is less than count."; + Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); this.WriteImpl(buffer.AsSpan().Slice(offset, count)); } @@ -415,7 +416,7 @@ namespace SixLabors.ImageSharp.IO this.writeOffset = 0; } - byte[] chunkBuffer = this.writeChunk.Buffer.Array; + IMemoryOwner chunkBuffer = this.writeChunk.Buffer; int chunkSize = this.writeChunk.Length; if (this.writeOffset == chunkSize) @@ -424,10 +425,10 @@ namespace SixLabors.ImageSharp.IO this.writeChunk.Next = this.AllocateMemoryChunk(); this.writeChunk = this.writeChunk.Next; this.writeOffset = 0; - chunkBuffer = this.writeChunk.Buffer.Array; + chunkBuffer = this.writeChunk.Buffer; } - chunkBuffer[this.writeOffset++] = value; + chunkBuffer.GetSpan()[this.writeOffset++] = value; } /// @@ -473,7 +474,7 @@ namespace SixLabors.ImageSharp.IO this.readOffset = 0; } - byte[] chunkBuffer = this.readChunk.Buffer.Array; + IMemoryOwner chunkBuffer = this.readChunk.Buffer; int chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -495,7 +496,7 @@ namespace SixLabors.ImageSharp.IO this.readChunk = this.readChunk.Next; this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer.Array; + chunkBuffer = this.readChunk.Buffer; chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -504,7 +505,7 @@ namespace SixLabors.ImageSharp.IO } int writeCount = chunkSize - this.readOffset; - stream.Write(chunkBuffer, this.readOffset, writeCount); + stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount); this.readOffset = chunkSize; } } @@ -529,7 +530,7 @@ namespace SixLabors.ImageSharp.IO [MethodImpl(MethodImplOptions.AggressiveInlining)] private MemoryChunk AllocateMemoryChunk() { - IManagedByteBuffer buffer = this.allocator.AllocateManagedByteBuffer(this.chunkLength); + IMemoryOwner buffer = this.allocator.Allocate(this.chunkLength); return new MemoryChunk { Buffer = buffer, @@ -551,7 +552,7 @@ namespace SixLabors.ImageSharp.IO { private bool isDisposed; - public IManagedByteBuffer Buffer { get; set; } + public IMemoryOwner Buffer { get; set; } public MemoryChunk Next { get; set; } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index da23fb47dd..9d5ceeacfb 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using System.Linq; using System.Threading; @@ -57,8 +58,9 @@ namespace SixLabors.ImageSharp return null; } - using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean)) + using (IMemoryOwner buffer = config.MemoryAllocator.Allocate(headerSize, AllocationOptions.Clean)) { + Span bufferSpan = buffer.GetSpan(); long startPosition = stream.Position; // Read doesn't always guarantee the full returned length so read a byte @@ -67,7 +69,7 @@ namespace SixLabors.ImageSharp int i; do { - i = stream.Read(buffer.Array, n, headerSize - n); + i = stream.Read(bufferSpan, n, headerSize - n); n += i; } while (n < headerSize && i > 0); diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index fbb3ec2069..ce6aa69b58 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; @@ -19,6 +20,8 @@ namespace SixLabors.ImageSharp /// public abstract partial class Image : IImage, IConfigurationProvider { + private bool isDisposed; + private Size size; private readonly Configuration configuration; @@ -80,8 +83,15 @@ namespace SixLabors.ImageSharp /// public void Dispose() { + if (this.isDisposed) + { + return; + } + this.Dispose(true); GC.SuppressFinalize(this); + + this.isDisposed = true; } /// @@ -89,7 +99,7 @@ namespace SixLabors.ImageSharp /// /// The stream to save the image to. /// The encoder to save the image with. - /// Thrown if the stream or encoder is null. + /// Thrown if the stream or encoder is null. public void Save(Stream stream, IImageEncoder encoder) { Guard.NotNull(stream, nameof(stream)); @@ -148,7 +158,13 @@ namespace SixLabors.ImageSharp /// /// Throws if the image is disposed. /// - internal abstract void EnsureNotDisposed(); + internal void EnsureNotDisposed() + { + if (this.isDisposed) + { + ThrowObjectDisposedException(this.GetType()); + } + } /// /// Accepts a . @@ -167,6 +183,9 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken); + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); + private class EncodeVisitor : IImageVisitor, IImageVisitorAsync { private readonly IImageEncoder encoder; diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 62ecc71f55..07ba8c87f3 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp { @@ -11,8 +12,10 @@ namespace SixLabors.ImageSharp /// Encapsulates a pixel-agnostic collection of instances /// that make up an . /// - public abstract class ImageFrameCollection : IEnumerable + public abstract class ImageFrameCollection : IDisposable, IEnumerable { + private bool isDisposed; + /// /// Gets the number of frames. /// @@ -21,7 +24,15 @@ namespace SixLabors.ImageSharp /// /// Gets the root frame. /// - public ImageFrame RootFrame => this.NonGenericRootFrame; + public ImageFrame RootFrame + { + get + { + this.EnsureNotDisposed(); + + return this.NonGenericRootFrame; + } + } /// /// Gets the root frame. (Implements .) @@ -36,7 +47,15 @@ namespace SixLabors.ImageSharp /// /// The index. /// The at the specified index. - public ImageFrame this[int index] => this.NonGenericGetFrame(index); + public ImageFrame this[int index] + { + get + { + this.EnsureNotDisposed(); + + return this.NonGenericGetFrame(index); + } + } /// /// Determines the index of a specific in the . @@ -52,14 +71,24 @@ namespace SixLabors.ImageSharp /// The to clone and insert into the . /// Frame must have the same dimensions as the image. /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) => this.NonGenericInsertFrame(index, source); + public ImageFrame InsertFrame(int index, ImageFrame source) + { + this.EnsureNotDisposed(); + + return this.NonGenericInsertFrame(index, source); + } /// /// Clones the frame and appends the clone to the end of the collection. /// /// The raw pixel data to generate the from. /// The cloned . - public ImageFrame AddFrame(ImageFrame source) => this.NonGenericAddFrame(source); + public ImageFrame AddFrame(ImageFrame source) + { + this.EnsureNotDisposed(); + + return this.NonGenericAddFrame(source); + } /// /// Removes the frame at the specified index and frees all freeable resources associated with it. @@ -91,7 +120,12 @@ namespace SixLabors.ImageSharp /// The zero-based index of the frame to export. /// Cannot remove last frame. /// The new with the specified frame. - public Image ExportFrame(int index) => this.NonGenericExportFrame(index); + public Image ExportFrame(int index) + { + this.EnsureNotDisposed(); + + return this.NonGenericExportFrame(index); + } /// /// Creates an with only the frame at the specified index @@ -99,7 +133,12 @@ namespace SixLabors.ImageSharp /// /// The zero-based index of the frame to clone. /// The new with the specified frame. - public Image CloneFrame(int index) => this.NonGenericCloneFrame(index); + public Image CloneFrame(int index) + { + this.EnsureNotDisposed(); + + return this.NonGenericCloneFrame(index); + } /// /// Creates a new and appends it to the end of the collection. @@ -107,7 +146,12 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame() => this.NonGenericCreateFrame(); + public ImageFrame CreateFrame() + { + this.EnsureNotDisposed(); + + return this.NonGenericCreateFrame(); + } /// /// Creates a new and appends it to the end of the collection. @@ -116,14 +160,55 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor); + public ImageFrame CreateFrame(Color backgroundColor) + { + this.EnsureNotDisposed(); + + return this.NonGenericCreateFrame(backgroundColor); + } /// - public IEnumerator GetEnumerator() => this.NonGenericGetEnumerator(); + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.Dispose(true); + GC.SuppressFinalize(this); + + this.isDisposed = true; + } + + /// + public IEnumerator GetEnumerator() + { + this.EnsureNotDisposed(); + + return this.NonGenericGetEnumerator(); + } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + /// Throws if the image frame is disposed. + /// + protected void EnsureNotDisposed() + { + if (this.isDisposed) + { + ThrowObjectDisposedException(this.GetType()); + } + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected abstract void Dispose(bool disposing); + /// /// Implements . /// @@ -178,5 +263,8 @@ namespace SixLabors.ImageSharp /// The background color. /// The new frame. protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); } } diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 36c3ee481f..da024c9176 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -67,7 +67,26 @@ namespace SixLabors.ImageSharp /// /// Gets the root frame. /// - public new ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; + public new ImageFrame RootFrame + { + get + { + this.EnsureNotDisposed(); + + // frame collection would always contain at least 1 frame + // the only exception is when collection is disposed what is checked via EnsureNotDisposed() call + return this.frames[0]; + } + } + + /// + /// Gets root frame accessor in unsafe manner without any checks. + /// + /// + /// This property is most likely to be called from for indexing pixels. + /// already checks if it was disposed before querying for root frame. + /// + internal ImageFrame RootFrameUnsafe => this.frames[0]; /// protected override ImageFrame NonGenericRootFrame => this.RootFrame; @@ -80,12 +99,22 @@ namespace SixLabors.ImageSharp /// /// The index. /// The at the specified index. - public new ImageFrame this[int index] => this.frames[index]; + public new ImageFrame this[int index] + { + get + { + this.EnsureNotDisposed(); + + return this.frames[index]; + } + } /// public override int IndexOf(ImageFrame frame) { - return frame is ImageFrame specific ? this.IndexOf(specific) : -1; + this.EnsureNotDisposed(); + + return frame is ImageFrame specific ? this.frames.IndexOf(specific) : -1; } /// @@ -93,7 +122,12 @@ namespace SixLabors.ImageSharp /// /// The to locate in the . /// The index of item if found in the list; otherwise, -1. - public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame); + public int IndexOf(ImageFrame frame) + { + this.EnsureNotDisposed(); + + return this.frames.IndexOf(frame); + } /// /// Clones and inserts the into the at the specified . @@ -104,6 +138,8 @@ namespace SixLabors.ImageSharp /// The cloned . public ImageFrame InsertFrame(int index, ImageFrame source) { + this.EnsureNotDisposed(); + this.ValidateFrame(source); ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Insert(index, clonedFrame); @@ -117,6 +153,8 @@ namespace SixLabors.ImageSharp /// The cloned . public ImageFrame AddFrame(ImageFrame source) { + this.EnsureNotDisposed(); + this.ValidateFrame(source); ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Add(clonedFrame); @@ -131,6 +169,8 @@ namespace SixLabors.ImageSharp /// The new . public ImageFrame AddFrame(ReadOnlySpan source) { + this.EnsureNotDisposed(); + var frame = ImageFrame.LoadPixelData( this.parent.GetConfiguration(), source, @@ -149,6 +189,7 @@ namespace SixLabors.ImageSharp public ImageFrame AddFrame(TPixel[] source) { Guard.NotNull(source, nameof(source)); + return this.AddFrame(source.AsSpan()); } @@ -159,6 +200,8 @@ namespace SixLabors.ImageSharp /// Cannot remove last frame. public override void RemoveFrame(int index) { + this.EnsureNotDisposed(); + if (index == 0 && this.Count == 1) { throw new InvalidOperationException("Cannot remove last frame."); @@ -170,8 +213,12 @@ namespace SixLabors.ImageSharp } /// - public override bool Contains(ImageFrame frame) => - frame is ImageFrame specific && this.Contains(specific); + public override bool Contains(ImageFrame frame) + { + this.EnsureNotDisposed(); + + return frame is ImageFrame specific && this.frames.Contains(specific); + } /// /// Determines whether the contains the . @@ -180,7 +227,12 @@ namespace SixLabors.ImageSharp /// /// true if the contains the specified frame; otherwise, false. /// - public bool Contains(ImageFrame frame) => this.frames.Contains(frame); + public bool Contains(ImageFrame frame) + { + this.EnsureNotDisposed(); + + return this.frames.Contains(frame); + } /// /// Moves an from to . @@ -189,6 +241,8 @@ namespace SixLabors.ImageSharp /// The index to move the frame to. public override void MoveFrame(int sourceIndex, int destinationIndex) { + this.EnsureNotDisposed(); + if (sourceIndex == destinationIndex) { return; @@ -208,6 +262,8 @@ namespace SixLabors.ImageSharp /// The new with the specified frame. public new Image ExportFrame(int index) { + this.EnsureNotDisposed(); + ImageFrame frame = this[index]; if (this.Count == 1 && this.frames.Contains(frame)) @@ -228,6 +284,8 @@ namespace SixLabors.ImageSharp /// The new with the specified frame. public new Image CloneFrame(int index) { + this.EnsureNotDisposed(); + ImageFrame frame = this[index]; ImageFrame clonedFrame = frame.Clone(); return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); @@ -241,6 +299,8 @@ namespace SixLabors.ImageSharp /// public new ImageFrame CreateFrame() { + this.EnsureNotDisposed(); + var frame = new ImageFrame( this.parent.GetConfiguration(), this.RootFrame.Width, @@ -335,14 +395,18 @@ namespace SixLabors.ImageSharp } } - internal void Dispose() + /// + protected override void Dispose(bool disposing) { - foreach (ImageFrame f in this.frames) + if (disposing) { - f.Dispose(); - } + foreach (ImageFrame f in this.frames) + { + f.Dispose(); + } - this.frames.Clear(); + this.frames.Clear(); + } } private ImageFrame CopyNonCompatibleFrame(ImageFrame source) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 510f34dc7d..7719b12420 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -12,7 +12,7 @@ $(RepositoryUrl) Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 83ecc37530..b43ff0422b 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp public sealed class Image : Image where TPixel : unmanaged, IPixel { - private bool isDisposed; + private readonly ImageFrameCollection frames; /// /// Initializes a new instance of the class @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp internal Image(Configuration configuration, int width, int height, ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, default(TPixel)); + this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); } /// @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); + this.frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, backgroundColor); + this.frames = new ImageFrameCollection(this, width, height, backgroundColor); } /// @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) : base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(frames)) { - this.Frames = new ImageFrameCollection(this, frames); + this.frames = new ImageFrameCollection(this, frames); } /// @@ -146,12 +146,19 @@ namespace SixLabors.ImageSharp /// /// Gets the collection of image frames. /// - public new ImageFrameCollection Frames { get; } + public new ImageFrameCollection Frames + { + get + { + this.EnsureNotDisposed(); + return this.frames; + } + } /// /// Gets the root frame. /// - private IPixelSource PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); + private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe; /// /// Gets or sets the pixel at the specified position. @@ -165,15 +172,19 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] get { + this.EnsureNotDisposed(); + this.VerifyCoords(x, y); - return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y); + return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y); } [MethodImpl(InliningOptions.ShortMethod)] set { + this.EnsureNotDisposed(); + this.VerifyCoords(x, y); - this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value; + this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y) = value; } } @@ -189,7 +200,9 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex); + this.EnsureNotDisposed(); + + return this.PixelSourceUnsafe.PixelBuffer.GetRowSpan(rowIndex); } /// @@ -226,10 +239,10 @@ namespace SixLabors.ImageSharp { this.EnsureNotDisposed(); - var clonedFrames = new ImageFrame[this.Frames.Count]; + var clonedFrames = new ImageFrame[this.frames.Count]; for (int i = 0; i < clonedFrames.Length; i++) { - clonedFrames[i] = this.Frames[i].Clone(configuration); + clonedFrames[i] = this.frames[i].Clone(configuration); } return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); @@ -245,10 +258,10 @@ namespace SixLabors.ImageSharp { this.EnsureNotDisposed(); - var clonedFrames = new ImageFrame[this.Frames.Count]; + var clonedFrames = new ImageFrame[this.frames.Count]; for (int i = 0; i < clonedFrames.Length; i++) { - clonedFrames[i] = this.Frames[i].CloneAs(configuration); + clonedFrames[i] = this.frames[i].CloneAs(configuration); } return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); @@ -257,25 +270,9 @@ namespace SixLabors.ImageSharp /// protected override void Dispose(bool disposing) { - if (this.isDisposed) - { - return; - } - if (disposing) { - this.Frames.Dispose(); - } - - this.isDisposed = true; - } - - /// - internal override void EnsureNotDisposed() - { - if (this.isDisposed) - { - throw new ObjectDisposedException("Trying to execute an operation on a disposed image."); + this.frames.Dispose(); } } @@ -306,9 +303,12 @@ namespace SixLabors.ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - for (int i = 0; i < this.Frames.Count; i++) + this.EnsureNotDisposed(); + + ImageFrameCollection sourceFrames = pixelSource.Frames; + for (int i = 0; i < this.frames.Count; i++) { - this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]); + this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); } this.UpdateSize(pixelSource.Size()); diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index 8814bbe1f5..a79e042a32 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -131,10 +131,6 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); int itemSizeBytes = Unsafe.SizeOf(); int bufferSizeInBytes = length * itemSizeBytes; - if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes) - { - ThrowInvalidAllocationException(length); - } ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); byte[] byteArray = pool.Rent(bufferSizeInBytes); @@ -171,9 +167,9 @@ namespace SixLabors.ImageSharp.Memory } [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowInvalidAllocationException(int length) => + private static void ThrowInvalidAllocationException(int length, int max) => throw new InvalidMemoryOperationException( - $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); + $"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator."); private ArrayPool GetArrayPool(int bufferSizeInBytes) { diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index ff376a6186..af56b99a08 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Memory protected internal abstract int GetBufferCapacityInBytes(); /// - /// Allocates an , holding a of length . + /// Allocates an , holding a of length . /// /// Type of the data stored in the buffer. /// Size of the buffer to allocate. diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 9fce9a4f4e..6458ad7e4c 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Memory /// Copy columns of inplace, /// from positions starting at to positions at . /// - internal static unsafe void CopyColumns( + internal static unsafe void DangerousCopyColumns( this Buffer2D buffer, int sourceIndex, int destIndex, @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); + Span span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span); fixed (byte* ptr = span) { diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 38ca89e59b..21c19f5d52 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -168,10 +168,10 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span GetSingleSpan() + internal Span DangerousGetSingleSpan() { // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow(); + return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow(); } /// @@ -183,10 +183,10 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory GetSingleMemory() + internal Memory DangerousGetSingleMemory() { // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow(); + return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow(); } /// @@ -203,10 +203,10 @@ namespace SixLabors.ImageSharp.Memory private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); [MethodImpl(InliningOptions.ColdPath)] - private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single(); + private Memory DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single(); [MethodImpl(InliningOptions.ColdPath)] - private Span GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; + private Span DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; [MethodImpl(InliningOptions.ColdPath)] private ref T GetElementSlow(int x, int y) diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 922088b26d..2f70ac05e8 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -67,21 +67,21 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). + /// Allocates padded buffers. Generally used by encoder/decoders. /// /// The . /// Pixel count in the row /// The pixel size in bytes, eg. 3 for RGB. /// The padding. - /// A . - internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + /// A . + internal static IMemoryOwner AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, int pixelSizeInBytes, int paddingInBytes) { int length = (width * pixelSizeInBytes) + paddingInBytes; - return memoryAllocator.AllocateManagedByteBuffer(length); + return memoryAllocator.Allocate(length); } /// diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 2021f1249b..1819fd2bc5 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 13e67554c5..733eb4a798 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 dc12f3819b..0a9c879ce6 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 55af45fb46..9265314edb 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 749c691865..6e671b3eca 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 a240c13925..e7a01b070f 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 e20867b43e..fdde66c513 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 7e73b75aad..680136b039 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 e9440119ef..e4ea6d0de6 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 f520455311..d27c26ee51 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 ce0bb36f0d..6ed9131c83 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 e07a32598d..3d13a82dcc 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 28002f0b75..2d3a93aed3 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 2d8aa92601..af1eee2dc8 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/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs index f4664a5c0f..296ed71b72 100644 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing Dither(source, KnownDitherings.Bayer8x8); /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// Dithers the image reducing it to a web-safe palette. /// /// The image this method extends. /// The ordered ditherer. @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither)); /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// Dithers the image reducing it to a web-safe palette. /// /// The image this method extends. /// The ordered ditherer. @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale)); /// - /// Dithers the image reducing it to the given palette using ordered dithering. + /// Dithers the image reducing it to the given palette. /// /// The image this method extends. /// The ordered ditherer. @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither, palette)); /// - /// Dithers the image reducing it to the given palette using ordered dithering. + /// Dithers the image reducing it to the given palette. /// /// The image this method extends. /// The ordered ditherer. @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing Dither(source, KnownDitherings.Bayer8x8, rectangle); /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// Dithers the image reducing it to a web-safe palette. /// /// The image this method extends. /// The ordered ditherer. @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); /// - /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// Dithers the image reducing it to a web-safe palette. /// /// The image this method extends. /// The ordered ditherer. @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle); /// - /// Dithers the image reducing it to the given palette using ordered dithering. + /// Dithers the image reducing it to the given palette. /// /// The image this method extends. /// The ordered ditherer. @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle); /// - /// Dithers the image reducing it to the given palette using ordered dithering. + /// Dithers the image reducing it to the given palette. /// /// The image this method extends. /// The ordered ditherer. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 2b7eb165eb..c317ddf02a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -110,17 +109,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel { - var ditherOperation = new QuantizeDitherRowOperation( - ref quantizer, - in Unsafe.AsRef(this), - source, - destination, - bounds); + int spread = CalculatePaletteSpread(destination.Palette.Length); + float scale = quantizer.Options.DitherScale; - ParallelRowIterator.IterateRows( - quantizer.Configuration, - bounds, - in ditherOperation); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ReadOnlySpan sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length); + + for (int x = 0; x < sourceRow.Length; x++) + { + TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale); + destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); + } + } } /// @@ -132,16 +134,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { - var ditherOperation = new PaletteDitherRowOperation( - in processor, - in Unsafe.AsRef(this), - source, - bounds); + int spread = CalculatePaletteSpread(processor.Palette.Length); + float scale = processor.DitherScale; - ParallelRowIterator.IterateRows( - processor.Configuration, - bounds, - in ditherOperation); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + + for (int x = 0; x < row.Length; x++) + { + ref TPixel sourcePixel = ref row[x]; + TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale); + sourcePixel = processor.GetPaletteColor(dithered); + } + } } // Spread assumes an even colorspace distribution and precision. @@ -195,95 +201,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); - - private readonly struct QuantizeDitherRowOperation : IRowOperation - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - private readonly TFrameQuantizer quantizer; - private readonly OrderedDither dither; - private readonly ImageFrame source; - private readonly IndexedImageFrame destination; - private readonly Rectangle bounds; - private readonly int spread; - - [MethodImpl(InliningOptions.ShortMethod)] - public QuantizeDitherRowOperation( - ref TFrameQuantizer quantizer, - in OrderedDither dither, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - { - this.quantizer = quantizer; - this.dither = dither; - this.source = source; - this.destination = destination; - this.bounds = bounds; - this.spread = CalculatePaletteSpread(destination.Palette.Length); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - ref TFrameQuantizer quantizer = ref Unsafe.AsRef(this.quantizer); - int spread = this.spread; - float scale = this.quantizer.Options.DitherScale; - - ReadOnlySpan sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - Span destRow = - this.destination.GetWritablePixelRowSpanUnsafe(y - this.bounds.Y).Slice(0, sourceRow.Length); - - for (int x = 0; x < sourceRow.Length; x++) - { - TPixel dithered = this.dither.Dither(sourceRow[x], x, y, spread, scale); - destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); - } - } - } - - private readonly struct PaletteDitherRowOperation : IRowOperation - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel - { - private readonly TPaletteDitherImageProcessor processor; - private readonly OrderedDither dither; - private readonly ImageFrame source; - private readonly Rectangle bounds; - private readonly float scale; - private readonly int spread; - - [MethodImpl(InliningOptions.ShortMethod)] - public PaletteDitherRowOperation( - in TPaletteDitherImageProcessor processor, - in OrderedDither dither, - ImageFrame source, - Rectangle bounds) - { - this.processor = processor; - this.dither = dither; - this.source = source; - this.bounds = bounds; - this.scale = processor.DitherScale; - this.spread = CalculatePaletteSpread(processor.Palette.Length); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - ref TPaletteDitherImageProcessor processor = ref Unsafe.AsRef(this.processor); - int spread = this.spread; - float scale = this.scale; - - Span row = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - for (int x = 0; x < row.Length; x++) - { - ref TPixel sourcePixel = ref row[x]; - TPixel dithered = this.dither.Dither(sourcePixel, x, y, spread, scale); - sourcePixel = processor.GetPaletteColor(dithered); - } - } - } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 4631cd4229..07af8a5af0 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -62,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering if (disposing) { this.paletteOwner.Dispose(); + this.ditherProcessor.Dispose(); } this.paletteOwner = null; @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// . /// /// Internal for AOT - internal readonly struct DitherProcessor : IPaletteDitherImageProcessor + internal readonly struct DitherProcessor : IPaletteDitherImageProcessor, IDisposable { private readonly EuclideanPixelMap pixelMap; @@ -101,6 +102,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.pixelMap.GetClosestColor(color, out TPixel match); return match; } + + public void Dispose() => this.pixelMap.Dispose(); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs index 56593acb84..9b28a8fdd8 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 14687426d0..91ed9f5de4 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 60686f4014..f93334beb0 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 59df3058d9..9227cb0c01 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/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index c194f402a3..b82ce71bbd 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; -using System.Numerics; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -14,26 +14,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Gets the closest color to the supplied color based upon the Euclidean distance. /// /// The pixel format. - internal readonly struct EuclideanPixelMap + /// + /// This class is not threadsafe and should not be accessed in parallel. + /// Doing so will result in non-idempotent results. + /// + internal sealed class EuclideanPixelMap : IDisposable where TPixel : unmanaged, IPixel { - private readonly Vector4[] vectorCache; - private readonly ConcurrentDictionary distanceCache; + private Rgba32[] rgbaPalette; + private readonly ColorDistanceCache cache; + private readonly Configuration configuration; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// /// The configuration. /// The color palette to map from. - [MethodImpl(InliningOptions.ShortMethod)] public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { + this.configuration = configuration; this.Palette = palette; - this.vectorCache = new Vector4[palette.Length]; - - // Use the same rules across all target frameworks. - this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); - PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); + this.rgbaPalette = new Rgba32[palette.Length]; + this.cache = new ColorDistanceCache(configuration.MemoryAllocator); + PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); } /// @@ -44,6 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { [MethodImpl(InliningOptions.ShortMethod)] get; + + [MethodImpl(InliningOptions.ShortMethod)] + private set; } /// @@ -57,29 +63,41 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public int GetClosestColor(TPixel color, out TPixel match) { ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); + Unsafe.SkipInit(out Rgba32 rgba); + color.ToRgba32(ref rgba); // Check if the color is in the lookup table - if (!this.distanceCache.TryGetValue(color, out int index)) + if (!this.cache.TryGetValue(rgba, out short index)) { - return this.GetClosestColorSlow(color, ref paletteRef, out match); + return this.GetClosestColorSlow(rgba, ref paletteRef, out match); } match = Unsafe.Add(ref paletteRef, index); return index; } + /// + /// Clears the map, resetting it to use the given palette. + /// + /// The color palette to map from. + public void Clear(ReadOnlyMemory palette) + { + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); + this.cache.Clear(); + } + [MethodImpl(InliningOptions.ShortMethod)] - private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) + private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { // Loop through the palette and find the nearest match. int index = 0; float leastDistance = float.MaxValue; - var vector = color.ToVector4(); - ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); - for (int i = 0; i < this.Palette.Length; i++) + for (int i = 0; i < this.rgbaPalette.Length; i++) { - Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); - float distance = Vector4.DistanceSquared(vector, candidate); + Rgba32 candidate = this.rgbaPalette[i]; + int distance = DistanceSquared(rgba, candidate); // If it's an exact match, exit the loop if (distance == 0) @@ -97,9 +115,109 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // Now I have the index, pop it into the cache for next time - this.distanceCache[color] = index; + this.cache.Add(rgba, (byte)index); match = Unsafe.Add(ref paletteRef, index); return index; } + + /// + /// Returns the Euclidean distance squared between two specified points. + /// + /// The first point. + /// The second point. + /// The distance squared. + [MethodImpl(InliningOptions.ShortMethod)] + private static int DistanceSquared(Rgba32 a, Rgba32 b) + { + int deltaR = a.R - b.R; + int deltaG = a.G - b.G; + int deltaB = a.B - b.B; + int deltaA = a.A - b.A; + return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); + } + + public void Dispose() => this.cache.Dispose(); + + /// + /// A cache for storing color distance matching results. + /// + /// + /// + /// The granularity of the cache has been determined based upon the current + /// suite of test images and provides the lowest possible memory usage while + /// providing enough match accuracy. + /// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB). + /// + /// + private unsafe struct ColorDistanceCache : IDisposable + { + private const int IndexBits = 5; + private const int IndexAlphaBits = 5; + private const int IndexCount = (1 << IndexBits) + 1; + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + private const int RgbShift = 8 - IndexBits; + private const int AlphaShift = 8 - IndexAlphaBits; + private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + private MemoryHandle tableHandle; + private readonly IMemoryOwner table; + private readonly short* tablePointer; + + public ColorDistanceCache(MemoryAllocator allocator) + { + this.table = allocator.Allocate(Entries); + this.table.GetSpan().Fill(-1); + this.tableHandle = this.table.Memory.Pin(); + this.tablePointer = (short*)this.tableHandle.Pointer; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Add(Rgba32 rgba, byte index) + { + int r = rgba.R >> RgbShift; + int g = rgba.G >> RgbShift; + int b = rgba.B >> RgbShift; + int a = rgba.A >> AlphaShift; + int idx = GetPaletteIndex(r, g, b, a); + this.tablePointer[idx] = index; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public bool TryGetValue(Rgba32 rgba, out short match) + { + int r = rgba.R >> RgbShift; + int g = rgba.G >> RgbShift; + int b = rgba.B >> RgbShift; + int a = rgba.A >> AlphaShift; + int idx = GetPaletteIndex(r, g, b, a); + match = this.tablePointer[idx]; + return match > -1; + } + + /// + /// Clears the cache resetting each entry to empty. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Clear() => this.table.GetSpan().Fill(-1); + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetPaletteIndex(int r, int g, int b, int a) + => (r << ((IndexBits << 1) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits << 1)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; + + public void Dispose() + { + if (this.table != null) + { + this.tableHandle.Dispose(); + this.table.Dispose(); + } + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 700314f26c..311a8aa2e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { private readonly int maxColors; + private readonly int bitDepth; private readonly Octree octree; private IMemoryOwner paletteOwner; private ReadOnlyMemory palette; @@ -42,10 +43,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Options = options; this.maxColors = this.Options.MaxColors; - this.octree = new Octree(Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8)); + this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); + this.octree = new Octree(this.bitDepth); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); - this.palette = default; this.pixelMap = default; + this.palette = default; this.isDithering = !(this.Options.Dither is null); this.isDisposed = false; } @@ -67,36 +69,57 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - [MethodImpl(InliningOptions.ShortMethod)] public void AddPaletteColors(Buffer2DRegion pixelRegion) { Rectangle bounds = pixelRegion.Rectangle; Buffer2D source = pixelRegion.Buffer; - using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); - Span bufferSpan = buffer.GetSpan(); - - // Loop through each row - for (int y = bounds.Top; y < bounds.Bottom; y++) + using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width)) { - Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + Span bufferSpan = buffer.GetSpan(); - for (int x = 0; x < bufferSpan.Length; x++) + // Loop through each row + for (int y = bounds.Top; y < bounds.Bottom; y++) { - Rgba32 rgba = bufferSpan[x]; + Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[x]; - // Add the color to the Octree - this.octree.AddColor(rgba); + // Add the color to the Octree + this.octree.AddColor(rgba); + } } } - Span paletteSpan = this.paletteOwner.GetSpan(); int paletteIndex = 0; - this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); + Span paletteSpan = this.paletteOwner.GetSpan(); + + // On very rare occasions, (blur.png), the quantizer does not preserve a + // transparent entry when palletizing the captured colors. + // To workaround this we ensure the palette ends with the default color + // for higher bit depths. Lower bit depths will correctly reduce the palette. + // TODO: Investigate more evenly reduced palette reduction. + int max = this.maxColors; + if (this.bitDepth == 8) + { + max--; + } + + this.octree.Palletize(paletteSpan, max, ref paletteIndex); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); - // Length of reduced palette + transparency. - ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors)); - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) + { + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + } + else + { + this.pixelMap.Clear(result); + } this.palette = result; } @@ -118,8 +141,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return (byte)this.pixelMap.GetClosestColor(color, out match); } - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); - var index = (byte)this.octree.GetPaletteIndex(color); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); + byte index = (byte)this.octree.GetPaletteIndex(color); match = Unsafe.Add(ref paletteRef, index); return index; } @@ -130,8 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (!this.isDisposed) { this.isDisposed = true; - this.paletteOwner.Dispose(); + this.paletteOwner?.Dispose(); this.paletteOwner = null; + this.pixelMap?.Dispose(); + this.pixelMap = null; } } @@ -176,21 +201,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.previousNode = null; } - /// - /// Gets the mask used when getting the appropriate pixels for a given node. - /// - private static ReadOnlySpan Mask => new byte[] - { - 0b10000000, - 0b1000000, - 0b100000, - 0b10000, - 0b1000, - 0b100, - 0b10, - 0b1 - }; - /// /// Gets or sets the number of leaves in the tree /// @@ -251,7 +261,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public void Palletize(Span palette, int colorCount, ref int paletteIndex) { - while (this.Leaves > colorCount - 1) + while (this.Leaves > colorCount) { this.Reduce(); } @@ -269,7 +279,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public int GetPaletteIndex(TPixel color) { - Rgba32 rgba = default; + Unsafe.SkipInit(out Rgba32 rgba); color.ToRgba32(ref rgba); return this.root.GetPaletteIndex(ref rgba, 0); } @@ -468,7 +478,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Vector3.Zero, new Vector3(255)); - TPixel pixel = default; + Unsafe.SkipInit(out TPixel pixel); pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); palette[index] = pixel; @@ -517,7 +527,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization child = this.children[i]; if (child != null) { - var childIndex = child.GetPaletteIndex(ref pixel, level + 1); + int childIndex = child.GetPaletteIndex(ref pixel, level + 1); if (childIndex != 0) { return childIndex; @@ -538,15 +548,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] private static int GetColorIndex(ref Rgba32 color, int level) { - DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level)); - int shift = 7 - level; - ref byte maskRef = ref MemoryMarshal.GetReference(Mask); - byte mask = Unsafe.Add(ref maskRef, level); + byte mask = (byte)(1 << shift); return ((color.R & mask) >> shift) - | ((color.G & mask) >> (shift - 1)) - | ((color.B & mask) >> (shift - 2)); + | (((color.G & mask) >> shift) << 1) + | (((color.B & mask) >> shift) << 2); } /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index bc5eb783f7..4f73f4ac81 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -58,9 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var palette = new TPixel[length]; Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); - - var pixelMap = new EuclideanPixelMap(configuration, palette); - return new PaletteQuantizer(configuration, options, pixelMap); + return new PaletteQuantizer(configuration, options, palette); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index d0dbdae204..284f4fa701 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -16,26 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal struct PaletteQuantizer : IQuantizer where TPixel : unmanaged, IPixel { - private readonly EuclideanPixelMap pixelMap; + private EuclideanPixelMap pixelMap; /// /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. - /// The pixel map for looking up color matches from a predefined palette. + /// The palette to use. [MethodImpl(InliningOptions.ShortMethod)] - public PaletteQuantizer( - Configuration configuration, - QuantizerOptions options, - EuclideanPixelMap pixelMap) + public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options)); this.Configuration = configuration; this.Options = options; - this.pixelMap = pixelMap; + this.pixelMap = new EuclideanPixelMap(configuration, palette); } /// @@ -66,6 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public void Dispose() { + this.pixelMap?.Dispose(); + this.pixelMap = null; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index bb6d3d44a6..93bca60756 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -41,46 +41,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); - var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); - ParallelRowIterator.IterateRowIntervals( - configuration, - interest, - in operation); - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle bounds; - private readonly ImageFrame source; - private readonly IndexedImageFrame quantized; + ReadOnlySpan paletteSpan = quantized.Palette.Span; + int offsetY = interest.Top; + int offsetX = interest.Left; - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Rectangle bounds, - ImageFrame source, - IndexedImageFrame quantized) + for (int y = interest.Y; y < interest.Height; y++) { - this.bounds = bounds; - this.source = source; - this.quantized = quantized; - } + Span row = source.GetPixelRowSpan(y); + ReadOnlySpan quantizedRow = quantized.GetPixelRowSpan(y - offsetY); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - ReadOnlySpan paletteSpan = this.quantized.Palette.Span; - int offsetY = this.bounds.Top; - int offsetX = this.bounds.Left; - - for (int y = rows.Min; y < rows.Max; y++) + for (int x = interest.Left; x < interest.Right; x++) { - Span row = this.source.GetPixelRowSpan(y); - ReadOnlySpan quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY); - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - row[x] = paletteSpan[quantizedRow[x - offsetX]]; - } + row[x] = paletteSpan[quantizedRow[x - offsetX]]; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index d9bc818560..6c963bfabd 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -126,62 +125,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { - var operation = new RowIntervalOperation( - ref quantizer, - source, - destination, - bounds); + int offsetY = bounds.Top; + int offsetX = bounds.Left; - ParallelRowIterator.IterateRowIntervals( - quantizer.Configuration, - bounds, - in operation); - - return; - } - - dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - private readonly TFrameQuantizer quantizer; - private readonly ImageFrame source; - private readonly IndexedImageFrame destination; - private readonly Rectangle bounds; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - { - this.quantizer = quantizer; - this.source = source; - this.destination = destination; - this.bounds = bounds; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - int offsetY = this.bounds.Top; - int offsetX = this.bounds.Left; - - for (int y = rows.Min; y < rows.Max; y++) + for (int y = bounds.Y; y < bounds.Height; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); + Span sourceRow = source.GetPixelRowSpan(y); + Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); - for (int x = this.bounds.Left; x < this.bounds.Right; x++) + for (int x = bounds.Left; x < bounds.Right; x++) { - destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); + destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); } } + + return; } + + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index e449678559..bf4a5ca41e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -94,10 +93,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); - this.palette = default; this.colorCube = new Box[this.maxColors]; this.isDisposed = false; this.pixelMap = default; + this.palette = default; this.isDithering = this.isDithering = !(this.Options.Dither is null); } @@ -127,9 +126,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.Get3DMoments(this.memoryAllocator); this.BuildCube(); + // Slice again since maxColors has been updated since the buffer was created. + Span paletteSpan = this.paletteOwner.GetSpan().Slice(0, this.maxColors); ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); - Span paletteSpan = this.paletteOwner.GetSpan(); - for (int k = 0; k < this.maxColors; k++) + for (int k = 0; k < paletteSpan.Length; k++) { this.Mark(ref this.colorCube[k], (byte)k); @@ -142,8 +142,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); + if (this.isDithering) + { + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) + { + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + } + else + { + this.pixelMap.Clear(result); + } + } + this.palette = result; } @@ -170,7 +183,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); match = Unsafe.Add(ref paletteRef, index); return index; } @@ -187,6 +200,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.momentsOwner = null; this.tagsOwner = null; this.paletteOwner = null; + this.pixelMap?.Dispose(); + this.pixelMap = null; } } @@ -200,16 +215,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The index. [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) - { - return (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - } + => (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; /// /// Computes sum over a box of any given statistic. @@ -218,24 +231,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The moment. /// The result. private static Moment Volume(ref Box cube, ReadOnlySpan moments) - { - return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - } + => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; /// /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). @@ -820,7 +831,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public int Volume; /// - public readonly override bool Equals(object obj) + public override readonly bool Equals(object obj) => obj is Box box && this.Equals(box); @@ -837,7 +848,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization && this.Volume == other.Volume; /// - public readonly override int GetHashCode() + public override readonly int GetHashCode() { HashCode hash = default; hash.Add(this.RMin); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index ab6040c17b..a58c20f687 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.GetSingleMemory().Pin(); + this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; } @@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); // 'ratio' is a rational number. - // Multiplying it by LCM(sourceSize, destSize)/sourceSize will result in a whole number "again". + // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again". // This value is determining the length of the periods in repeating kernel map rows. - int period = Numerics.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; + int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize); // the center position at i == 0: double center0 = (ratio - 1) * 0.5; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index e7207c7e63..7ade3aeeea 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempColSpan = this.tempColumnBuffer.GetSpan(); // When creating transposedFirstPassBuffer, we made sure it's contiguous: - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) { @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Copy previous bottom band to the new top: // (rows <--> columns, because the buffer is transposed) - this.transposedFirstPassBuffer.CopyColumns( + this.transposedFirstPassBuffer.DangerousCopyColumns( this.workerHeight - this.windowBandHeight, 0, this.windowBandHeight); @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index af86f49b09..53b4f9632c 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -18,17 +18,21 @@ - - + + - - - + + + + + + - - - + + + + diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs new file mode 100644 index 0000000000..e77bb8b3e4 --- /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 bcb015e570..0e320e4a7f 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 0000000000..025412adcd --- /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/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs deleted file mode 100644 index ebd3e40130..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components -{ - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_Scale16X16To8X8 - { - private Block8x8F source; - private readonly Block8x8F[] target = new Block8x8F[4]; - - [GlobalSetup] - public void Setup() - { - var random = new Random(); - - float[] f = new float[8 * 8]; - for (int i = 0; i < f.Length; i++) - { - f[i] = (float)random.NextDouble(); - } - - for (int i = 0; i < 4; i++) - { - this.target[i] = Block8x8F.Load(f); - } - - this.source = Block8x8F.Load(f); - } - - [Benchmark] - public void Scale16X16To8X8() => Block8x8F.Scale16X16To8X8(ref this.source, this.target); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index 5a9ceea946..87170e8d24 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -4,6 +4,7 @@ using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -12,10 +13,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { public class EncodeJpeg { - // System.Drawing needs this. - private Stream bmpStream; + [Params(75, 90, 100)] + public int Quality; + + private const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr; + + // System.Drawing private SDImage bmpDrawing; + private Stream bmpStream; + private ImageCodecInfo jpegCodec; + private EncoderParameters encoderParameters; + + // ImageSharp private Image bmpCore; + private JpegEncoder encoder420; + private JpegEncoder encoder444; + private MemoryStream destinationStream; [GlobalSetup] @@ -23,12 +36,23 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { if (this.bmpStream == null) { - const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr; this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + this.bmpCore = Image.Load(this.bmpStream); this.bmpCore.Metadata.ExifProfile = null; + this.encoder420 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio420 }; + this.encoder444 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio444 }; + this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); + this.jpegCodec = GetEncoder(ImageFormat.Jpeg); + this.encoderParameters = new EncoderParameters(1); + + // Quality cast to long is necessary +#pragma warning disable IDE0004 // Remove Unnecessary Cast + this.encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)this.Quality); +#pragma warning restore IDE0004 // Remove Unnecessary Cast + this.destinationStream = new MemoryStream(); } } @@ -38,36 +62,73 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { this.bmpStream.Dispose(); this.bmpStream = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); + + this.encoderParameters.Dispose(); } - [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] + [Benchmark(Baseline = true, Description = "System.Drawing Jpeg 4:2:0")] public void JpegSystemDrawing() { - this.bmpDrawing.Save(this.destinationStream, ImageFormat.Jpeg); + this.bmpDrawing.Save(this.destinationStream, this.jpegCodec, this.encoderParameters); + this.destinationStream.Seek(0, SeekOrigin.Begin); + } + + [Benchmark(Description = "ImageSharp Jpeg 4:2:0")] + public void JpegCore420() + { + this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder420); this.destinationStream.Seek(0, SeekOrigin.Begin); } - [Benchmark(Description = "ImageSharp Jpeg")] - public void JpegCore() + [Benchmark(Description = "ImageSharp Jpeg 4:4:4")] + public void JpegCore444() { - this.bmpCore.SaveAsJpeg(this.destinationStream); + this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder444); this.destinationStream.Seek(0, SeekOrigin.Begin); } + + // https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparameter?redirectedfrom=MSDN&view=net-5.0 + private static ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + foreach (ImageCodecInfo codec in codecs) + { + if (codec.FormatID == format.Guid) + { + return codec; + } + } + + return null; + } } } /* -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2) -Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.302 - [Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT - DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 +Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=6.0.100-preview.3.21202.5 + [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT + DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -| Method | Mean | Error | StdDev | Ratio | RatioSD | -|---------------------- |---------:|----------:|----------:|------:|--------:| -| 'System.Drawing Jpeg' | 4.297 ms | 0.0244 ms | 0.0228 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg' | 5.286 ms | 0.1034 ms | 0.0967 ms | 1.23 | 0.02 | +| Method | Quality | Mean | Error | StdDev | Ratio | +|---------------------------- |-------- |---------:|---------:|---------:|------:| +| 'System.Drawing Jpeg 4:2:0' | 75 | 29.41 ms | 0.108 ms | 0.096 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 75 | 26.30 ms | 0.131 ms | 0.109 ms | 0.89 | +| 'ImageSharp Jpeg 4:4:4' | 75 | 36.70 ms | 0.303 ms | 0.269 ms | 1.25 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 90 | 32.67 ms | 0.226 ms | 0.211 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 90 | 33.56 ms | 0.237 ms | 0.222 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 90 | 44.82 ms | 0.250 ms | 0.234 ms | 1.37 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 100 | 39.06 ms | 0.233 ms | 0.218 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 100 | 40.23 ms | 0.225 ms | 0.277 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 100 | 63.35 ms | 0.486 ms | 0.431 ms | 1.62 | */ diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 0c40b482ad..63064eec5b 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/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs index 1db4072932..9aafb6936b 100644 --- a/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder Block8x8F cb = default; Block8x8F cr = default; - this.converter.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + this.converter.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); } [Benchmark] @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder if (RgbToYCbCrConverterVectorized.IsSupported) { - RgbToYCbCrConverterVectorized.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + RgbToYCbCrConverterVectorized.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs index 2d0fce0a01..416a62833c 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 0153ca8b1e..8a90b44cfb 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.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index a146dc03ee..84b83ee14a 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -8,8 +8,8 @@ false false - Debug;Release;Release-InnerLoop;Debug-InnerLoop - + Debug;Release;Debug-InnerLoop;Release-InnerLoop + 9 @@ -38,8 +38,13 @@ + + + + + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs new file mode 100644 index 0000000000..f1f7de3dc6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + // See README.md for instructions about initialization. + [MemoryDiagnoser] + [ShortRunJob] + public class LoadResizeSaveStressBenchmarks + { + private LoadResizeSaveStressRunner runner; + + // private const JpegKind Filter = JpegKind.Progressive; + private const JpegKind Filter = JpegKind.Any; + + [GlobalSetup] + public void Setup() + { + this.runner = new LoadResizeSaveStressRunner() + { + ImageCount = Environment.ProcessorCount, + Filter = Filter + }; + Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); + this.runner.Init(); + } + + private void ForEachImage(Action action, int maxDegreeOfParallelism) + { + this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.runner.ForEachImageParallel(action); + } + + public int[] ParallelismValues { get; } = + { + Environment.ProcessorCount, + Environment.ProcessorCount / 2, + Environment.ProcessorCount / 4, + 1 + }; + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(ParallelismValues))] + public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs new file mode 100644 index 0000000000..c15f641b4a --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -0,0 +1,280 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using ImageMagick; +using PhotoSauce.MagicScaler; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +using SkiaSharp; +using ImageSharpImage = SixLabors.ImageSharp.Image; +using ImageSharpSize = SixLabors.ImageSharp.Size; +using NetVipsImage = NetVips.Image; +using SystemDrawingImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + public enum JpegKind + { + Baseline = 1, + Progressive = 2, + Any = Baseline | Progressive + } + + public class LoadResizeSaveStressRunner + { + private const int ThumbnailSize = 150; + private const int Quality = 75; + private const string ImageSharp = nameof(ImageSharp); + private const string SystemDrawing = nameof(SystemDrawing); + private const string MagickNET = nameof(MagickNET); + private const string NetVips = nameof(NetVips); + private const string MagicScaler = nameof(MagicScaler); + private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); + private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); + + // Set the quality for ImagSharp + private readonly JpegEncoder imageSharpJpegEncoder = new () { Quality = Quality }; + private readonly ImageCodecInfo systemDrawingJpegCodec = + ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); + + public string[] Images { get; private set; } + + public double TotalProcessedMegapixels { get; private set; } + + private string outputDirectory; + + public int ImageCount { get; set; } = int.MaxValue; + + public int MaxDegreeOfParallelism { get; set; } = -1; + + public JpegKind Filter { get; set; } + + private static readonly string[] ProgressiveFiles = + { + "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", + "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", + "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", + "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", + "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", + "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", + "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", + "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", + "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", + "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", + "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", + "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", + "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", + "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", + "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", + "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", + "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", + "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", + "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", + "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", + "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", + "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", + "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", + "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", + "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", + "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", + "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", + "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", + "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", + "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", + "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", + }; + + public void Init() + { + if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) + { + // Workaround ImageMagick issue + OpenCL.IsEnabled = false; + } + + string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); + if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) + { + throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); + } + + // Get at most this.ImageCount images from there + bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); + + this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); + + // Create the output directory next to the images directory + this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + + static JpegKind GetJpegType(string f) => + ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) + ? JpegKind.Progressive + : JpegKind.Baseline; + } + + public void ForEachImageParallel(Action action) => Parallel.ForEach( + this.Images, + new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, + action); + + private void IncreaseTotalMegapixels(int width, int height) + { + double pixels = width * (double)height; + this.TotalProcessedMegapixels += pixels / 1_000_000.0; + } + + private string OutputPath(string inputPath, string postfix) => + Path.Combine( + this.outputDirectory, + Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); + + private (int width, int height) ScaledSize(int inWidth, int inHeight, int outSize) + { + int width, height; + if (inWidth > inHeight) + { + width = outSize; + height = (int)Math.Round(inHeight * outSize / (double)inWidth); + } + else + { + width = (int)Math.Round(inWidth * outSize / (double)inHeight); + height = outSize; + } + + return (width, height); + } + + public void SystemDrawingResize(string input) + { + using var image = SystemDrawingImage.FromFile(input, true); + this.IncreaseTotalMegapixels(image.Width, image.Height); + + (int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); + var resized = new Bitmap(scaled.width, scaled.height); + using var graphics = Graphics.FromImage(resized); + using var attributes = new ImageAttributes(); + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.AssumeLinear; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + + // Save the results + using var encoderParams = new EncoderParameters(1); + using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); + encoderParams.Param[0] = qualityParam; + resized.Save(this.OutputPath(input, SystemDrawing), this.systemDrawingJpegCodec, encoderParams); + } + + public void ImageSharpResize(string input) + { + using FileStream output = File.Open(this.OutputPath(input, ImageSharp), FileMode.Create); + + // Resize it to fit a 150x150 square + using var image = ImageSharpImage.Load(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); + + image.Mutate(i => i.Resize(new ResizeOptions + { + Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), + Mode = ResizeMode.Max + })); + + // Reduce the size of the file + image.Metadata.ExifProfile = null; + + // Save the results + image.Save(output, this.imageSharpJpegEncoder); + } + + public void MagickResize(string input) + { + using var image = new MagickImage(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); + + // Resize it to fit a 150x150 square + image.Resize(ThumbnailSize, ThumbnailSize); + + // Reduce the size of the file + image.Strip(); + + // Set the quality + image.Quality = Quality; + + // Save the results + image.Write(this.OutputPath(input, MagickNET)); + } + + public void MagicScalerResize(string input) + { + var settings = new ProcessImageSettings() + { + Width = ThumbnailSize, + Height = ThumbnailSize, + ResizeMode = CropScaleMode.Max, + SaveFormat = FileFormat.Jpeg, + JpegQuality = Quality, + JpegSubsampleMode = ChromaSubsampleMode.Subsample420 + }; + + // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? + using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); + MagicImageProcessor.ProcessImage(input, output, settings); + } + + public void SkiaCanvasResize(string input) + { + using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); + using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; + SKCanvas canvas = surface.Canvas; + canvas.Scale((float)scaled.width / original.Width); + canvas.DrawBitmap(original, 0, 0, paint); + canvas.Flush(); + + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpCanvas)); + surface.Snapshot() + .Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void SkiaBitmapResize(string input) + { + using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); + if (resized == null) + { + return; + } + + using var image = SKImage.FromBitmap(resized); + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpBitmap)); + image.Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void NetVipsResize(string input) + { + // Thumbnail to fit a 150x150 square + using var thumb = NetVipsImage.Thumbnail(input, ThumbnailSize, ThumbnailSize); + + // Save the results + thumb.Jpegsave(this.OutputPath(input, NetVips), q: Quality, strip: true); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md new file mode 100644 index 0000000000..6cb48eb48c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md @@ -0,0 +1,9 @@ +The benchmarks have been adapted from the +[PhotoSauce's MemoryStress project](https://github.com/saucecontrol/core-imaging-playground/tree/beeees/MemoryStress). + +### Setup + +Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr + and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 9159475326..10deb24c63 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -12,7 +12,9 @@ false false - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop + false + 9 @@ -30,11 +32,17 @@ + + + + + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs new file mode 100644 index 0000000000..2aadf02eb9 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -0,0 +1,142 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Text; +using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +{ + // See ImageSharp.Benchmarks/LoadResizeSave/README.md + internal class LoadResizeSaveParallelMemoryStress + { + private readonly LoadResizeSaveStressRunner benchmarks; + + private LoadResizeSaveParallelMemoryStress() + { + this.benchmarks = new LoadResizeSaveStressRunner() + { + // MaxDegreeOfParallelism = 10, + // Filter = JpegKind.Baseline + }; + this.benchmarks.Init(); + } + + private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; + + public static void Run() + { + Console.WriteLine(@"Choose a library for image resizing stress test: + +1. System.Drawing +2. ImageSharp +3. MagicScaler +4. SkiaSharp +5. NetVips +6. ImageMagick +"); + + ConsoleKey key = Console.ReadKey().Key; + if (key < ConsoleKey.D1 || key > ConsoleKey.D6) + { + Console.WriteLine("Unrecognized command."); + return; + } + + try + { + var lrs = new LoadResizeSaveParallelMemoryStress(); + + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); + var timer = Stopwatch.StartNew(); + + switch (key) + { + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaBitmapBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.MagickBenchmarkParallel(); + break; + } + + timer.Stop(); + var stats = new Stats(timer, lrs.TotalProcessedMegapixels); + Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); + Console.WriteLine(stats.GetMarkdown()); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + private struct Stats + { + public double TotalSeconds { get; } + + public double TotalMegapixels { get; } + + public double MegapixelsPerSec { get; } + + public double MegapixelsPerSecPerCpu { get; } + + public Stats(Stopwatch sw, double totalMegapixels) + { + this.TotalMegapixels = totalMegapixels; + this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; + this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; + this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; + } + + public string GetMarkdown() + { + var bld = new StringBuilder(); + bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); + bld.AppendLine( + $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); + + bld.Append("| "); + bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendLine(" |"); + + return bld.ToString(); + + static string L(string header) => new ('-', header.Length); + static string F(string column) => $"{{0,{column.Length}:f3}}"; + } + } + + private void ForEachImage(Action action) => this.benchmarks.ForEachImageParallel(action); + + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 50a930b6f1..9dd7e4c820 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -31,13 +32,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - RunJpegEncoderProfilingTests(); + LoadResizeSaveParallelMemoryStress.Run(); + // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - Console.ReadLine(); + // Console.ReadLine(); } private static void RunJpegEncoderProfilingTests() diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs new file mode 100644 index 0000000000..29eae6d488 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Common +{ + public class NumericsTests + { + private ITestOutputHelper Output { get; } + + public NumericsTests(ITestOutputHelper output) + { + this.Output = output; + } + + private static int Log2_ReferenceImplementation(uint value) + { + int n = 0; + while ((value >>= 1) != 0) + { + ++n; + } + + return n; + } + + [Fact] + public void Log2_ZeroConvention() + { + uint value = 0; + int expected = 0; + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + + [Fact] + public void Log2_PowersOfTwo() + { + for (int i = 0; i < sizeof(int) * 8; i++) + { + // from 2^0 to 2^32 + uint value = (uint)(1 << i); + int expected = i; + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + } + + [Theory] + [InlineData(1, 100)] + [InlineData(2, 100)] + public void Log2_RandomValues(int seed, int count) + { + var rng = new Random(seed); + byte[] bytes = new byte[4]; + + for (int i = 0; i < count; i++) + { + rng.NextBytes(bytes); + uint value = BitConverter.ToUInt32(bytes, 0); + int expected = Log2_ReferenceImplementation(value); + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + } + } +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 655e98c7f6..3ad8ef2f8a 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/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index ef245d4d0d..e64d8452f3 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -20,6 +20,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { + [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpDecoderTests { @@ -138,12 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); - - // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } + image.CompareToOriginal(provider); } } @@ -202,15 +198,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) { image.DebugSave(provider); - - // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } + image.CompareToOriginal(provider); } } @@ -219,15 +211,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) { image.DebugSave(provider); - - // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } + image.CompareToOriginal(provider); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 4eb3b900e1..f338c1affc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -19,6 +19,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { + [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpEncoderTests { @@ -181,11 +182,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp BmpBitsPerPixel bitsPerPixel) where TPixel : unmanaged, IPixel { - // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - } + // Oddly the difference only happens locally but we'll not test for that. + // I suspect the issue is with the reference codec. + ImageComparer comparer = TestEnvironment.IsFramework + ? ImageComparer.TolerantPercentage(0.0161F) + : ImageComparer.Exact; + + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, customComparer: comparer); } [Theory] @@ -195,11 +198,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp BmpBitsPerPixel bitsPerPixel) where TPixel : unmanaged, IPixel { - // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - } + // Oddly the difference only happens locally but we'll not test for that. + // I suspect the issue is with the reference codec. + ImageComparer comparer = TestEnvironment.IsFramework + ? ImageComparer.TolerantPercentage(0.0161F) + : ImageComparer.Exact; + + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); } [Theory] @@ -207,28 +212,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_1Bit_WithV3Header_Works( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - } - } + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] public void Encode_1Bit_WithV4Header_Works( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. - if (TestEnvironment.IsWindows) - { - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - } - } + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] @@ -343,7 +334,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Wu + Quantizer = quantizer ?? KnownQuantizers.Octree }; // Does DebugSave & load reference CompareToReferenceInput(): diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 44a6b17a66..ea5e8b0870 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 a171d6d523..c0843a51bb 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/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 446f1e9d47..c0df1e400d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -17,6 +17,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { + [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifDecoderTests { @@ -197,6 +198,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } + // https://github.com/SixLabors/ImageSharp/issues/1668 + [Theory] + [WithFile(TestImages.Gif.Issues.InvalidColorIndex, PixelTypes.Rgba32)] + public void Issue1668_InvalidColorIndex(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.DebugSave(provider); + + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } + } + [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 3a0f188ce3..bd24e1a8d9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -14,6 +14,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { + [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index 6ec1162c46..c4be71d2ab 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 db88cf5b3f..41ec1c7e8d 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 9773bcd612..6efa680c8c 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 dea8c62e19..1e00bfff8c 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/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 75ad5427c7..d49a6498cd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; - +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; +#endif using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -22,94 +24,180 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { } - [Fact] - public void IDCT2D8x4_LeftPart() + // Reference tests + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - float[] sourceArray = Create8x8FloatData(); - var expectedDestArray = new float[64]; + float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); + + var source = Block8x8F.Load(sourceArray); - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray, expectedDestArray); + Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); - var source = default(Block8x8F); - source.LoadFrom(sourceArray); + this.CompareBlocks(expected, actual, 1f); + } - var dest = default(Block8x8F); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToAccurate(int seed) + { + float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); - FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); + var source = Block8x8F.Load(sourceArray); - var actualDestArray = new float[64]; - dest.ScaledCopyTo(actualDestArray); + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); - Assert.Equal(expectedDestArray, actualDestArray); + this.CompareBlocks(expected, actual, 1f); } - [Fact] - public void IDCT2D8x4_RightPart() + // Inverse transform + [Theory] + [InlineData(1)] + [InlineData(2)] + public void IDCT8x4_LeftPart(int seed) { - float[] sourceArray = Create8x8FloatData(); - var expectedDestArray = new float[64]; - - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); - var source = default(Block8x8F); - source.LoadFrom(sourceArray); + var destBlock = default(Block8x8F); - var dest = default(Block8x8F); + var expectedDest = new float[64]; - FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest); - var actualDestArray = new float[64]; - dest.ScaledCopyTo(actualDestArray); + // testee + FastFloatingPointDCT.IDCT8x4_LeftPart(ref srcBlock, ref destBlock); - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); - Assert.Equal(expectedDestArray, actualDestArray); + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } [Theory] [InlineData(1)] [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToNonOptimized(int seed) + public void IDCT8x4_RightPart(int seed) { - float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); - var source = Block8x8F.Load(sourceArray); + var destBlock = default(Block8x8F); - Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + var expectedDest = new float[64]; - var temp = default(Block8x8F); - var actual = default(Block8x8F); - FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); - this.CompareBlocks(expected, actual, 1f); + // testee + FastFloatingPointDCT.IDCT8x4_RightPart(ref srcBlock, ref destBlock); + + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } [Theory] [InlineData(1)] [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToAccurate(int seed) + public void IDCT8x8_Avx(int seed) { - float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); +#if SUPPORTS_RUNTIME_INTRINSICS + var skip = !Avx.IsSupported; +#else + var skip = true; +#endif + + if (skip) + { + this.Output.WriteLine("No AVX present, skipping test!"); + return; + } - var source = Block8x8F.Load(sourceArray); + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + var destBlock = default(Block8x8F); - var temp = default(Block8x8F); - var actual = default(Block8x8F); - FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); + var expectedDest = new float[64]; - this.CompareBlocks(expected, actual, 1f); + // reference, left part + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest); + + // reference, right part + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + + // testee, whole 8x8 + FastFloatingPointDCT.IDCT8x8_Avx(ref srcBlock, ref destBlock); + + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformIDCT(int seed) + { + static void RunTest(string serialized) + { + int seed = FeatureTestRunner.Deserialize(serialized); + + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); + + var destBlock = default(Block8x8F); + + var expectedDest = new float[64]; + var temp1 = new float[64]; + var temp2 = default(Block8x8F); + + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp1); + + // testee + FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref destBlock, ref temp2); + + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + // 3 paths: + // 1. AllowAll - call avx/fma implementation + // 2. DisableFMA - call avx implementation without fma acceleration + // 3. DisableAvx - call fallback code of Vector4 implementation + // + // DisableSSE isn't needed because fallback Vector4 code will compile to either sse or fallback code with same result + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX); + } + + // Forward transform [Theory] [InlineData(1)] [InlineData(2)] @@ -123,7 +211,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var expectedDest = new float[64]; + // reference ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); + + // testee FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; @@ -145,7 +236,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var expectedDest = new float[64]; + // reference ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + + // testee FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; @@ -157,8 +251,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [InlineData(1)] [InlineData(2)] - public void TransformFDCT(int seed) + public void FDCT8x8_Avx(int seed) { +#if SUPPORTS_RUNTIME_INTRINSICS + var skip = !Avx.IsSupported; +#else + var skip = true; +#endif + if (skip) + { + this.Output.WriteLine("No AVX present, skipping test!"); + return; + } + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); @@ -166,17 +271,64 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var destBlock = default(Block8x8F); var expectedDest = new float[64]; - var temp1 = new float[64]; - var temp2 = default(Block8x8F); - ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); - FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); + // reference, left part + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); + + // reference, right part + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + + // testee, whole 8x8 + FastFloatingPointDCT.FDCT8x8_Avx(ref srcBlock, ref destBlock); var actualDest = new float[64]; destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) + { + static void RunTest(string serialized) + { + int seed = FeatureTestRunner.Deserialize(serialized); + + Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); + + var destBlock = default(Block8x8F); + + var expectedDest = new float[64]; + var temp1 = new float[64]; + var temp2 = default(Block8x8F); + + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + + // testee + FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); + + var actualDest = new float[64]; + destBlock.ScaledCopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + // 3 paths: + // 1. AllowAll - call avx/fma implementation + // 2. DisableFMA - call avx implementation without fma acceleration + // 3. DisableAvx - call fallback code of Vector4 implementation + // + // DisableSSE isn't needed because fallback Vector4 code will compile to either sse or fallback code with same result + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs new file mode 100644 index 0000000000..b953e80b80 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public class HuffmanScanEncoderTests + { + private ITestOutputHelper Output { get; } + + public HuffmanScanEncoderTests(ITestOutputHelper output) + { + this.Output = output; + } + + private static int GetHuffmanEncodingLength_Reference(uint number) + { + int bits = 0; + if (number > 32767) + { + number >>= 16; + bits += 16; + } + + if (number > 127) + { + number >>= 8; + bits += 8; + } + + if (number > 7) + { + number >>= 4; + bits += 4; + } + + if (number > 1) + { + number >>= 2; + bits += 2; + } + + if (number > 0) + { + bits++; + } + + return bits; + } + + [Fact] + public void GetHuffmanEncodingLength_Zero() + { + int expected = 0; + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetHuffmanEncodingLength_Random(int seed) + { + int maxNumber = 1 << 16; + + var rng = new Random(seed); + for (int i = 0; i < 1000; i++) + { + uint number = (uint)rng.Next(0, maxNumber); + + int expected = GetHuffmanEncodingLength_Reference(number); + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); + + Assert.Equal(expected, actual); + } + } + + [Fact] + public void GetLastValuableElementIndex_AllZero() + { + static void RunTest() + { + Block8x8F data = default; + + int expectedLessThan = 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.True(actual < expectedLessThan); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Fact] + public void GetLastValuableElementIndex_AllNonZero() + { + static void RunTest() + { + Block8x8F data = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + data[i] = 10; + } + + int expected = Block8x8F.Size - 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledSingle(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int setIndex = rng.Next(1, Block8x8F.Size); + data[setIndex] = rng.Next(); + + int expected = setIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledPartially(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int lastIndex = rng.Next(1, Block8x8F.Size); + int fillValue = rng.Next(); + for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledFragmented(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int fillValue = rng.Next(); + + // first filled chunk + int lastIndex1 = rng.Next(1, Block8x8F.Size / 2); + for (int dataIndex = 0; dataIndex <= lastIndex1; dataIndex++) + { + data[dataIndex] = fillValue; + } + + // second filled chunk, there might be a spot with zero(s) between first and second chunk + int lastIndex2 = rng.Next(lastIndex1 + 1, Block8x8F.Size); + for (int dataIndex = 0; dataIndex <= lastIndex2; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex2; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 3910b2c498..d13a9696c3 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; @@ -21,6 +22,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes + [Collection("RunSerial")] [Trait("Format", "Jpg")] public partial class JpegDecoderTests { @@ -117,7 +119,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 +129,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 9a1d423a6d..8e12b04be9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -13,12 +13,14 @@ 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; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + [Collection("RunSerial")] [Trait("Format", "Jpg")] public class JpegEncoderTests { @@ -310,28 +312,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/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs index 9a6fc8d6fd..24a8195217 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs @@ -1,7 +1,13 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; @@ -23,22 +29,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } [Fact] - public void TestLutConverter() + public void TestConverterLut444() { - Rgb24[] data = CreateTestData(); + int dataSize = 8 * 8; + Rgb24[] data = CreateTestData(dataSize); var target = RgbToYCbCrConverterLut.Create(); Block8x8F y = default; Block8x8F cb = default; Block8x8F cr = default; - target.Convert(data.AsSpan(), ref y, ref cb, ref cr); + target.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); + Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); } [Fact] - public void TestVectorizedConverter() + public void TestConverterVectorized444() { if (!RgbToYCbCrConverterVectorized.IsSupported) { @@ -46,18 +53,186 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - Rgb24[] data = CreateTestData(); + int dataSize = 8 * 8; + Rgb24[] data = CreateTestData(dataSize); Block8x8F y = default; Block8x8F cb = default; Block8x8F cr = default; - RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); + RgbToYCbCrConverterVectorized.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); + Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); } - private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) + [Fact] + public void TestConverterLut420() + { + int dataSize = 16 * 16; + Span data = CreateTestData(dataSize).AsSpan(); + var target = RgbToYCbCrConverterLut.Create(); + + var yBlocks = new Block8x8F[4]; + var cb = default(Block8x8F); + var cr = default(Block8x8F); + + target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); + target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); + + Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); + } + + [Fact] + public void TestConverterVectorized420() + { + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); + return; + } + + int dataSize = 16 * 16; + Span data = CreateTestData(dataSize).AsSpan(); + + var yBlocks = new Block8x8F[4]; + var cb = default(Block8x8F); + var cr = default(Block8x8F); + + RgbToYCbCrConverterVectorized.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); + RgbToYCbCrConverterVectorized.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); + + Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Scale16x2_8x1(int seed) + { + if (!Avx2.IsSupported) + { + return; + } + + Span data = new Random(seed).GenerateRandomFloatArray(Vector256.Count * 4, -1000, 1000); + + // Act: + Vector256 resultVector = RgbToYCbCrConverterVectorized.Scale16x2_8x1(MemoryMarshal.Cast>(data)); + ref float result = ref Unsafe.As, float>(ref resultVector); + + // Assert: + // Comparison epsilon is tricky but 10^(-4) is good enough (?) + var comparer = new ApproximateFloatComparer(0.0001f); + for (int i = 0; i < Vector256.Count; i++) + { + float actual = Unsafe.Add(ref result, i); + float expected = CalculateAverage16x2_8x1(data, i); + + Assert.True(comparer.Equals(actual, expected), $"Pos {i}, Expected: {expected}, Actual: {actual}"); + } + + static float CalculateAverage16x2_8x1(Span data, int index) + { + int upIdx = index * 2; + int lowIdx = (index + 8) * 2; + return 0.25f * (data[upIdx] + data[upIdx + 1] + data[lowIdx] + data[lowIdx + 1]); + } + } +#endif + + private static void Verify444( + ReadOnlySpan data, + ref Block8x8F yResult, + ref Block8x8F cbResult, + ref Block8x8F crResult, + ApproximateColorSpaceComparer comparer) + { + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + RgbToYCbCr(data, ref y, ref cb, ref cr); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.True(comparer.Equals(new YCbCr(y[i], cb[i], cr[i]), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y[i]} == {yResult[i]}, {cb[i]} == {cbResult[i]}, {cr[i]} == {crResult[i]}"); + } + } + + private static void Verify420( + ReadOnlySpan data, + Block8x8F[] yResult, + ref Block8x8F cbResult, + ref Block8x8F crResult, + ApproximateFloatComparer comparer) + { + var trueBlock = default(Block8x8F); + var cbTrue = new Block8x8F[4]; + var crTrue = new Block8x8F[4]; + + Span tempData = new Rgb24[8 * 8].AsSpan(); + + // top left + Copy8x8(data, tempData); + RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[0], ref crTrue[0]); + VerifyBlock(ref yResult[0], ref trueBlock, comparer); + + // top right + Copy8x8(data.Slice(8), tempData); + RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[1], ref crTrue[1]); + VerifyBlock(ref yResult[1], ref trueBlock, comparer); + + // bottom left + Copy8x8(data.Slice(8 * 16), tempData); + RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[2], ref crTrue[2]); + VerifyBlock(ref yResult[2], ref trueBlock, comparer); + + // bottom right + Copy8x8(data.Slice((8 * 16) + 8), tempData); + RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[3], ref crTrue[3]); + VerifyBlock(ref yResult[3], ref trueBlock, comparer); + + // verify Cb + Scale16X16To8X8(ref trueBlock, cbTrue); + VerifyBlock(ref cbResult, ref trueBlock, comparer); + + // verify Cr + Scale16X16To8X8(ref trueBlock, crTrue); + VerifyBlock(ref crResult, ref trueBlock, comparer); + + // extracts 8x8 blocks from 16x8 memory region + static void Copy8x8(ReadOnlySpan source, Span dest) + { + for (int i = 0; i < 8; i++) + { + source.Slice(i * 16, 8).CopyTo(dest.Slice(i * 8)); + } + } + + // scales 16x16 to 8x8, used in chroma subsampling tests + static void Scale16X16To8X8(ref Block8x8F dest, ReadOnlySpan source) + { + for (int i = 0; i < 4; i++) + { + int dstOff = ((i & 2) << 4) | ((i & 1) << 2); + Block8x8F iSource = source[i]; + + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + int j = (16 * y) + (2 * x); + float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9]; + dest[(8 * y) + x + dstOff] = (sum + 2) * .25F; + } + } + } + } + } + + private static void RgbToYCbCr(ReadOnlySpan data, ref Block8x8F y, ref Block8x8F cb, ref Block8x8F cr) { for (int i = 0; i < data.Length; i++) { @@ -65,17 +240,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int g = data[i].G; int b = data[i].B; - float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + y[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); + cb[i] = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + cr[i] = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + } + } - Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}"); + private static void VerifyBlock(ref Block8x8F res, ref Block8x8F target, ApproximateFloatComparer comparer) + { + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.True(comparer.Equals(res[i], target[i]), $"Pos {i}, Expected: {target[i]}, Got: {res[i]}"); } } - private static Rgb24[] CreateTestData() + private static Rgb24[] CreateTestData(int size) { - var data = new Rgb24[64]; + var data = new Rgb24[size]; var r = new Random(); var random = new byte[3]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 4d6de7e279..91b1b9cd78 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs index aadd30f2bc..0886bd84dc 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 1ae21e7715..6bdad6ed4d 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/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 7147f82d68..9832aeb7ba 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -16,6 +16,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { + [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 58d733c4f0..50bacfba4d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -15,6 +15,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { + [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs index 5f7b4f8327..80bfd34975 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 f9ff41df1e..b4307af5d1 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 dd8ecc096d..be9883a700 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 @@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(Span scanline, Span previousScanline, Span result, out int sum) + public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 2a7aca8820..ac94a8fc87 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -16,6 +16,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { + [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index d6eb333a20..1ad4f9a846 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -13,6 +13,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { + [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaEncoderTests { 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 0000000000..782a504d58 --- /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 0000000000..bf585e9c8f --- /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 0000000000..82ecb315b0 --- /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 0000000000..b67cb83254 --- /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 0000000000..3365a1eb39 --- /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 0000000000..769ab850e4 --- /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; +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, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new BlackIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).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 0000000000..e368cd5f1e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -0,0 +1,137 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.Tiff; +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 TiffBitsPerSample(bitsPerSample, 0, 0), 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 0000000000..0bb61ce1c4 --- /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 0000000000..73862b8523 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.Tiff; +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 TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(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 TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(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 TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(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, + TiffBitsPerSample bitsPerSample, + int left, + int top, + int width, + int height, + Rgba32[][] expectedResult) + => AssertDecode( + expectedResult, + pixels => + { + var buffers = new IMemoryOwner[inputData.Length]; + for (int i = 0; i < buffers.Length; i++) + { + buffers[i] = Configuration.Default.MemoryAllocator.Allocate(inputData[i].Length); + ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); + } + + new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); + + foreach (IMemoryOwner buffer in buffers) + { + buffer.Dispose(); + } + }); + } +} 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 0000000000..9adf59e484 --- /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; +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 TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(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 TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(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 TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(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, TiffBitsPerSample 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, TiffBitsPerSample 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 0000000000..1d3304e4c8 --- /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; +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, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).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 0000000000..a007cd3a9c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -0,0 +1,245 @@ +// 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 +{ + [Collection("RunSerial")] + [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 => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + + [Theory] + [WithFile(Flower2BitPalette, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + + [Theory] + [WithFile(Flower2BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] + [WithFile(Flower6BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_6Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower8BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower10BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_10Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] + [WithFile(Flower12BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower14BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_14Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower16BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_42Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [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 0000000000..acbe2b4896 --- /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 0000000000..0286671ae8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -0,0 +1,546 @@ +// 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 +{ + [Collection("RunSerial")] + [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(TiffBitsPerPixel.Bit48)] + [InlineData(TiffBitsPerPixel.Bit42)] + [InlineData(TiffBitsPerPixel.Bit36)] + [InlineData(TiffBitsPerPixel.Bit30)] + [InlineData(TiffBitsPerPixel.Bit12)] + [InlineData(TiffBitsPerPixel.Bit10)] + [InlineData(TiffBitsPerPixel.Bit6)] + public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(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(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); + } + + [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); + } + + // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. + [Theory] + [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] + public void TiffEncoder_EncodePlanar_AndReload_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, imageDecoder: new TiffDecoder()); + + [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 => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f); + + [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(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(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] + public void TiffEncoder_StripLength_WithPalette(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + TestStripLength(provider, photometricInterpretation, compression, false, 0.01f); + + [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)); + + [Theory] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)] + public void TiffEncode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + using Image image = provider.GetImage(); + + var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; + image.DebugSave(provider, encoder); + } + + private static void TestStripLength( + TestImageProvider provider, + TiffPhotometricInterpretation photometricInterpretation, + TiffCompression compression, + bool useExactComparer = true, + float compareTolerance = 0.01f) + 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, + useExactComparer: useExactComparer, + compareTolerance: compareTolerance); + } + + 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 0000000000..4510bf9127 --- /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 0000000000..c80d9fc165 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -0,0 +1,300 @@ +// 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(TiffBitsPerPixel.Bit4, 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.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); + + // Note that the encoded profile has PlanarConfiguration explicitly set, which is missing in the original image profile. + Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); + Assert.Equal(exifProfileInput.Values.Count + 1, encodedImageExifProfile.Values.Count); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs new file mode 100644 index 0000000000..5ac2f475ed --- /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 0000000000..6eea0a1a9c --- /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/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index c93eb41c27..7d4f2da425 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers in operation); // Assert: - TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); + TestImageExtensions.CompareBuffers(expected.DangerousGetSingleSpan(), actual.DangerousGetSingleSpan()); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index ecbc331b28..dbc5af536d 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,53 +292,95 @@ 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)); } + + [Fact] + public void DisposeCall_NoThrowIfCalledMultiple() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; + + image.Dispose(); // this should invalidate underlying collection as well + frameCollection.Dispose(); + } + + [Fact] + public void PublicProperties_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var prop = frameCollection.RootFrame; }); + } + + [Fact] + public void PublicMethods_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var res = frameCollection.AddFrame(default); }); + Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); + Assert.Throws(() => { var res = frameCollection.Contains(default); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); + Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); + Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); + Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); + Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); + Assert.Throws(() => { frameCollection.RemoveFrame(default); }); + Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index 92109ed479..15838f6902 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -263,6 +263,42 @@ namespace SixLabors.ImageSharp.Tests Assert.False(this.Image.Frames.Contains(frame)); } + [Fact] + public void PublicProperties_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var prop = frameCollection.RootFrame; }); + } + + [Fact] + public void PublicMethods_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames; + var rgba32Array = new Rgba32[0]; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); + Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array); }); + Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); + Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); + Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); + Assert.Throws(() => { var res = frameCollection.Contains(default); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); + Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); + Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); + Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); + Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); + Assert.Throws(() => { frameCollection.RemoveFrame(default); }); + Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); + } + /// /// Integration test for end-to end API validation. /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 3fbe1f70d8..271aa30cf4 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 ee46807e55..fe3df17215 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 4e6b002d0d..8bb121349f 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/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index bb75578a4b..27188b0b45 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); imageSpan.Fill(bg); for (var i = 10; i < 20; i++) @@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); imageSpan.Fill(bg); for (var i = 10; i < 20; i++) @@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height)) { Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span; + Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; // We can't compare the two Memory instances directly as they wrap different memory managers. // To check that the underlying data matches, we can just manually check their lenth, and the @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(p, bmp.Width, bmp.Height)) { Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span; + Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index b7c6b3835a..1296f26c47 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.IO; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -169,5 +171,72 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal("y", ex.ParamName); } } + + public class Dispose + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + public void MultipleDisposeCalls() + { + var image = new Image(this.configuration, 10, 10); + image.Dispose(); + image.Dispose(); + } + + [Fact] + public void NonPrivateProperties_ObjectDisposedException() + { + var image = new Image(this.configuration, 10, 10); + var genericImage = (Image)image; + + image.Dispose(); + + // Image + Assert.Throws(() => { var prop = image.Frames; }); + + // Image + Assert.Throws(() => { var prop = genericImage.Frames; }); + } + + [Fact] + public void Save_ObjectDisposedException() + { + using var stream = new MemoryStream(); + var image = new Image(this.configuration, 10, 10); + var encoder = new JpegEncoder(); + + image.Dispose(); + + // Image + Assert.Throws(() => image.Save(stream, encoder)); + } + + [Fact] + public void AcceptVisitor_ObjectDisposedException() + { + // This test technically should exist but it's impossible to write proper test case without reflection: + // All visitor types are private and can't be created without context of some save/processing operation + // Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway + return; + } + + [Fact] + public void NonPrivateMethods_ObjectDisposedException() + { + var image = new Image(this.configuration, 10, 10); + var genericImage = (Image)image; + + image.Dispose(); + + // Image + Assert.Throws(() => { var res = image.Clone(this.configuration); }); + Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); + Assert.Throws(() => { var res = image.GetPixelRowSpan(default); }); + Assert.Throws(() => { var res = image.TryGetSinglePixelSpan(out var _); }); + + // Image + Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); + } + } } } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index b6482455e0..30bd544fa3 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -6,7 +6,7 @@ SixLabors.ImageSharp.Tests AnyCPU;x64;x86 SixLabors.ImageSharp.Tests - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop @@ -39,6 +39,7 @@ + diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 939e5898cd..50ec09ce3f 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -223,19 +223,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(0, buffer.Memory.Length); } - [Theory] - [InlineData(101)] - [InlineData((int.MaxValue / SizeOfLargeStruct) - 1)] - [InlineData(int.MaxValue / SizeOfLargeStruct)] - [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] - [InlineData((int.MaxValue / SizeOfLargeStruct) + 137)] - public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length) - { - this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct; - Assert.Throws(() => - this.LocalFixture.MemoryAllocator.Allocate(length)); - } - [Theory] [InlineData(-1)] public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 1124b64394..1cadf16536 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/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 549ecb7f4f..015b3617bc 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.GetSingleSpan().Length); + Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); } } @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSingleSpan(); + Span span = buffer.DangerousGetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -249,9 +249,9 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - rnd.RandomFill(b.GetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.CopyColumns(startIndex, destIndex, columnCount); + b.DangerousCopyColumns(startIndex, destIndex, columnCount); for (int y = 0; y < b.Height; y++) { @@ -271,10 +271,10 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - rnd.RandomFill(b.GetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.CopyColumns(0, 50, 22); - b.CopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); for (int y = 0; y < b.Height; y++) { diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 3f89049049..f1a90d43e7 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 466568bfec..1f23838ab6 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 401546e5c6..7cd7da44e4 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 2b00cc5b48..2fec828ad0 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 5fe1b51baf..0a816bb21f 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 898c693562..3358b1f977 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 5451cbf37e..dff3701242 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 aa24c26739..411738158f 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 fe31d74ac9..49e0ea2623 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 3fbef46de9..5673ba75b3 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 cf4cf80d1b..7d1d07743f 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 2b2b564a7c..feff5e496d 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 ea77004ed0..45ad6ce49b 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 7c5070af1c..6e2bd05ee8 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 593eed97ce..3bb2ebc412 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 e48d89ddbb..23ea921ae1 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 711e3426d2..463804671c 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 ecfbad3951..b81dba24d0 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 4346265c76..30e8da2dae 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 bf8b7d0699..78826bb4d6 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 a918adc3f8..aa51b149da 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 1dc37a1953..9946d55f9c 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 6325f26ce0..7fd79994aa 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 9fa3e644cd..325eac1463 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 a40082f78d..ad2619f033 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 e9d960ebbc..c2b57d0ba5 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 0d4495912c..bd9f55d3e0 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 b06a529641..aa24d191b5 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 9e763536bc..3c60f4526a 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 cc7f32bef7..a2688359f1 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 ae9befba0d..d144c876fd 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 f4f800107f..e6a34d1f08 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 a2fb9f9bad..e241f729ae 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 0bbb962fc9..191b195b49 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 eb176f5f03..65d9564243 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 3ffb8f4e33..56a73b3ff8 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 26454fcb6b..ce21cab5ff 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 d264e82e1d..e94683092a 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 73f6a3f473..31a1fc2d43 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 9f0a80453a..71cee8f7f5 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 5bc6256d9d..11c90a5078 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 2fd7ac7efc..c1c0dc07a1 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 33061e1e48..e13b22a946 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 f87ace1897..6eaa9937bb 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 680a6afdce..7f03301481 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 e65b67815a..237f132a42 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 e181999fa3..b968e023f8 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 15945e4680..3965d10c95 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 36c2ff7699..2e56331a7c 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 9d85af5896..04fb3d5996 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 e773a177fe..ed1c729e6e 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 798c0e0550..72be60b395 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 cbf44e4c69..2b8a8be88c 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 e7d289ea5d..f28601fe3b 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 8e8b4636ce..526fd9a2de 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 2cfd8519d8..5601920e96 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 b61a12102e..e6542e79a9 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 c7f85b732d..7a9242cb38 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 cd0a65ad51..d85495b92b 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 c206938a24..220bd5f059 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 481463f47e..285535b9f1 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 4460f04fb1..85b7530247 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 8bc0a2c97f..a84751f5a7 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 32e8ba3846..3e0c851d2e 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 24e52d5d0e..0103b138a0 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 fd08eb2dea..446ac70d4e 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 dbf59a29ba..4ab053a310 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -11,11 +11,13 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { + [Trait("Category", "Processors")] public class BokehBlurTest { private static readonly string Components10x2 = @" @@ -153,8 +155,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution appendSourceFileOrDescription: false); [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] - public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] + public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) { static void RunTest(string arg1, string arg2) { @@ -172,12 +175,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); }, testOutputDetails: value.ToString(), + ImageComparer.TolerantPercentage(0.05f), appendPixelTypeToFileName: false); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.DisableSSE41, + intrinsicsFilter, provider, value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 529a4b49c3..eadee0c2e7 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 e468778de4..cc28bf304b 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 31b3d20db0..44fe673ece 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 7d3e918038..2b4f38e899 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 5c1b5da7f1..37443a5b40 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -8,8 +9,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 = @@ -171,8 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization provider.RunBufferCapacityLimitProcessorTest( 41, c => c.Dither(dither), - name, - ImageComparer.TolerantPercentage(0.001f)); + name); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index b29e452216..acf2c06136 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 0d68a860d3..1dcd8181fb 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 919cb31379..6edde73cd3 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 2173cbef82..e4de119fc3 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 fdcc3c6f79..0927a8b810 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 d7e5b13cce..97f04440b2 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 a007f71940..f86858c84b 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 25fe9c84c2..81a7e24ff1 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 535179cb16..b5c0e583c6 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 279b699ee3..c568188fb3 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 3538f0dba9..0c2b455e31 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 a2e0b0b4b2..8c435d23af 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 f21d458365..2fecac32c6 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 c924ddc4f2..69fa8cdea4 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 a7ef2f8625..e5621b592c 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 64025a6fba..645746a216 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 8be43efa92..8077051cd9 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 91c6e4af82..e102432893 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 af2c2136a9..86e3050c23 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 f0d6b784b1..0a2b9921cd 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 fa4d422b1d..6814a91327 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 6eccde4bc5..3a6c8a11a6 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 2b4460429c..cccb77e86b 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 0df498cd1c..991a2bcb7f 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 3f4656d411..af1d7f3f38 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 8881aa9ad5..639f8fd2d3 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 49a443d927..d2d2fcc1f7 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 44f88c3a21..38fde5060a 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 78c35fa9b3..52f3b65de7 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 16668fb207..4e8a65ddcb 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 c094febc95..b9f0fb9e88 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 2ea8336401..b1441d1093 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 4691fc82b6..43fe196f79 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 ceee3e7e02..253d29eea2 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 da567f18c5..dfab25b114 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 991bca80ec..1d4629ccc1 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; } @@ -79,6 +80,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { KnownResamplers.Bicubic, 1680, 1200 }, { KnownResamplers.Box, 13, 299 }, { KnownResamplers.Lanczos5, 3032, 600 }, + + // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 + { KnownResamplers.Bicubic, 207773, 51943 } }; public static TheoryData GeneratedImageResizeData = diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 58b7fd12e8..42cf1e3c12 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 398039e433..0648c48b4c 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 1e888a51a1..61b63d0648 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 2fd87de29e..05d5095afc 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 f508744fae..61af13ea38 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 50fff725bf..1b681a82f6 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 9fa75448b0..ed56f681c8 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 f2ca8dee5f..53fa02edb2 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 3f6e26b8ee..843cd30400 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 3f49b0f02c..227e470d4e 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 d95992d6b5..2f0f8f6ac6 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 2fd5f2a7d4..ef8e03763c 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 5f426083c2..60f7aaa0ba 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 379d399669..90a96972a1 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 6f7dbd9de9..b79bb29ebe 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 de276b427e..06282494ae 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 cde6aeca36..a6d0323355 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 2e0dfd59e7..d4540e433a 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 81c415c065..869162b386 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 7273a65f79..c8d0633d7e 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 c15327e0de..6d2f65f575 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -418,6 +418,7 @@ namespace SixLabors.ImageSharp.Tests public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; public const string Issue1530 = "Gif/issues/issue1530.gif"; + public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif"; } public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; @@ -497,5 +498,116 @@ 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 FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; + public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; + public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; + public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; + public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; + public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; + public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff"; + public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; + public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; + public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; + public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; + public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; + public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; + public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; + public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; + public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; + public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; + public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; + public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; + public const string Flower16BitGray = "Tiff/flower-minisblack-16.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 0cf76a3890..12db71e66f 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 0000000000..501651285d --- /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 0000000000..bbb75a9cf2 --- /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 f186ed318a..7612b663aa 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 8a038a6912..07acabccf2 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 0000000000..4d3646301f --- /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 163bb84d56..294bd20fb5 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 @@ -23,10 +25,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { } - public MagickReferenceDecoder(bool validate) - { - this.validate = validate; - } + public MagickReferenceDecoder(bool validate) => this.validate = validate; public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); @@ -75,23 +74,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 == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - FromRgba32Bytes(configuration, data, resultPixels); + FromRgba32Bytes(configuration, data, framePixels); } - else if (magickImage.Depth == 16) + else if (magicFrame.Depth == 16 || magicFrame.Depth == 14) { ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, resultPixels); + FromRgba64Bytes(configuration, bytes, framePixels); } else { @@ -99,7 +102,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 0000000000..ddd1ec7506 --- /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 ea5b2f6652..f68fb4d95d 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 1c5aedd9b7..26378796bd 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) @@ -647,7 +672,7 @@ namespace SixLabors.ImageSharp.Tests var image = new Image(buffer.Width, buffer.Height); Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); - Span bufferSpan = buffer.GetSingleSpan(); + Span bufferSpan = buffer.DangerousGetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 5f41021a06..32b5eaf182 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 87f8cb8c17..92f9729413 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/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 84b9297295..05f4f032bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -3,7 +3,7 @@ using System; using System.IO; - +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -114,5 +114,20 @@ namespace SixLabors.ImageSharp.Tests IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); } + + // RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework: + // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 + public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; + + [ConditionalFact(nameof(IsNot32BitNetFramework))] + public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() + { + static void FailingCode() + { + Assert.False(true); + } + + Assert.ThrowsAny(() => RemoteExecutor.Invoke(FailingCode).Dispose()); + } } } diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp index b4d4754885..2b8e05b070 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dba331639d724f198d7d11af971156d34076b57bba0f2d0d45e699104a3a674 +oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp index 01c9196964..f7eb06c558 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9213e188b3a2f715ae21a5ab2fb2acedc23397207820c0999b06fa60e7052b85 +oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 size 9270 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png index 80149fa376..dd2f49f08b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aa62e798c085eb7b0e8e5ce5e4cb2cccfe925dd8ac3e29659f9afd53fca977c -size 329912 +oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4 +size 266391 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png index 5059748d2b..79a43ed87a 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900 -size 326504 +oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b +size 262887 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png index 8f0ad4f184..daa4b5e437 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb -size 727 +oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 +size 720 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png index 8f0ad4f184..daa4b5e437 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb -size 727 +oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 +size 720 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png index 8f0ad4f184..daa4b5e437 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb -size 727 +oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0 +size 720 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png index ca40d71efa..d8f9b640dd 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24191da3ce18438edefa7a189d9beadaa3057b5e4d4c550254e3a81ed159c0f8 -size 723 +oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b +size 720 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png index b03fe7b9fb..3656e32db6 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:087148425a048f33c6ae063064cfe374f7fb88f075d767e62c73675ec52a3e0a -size 100066 +oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934 +size 52070 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png index 33cd02bda3..7cafd50c17 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2a90c8463632606b40461ad91d80d44826f7b468ba5f1a905acfc85ad0344c9 -size 114413 +oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03 +size 61447 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png index e0d901ea77..5d0c82e058 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307 -size 114842 +oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5 +size 61183 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png index aa0446d48d..584e677e20 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50c12659dc05b3ce8a6692cdbc72971bbc691cc7fd26c34df65b4bd71d190e5b -size 108799 +oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa +size 56070 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png index ef0afb9bd7..641ecaca19 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a1c408687899b57b96e9f01ea889bc6f16d9a479386346c6bd9babc45ef99d0 -size 109095 +oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870 +size 58502 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png index 8ecbc15453..61bbf2b155 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3d8aead7f9f69dac7eec1b2d8e2200331bf28218c98b7fa3435c9610ff88264 -size 110221 +oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46 +size 58480 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png index 417ee7b49e..42e595b0ab 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1fed9d8f58e38cfa938d7735cbdfcbac0aa02f58eda0dddfe29a5ebed0e74eb -size 117802 +oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264 +size 62418 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png index b668b84cb9..5cd6eca10d 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbeeda3f4e505c46e1ec218001f0f1aade4eb64c3932e2c374a74c4b0702d7a8 -size 103735 +oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c +size 54464 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png index ea7a103ba5..5a97796404 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8003014c90f6c3a722c75e9cafb397d1be3818bb84c3484e28ee79ae273d7d0b -size 109707 +oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa +size 60074 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png index a519e10948..d0c3196426 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f02a9465aaaa62b6fc0e9e0578362ccf65ce57bf7a8e1e2899f254863e72807f -size 100060 +oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2 +size 57501 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png index 5fa4e46134..773ff203ac 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f50c240c062cb66e820e8f632107e63bc0de85013143a81975e56f0b72499d8f -size 102871 +oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a +size 59377 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png index d87f3fd5a2..a41b9989f8 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318 -size 104334 +oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295 +size 60377 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png index 3a8de62bef..39fc93541e 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bddfb2d8b8aa83fd532b3157fe78e75199d591d415524f04f688baafd1744a8 -size 101155 +oid sha256:90fc8048141b2182e4851a48ac5a79c96210eab9e56468fe06f90e7e70a7c180 +size 58539 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png index 184c917957..e7bd1c6f36 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ce16c4b23075e784927143d4077063be3b42bd8a88ca1358082c00974c40150 -size 102434 +oid sha256:b312bd18eba03a37121bbcfb3b285f97fe22283b51256883ce0235bb8605b757 +size 58616 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png index 79ec8e0702..f3155ba80b 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddbd3cc8250205b38fbedef85c938920608826d5a39e5e9ecfc835b6b2583453 -size 101438 +oid sha256:750ccd26984a4d5a370c1af6ca5dd1c9c5c6c66e693f7645130fd1669e3b7b4e +size 58923 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png index 5848f60bf2..d5cbbd3e04 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a880481e38ca282f29a8d00fb041de67c5231304ecdc8cef9167efb58dd482ff -size 105295 +oid sha256:f9d3777a936883a2177a964f24d9ac86c8a106c375583bc9a8fbeb0ec39a7dc6 +size 60610 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png index 300d827952..5b83ace203 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df9945efaa843da6b95c883f109075f116009bec688191d7dae5429a7fa157fc -size 100713 +oid sha256:f638821c29d852d6fabe4cc4cfe802e386024835ad07ee496a7bec7a930e851b +size 57886 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png index a0a7af21ba..46dace67b2 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c58aeb0e9bcc20b405b5700ec1ac12c7759e77e16da7887186b8d61903e9d906 -size 101013 +oid sha256:c6e86bfc1594ec4cb8f89a1c92a42778c59aa755ce170a97afb8cab3e623aa79 +size 58376 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png index 0082bae441..909af9b6d3 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c -size 1049 +oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 +size 865 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png index 0082bae441..909af9b6d3 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c -size 1049 +oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 +size 865 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png index 208e4fe0ef..909af9b6d3 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d55bf31ae306fcf91993b488444e83ad0f684f4a2642879e38e27e7b9fb1fa56 -size 1051 +oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 +size 865 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png index 0082bae441..909af9b6d3 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c -size 1049 +oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5 +size 865 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png index 6b7ee76a90..f16ff0ef75 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:208a0b9a189c8801e97495a93302814679441bbbe1769810eb37bcb52a78518f -size 83344 +oid sha256:97cfbef27319988b67aeac87d469d044edd925c90e4774170465f51eed85c16a +size 42915 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png index e91a9551f1..05d26b6475 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c95ae441b8b090a0c838db5ed3e9b3ae1040225420e79b76c806f88b96716b8f -size 80344 +oid sha256:3a799b69938507e3fd2a74ffa7c6c6ad6574acb25861a0a50cb8361520d468de +size 41809 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png index ffd30f62ce..b437c0d034 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5ab9eb0b80de50f117446c46025918893c431c228e212bef9371f4f788cee14 -size 82652 +oid sha256:9932db58eeb966cd293b1b7a375e9c1b17b6d09153c679ebf03d42a08d2ce9b3 +size 43332 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png index e24920a4d5..9e97e5f96c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f76c909b7e804c8dd80b07fd5346d2036d2fded2bf9a855bd20f7da154a111f3 -size 83554 +oid sha256:67ebf42bc82483d1778254d95a376230437611dce91c80f8ecda608de56bffe7 +size 43108 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png index d70774d3a7..b84521842c 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:656dfb6c9a53830d915a8c8810d09872333a9230073e25b4f0668269afb15e00 -size 83188 +oid sha256:8d5cdda990ac146a7580f58cc2bcab72f903dde564a394de7df4cc37e6dcf2dd +size 43906 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png index c3eda832a4..436c676926 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b56aa9a03e7f6733fac6b6ceddba50e85727201c4f79aea64540cc79f7fd942e -size 88333 +oid sha256:c11e6c197bd1c227ae8f4af7e8c232cfe75db6929ab12bddf5e6554fbaed3f01 +size 50716 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png index 56660f434b..6e1ad33117 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:863debcf1bc4a4e3fb0e3c29b8b3f8b98bb7ac47901e89a90a57a2dde5d81f53 -size 90431 +oid sha256:69ff9654eb61f2bfdd44fb25aff959c5b831015e283cc91a90e3abf6f681dc88 +size 52429 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png index c434e317af..a257ccd615 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a92785de634c09d73dc91d1a33e52dedd7d5dea79d269753d959f2a1f81afb2b -size 89207 +oid sha256:6ee945ac5120e4198d1f94e6467cc0f77c90869bf5a09942e7720dddcfdfbe07 +size 51262 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png index 4b04715b9a..d8cb415022 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0aeb15a04553142cade051d523bbc18b2e63997efa0b0c5f5b8bab8662074f7 -size 88550 +oid sha256:1b023505175ae39a93fa55c85aa31466f0aca76fab0ee54f9667648b91f9aeb9 +size 50789 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png index fc1e540cc0..d6be5125f8 100644 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png +++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4801d48fc6691bc2fd555a4bed8a7abdde7edac3dc13b33da580688d11bc4eb4 -size 89543 +oid sha256:6b18e8b80035a3c5985ebedab5eaf1b0e580d26dd2a8167e687e7b3dd6536751 +size 51922 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png new file mode 100644 index 0000000000..fc713e3851 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8507b2f70c1dd2ef3d3ef616419825cf70c7453abaf7fd490349f85f4b589cb5 +size 408 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png index 7fd7ab9e3e..4011bbc38e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24acf8421048a6b1a95c8fd31e8b03c1a0b0f3b2ff155c0b9747fabb44060c25 -size 319596 +oid sha256:df15b095693880ec25f4fda378c8404a55064d83a40fc889f4e7ebb251dd88cf +size 272529 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png index 5fbc15f70b..0c53f8d42d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26204cd4a30538a667b17e68319747ec0a9726f6955d154c3f9f8fcd73774bd3 -size 304297 +oid sha256:fd18f2ba17869695efda6acf7daa0f4def11a4f5ba6cee95e06cee505f076c77 +size 263994 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png index 5d8e6b4565..ff1e888096 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adc156f6010679f2ff076405557d0a34cd50464240bbafafbf44edf37b5a1186 -size 321968 +oid sha256:7bcd315c4f140b55b294216de83f7835dcdf027acbd9cdb5e8bcbd89360c4781 +size 272971 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png index a569c4efdd..081e6dbdfe 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07986a3bde930100c20a93e8fa8b03f0f9c822853ddc07d71ebf2be5a36c4620 -size 308767 +oid sha256:fb9b649fd0b217ce548d46b0e7958f5ab74b5862678d34839d7b7ab29e3722ee +size 255871 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png index 97b3521131..c0186e4272 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3774888a23cd3be4d0d3edad8ddfeab86fd52e5605803eacbb50d3eac2f9caaa -size 291234 +oid sha256:c0374d786d726692e83022a5d8642807ad24f9d484393d564a4cc73a3f8971f8 +size 250230 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png index 97613bcaa3..05f9404ed1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec93dd8fc45e9eb3b1ad13bd89dfc487f5d6eccd2ad8fa1fede67fa7819a263a -size 299393 +oid sha256:a8a9f1fab68b71ae87b7f8f8fa61cd73c6e868359bff60e91c1246eb04c92740 +size 252981 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png index 45e966e85c..1eeabc6664 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1de5e7da6659b9d235b0c9b0d55bdd71a3608d72e7a38259b34936a166c11d77 -size 292205 +oid sha256:216d096da3a1e5df9cffa1dddc2c136c4ad0db1ca3ff930a46193352680e91d6 +size 257442 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png index d3e0a03e72..afa308a920 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6e11c3e422be8aae08f3d741ec4b45ce79af3603518784d22ff646cbd00c312 -size 291259 +oid sha256:8c15a5b6114825ff1f118209831a89d8619ea2c956ad52f9564dfc41be94c6cb +size 255797 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png index 2ea043d6fe..2d61083331 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f3ef9dab0169bd262408a30ce2a1d20da5acb331fd56ce66de2f7efe4555a9a -size 299734 +oid sha256:9694b6b29e33c5b0b5a8f662246f5ad0af03b900d52615fa61cad6d16cebb31c +size 259740 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png index 01fa37df53..82c6b3ed58 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd9d03b02c51eadc9b27f165771c1407391bc1d29c2b10a4175324ab29152cbb -size 329877 +oid sha256:cc776a1039f25212cbe983ae41de4bc3d8e53dd3f692c327da42d91fe983fe5d +size 275846 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png index 3e06cf66fb..5ea0460c1c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2eba143227c5fe09d407e9ece1be5480fc55edab5f8464393d13c642b01791f3 -size 321299 +oid sha256:8aced00a35f19ccb7011cc7ef04bcbe79b064078a5b7b1649ecab789da13160e +size 273774 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png index e04186940b..d96ad1e233 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3d8d9e978668ae8f76004dc2a8440ffe2f55875ee92046ca2be02f426def1a6 -size 333260 +oid sha256:f4fe9d03e33808cf97e6ee3a4a877160b04746e46a3e3c56c0cdf7ab617e90d9 +size 276397 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index fe32f95439..0e1781b119 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37a2c548b78e117848d294ab55c6b8f4cf85ad2c6bdf84f9eec8f6eefc07b0fe -size 349177 +oid sha256:2358c7b0c3de1f13d9d7840108ffd1b65751946ba28a697d6ae48b7445541807 +size 308226 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png index 211a6c6a66..5c58149639 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfdf8fa9d082c88dd902d927a525698e9752a3738771ba2a0b6ff67568b2f116 -size 344607 +oid sha256:38c112f9edef86df31b8ccec63bffdd3d4426eb5fd44b774bef4166c70f31a90 +size 303086 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index b912690dee..1b7ed02df8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdb6866053be7dbe1e56e6972b50bc030d30a050f73a4429993e3c639e06d345 -size 349125 +oid sha256:93fd2a28153ec292c0d6b2651830566fa3ee0cdcad7f6978ff8b49cd7fb2ac27 +size 308104 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index e8d6878862..a4d2d92a53 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23be2cf98ea2cd0e5b00fc1b771ea7ba490a3ac9e1de40540fd0d20a61af820c -size 330677 +oid sha256:faf061e22dd0e34c62929e9e742c279f400293b87fca15e2e6423115b3e02862 +size 290244 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index 58e77a377e..bb973a0000 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f032414fd20b82c0bdbaaa3e905c296961f9cdd605408f59cba7de657d8421b0 -size 324042 +oid sha256:f9a368ff9fbb4d462a99b9eaab8e2ec81e4b1ae1d120cf5abc0cc5fe02ea941c +size 285759 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index f1b04e74c6..83ae37b086 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0adaaae399376c94af866adfcb2c5777c0dd91d2d4424f24490909e68d2483c9 -size 326321 +oid sha256:1926eec3a84dd8601ce0de5d8b1b70d25ebd120f4b9877b33266c18404a051fe +size 286469 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index b0f969da90..d3ca7f8c1c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c06a456d0c38121051d91d2cbfa4fcdcf8df4bc6ece89a0bda4b0f7e2a06b6f4 -size 333368 +oid sha256:2c45b7993e7019efae493f738d6fd441446d9ff5fdf14200003a1a8a90d67b97 +size 292334 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index ea1442b28a..37181fd36d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0e2edc030a20998d3a14bb6715417bb6b561599601710497372ed90b27a5493 -size 332861 +oid sha256:94edf1b16733a2632406f70b61bcb4f95bc9044706f63b1840cede693330814d +size 291415 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index f54900a2f1..827fc0a694 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0571bde66f19b41cf1ba6f3b63f3d380a1025ae2f92dda8b9c494f8869c325e4 -size 334758 +oid sha256:93ac2cc58c94e036287e76cda3970f070d15c4ded5dc2e553177772d327d56f6 +size 292742 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png index c2ec04c4be..6164b3ed6b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8a04e02cdac3b2ce2db20c5108636d40ce13e8d165c4b859cc4794f89cf7f4a -size 352342 +oid sha256:307cd34267e96ca51d82873138e319830d13743c2085788ffcdec9bf60d45671 +size 310380 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png index bb6c8c58b7..4981078c40 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e03a52138efa504252053f39f36dbfbe6a477a9ffdd0f8bba633ab74d0088ed -size 351591 +oid sha256:a8c296a49104edbd0ccb237c0333d3ab403e8ad5cc15c91f1734d2c3d78cf135 +size 309488 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png index 8165d47763..f392f00d91 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cc6c263430489a8866fab47c26f399a034b0dd583d27b12edc68244919321d0 -size 353592 +oid sha256:1874dab1b45fd976751395e1e9336ffb4d58e2e3d1643f48beea42f39245c98e +size 311280 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png index 1783d1b8af..fccbe25877 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19 +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png index 1783d1b8af..8d0c3a5d99 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:3c7d3da0ced1c66c6351d530565a190cfc1fdb7f3b7b05d39844f61fb87871ad +size 13758 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png index 1783d1b8af..cffaa87b48 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:2a6bb9a04f0663eb8a95d6d46c72557078de35ac935499d5ec4ab591d7f59eb9 +size 13940 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png index 1783d1b8af..fccbe25877 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19 +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png index 1783d1b8af..8ea07490e3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:f0facae77f6022c92cdaaa7f27efb424962933c0e86ec4e8a7d62237a0f58d03 +size 13919 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png index 47552e4571..e7fe3bc772 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec01d4ee9173d01f92b5643782f4b6c7e0b4342b530acf6062f5f17c6d7b1e9a -size 36290 +oid sha256:ec99338895bdada5cabe504afdcb0c0c95d8951e4404d31615a406b9956995c0 +size 14154 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png index 36e1349ede..853e368e3e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ed04ff17bc4d7c57a9594bb4872f430cc3df4d92c7199d5c5db2420ecc20a95 -size 38303 +oid sha256:9699207803467b8718a719c7581e1ed6bf0c923a5adaf325aea8358d274fece5 +size 18334 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png index 760d17d5aa..5ace2a5059 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f8e53d995f27780851c044d552473ee52ec9dcc2e0dfa9a806c9f8d2fd62692 -size 39251 +oid sha256:0e3acfa5b7c6ef3bec68b5fa8db91b2e6160e01d1f952a055831cea2f0a58b0f +size 18675 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png index 1783d1b8af..fccbe25877 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4 -size 32583 +oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19 +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png index efaa7bb44b..e4e4e10942 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a7a1cefa7e70387ccb9e90c5633725ce936635da39c131a59cec7089392c358 -size 39744 +oid sha256:fc1c1b5d0d0abec9b52ae7a83946a46020d2394a5f49f42e2ddb50fac988e974 +size 18874 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png index a6d0c833f9..d8e5bc5798 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:733b748c42d4bc7103e8edf264fad4af268f2ee7ad7bab84f4ade6e8d91227e9 -size 17206 +oid sha256:2eac7954110e82c7c9cb1c0d3734467b7e46745ea19b2fd10d0af7df0aad552c +size 9007 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png index 182a2cb770..2f0961df37 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b44413544d4286aff611c94bb026562b0b0913db6d804ec7c9c82a595d2cd00 -size 18474 +oid sha256:51b06fc436e322ff9fc9e367b8117eb1178e112eb90fbd41a87847ab64a24136 +size 8801 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png index 08f457ca0c..0858c8e20c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8f597c6b7abc7cd729c034d8e34a0aeef19666f8accf997767f0d963e3818ec -size 20022 +oid sha256:e46c5f17ef76f11ca1dfe70dd4b38858de049832c26add1e9f987f87319a3491 +size 11029 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png index 6c3b1345a4..b8f2940f5c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e970fa92294f6eb9e20f9d780f046a85f0e569660b4500ad4c8fca284b4fa27d -size 15992 +oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8 +size 7702 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png index 2fa10ee19f..c6818e906c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0586ae018f98d26298d3dc4af329eeca044c1cdc5ed5a71ff22e1b9ca46c122 -size 22701 +oid sha256:e73014c6698526f3341e1f6001938bf5c60501bd6114451903a654c43c5f1997 +size 11719 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png index 94175f4895..b120b7fe98 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3b56c451b5e7461782dec2f5dccab18e7ad33efe3d9f1906421c32c75923648 -size 17790 +oid sha256:420d8ff32aa8ffa789e0c5dd00151856a016bc4f83ad035fdb4a8a22c338e247 +size 8952 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png index c227f65870..e58dac830f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4d81ab162bd065f438504ea2a44be93cefd7f1b31d7d983e23108e8e19b86fa -size 18390 +oid sha256:93f3be15cb660c7c74c0de12d459c390c5f3c950d09dc4bcf617f5093e2b818b +size 8606 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png index 35e12cf859..b6bb89b9ec 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d2cb1111d2a3915072ca53404215052bbff42ff9639e8e3c2b4f6a70591fd0e -size 19145 +oid sha256:a035a0b97ac471500a9dbead47a0d13deb449136980b89795b671b3e14481c9e +size 9716 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png index 6c3b1345a4..b8f2940f5c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e970fa92294f6eb9e20f9d780f046a85f0e569660b4500ad4c8fca284b4fa27d -size 15992 +oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8 +size 7702 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png index 6ff5504ab7..f6bae9649e 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1133884d19f663d3c643ebe11bdeac65e2ab3d533be43a40b61b3292ea59cd3b -size 19680 +oid sha256:fac9fc2316ccf7a464e93d0406acdf37d5aac7f76f54c87454fa41b13c8224fc +size 9731 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png index 4d2011af41..beb4248eda 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:770fa2009e0c6adf462db16e70ca3a2d3a97722604a28fba6c0154e660387524 -size 20899 +oid sha256:c738cea16a714bdfa54cfcc213102d53ebe3aee576390902d352f04be65edac2 +size 11166 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png index 738a0e6371..7d271c8065 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f2a4128cb456fe55a7ef188592050270e4cc241542e59978a11222def40564a -size 21413 +oid sha256:42b44354480a1d2c869f541e6f3ed9feec15fb04ad32eb2a21b7d65290eeec54 +size 11972 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png index 00f6e44fff..6ef7e65498 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeb8c94db2e35c42f0d7b59102d35f6b00f6067870c5068e5e925e53d6e64ffd -size 22312 +oid sha256:d7d62b46acff22858a1621656ddaa97c3610a7f13df9c5d77747b7364620b174 +size 12772 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png index 8cea7036fb..0062fbcb98 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f00ea6a1901987c517a5490e7965bc9408c88ff292b2c4c069b87d5d899c638 -size 19458 +oid sha256:9e532758291dd3d18b5b81c1d788db7854b322a633557f3ee273bb9d68c465ba +size 9582 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png index 92a4779e3e..e20bd0e4b0 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:505bef8573a04cc809edbc671cb9d26bde49708521de1286406c3164cb9d8988 -size 24011 +oid sha256:581febc9878288785ae82b23f2946dc0c506ae86fba32bb02ba5e69cf1c8cda1 +size 14069 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png index 3b9f8866b4..3c2d6529fb 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64b29bbd6edca8e444822a97ce9bc674db175c299cbec1cbe596552419f49be7 -size 22239 +oid sha256:12f7bacf0402f821e3c80f65c29218bc1f1334392edc463b617cf711667db722 +size 12381 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png index 1efaf38b6c..07790191db 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae09ad6a81dbfc56c60b7e47720338b3ba3b8aa29982016c36a39baa33f75054 -size 23353 +oid sha256:436d168a3501da20c327cb3d2909cdd465585ee3f76a2534e37a36771e10115e +size 12596 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png index ed9531e7bb..49a4514226 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9e9094177282dd635a02b97855299e9275af364fd66812dd72b3ef2545b5660 -size 24487 +oid sha256:c952f81377c83b2255c427d0911b898e500d163d870de778b69778a9ab8c8278 +size 12459 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png index 8cea7036fb..0062fbcb98 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f00ea6a1901987c517a5490e7965bc9408c88ff292b2c4c069b87d5d899c638 -size 19458 +oid sha256:9e532758291dd3d18b5b81c1d788db7854b322a633557f3ee273bb9d68c465ba +size 9582 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png index 22b642dffa..394f8f85b4 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:874ffc514300dd727c6c46943fc9f8955013c1d355fc1bd60848660ed9b4f6b2 -size 25182 +oid sha256:2b542c86ea4fef3a37e89c1087dddafeeccf523e7c0721743f34d35da5e0653e +size 13116 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png index f0b5f034c2..296267b8cf 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef65b07e0d25a3ce83eae05f62d938220226cdfccc641a3b51e7a03283f61e1e -size 25430 +oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png index 72486e2621..e710de72c8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51740a55ef58532b2e753e6e26f2b4ae622db59b6a3df08aad58701ac058975f -size 25518 +oid sha256:5b55add6cd3dc0e130f399a6932ba279aa29dc72579ee575df88e0faf76a3835 +size 13073 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png index 36a2e98af4..fb03fbbf9c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7d276ba0d498bac579b3944644542b41e0e8d5a50c420e75d455ce51a49393f -size 25893 +oid sha256:ed5c1745e2ef33654023ed0a8bfabe5a75d46186aa5c42df54ac1a9506dcf632 +size 13431 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png index f0b5f034c2..296267b8cf 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef65b07e0d25a3ce83eae05f62d938220226cdfccc641a3b51e7a03283f61e1e -size 25430 +oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png index c4dacb6ad0..28b9e8811d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f1e1e92f72b2fcbc0f0659e7ef5c7c5eabea7968ec7975925480f11e639c0a0 -size 26509 +oid sha256:945c33feb2f3408b54e4574781eee3c2868885af25acd9172d420360e505b54a +size 13463 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png index 39820f08c6..554f587743 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46989a5fd14a9555eee28081ad78c34e26f5c38e6d7360cb36de8a87d2916685 -size 29187 +oid sha256:3edb6672168fc58a2bb6766d48a0883aa35fdc6873d2f4b9f26d3b5fa6cb46dd +size 15574 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png index e152e9c48e..fc6da7bbb1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c73448e92f13c979c3a0c4f16532a6f47a14e6e1974d686674862070787b6489 -size 31145 +oid sha256:9fd79ec840f8bd82b41d93987187531187a4bd957d7f0d497a86fa61de52cec7 +size 16733 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png index f37e332f37..36015f6638 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afc516374154a209a07f069eb7832808eefc0db4f2a3fbfa765848ca0d7acedf -size 31974 +oid sha256:360121a75c96434daa57f2b996e9776cc1efdf25aa3f7e926abd6d04c9ee4184 +size 17355 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png index f0b5f034c2..296267b8cf 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef65b07e0d25a3ce83eae05f62d938220226cdfccc641a3b51e7a03283f61e1e -size 25430 +oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc +size 13097 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png index e4b8623075..777be644a4 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be814de172c0b290e4af81ea175e14643e9dc34ce3400ae1f3b64228e29bf49d -size 32237 +oid sha256:97d3b5d804c50da0d9c5db7278b16bb807e246dbd083c8c62ec7d4d7a65a1b45 +size 18070 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png index 66bc734bb5..8f4f0e32e6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44dfa754a90a27a343ceba8bb68c42b255331fdbe2f1d1c5b1f64d47a6db0e89 -size 136581 +oid sha256:7f7ce90fb4dec4b890eb8bfd182e009b2769104ab2f14e926381c4949d6f7453 +size 82121 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png index d78df0b1f8..a0a5cc565d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b12689f5116ac8077e1fec5c556b0276e2d53241bbbf0d4be078186c9280d7e8 -size 95223 +oid sha256:2430b92bc20b2c3d142b5f84ae9fd62856fc4c717b0b226c2e096d725883d41f +size 54154 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png index 302188cf58..4b7a06f302 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3528fe676ae29534d80edcd08ca5874bcaaae6c1133332070dcd008df2c50da7 -size 138694 +oid sha256:4cd9433cdab37510cf6d98ce5838a69675359982376f7ef5c9e716c49772af74 +size 79370 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png index e0d901ea77..5d0c82e058 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307 -size 114842 +oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5 +size 61183 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png index a9e9a643a0..6fd875a6fd 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9cae72debcf389db95fd4dc5053a6b1d2ea133cd56b6f268b929c68b6bf0e2e0 -size 64044 +oid sha256:a5ad9cb26866b35f6ad8c0ae054c7172a15b2fb2512bd123af3c0e5685c30410 +size 32766 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png index e24920a4d5..9e97e5f96c 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f76c909b7e804c8dd80b07fd5346d2036d2fded2bf9a855bd20f7da154a111f3 -size 83554 +oid sha256:67ebf42bc82483d1778254d95a376230437611dce91c80f8ecda608de56bffe7 +size 43108 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png index 1550ce0e66..cfcafba1a7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:566cf4c7ef7f08597c7381b67fd14489f7445dd216f12059a4888bd948e9e5d3 -size 62638 +oid sha256:b5271fba5dcee48982ccad321f987a67d6663dabc01d380eb0cafc178251bc00 +size 33971 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png index 26a1479858..2fc55fb4dd 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b86134c61484e0189f2b73417d36321d930474026421ba80a9ebc33a23b878 -size 61324 +oid sha256:1ba613bc2cf88dfb357e88671464272ab4279667b8c776b8b9db913161b7f450 +size 33060 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png index 32475f3874..e3e48a17a6 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22cebdb64f32d4818a35c07a4a2f5c2b1bae1fd465944d553b37a211f3e78ff8 -size 79480 +oid sha256:242379eee61c3d82f10e8b36db0567749443f91a6e13e766cc1ee3a3eeff7e2c +size 43006 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png index 5ca3acc8fd..50d141aa1d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:204fe52c4d99b661b2429c4659cde8fb04366c038ac7be0aa507cfba7c5aecfb -size 173275 +oid sha256:fe72f6268d445f204afcea4723624398ff49e479e8b608843cf287dfb94ebe4e +size 101257 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png index 387e79ad34..e555a2cbd9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af5be90cac48d0c86c6115b0fc6ceff3fdf934cecf5b332f2942f680a0636f08 -size 140801 +oid sha256:c29c21979beeb7f659979893d05d1da15602a8fbc4a61309cd6380b296d69367 +size 83563 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png index 74c5cb62da..54cacf5a10 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:385063f3976342ea525487e53801df14e644eb0a56898b1e81e0667323ff3f1a -size 172869 +oid sha256:49e072dc73ba96dffa021b3e9bbf169102bd9ae7b9d4ed0a69b55178f1592ae5 +size 97415 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png index 9d73e42801..bbe5e4a20a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d9ecfd740c88faf8f159da4d704ec160fd17a4de63a870c4590cd16475248dc -size 146902 +oid sha256:8c6041ecc220ee8cd576aff06871bc1f3b7363dffe334bbab83344c5b96cbde3 +size 94511 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png index be9e2718dd..a09d04c793 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a247d55c2ee6b39707d929da18fa4242343c7819cd76a973397754a4dfb197f -size 123976 +oid sha256:912de82dc98a8dd72ffc5549125c397379a859a23ffe48f01e4f1c5a28ff1d18 +size 77029 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png index 2d16e4af11..44139c4e00 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27e0be11cb36a419a590de19cce432f5b78d9a3c86d024ca43b5904e758c569d -size 144104 +oid sha256:bedb363c412c4c387fabe4d65ca769079376f4cc56a3bfdd767f0ae8441b3dfb +size 92003 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index d87f3fd5a2..a41b9989f8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318 -size 104334 +oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295 +size 60377 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index c7e9e58f93..9cbb203986 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e236adf08358a44452d4a215f6267a5596ce7e824bf9818d1e6180366833b1f -size 82182 +oid sha256:eee438f7cbe6615bab0df73689f6924ea153da28eaf1f4c0c22076f24f18085d +size 46476 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 4b04715b9a..d8cb415022 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0aeb15a04553142cade051d523bbc18b2e63997efa0b0c5f5b8bab8662074f7 -size 88550 +oid sha256:1b023505175ae39a93fa55c85aa31466f0aca76fab0ee54f9667648b91f9aeb9 +size 50789 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index 2a0072cc67..d7c0cbc019 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf1d0ab1bead910fa3d0f0b3a6ed5bb9f26a470adc6019e1542b279b81d8d81a -size 109775 +oid sha256:c175f0db79d3ac74043dce3fe57d5c15c6ca38c954c008baf5fa917d3b9d4e0e +size 67374 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index b7b3619539..529557d9d8 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:171a705658d932b14073ef2affbf68829b4c0a4cfdecaa6672bf8ca63c03e4ff -size 103581 +oid sha256:7c76f0df12da8eac1fefb6ba9c0c89f5c8a7bcfbff442a4ebd763f1a4b359637 +size 63046 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index 07003dfa4e..efbb6a013b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e720cb4ab955614764cc0c10f08146a50e08d4c5712a02b581ae25a4e4935c3a -size 113199 +oid sha256:f2636953295972ede173dbfaf3b67f7cb91f1c3f4ccc79f70e078bd94af9422d +size 68579 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png index f62dcb0bf1..ca83b5de0d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed70179c085142e5629075084348fe3b78fb027039b73c570510f22489dfb2dd -size 170507 +oid sha256:68e401e5f9aeb4c5fa0b8413871436f1eb33fe5eb82026f2ad5665169a13d0de +size 112784 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png index 3e0a6ea3a4..485f36f456 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0ab1a9d1ac3ad5d9065cb7b436d1fa12eefcd467bb78defcf930e25df33a773 -size 165594 +oid sha256:7a59de505b2f7f0f14a3bc513f477f6ae6fd3a72ff7bc7c628a4efba18fed565 +size 108009 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png index 9f04685444..c29d9ec100 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:462a0d7d7d8056042e49dff3a896114d7db09b9e40e72e6b87f711caf6c1a993 -size 175519 +oid sha256:a65928b17616922155b030737af67de806c195bd993752a7d5e17ec7e94150fc +size 113919 diff --git a/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif b/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif new file mode 100644 index 0000000000..6847817fa4 --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:712d53330f8774ec4ec73fe8321641e2a457ec4bdef813352940dfc93c83c789 +size 3256 diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff new file mode 100644 index 0000000000..5574bd58ed --- /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 0000000000..ab0f2bdbc2 --- /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 0000000000..3a0a032c1d --- /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 0000000000..9d0c137f7e --- /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 0000000000..9bfc650668 --- /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 0000000000..6ed0c080cd --- /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 0000000000..1887fa4a51 --- /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 0000000000..650aff92b8 --- /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 0000000000..0300c67ab2 --- /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 0000000000..b0dbdde549 --- /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 0000000000..39852d5345 --- /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 0000000000..2072b0c47d --- /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 0000000000..384d00eaa9 --- /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 0000000000..621ef158aa --- /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 0000000000..f44a6e9343 --- /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 0000000000..b14eeba8d5 --- /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 0000000000..5db7ef564e --- /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 0000000000..e0a39d2483 --- /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 0000000000..1592645c8e --- /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 0000000000..c2ebed3649 --- /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 0000000000..c9f5fadee8 --- /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 0000000000..7450522679 --- /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 0000000000..99642af524 --- /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 0000000000..862db0b39f --- /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 0000000000..be84f0a30a --- /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 0000000000..7ebd74d9d4 --- /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 0000000000..3b7ee6ac3a --- /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 0000000000..2b290438a3 --- /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 0000000000..d76966336d --- /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 0000000000..6a1153bac7 --- /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 0000000000..a2da71cf61 --- /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 0000000000..d1bbbec427 --- /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-02.tiff b/tests/Images/Input/Tiff/flower-minisblack-02.tiff new file mode 100644 index 0000000000..d6ce305fe6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3122afede012fa00b8cb379b2f9125a34a38188c3346ec5e18d3b4bddcbb451b +size 1131 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 0000000000..e6d1e13360 --- /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-minisblack-06.tiff b/tests/Images/Input/Tiff/flower-minisblack-06.tiff new file mode 100644 index 0000000000..53db4e1126 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-06.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0c13012d8d35215b01192eb38058db4543486c60b4918beec8719a94d1e208e +size 2679 diff --git a/tests/Images/Input/Tiff/flower-minisblack-08.tiff b/tests/Images/Input/Tiff/flower-minisblack-08.tiff new file mode 100644 index 0000000000..02acb15113 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-08.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1268d843a2338409ec3a9f5a5a62e23d38c3a898035619994a02f21eff7590bf +size 3453 diff --git a/tests/Images/Input/Tiff/flower-minisblack-10.tiff b/tests/Images/Input/Tiff/flower-minisblack-10.tiff new file mode 100644 index 0000000000..770197726f --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-10.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a91d6946730604dd65c63f1653fb33031682f26218de33ebf3d0b362cb6883af +size 4269 diff --git a/tests/Images/Input/Tiff/flower-minisblack-12.tiff b/tests/Images/Input/Tiff/flower-minisblack-12.tiff new file mode 100644 index 0000000000..320083c323 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-12.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86fc9309872f4e4668350b95fae315d878ec9658046d738050a2743f5fa44446 +size 5043 diff --git a/tests/Images/Input/Tiff/flower-minisblack-14.tiff b/tests/Images/Input/Tiff/flower-minisblack-14.tiff new file mode 100644 index 0000000000..34fca95b56 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-14.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcd07668c73f24c2a13133ac4910b59a568502a6d3762675eef61a7e3b090165 +size 5817 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16.tiff b/tests/Images/Input/Tiff/flower-minisblack-16.tiff new file mode 100644 index 0000000000..0791941f99 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79531a10710dee89b86e2467818b7c03a24ff28ebd98c7bdcc292559671e1887 +size 6591 diff --git a/tests/Images/Input/Tiff/flower-palette-02.tiff b/tests/Images/Input/Tiff/flower-palette-02.tiff new file mode 100644 index 0000000000..eb80e4de85 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-palette-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75e74d8816942ff6e9dfda411f9171f0f1dd1a5a88cb1410238b55a2b2aeeb71 +size 1164 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 0000000000..8594a0b00a --- /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/flower-rgb-contig-02.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-02.tiff new file mode 100644 index 0000000000..a2d253dbd5 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbcd225c0db343f0cc984c35609b81f6413ebc1ba2ce2494d3607db375e969ff +size 2685 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff new file mode 100644 index 0000000000..d9a141f29a --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96c4c1dfc23a0d9e5c6189717647fa117b08aac9a40c63e3945d3e674df4c3c6 +size 5049 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff new file mode 100644 index 0000000000..2b271c8004 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68168ea1c2e50e674a7c5c41e5b055c881adf8cb940d0fd033a927a7ebdd7b6f +size 12117 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff new file mode 100644 index 0000000000..c890c777a6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f7a63eb8636e2b1ee39dfda4d0bddfc98bdc9eb94bea2dd657619331fa38b5b +size 14483 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff new file mode 100644 index 0000000000..d4d6a9492d --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a419a8e2f89321501ca8ad70d2a19d37a7bf3a8c2f45c809acc30be59139ae29 +size 16855 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff new file mode 100644 index 0000000000..125de5b9fd --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab3d6b619a198ff2e5fdd8f9752bf43c5b03a782625b1f0e3f2cfe0f20c4b24a +size 19177 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff new file mode 100644 index 0000000000..8b301a534d --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21c4ede6382d8c72cb8e6f7939203d5111b362646a9727d95a2f63310ec8e5b3 +size 2795 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff new file mode 100644 index 0000000000..7a2270e486 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca4434aa1a8c52654b20596c7c428c9016e089de75c29dc6ddcd32708874005c +size 5117 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff new file mode 100644 index 0000000000..be0acd6465 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f53948d4a36c80f45d70a315d2e76514ec41cabe982c06dbbd0d47e671120e2 +size 12211 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff new file mode 100644 index 0000000000..2d517268e9 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d28f021d40f53a011053f9644400fee2d29c02f97b4101fec899251125dbb18e +size 16855 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff new file mode 100644 index 0000000000..939fd9471b --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a143fb6c5792fa7755e06feb757c745ad68944336985dc5be8a0c37247fe36d +size 19177 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 0000000000..6e57bb56e8 --- /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 0000000000..570edfc6d9 --- /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 0000000000..643805ca77 --- /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 0000000000..2ab78c71ec --- /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 0000000000..ed06ff4117 --- /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 0000000000..64653d620e --- /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 0000000000..d767352688 --- /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 0000000000..197aade491 --- /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 0000000000..164740c4a5 --- /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 0000000000..4a4db4524e --- /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 0000000000..7caa44710d --- /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 0000000000..9bbb84d8ba --- /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 0000000000..d2595dcdc5 --- /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 0000000000..d68c53483d --- /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 0000000000..b282d65b56 --- /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 0000000000..7abd84d866 --- /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 0000000000..dc9b36a9f5 --- /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 0000000000..97623cd5b6 --- /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 0000000000..b198d1aba9 --- /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 0000000000..6cfc803bb6 --- /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 0000000000..4d8c11afe2 --- /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 0000000000..59290df1c3 --- /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 0000000000..557fb4c517 --- /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 0000000000..44092f6c7c --- /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 0000000000..a1d3d77f46 --- /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 0000000000..28310cade6 --- /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 0000000000..fa9a8f2aeb --- /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 0000000000..b282d65b56 --- /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 0000000000..ef03cdb3e4 --- /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 0000000000..cd78dfc883 --- /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 0000000000..deaeda645d --- /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 0000000000..c9602763da --- /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 0000000000..0f49121361 --- /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