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)); + } + } + } + ); + } + }