Browse Source

Apply ICC profile when decoding InterlacedRgba PNG

pull/3028/head
Socolin 5 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() public Vector4 ToScaledVector4()
=> new(this.AsVector3Unsafe(), 1F); => 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/> /// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<Rgb> source, Span<Vector4> destination) 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()); currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break; break;
case PngChunkType.FrameData: case PngChunkType.FrameData:
{
if (frameCount >= this.maxFrames) if (frameCount >= this.maxFrames)
{ {
goto EOF; goto EOF;
@ -233,6 +234,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore
this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame); 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.currentStream.Position += 4;
this.ReadScanlines( this.ReadScanlines(
chunk.Length - 4, chunk.Length - 4,
@ -240,6 +246,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
pngMetadata, pngMetadata,
this.ReadNextFrameDataChunk, this.ReadNextFrameDataChunk,
currentFrameControl.Value, currentFrameControl.Value,
iccProfile,
cancellationToken); cancellationToken);
// if current frame dispose is restore to previous, then from future frame's perspective, it never happened // 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; break;
}
case PngChunkType.Data: case PngChunkType.Data:
{
pngMetadata.AnimateRootFrame = currentFrameControl != null; pngMetadata.AnimateRootFrame = currentFrameControl != null;
currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height); currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height);
if (image is null) if (image is null)
@ -261,12 +271,18 @@ internal sealed class PngDecoderCore : ImageDecoderCore
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
} }
if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
{
metadata.IccProfile = null;
}
this.ReadScanlines( this.ReadScanlines(
chunk.Length, chunk.Length,
image.Frames.RootFrame, image.Frames.RootFrame,
pngMetadata, pngMetadata,
this.ReadNextDataChunk, this.ReadNextDataChunk,
currentFrameControl.Value, currentFrameControl.Value,
iccProfile,
cancellationToken); cancellationToken);
if (pngMetadata.AnimateRootFrame) if (pngMetadata.AnimateRootFrame)
{ {
@ -280,6 +296,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
} }
break; break;
}
case PngChunkType.Palette: case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray(); this.palette = chunk.Data.GetSpan().ToArray();
break; break;
@ -327,9 +345,9 @@ internal sealed class PngDecoderCore : ImageDecoderCore
PngThrowHelper.ThrowNoData(); 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; return image;
@ -752,6 +770,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="pngMetadata">The png metadata</param> /// <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="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="frameControl">The frame control</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
private void ReadScanlines<TPixel>( private void ReadScanlines<TPixel>(
int chunkLength, int chunkLength,
@ -759,6 +778,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
PngMetadata pngMetadata, PngMetadata pngMetadata,
Func<int> getData, Func<int> getData,
in FrameControl frameControl, in FrameControl frameControl,
IccProfile? iccProfile,
CancellationToken cancellationToken) CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -772,11 +792,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore
if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) if (this.header.InterlaceMethod is PngInterlaceMode.Adam7)
{ {
this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken);
} }
else 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="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The image frame to decode to.</param> /// <param name="imageFrame">The image frame to decode to.</param>
/// <param name="pngMetadata">The png metadata</param> /// <param name="pngMetadata">The png metadata</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The CancellationToken</param> /// <param name="cancellationToken">The CancellationToken</param>
private void DecodePixelData<TPixel>( private void DecodePixelData<TPixel>(
FrameControl frameControl, FrameControl frameControl,
DeflateStream compressedStream, DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame, ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata, PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken) CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -860,7 +882,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
break; break;
} }
this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer, iccProfile);
this.SwapScanlineBuffers(); this.SwapScanlineBuffers();
currentRow++; currentRow++;
} }
@ -878,12 +900,14 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The current image frame.</param> /// <param name="imageFrame">The current image frame.</param>
/// <param name="pngMetadata">The png metadata.</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> /// <param name="cancellationToken">The cancellation token.</param>
private void DecodeInterlacedPixelData<TPixel>( private void DecodeInterlacedPixelData<TPixel>(
in FrameControl frameControl, in FrameControl frameControl,
DeflateStream compressedStream, DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame, ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata, PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken) CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -974,6 +998,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan, rowSpan,
pngMetadata, pngMetadata,
blendRowBuffer, blendRowBuffer,
iccProfile,
pixelOffset: Adam7.FirstColumn[pass], pixelOffset: Adam7.FirstColumn[pass],
increment: Adam7.ColumnIncrement[pass]); increment: Adam7.ColumnIncrement[pass]);
@ -1012,13 +1037,15 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="pixels">The image</param> /// <param name="pixels">The image</param>
/// <param name="pngMetadata">The png metadata.</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="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>( private void ProcessDefilteredScanline<TPixel>(
in FrameControl frameControl, in FrameControl frameControl,
int currentRow, int currentRow,
ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> scanline,
ImageFrame<TPixel> pixels, ImageFrame<TPixel> pixels,
PngMetadata pngMetadata, PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer) Span<TPixel> blendRowBuffer,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow); Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
@ -1052,7 +1079,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
in frameControl, in frameControl,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pngMetadata.TransparentColor); pngMetadata.TransparentColor,
iccProfile);
break; break;
@ -1063,7 +1091,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
(uint)this.bytesPerPixel, (uint)this.bytesPerPixel,
(uint)this.bytesPerSample); (uint)this.bytesPerSample,
iccProfile);
break; break;
@ -1072,7 +1101,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
in frameControl, in frameControl,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pngMetadata.ColorTable); pngMetadata.ColorTable,
iccProfile);
break; break;
@ -1085,7 +1115,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.TransparentColor); pngMetadata.TransparentColor,
iccProfile);
break; break;
@ -1097,7 +1128,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample,
iccProfile);
break; break;
} }
@ -1124,6 +1156,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="destination">The current image row.</param> /// <param name="destination">The current image row.</param>
/// <param name="pngMetadata">The png metadata.</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="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="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> /// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>( private void ProcessInterlacedDefilteredScanline<TPixel>(
@ -1132,6 +1165,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
Span<TPixel> destination, Span<TPixel> destination,
PngMetadata pngMetadata, PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer, Span<TPixel> blendRowBuffer,
IccProfile? iccProfile,
int pixelOffset = 0, int pixelOffset = 0,
int increment = 1) int increment = 1)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -1166,7 +1200,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan, rowSpan,
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
pngMetadata.TransparentColor); pngMetadata.TransparentColor,
iccProfile);
break; break;
@ -1179,7 +1214,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
(uint)this.bytesPerPixel, (uint)this.bytesPerPixel,
(uint)this.bytesPerSample); (uint)this.bytesPerSample,
iccProfile);
break; break;
@ -1190,7 +1226,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan, rowSpan,
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
pngMetadata.ColorTable); pngMetadata.ColorTable,
iccProfile);
break; break;
@ -1205,7 +1242,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)increment, (uint)increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.TransparentColor); pngMetadata.TransparentColor,
iccProfile);
break; break;
@ -1219,7 +1257,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample,
iccProfile);
break; break;
} }

