From 0582e85e4957ed29f57d721d4037aa8a7b3076f3 Mon Sep 17 00:00:00 2001 From: Socolin Date: Wed, 10 Dec 2025 23:12:33 -0500 Subject: [PATCH] Fix decoding tiff image with BigEndian + 64 bit / pixel + associated alpha --- .../Formats/Tiff/Utils/TiffUtilities.cs | 7 ++- .../Formats/Tiff/TiffDecoderTests.cs | 13 +++++ .../Formats/Tiff/Utils/TiffUtilitiesTest.cs | 51 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + ...t_WithAssociatedAlpha_Rgba64_Issue3031.png | 3 ++ ...ha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png | 3 ++ ...ha_Rgba64_RgbaAssociatedAlpha16bit_msb.png | 3 ++ ...tedAlpha_Rgba16161616_Rgba64_Issue3031.png | 3 ++ tests/Images/Input/Tiff/Issues/Issue3031.tiff | 3 ++ 9 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png create mode 100644 tests/Images/Input/Tiff/Issues/Issue3031.tiff diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs index e30765b1f4..b7d412f3c8 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs @@ -45,7 +45,12 @@ internal static class TiffUtilities return TPixel.FromRgba64(default); } - return TPixel.FromRgba64(new Rgba64((ushort)(r / a), (ushort)(g / a), (ushort)(b / a), a)); + float scale = 65535f / a; + ushort ur = (ushort)Math.Min(r * scale, 65535); + ushort ug = (ushort)Math.Min(g * scale, 65535); + ushort ub = (ushort)Math.Min(b * scale, 65535); + + return TPixel.FromRgba64(new Rgba64(ur, ug, ub, a)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index d850c67a51..5096d93bd7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -365,6 +365,19 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider); } + [Theory] + [WithFile(Issues3031, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaBigEndian, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaLittleEndian, PixelTypes.Rgba64)] + public void TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + image.DebugSave(provider); + + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + [Theory] [WithFile(Issues2454_A, PixelTypes.Rgba32)] [WithFile(Issues2454_B, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs new file mode 100644 index 0000000000..6483b63c52 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils; + +[Trait("Format", "Tiff")] +public class TiffUtilitiesTest +{ + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(42, 84, 128, 0)] + [InlineData(65535, 65535, 65535, 0)] + public void ColorFromRgba64Premultiplied_WithZeroAlpha_ReturnsDefaultPixel(ushort r, ushort g, ushort b, ushort a) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(default, actual); + } + + [Theory] + [InlineData(65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)] + [InlineData(32767, 0, 0, 65535, 32767, 0, 0, 65535)] + [InlineData(0, 32767, 0, 65535, 0, 32767, 0, 65535)] + [InlineData(0, 0, 32767, 65535, 0, 0, 32767, 65535)] + public void ColorFromRgba64Premultiplied_WithNoAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } + + [Theory] + [InlineData(32766, 0, 0, 32766, 65535, 0, 0, 32766)] // Red, 50% Alpha + [InlineData(0, 32766, 0, 32766, 0, 65535, 0, 32766)] // Green, 50% Alpha + [InlineData(0, 0, 32766, 32766, 0, 0, 65535, 32766)] // Blue, 50% Alpha + [InlineData(8191, 0, 0, 16383, 32765, 0, 0, 16383)] // Red, 25% Alpha + [InlineData(0, 8191, 0, 16383, 0, 32765, 0, 16383)] // Green, 25% Alpha + [InlineData(0, 0, 8191, 16383, 0, 0, 32765, 16383)] // Blue, 25% Alpha + [InlineData(8191, 0, 0, 0, 0, 0, 0, 0)] // Red, 0% Alpha + [InlineData(0, 8191, 0, 0, 0, 0, 0, 0)] // Green, 0% Alpha + [InlineData(0, 0, 8191, 0, 0, 0, 0, 0)] // Blue, 0% Alpha + public void ColorFromRgba64Premultiplied_WithAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index bc699da88e..161b1a709d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1142,6 +1142,7 @@ public static class TestImages public const string Issues2435 = "Tiff/Issues/Issue2435.tiff"; public const string Issues2454_A = "Tiff/Issues/Issue2454_A.tif"; public const string Issues2454_B = "Tiff/Issues/Issue2454_B.tif"; + public const string Issues3031 = "Tiff/Issues/Issue3031.tiff"; public const string Issues2587 = "Tiff/Issues/Issue2587.tiff"; public const string Issues2679 = "Tiff/Issues/Issue2679.tiff"; public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff"; diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png new file mode 100644 index 0000000000..d5a017475f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png new file mode 100644 index 0000000000..a3317a6b48 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50dfa103459d21642df5e1ca760081fbdfe3b7244624d9d87d8a20f45b51bbe +size 117953 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png new file mode 100644 index 0000000000..5b72c3732c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3333503012aa29e23f0d0e52993ad3499514251208fb3318e2bb1560d54650fa +size 117956 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png new file mode 100644 index 0000000000..d5a017475f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 diff --git a/tests/Images/Input/Tiff/Issues/Issue3031.tiff b/tests/Images/Input/Tiff/Issues/Issue3031.tiff new file mode 100644 index 0000000000..bc3ef7d7cf --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue3031.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4d2db56a1b7fdea09ed65eab1d10a02952821f6662ca77caa44b8c51b30310 +size 416