From 33aa0c84b78d4d525f989ac5fbddaef02676fa7e Mon Sep 17 00:00:00 2001 From: Socolin Date: Sat, 20 Dec 2025 01:01:10 -0500 Subject: [PATCH] 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 42e502592..73c761198 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -100,6 +100,17 @@ public readonly struct Rgb : IProfileConnectingSpace 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 0bea161dc..b0a84341f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -216,6 +216,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: + { if (frameCount >= this.maxFrames) { goto EOF; @@ -233,6 +234,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore } 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore } break; + } + case PngChunkType.Palette: this.palette = chunk.Data.GetSpan().ToArray(); break; @@ -327,9 +345,9 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore PngMetadata pngMetadata, Func getData, in FrameControl frameControl, + IccProfile? iccProfile, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -772,11 +792,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore rowSpan, pngMetadata, blendRowBuffer, + iccProfile, pixelOffset: Adam7.FirstColumn[pass], increment: Adam7.ColumnIncrement[pass]); @@ -1012,13 +1037,15 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore in frameControl, scanlineSpan, rowSpan, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1063,7 +1091,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore scanlineSpan, rowSpan, (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); + (uint)this.bytesPerSample, + iccProfile); break; @@ -1072,7 +1101,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore in frameControl, scanlineSpan, rowSpan, - pngMetadata.ColorTable); + pngMetadata.ColorTable, + iccProfile); break; @@ -1085,7 +1115,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore rowSpan, this.bytesPerPixel, this.bytesPerSample, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1097,7 +1128,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore scanlineSpan, rowSpan, this.bytesPerPixel, - this.bytesPerSample); + this.bytesPerSample, + iccProfile); break; } @@ -1124,6 +1156,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// 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 @@ internal sealed class PngDecoderCore : ImageDecoderCore Span destination, PngMetadata pngMetadata, Span blendRowBuffer, + IccProfile? iccProfile, int pixelOffset = 0, int increment = 1) where TPixel : unmanaged, IPixel @@ -1166,7 +1200,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore rowSpan, (uint)pixelOffset, (uint)increment, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1179,7 +1214,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore (uint)pixelOffset, (uint)increment, (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); + (uint)this.bytesPerSample, + iccProfile); break; @@ -1190,7 +1226,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore rowSpan, (uint)pixelOffset, (uint)increment, - pngMetadata.ColorTable); + pngMetadata.ColorTable, + iccProfile); break; @@ -1205,7 +1242,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore (uint)increment, this.bytesPerPixel, this.bytesPerSample, - pngMetadata.TransparentColor); + pngMetadata.TransparentColor, + iccProfile); break; @@ -1219,7 +1257,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore (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 33ba58f54..ca4eaa58d 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 @@ internal static class PngScanlineProcessor in FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleScanline( bitDepth, @@ -29,7 +35,8 @@ internal static class PngScanlineProcessor rowSpan, 0, 1, - transparentColor); + transparentColor, + iccProfile); public static void ProcessInterlacedGrayscaleScanline( int bitDepth, @@ -38,9 +45,11 @@ internal static class PngScanlineProcessor 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 @@ internal static class PngScanlineProcessor ReadOnlySpan scanlineSpan, Span rowSpan, uint bytesPerPixel, - uint bytesPerSample) + uint bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleWithAlphaScanline( bitDepth, @@ -108,7 +118,8 @@ internal static class PngScanlineProcessor 0, 1, bytesPerPixel, - bytesPerSample); + bytesPerSample, + iccProfile); public static void ProcessInterlacedGrayscaleWithAlphaScanline( int bitDepth, @@ -118,9 +129,11 @@ internal static class PngScanlineProcessor 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 @@ internal static class PngScanlineProcessor in FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, - ReadOnlyMemory? palette) + ReadOnlyMemory? palette, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedPaletteScanline( frameControl, @@ -161,7 +175,8 @@ internal static class PngScanlineProcessor rowSpan, 0, 1, - palette); + palette, + iccProfile); public static void ProcessInterlacedPaletteScanline( in FrameControl frameControl, @@ -169,9 +184,11 @@ internal static class PngScanlineProcessor 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 @@ internal static class PngScanlineProcessor Span rowSpan, int bytesPerPixel, int bytesPerSample, - Color? transparentColor) + Color? transparentColor, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedRgbScanline( configuration, @@ -210,7 +228,8 @@ internal static class PngScanlineProcessor 1, bytesPerPixel, bytesPerSample, - transparentColor); + transparentColor, + iccProfile); public static void ProcessInterlacedRgbScanline( Configuration configuration, @@ -222,9 +241,11 @@ internal static class PngScanlineProcessor 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 @@ internal static class PngScanlineProcessor ReadOnlySpan scanlineSpan, Span rowSpan, int bytesPerPixel, - int bytesPerSample) + int bytesPerSample, + IccProfile? iccProfile) where TPixel : unmanaged, IPixel => ProcessInterlacedRgbaScanline( configuration, @@ -313,7 +335,8 @@ internal static class PngScanlineProcessor 0, 1, bytesPerPixel, - bytesPerSample); + bytesPerSample, + iccProfile); public static void ProcessInterlacedRgbaScanline( Configuration configuration, @@ -324,43 +347,104 @@ internal static class PngScanlineProcessor 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 98cfd06b6..a58101a6b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -210,6 +210,8 @@ public partial class PngDecoderTests [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 18ad62372..25bec1266 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -169,6 +169,8 @@ public static class TestImages 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 000000000..270555a55 --- /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 000000000..dc5f4a559 --- /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 000000000..7afc51cfe --- /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 000000000..822aca4f5 --- /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