162
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -1,10 +1,15 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png; namespace SixLabors.ImageSharp.Formats.Png;
@ -20,7 +25,8 @@ internal static class PngScanlineProcessor
in FrameControl frameControl, in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
Color? transparentColor) Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleScanline( ProcessInterlacedGrayscaleScanline(
bitDepth, bitDepth,
@ -29,7 +35,8 @@ internal static class PngScanlineProcessor
rowSpan, rowSpan,
0, 0,
1, 1,
transparentColor); transparentColor,
iccProfile);
public static void ProcessInterlacedGrayscaleScanline<TPixel>( public static void ProcessInterlacedGrayscaleScanline<TPixel>(
int bitDepth, int bitDepth,
@ -38,9 +45,11 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
Color? transparentColor) Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset; uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -98,7 +107,8 @@ internal static class PngScanlineProcessor
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
uint bytesPerPixel, uint bytesPerPixel,
uint bytesPerSample) uint bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleWithAlphaScanline( ProcessInterlacedGrayscaleWithAlphaScanline(
bitDepth, bitDepth,
@ -108,7 +118,8 @@ internal static class PngScanlineProcessor
0, 0,
1, 1,
bytesPerPixel, bytesPerPixel,
bytesPerSample); bytesPerSample,
iccProfile);
public static void ProcessInterlacedGrayscaleWithAlphaScanline<TPixel>( public static void ProcessInterlacedGrayscaleWithAlphaScanline<TPixel>(
int bitDepth, int bitDepth,
@ -118,9 +129,11 @@ internal static class PngScanlineProcessor
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
uint bytesPerPixel, uint bytesPerPixel,
uint bytesPerSample) uint bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset; uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -153,7 +166,8 @@ internal static class PngScanlineProcessor
in FrameControl frameControl, in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
ReadOnlyMemory<Color>? palette) ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedPaletteScanline( ProcessInterlacedPaletteScanline(
frameControl, frameControl,
@ -161,7 +175,8 @@ internal static class PngScanlineProcessor
rowSpan, rowSpan,
0, 0,
1, 1,
palette); palette,
iccProfile);
public static void ProcessInterlacedPaletteScanline<TPixel>( public static void ProcessInterlacedPaletteScanline<TPixel>(
in FrameControl frameControl, in FrameControl frameControl,
@ -169,9 +184,11 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
ReadOnlyMemory<Color>? palette) ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// FIXME-icc
if (palette is null) if (palette is null)
{ {
PngThrowHelper.ThrowMissingPalette(); PngThrowHelper.ThrowMissingPalette();
@ -198,7 +215,8 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample, int bytesPerSample,
Color? transparentColor) Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbScanline( ProcessInterlacedRgbScanline(
configuration, configuration,
@ -210,7 +228,8 @@ internal static class PngScanlineProcessor
1, 1,
bytesPerPixel, bytesPerPixel,
bytesPerSample, bytesPerSample,
transparentColor); transparentColor,
iccProfile);
public static void ProcessInterlacedRgbScanline<TPixel>( public static void ProcessInterlacedRgbScanline<TPixel>(
Configuration configuration, Configuration configuration,
@ -222,9 +241,11 @@ internal static class PngScanlineProcessor
uint increment, uint increment,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample, int bytesPerSample,
Color? transparentColor) Color? transparentColor,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset; uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -302,7 +323,8 @@ internal static class PngScanlineProcessor
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample) int bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbaScanline( ProcessInterlacedRgbaScanline(
configuration, configuration,
@ -313,7 +335,8 @@ internal static class PngScanlineProcessor
0, 0,
1, 1,
bytesPerPixel, bytesPerPixel,
bytesPerSample); bytesPerSample,
iccProfile);
public static void ProcessInterlacedRgbaScanline<TPixel>( public static void ProcessInterlacedRgbaScanline<TPixel>(
Configuration configuration, Configuration configuration,
@ -324,43 +347,104 @@ internal static class PngScanlineProcessor
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample) int bytesPerSample,
IccProfile? iccProfile)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
uint offset = pixelOffset + frameControl.XOffset; uint offset = pixelOffset + frameControl.XOffset;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
if (bitDepth == 16) if (iccProfile != null)
{ {
int o = 0; ColorConversionOptions options = new()
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) {
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)); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); int o = 0;
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); for (int i = 0; i < rgbPacked.Length; o += bytesPerPixel, i++)
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); {
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a)); 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 else
{ {
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); if (bitDepth == 16)
int o = 0; {
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) 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); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); int o = 0;
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample))); {
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a)); 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.Perceptual, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Icc.SRgbGray, 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) public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

2
tests/ImageSharp.Tests/TestImages.cs

@ -169,6 +169,8 @@ public static class TestImages
public static class Icc public static class Icc
{ {
public const string SRgbGray = "Png/icc-profiles/sRGB_Gray.png"; 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 Perceptual = "Png/icc-profiles/Perceptual.png";
public const string PerceptualcLUTOnly = "Png/icc-profiles/Perceptual-cLUT-only.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