From e965d3fac69ff87505a292dda60fc1f72b4db701 Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 5 Dec 2025 00:27:18 -0500 Subject: [PATCH 1/8] Apply color conversion when decoding PNG --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 62 ++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 38f964d37b..f7eb0de1f2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -7,9 +7,12 @@ using System.Globalization; using System.IO.Compression; using System.IO.Hashing; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; @@ -323,6 +326,11 @@ protected override Image Decode(BufferedReadStream stream, Cance PngThrowHelper.ThrowNoData(); } + if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile)) + { + ApplyIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile); + } + return image; } catch @@ -2153,4 +2161,58 @@ private static bool IsXmpTextData(ReadOnlySpan keywordBytes) private void SwapScanlineBuffers() => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); + + // FIXME: Maybe this could be a .Mutate(x => x.ApplyIccProfile(destinationProfile)) ? Nothing related to png here + private static void ApplyIccProfile(Image image, IccProfile sourceProfile, IccProfile destinationProfile) + where TPixel : unmanaged, IPixel + { + ColorConversionOptions options = new() + { + SourceIccProfile = sourceProfile, + TargetIccProfile = destinationProfile, + }; + + ColorProfileConverter converter = new(options); + + image.ProcessPixelRows(pixelAccessor => + { + using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelAccessor.Width * 3); + using IMemoryOwner alphaBuffer = image.Configuration.MemoryAllocator.Allocate(pixelAccessor.Width); + Span rgbPacked = rgbBuffer.Memory.Span; + ref float rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked); + Span alphaPacked = alphaBuffer.Memory.Span; + ref float alphaPackedRef = ref MemoryMarshal.GetReference(alphaPacked); + + for (int y = 0; y < pixelAccessor.Height; y++) + { + Span pixelsRow = pixelAccessor.GetRowSpan(y); + int rgbIdx = 0; + for (int x = 0; x < pixelsRow.Length; x++, rgbIdx += 3) + { + Vector4 rgba = pixelsRow[x].ToScaledVector4(); + Unsafe.Add(ref rgbPackedRef, rgbIdx) = rgba.X; + Unsafe.Add(ref rgbPackedRef, rgbIdx + 1) = rgba.Y; + Unsafe.Add(ref rgbPackedRef, rgbIdx + 2) = rgba.Z; + Unsafe.Add(ref alphaPackedRef, x) = rgba.W; + } + + Span source = MemoryMarshal.Cast(rgbPacked); + Span destination = MemoryMarshal.Cast(rgbPacked); + converter.Convert(source, destination); + + rgbIdx = 0; + for (int x = 0; x < pixelsRow.Length; x++, rgbIdx += 3) + { + float r = Unsafe.Add(ref rgbPackedRef, rgbIdx); + float g = Unsafe.Add(ref rgbPackedRef, rgbIdx + 1); + float b = Unsafe.Add(ref rgbPackedRef, rgbIdx + 2); + float a = Unsafe.Add(ref alphaPackedRef, x); + + pixelsRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); + } + } + } + ); + } + } From bd532d66961bc56ac47852ce19dacb33233eaf1c Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 00:07:00 -0500 Subject: [PATCH 2/8] Add tests to check if PngDecoder apply ICC profile --- .../Formats/Png/PngDecoderTests.cs | 14 ++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 7 +++++++ ...ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png | 3 +++ ...IsConvert_ApplyIccProfile_Rgba32_Perceptual.png | 3 +++ ...gIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png | 3 +++ .../Png/icc-profiles/Perceptual-cLUT-only.png | 3 +++ tests/Images/Input/Png/icc-profiles/Perceptual.png | 3 +++ tests/Images/Input/Png/icc-profiles/sRGB_Gray.png | 3 +++ 8 files changed, 39 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png create mode 100644 tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png create mode 100644 tests/Images/Input/Png/icc-profiles/Perceptual.png create mode 100644 tests/Images/Input/Png/icc-profiles/sRGB_Gray.png diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 3589a25a2d..98cfd06b63 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -206,6 +206,20 @@ public void Decode_WithAverageFilter(TestImageProvider provider) image.CompareToOriginal(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Png.Icc.Perceptual, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGray, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert }); + + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + Assert.Null(image.Metadata.IccProfile); + } + [Theory] [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index bc699da88e..18ad623729 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -166,6 +166,13 @@ public static class Png // Issue 3000: https://github.com/SixLabors/ImageSharp/issues/3000 public const string Issue3000 = "Png/issues/issue_3000.png"; + public static class Icc + { + public const string SRgbGray = "Png/icc-profiles/sRGB_Gray.png"; + public const string Perceptual = "Png/icc-profiles/Perceptual.png"; + public const string PerceptualcLUTOnly = "Png/icc-profiles/Perceptual-cLUT-only.png"; + } + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png new file mode 100644 index 0000000000..ffc9839019 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b72c885278a066e63c013885c42b772275f25a5f0b2290aa38c87f3dbeac984b +size 81432 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png new file mode 100644 index 0000000000..25a97ca48d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:936261278b1a9f5bf9a2bb4f8da09f2a82e1b5c693790e137c5f98fa4d885735 +size 81785 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png new file mode 100644 index 0000000000..5a35cf5796 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf856e49e4ece7e59eea684f6fa533ba313a36955be4703894f16b100283cb4a +size 2687 diff --git a/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png b/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png new file mode 100644 index 0000000000..8ac1afde9f --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c734cacc2c6e761bab088cac80ef09da7b56a545ce71c6cced4cac31e661795 +size 119811 diff --git a/tests/Images/Input/Png/icc-profiles/Perceptual.png b/tests/Images/Input/Png/icc-profiles/Perceptual.png new file mode 100644 index 0000000000..cac9bcb1e5 --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208a325dedea4453b7accce1ec540452af2e9be0f8c1f636f1d61a463eb3a9ae +size 123151 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png new file mode 100644 index 0000000000..3326936ceb --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64e0f6cc38750c83e6ff0cf1911e210c342900bb2cd6c88d3daed30c854e863 +size 4531 From 15d11515b77c925b884397217d56ae34cd7dd44a Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 00:07:37 -0500 Subject: [PATCH 3/8] Fix concurrency problem with MemoryAllocatorValidator --- tests/ImageSharp.Tests/MemoryAllocatorValidator.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs index 395dfd455f..2afa5fdc9d 100644 --- a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs +++ b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs @@ -22,7 +22,10 @@ private static void MemoryDiagnostics_MemoryReleased() TestMemoryDiagnostics backing = LocalInstance.Value; if (backing != null) { - backing.TotalRemainingAllocated--; + lock (backing) + { + backing.TotalRemainingAllocated--; + } } } @@ -31,8 +34,11 @@ private static void MemoryDiagnostics_MemoryAllocated() TestMemoryDiagnostics backing = LocalInstance.Value; if (backing != null) { - backing.TotalAllocated++; - backing.TotalRemainingAllocated++; + lock (backing) + { + backing.TotalAllocated++; + backing.TotalRemainingAllocated++; + } } } From f8c8174b575fedf89e1e2924a8d187794137125b Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 00:09:45 -0500 Subject: [PATCH 4/8] Rework ApplyIccProfile to use ProcessPixelRowsAsVector4 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 54 +++++++------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f7eb0de1f2..2a2b07389d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -26,6 +26,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats.Png; @@ -328,7 +329,7 @@ protected override Image Decode(BufferedReadStream stream, Cance if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile)) { - ApplyIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile); + ApplyRgbaCompatibleIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile); } return image; @@ -2162,8 +2163,7 @@ private static bool IsXmpTextData(ReadOnlySpan keywordBytes) private void SwapScanlineBuffers() => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); - // FIXME: Maybe this could be a .Mutate(x => x.ApplyIccProfile(destinationProfile)) ? Nothing related to png here - private static void ApplyIccProfile(Image image, IccProfile sourceProfile, IccProfile destinationProfile) + private static void ApplyRgbaCompatibleIccProfile(Image image, IccProfile sourceProfile, IccProfile destinationProfile) where TPixel : unmanaged, IPixel { ColorConversionOptions options = new() @@ -2174,45 +2174,27 @@ private static void ApplyIccProfile(Image image, IccProfile sour ColorProfileConverter converter = new(options); - image.ProcessPixelRows(pixelAccessor => - { - using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelAccessor.Width * 3); - using IMemoryOwner alphaBuffer = image.Configuration.MemoryAllocator.Allocate(pixelAccessor.Width); - Span rgbPacked = rgbBuffer.Memory.Span; - ref float rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked); - Span alphaPacked = alphaBuffer.Memory.Span; - ref float alphaPackedRef = ref MemoryMarshal.GetReference(alphaPacked); - - for (int y = 0; y < pixelAccessor.Height; y++) + image.Mutate(o => o.ProcessPixelRowsAsVector4((pixelsRow, _) => { - Span pixelsRow = pixelAccessor.GetRowSpan(y); - int rgbIdx = 0; - for (int x = 0; x < pixelsRow.Length; x++, rgbIdx += 3) + using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelsRow.Length); + Span rgbPacked = rgbBuffer.Memory.Span; + ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked); + + for (int x = 0; x < pixelsRow.Length; x++) { - Vector4 rgba = pixelsRow[x].ToScaledVector4(); - Unsafe.Add(ref rgbPackedRef, rgbIdx) = rgba.X; - Unsafe.Add(ref rgbPackedRef, rgbIdx + 1) = rgba.Y; - Unsafe.Add(ref rgbPackedRef, rgbIdx + 2) = rgba.Z; - Unsafe.Add(ref alphaPackedRef, x) = rgba.W; + Unsafe.Add(ref rgbPackedRef, x) = Rgb.FromScaledVector4(pixelsRow[x]); } - Span source = MemoryMarshal.Cast(rgbPacked); - Span destination = MemoryMarshal.Cast(rgbPacked); - converter.Convert(source, destination); + converter.Convert(rgbPacked, rgbPacked); - rgbIdx = 0; - for (int x = 0; x < pixelsRow.Length; x++, rgbIdx += 3) - { - float r = Unsafe.Add(ref rgbPackedRef, rgbIdx); - float g = Unsafe.Add(ref rgbPackedRef, rgbIdx + 1); - float b = Unsafe.Add(ref rgbPackedRef, rgbIdx + 2); - float a = Unsafe.Add(ref alphaPackedRef, x); + Span pixelsRowAsFloats = MemoryMarshal.Cast(pixelsRow); + ref float pixelsRowAsFloatsRef = ref MemoryMarshal.GetReference(pixelsRowAsFloats); - pixelsRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); + int cIdx = 0; + for (int x = 0; x < pixelsRow.Length; x++, cIdx += 4) + { + Unsafe.As(ref Unsafe.Add(ref pixelsRowAsFloatsRef, cIdx)) = rgbPacked[x]; } - } - } - ); + })); } - } From 472089abfbe9eaf9771c226471752a3a7b5558fd Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 02:26:03 -0500 Subject: [PATCH 5/8] Use bulk overload to convert the pixel row from Vector4 to Rgb in ApplyRgbaCompatibleIccProfile --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 2a2b07389d..5ad2682335 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2178,13 +2178,8 @@ private static void ApplyRgbaCompatibleIccProfile(Image image, I { using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelsRow.Length); Span rgbPacked = rgbBuffer.Memory.Span; - ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked); - - for (int x = 0; x < pixelsRow.Length; x++) - { - Unsafe.Add(ref rgbPackedRef, x) = Rgb.FromScaledVector4(pixelsRow[x]); - } + Rgb.FromScaledVector4(pixelsRow, rgbPacked); converter.Convert(rgbPacked, rgbPacked); Span pixelsRowAsFloats = MemoryMarshal.Cast(pixelsRow); From e6d62d019a75b8d14974446ee06697dd02426a6b Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 14:02:47 -0500 Subject: [PATCH 6/8] Make sure pixel values are float between 0 and 1 during processing of ApplyRgbaCompatibleIccProfile --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 5ad2682335..9da5330778 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2174,7 +2174,8 @@ private static void ApplyRgbaCompatibleIccProfile(Image image, I ColorProfileConverter converter = new(options); - image.Mutate(o => o.ProcessPixelRowsAsVector4((pixelsRow, _) => + image.Mutate(o => o.ProcessPixelRowsAsVector4( + (pixelsRow, _) => { using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelsRow.Length); Span rgbPacked = rgbBuffer.Memory.Span; @@ -2190,6 +2191,7 @@ private static void ApplyRgbaCompatibleIccProfile(Image image, I { Unsafe.As(ref Unsafe.Add(ref pixelsRowAsFloatsRef, cIdx)) = rgbPacked[x]; } - })); + }, + PixelConversionModifiers.Scale)); } } From 7d06e6df3d09d50306cb4e5213e41de2d9771b8f Mon Sep 17 00:00:00 2001 From: Socolin Date: Fri, 19 Dec 2025 14:10:56 -0500 Subject: [PATCH 7/8] Use the same MemoryAllocator as the one of the image when decoding icc profile of png with ApplyRgbaCompatibleIccProfile --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 9da5330778..0bea161dc9 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2170,6 +2170,7 @@ private static void ApplyRgbaCompatibleIccProfile(Image image, I { SourceIccProfile = sourceProfile, TargetIccProfile = destinationProfile, + MemoryAllocator = image.Configuration.MemoryAllocator, }; ColorProfileConverter converter = new(options); From 33aa0c84b78d4d525f989ac5fbddaef02676fa7e Mon Sep 17 00:00:00 2001 From: Socolin Date: Sat, 20 Dec 2025 01:01:10 -0500 Subject: [PATCH 8/8] Apply ICC profile when decoding InterlacedRgba PNG --- src/ImageSharp/ColorProfiles/Rgb.cs | 11 ++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 71 ++++++-- .../Formats/Png/PngScanlineProcessor.cs | 162 +++++++++++++----- .../Formats/Png/PngDecoderTests.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 2 + ...ile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png | 3 + ...ile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png | 3 + .../sRGB_Gray_Interlaced_Rgba32.png | 3 + .../sRGB_Gray_Interlaced_Rgba64.png | 3 + 9 files changed, 205 insertions(+), 55 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png create mode 100644 tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png create mode 100644 tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs index 42e502592c..73c7611985 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -100,6 +100,17 @@ public static Rgb FromScaledVector4(Vector4 source) public Vector4 ToScaledVector4() => new(this.AsVector3Unsafe(), 1F); + /// + /// Expands the color into a generic ("scaled") representation + /// with values scaled and usually clamped between 0 and 1. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The alpha component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4(float alpha) + => new(this.AsVector3Unsafe(), 1F); + /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 0bea161dc9..b0a84341fd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -216,6 +216,7 @@ protected override Image Decode(BufferedReadStream stream, Cance currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: + { if (frameCount >= this.maxFrames) { goto EOF; @@ -233,6 +234,11 @@ protected override Image Decode(BufferedReadStream stream, Cance this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame); + if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile)) + { + metadata.IccProfile = null; + } + this.currentStream.Position += 4; this.ReadScanlines( chunk.Length - 4, @@ -240,6 +246,7 @@ protected override Image Decode(BufferedReadStream stream, Cance pngMetadata, this.ReadNextFrameDataChunk, currentFrameControl.Value, + iccProfile, cancellationToken); // if current frame dispose is restore to previous, then from future frame's perspective, it never happened @@ -250,7 +257,10 @@ protected override Image Decode(BufferedReadStream stream, Cance } break; + } + case PngChunkType.Data: + { pngMetadata.AnimateRootFrame = currentFrameControl != null; currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height); if (image is null) @@ -261,12 +271,18 @@ protected override Image Decode(BufferedReadStream stream, Cance AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); } + if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile)) + { + metadata.IccProfile = null; + } + this.ReadScanlines( chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, currentFrameControl.Value, + iccProfile, cancellationToken); if (pngMetadata.AnimateRootFrame) { @@ -280,6 +296,8 @@ protected override Image Decode(BufferedReadStream stream, Cance } break; + } + case PngChunkType.Palette: this.palette = chunk.Data.GetSpan().ToArray(); break; @@ -327,9 +345,9 @@ protected override Image Decode(BufferedReadStream stream, Cance PngThrowHelper.ThrowNoData(); } - if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile)) + if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfileToApply)) { - ApplyRgbaCompatibleIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile); + ApplyRgbaCompatibleIccProfile(image, iccProfileToApply, CompactSrgbV4Profile.Profile); } return image; @@ -752,6 +770,7 @@ private int CalculateScanlineLength(int width) /// The png metadata /// A delegate to get more data from the inner stream for . /// The frame control + /// Optional ICC profile for color conversion. /// The cancellation token. private void ReadScanlines( int chunkLength, @@ -759,6 +778,7 @@ private void ReadScanlines( PngMetadata pngMetadata, Func getData, in FrameControl frameControl, + IccProfile? iccProfile, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -772,11 +792,11 @@ private void ReadScanlines( if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) { - this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); + this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken); } else { - this.DecodePixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); + this.DecodePixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken); } } @@ -788,12 +808,14 @@ private void ReadScanlines( /// The compressed pixel data stream. /// The image frame to decode to. /// The png metadata + /// Optional ICC profile for color conversion. /// The CancellationToken private void DecodePixelData( FrameControl frameControl, DeflateStream compressedStream, ImageFrame imageFrame, PngMetadata pngMetadata, + IccProfile? iccProfile, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -860,7 +882,7 @@ private void DecodePixelData( break; } - this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); + this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer, iccProfile); this.SwapScanlineBuffers(); currentRow++; } @@ -878,12 +900,14 @@ private void DecodePixelData( /// The compressed pixel data stream. /// The current image frame. /// The png metadata. + /// Optional ICC profile for color conversion. /// The cancellation token. private void DecodeInterlacedPixelData( in FrameControl frameControl, DeflateStream compressedStream, ImageFrame imageFrame, PngMetadata pngMetadata, + IccProfile? iccProfile, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -974,6 +998,7 @@ private void DecodeInterlacedPixelData( rowSpan, pngMetadata, blendRowBuffer, + iccProfile, pixelOffset: Adam7.FirstColumn[pass], increment: Adam7.ColumnIncrement[pass]); @@ -1012,13 +1037,15 @@ private void DecodeInterlacedPixelData( /// The image /// The png metadata. /// A span used to temporarily hold the decoded row pixel data for alpha blending. + /// Optional ICC profile for color conversion. private void ProcessDefilteredScanline( in FrameControl frameControl, int currentRow, ReadOnlySpan scanline, ImageFrame pixels, PngMetadata pngMetadata, - Span blendRowBuffer) + Span blendRowBuffer, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { Span destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow); @@ -1052,7 +1079,8 @@ private void ProcessDefilteredScanline( in frameControl, scanlineSpan, rowSpan, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1063,7 +1091,8 @@ private void ProcessDefilteredScanline( scanlineSpan, rowSpan, (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); + (uint)this.bytesPerSample, + iccProfile); break; @@ -1072,7 +1101,8 @@ private void ProcessDefilteredScanline( in frameControl, scanlineSpan, rowSpan, - pngMetadata.ColorTable); + pngMetadata.ColorTable, + iccProfile); break; @@ -1085,7 +1115,8 @@ private void ProcessDefilteredScanline( rowSpan, this.bytesPerPixel, this.bytesPerSample, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1097,7 +1128,8 @@ private void ProcessDefilteredScanline( scanlineSpan, rowSpan, this.bytesPerPixel, - this.bytesPerSample); + this.bytesPerSample, + iccProfile); break; } @@ -1124,6 +1156,7 @@ private void ProcessDefilteredScanline( /// The current image row. /// The png metadata. /// A span used to temporarily hold the decoded row pixel data for alpha blending. + /// Optional ICC profile for color conversion. /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. private void ProcessInterlacedDefilteredScanline( @@ -1132,6 +1165,7 @@ private void ProcessInterlacedDefilteredScanline( Span destination, PngMetadata pngMetadata, Span blendRowBuffer, + IccProfile? iccProfile, int pixelOffset = 0, int increment = 1) where TPixel : unmanaged, IPixel @@ -1166,7 +1200,8 @@ private void ProcessInterlacedDefilteredScanline( rowSpan, (uint)pixelOffset, (uint)increment, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1179,7 +1214,8 @@ private void ProcessInterlacedDefilteredScanline( (uint)pixelOffset, (uint)increment, (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); + (uint)this.bytesPerSample, + iccProfile); break; @@ -1190,7 +1226,8 @@ private void ProcessInterlacedDefilteredScanline( rowSpan, (uint)pixelOffset, (uint)increment, - pngMetadata.ColorTable); + pngMetadata.ColorTable, + iccProfile); break; @@ -1205,7 +1242,8 @@ private void ProcessInterlacedDefilteredScanline( (uint)increment, this.bytesPerPixel, this.bytesPerSample, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1219,7 +1257,8 @@ private void ProcessInterlacedDefilteredScanline( (uint)pixelOffset, (uint)increment, this.bytesPerPixel, - this.bytesPerSample); + this.bytesPerSample, + iccProfile); break; } diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 33ba58f545..ca4eaa58d2 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -1,10 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Png.Chunks; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png; @@ -20,7 +25,8 @@ public static void ProcessGrayscaleScanline( in FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleScanline( bitDepth, @@ -29,7 +35,8 @@ public static void ProcessGrayscaleScanline( rowSpan, 0, 1, - transparentColor); + transparentColor, + iccProfile); public static void ProcessInterlacedGrayscaleScanline( int bitDepth, @@ -38,9 +45,11 @@ public static void ProcessInterlacedGrayscaleScanline( Span rowSpan, uint pixelOffset, uint increment, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { + // FIXME-icc uint offset = pixelOffset + frameControl.XOffset; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -98,7 +107,8 @@ public static void ProcessGrayscaleWithAlphaScanline( ReadOnlySpan scanlineSpan, Span rowSpan, uint bytesPerPixel, - uint bytesPerSample) + uint bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleWithAlphaScanline( bitDepth, @@ -108,7 +118,8 @@ public static void ProcessGrayscaleWithAlphaScanline( 0, 1, bytesPerPixel, - bytesPerSample); + bytesPerSample, + iccProfile); public static void ProcessInterlacedGrayscaleWithAlphaScanline( int bitDepth, @@ -118,9 +129,11 @@ public static void ProcessInterlacedGrayscaleWithAlphaScanline( uint pixelOffset, uint increment, uint bytesPerPixel, - uint bytesPerSample) + uint bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { + // FIXME-icc uint offset = pixelOffset + frameControl.XOffset; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -153,7 +166,8 @@ public static void ProcessPaletteScanline( in FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, - ReadOnlyMemory? palette) + ReadOnlyMemory? palette, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedPaletteScanline( frameControl, @@ -161,7 +175,8 @@ public static void ProcessPaletteScanline( rowSpan, 0, 1, - palette); + palette, + iccProfile); public static void ProcessInterlacedPaletteScanline( in FrameControl frameControl, @@ -169,9 +184,11 @@ public static void ProcessInterlacedPaletteScanline( Span rowSpan, uint pixelOffset, uint increment, - ReadOnlyMemory? palette) + ReadOnlyMemory? palette, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { +// FIXME-icc if (palette is null) { PngThrowHelper.ThrowMissingPalette(); @@ -198,7 +215,8 @@ public static void ProcessRgbScanline( Span rowSpan, int bytesPerPixel, int bytesPerSample, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedRgbScanline( configuration, @@ -210,7 +228,8 @@ public static void ProcessRgbScanline( 1, bytesPerPixel, bytesPerSample, - transparentColor); + transparentColor, + iccProfile); public static void ProcessInterlacedRgbScanline( Configuration configuration, @@ -222,9 +241,11 @@ public static void ProcessInterlacedRgbScanline( uint increment, int bytesPerPixel, int bytesPerSample, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { + // FIXME-icc uint offset = pixelOffset + frameControl.XOffset; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -302,7 +323,8 @@ public static void ProcessRgbaScanline( ReadOnlySpan scanlineSpan, Span rowSpan, int bytesPerPixel, - int bytesPerSample) + int bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedRgbaScanline( configuration, @@ -313,7 +335,8 @@ public static void ProcessRgbaScanline( 0, 1, bytesPerPixel, - bytesPerSample); + bytesPerSample, + iccProfile); public static void ProcessInterlacedRgbaScanline( Configuration configuration, @@ -324,43 +347,104 @@ public static void ProcessInterlacedRgbaScanline( uint pixelOffset, uint increment, int bytesPerPixel, - int bytesPerSample) + int bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel { uint offset = pixelOffset + frameControl.XOffset; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - if (bitDepth == 16) + if (iccProfile != null) { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + ColorConversionOptions options = new() + { + SourceIccProfile = iccProfile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + MemoryAllocator = configuration.MemoryAllocator, + }; + + ColorProfileConverter converter = new(options); + using IMemoryOwner rgbBuffer = configuration.MemoryAllocator.Allocate((int)(frameControl.XMax - offset)); + Span rgbPacked = rgbBuffer.Memory.Span; + ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked); + using IMemoryOwner alphaBuffer = configuration.MemoryAllocator.Allocate((int)(frameControl.XMax - offset)); + Span alphaPacked = alphaBuffer.Memory.Span; + ref float alphaPackedRef = ref MemoryMarshal.GetReference(alphaPacked); + + if (bitDepth == 16) + { + int o = 0; + for (int i = 0; i < rgbPacked.Length; o += bytesPerPixel, i++) + { + ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + + Unsafe.Add(ref rgbPackedRef, i) = new Rgb(r / (float)ushort.MaxValue, g / (float)ushort.MaxValue, b / (float)ushort.MaxValue); + Unsafe.Add(ref alphaPackedRef, i) = a / (float)ushort.MaxValue; + } + } + else { - ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a)); + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + int o = 0; + for (int i = 0; i < rgbPacked.Length; o += bytesPerPixel, i++) + { + byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o); + byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); + byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); + byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample))); + + Unsafe.Add(ref rgbPackedRef, i) = new Rgb(r / (float)byte.MaxValue, g / (float)byte.MaxValue, b / (float)byte.MaxValue); + Unsafe.Add(ref alphaPackedRef, i) = a / (float)byte.MaxValue; + } + } + + converter.Convert(rgbPacked, rgbPacked); + + int idx = 0; + for (nuint x = offset; x < frameControl.XMax; x += increment, idx++) + { + Rgb rgb = Unsafe.Add(ref rgbPackedRef, idx); + Vector4 rgba = rgb.ToScaledVector4(Unsafe.Add(ref alphaPackedRef, idx)); + Unsafe.Add(ref rowSpanRef, x) = TPixel.FromScaledVector4(rgba); } - } - else if (pixelOffset == 0 && increment == 1) - { - PixelOperations.Instance.FromRgba32Bytes( - configuration, - scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)], - rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width), - (int)frameControl.Width); } else { - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + if (bitDepth == 16) + { + int o = 0; + for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + { + ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a)); + } + } + else if (pixelOffset == 0 && increment == 1) + { + PixelOperations.Instance.FromRgba32Bytes( + configuration, + scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)], + rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width), + (int)frameControl.Width); + } + else { - byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o); - byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); - byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); - byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample))); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a)); + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + int o = 0; + for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + { + byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o); + byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); + byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); + byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample))); + Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a)); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 98cfd06b63..a58101a6bd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -210,6 +210,8 @@ public void Decode_WithAverageFilter(TestImageProvider provider) [WithFile(TestImages.Png.Icc.Perceptual, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Icc.SRgbGray, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGrayInterlacedRgba32, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGrayInterlacedRgba64, PixelTypes.Rgba32)] public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 18ad623729..25bec12664 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -169,6 +169,8 @@ public static class Png public static class Icc { public const string SRgbGray = "Png/icc-profiles/sRGB_Gray.png"; + public const string SRgbGrayInterlacedRgba32 = "Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png"; + public const string SRgbGrayInterlacedRgba64 = "Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png"; public const string Perceptual = "Png/icc-profiles/Perceptual.png"; public const string PerceptualcLUTOnly = "Png/icc-profiles/Perceptual-cLUT-only.png"; } diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png new file mode 100644 index 0000000000..270555a555 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:337e84b78fb07359a42e7eee0eed32e6728497c64aa30c6bd5ea8a3a5ec67ebc +size 5151 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png new file mode 100644 index 0000000000..dc5f4a559c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:456ae30184b13aa2dc3d922db433017e076ff969862fe506436ed96c2d9be0a1 +size 6143 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png new file mode 100644 index 0000000000..7afc51cfe3 --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fc63cea5de188e76503bde2fce3ff84518af5064bb46d506420cd6d7e58285b +size 7237 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png new file mode 100644 index 0000000000..822aca4f53 --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64343871be4ad61451ef968fa9f07c6a11dee65d0f8fd718ae8c4941586aa60c +size 8227