Browse Source

Apply ICC profile when decoding InterlacedRgba PNG

pull/3028/head
Socolin 2 months ago
parent
commit
33aa0c84b7
  1. 11
      src/ImageSharp/ColorProfiles/Rgb.cs
  2. 71
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 162
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
  4. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  5. 2
      tests/ImageSharp.Tests/TestImages.cs
  6. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png
  7. 3
      tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png
  8. 3
      tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png
  9. 3
      tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png

11
src/ImageSharp/ColorProfiles/Rgb.cs

@ -100,6 +100,17 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
public Vector4 ToScaledVector4()
=> new(this.AsVector3Unsafe(), 1F);
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
/// with values scaled and usually clamped between <value>0</value> and <value>1</value>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <param name="alpha">The alpha component.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4(float alpha)
=> new(this.AsVector3Unsafe(), 1F);
/// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<Rgb> source, Span<Vector4> destination)
{

71
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
/// <param name="pngMetadata">The png metadata</param>
/// <param name="getData">A delegate to get more data from the inner stream for <see cref="ZlibInflateStream"/>.</param>
/// <param name="frameControl">The frame control</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void ReadScanlines<TPixel>(
int chunkLength,
@ -759,6 +778,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
PngMetadata pngMetadata,
Func<int> getData,
in FrameControl frameControl,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -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
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The image frame to decode to.</param>
/// <param name="pngMetadata">The png metadata</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The CancellationToken</param>
private void DecodePixelData<TPixel>(
FrameControl frameControl,
DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -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
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The current image frame.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void DecodeInterlacedPixelData<TPixel>(
in FrameControl frameControl,
DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -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
/// <param name="pixels">The image</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
private void ProcessDefilteredScanline<TPixel>(
in FrameControl frameControl,
int currentRow,
ReadOnlySpan<byte> scanline,
ImageFrame<TPixel> pixels,
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer)
Span<TPixel> blendRowBuffer,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> 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
/// <param name="destination">The current image row.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(
@ -1132,6 +1165,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
Span<TPixel> destination,
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer,
IccProfile? iccProfile,
int pixelOffset = 0,
int increment = 1)
where TPixel : unmanaged, IPixel<TPixel>
@ -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;
}

162
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<byte> scanlineSpan,
Span<TPixel> rowSpan,
Color? transparentColor)
Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleScanline(
bitDepth,
@ -29,7 +35,8 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
transparentColor);
transparentColor,
iccProfile);
public static void ProcessInterlacedGrayscaleScanline<TPixel>(
int bitDepth,
@ -38,9 +45,11 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
uint pixelOffset,
uint increment,
Color? transparentColor)
Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<byte> scanlineSpan,
Span<TPixel> rowSpan,
uint bytesPerPixel,
uint bytesPerSample)
uint bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleWithAlphaScanline(
bitDepth,
@ -108,7 +118,8 @@ internal static class PngScanlineProcessor
0,
1,
bytesPerPixel,
bytesPerSample);
bytesPerSample,
iccProfile);
public static void ProcessInterlacedGrayscaleWithAlphaScanline<TPixel>(
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<TPixel>
{
// 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<byte> scanlineSpan,
Span<TPixel> rowSpan,
ReadOnlyMemory<Color>? palette)
ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedPaletteScanline(
frameControl,
@ -161,7 +175,8 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
palette);
palette,
iccProfile);
public static void ProcessInterlacedPaletteScanline<TPixel>(
in FrameControl frameControl,
@ -169,9 +184,11 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
uint pixelOffset,
uint increment,
ReadOnlyMemory<Color>? palette)
ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel>
{
// FIXME-icc
if (palette is null)
{
PngThrowHelper.ThrowMissingPalette();
@ -198,7 +215,8 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample,
Color? transparentColor)
Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbScanline(
configuration,
@ -210,7 +228,8 @@ internal static class PngScanlineProcessor
1,
bytesPerPixel,
bytesPerSample,
transparentColor);
transparentColor,
iccProfile);
public static void ProcessInterlacedRgbScanline<TPixel>(
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<TPixel>
{
// 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<byte> scanlineSpan,
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample)
int bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbaScanline(
configuration,
@ -313,7 +335,8 @@ internal static class PngScanlineProcessor
0,
1,
bytesPerPixel,
bytesPerSample);
bytesPerSample,
iccProfile);
public static void ProcessInterlacedRgbaScanline<TPixel>(
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<TPixel>
{
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<Rgb> rgbBuffer = configuration.MemoryAllocator.Allocate<Rgb>((int)(frameControl.XMax - offset));
Span<Rgb> rgbPacked = rgbBuffer.Memory.Span;
ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked);
using IMemoryOwner<float> alphaBuffer = configuration.MemoryAllocator.Allocate<float>((int)(frameControl.XMax - offset));
Span<float> 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<Rgb, Rgb>(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<TPixel>.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<TPixel>.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));
}
}
}
}

2
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{

2
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";
}

3
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

3
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

3
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

3
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
Loading…
Cancel
Save