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 38f964d37b..b0a84341fd 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;
@@ -23,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;
@@ -212,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;
@@ -229,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,
@@ -236,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
@@ -246,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)
@@ -257,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)
{
@@ -276,6 +296,8 @@ protected override Image Decode(BufferedReadStream stream, Cance
}
break;
+ }
+
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
break;
@@ -323,6 +345,11 @@ protected override Image Decode(BufferedReadStream stream, Cance
PngThrowHelper.ThrowNoData();
}
+ if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfileToApply))
+ {
+ ApplyRgbaCompatibleIccProfile(image, iccProfileToApply, CompactSrgbV4Profile.Profile);
+ }
+
return image;
}
catch
@@ -743,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,
@@ -750,6 +778,7 @@ private void ReadScanlines(
PngMetadata pngMetadata,
Func getData,
in FrameControl frameControl,
+ IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
@@ -763,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);
}
}
@@ -779,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
{
@@ -851,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++;
}
@@ -869,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
{
@@ -965,6 +998,7 @@ private void DecodeInterlacedPixelData(
rowSpan,
pngMetadata,
blendRowBuffer,
+ iccProfile,
pixelOffset: Adam7.FirstColumn[pass],
increment: Adam7.ColumnIncrement[pass]);
@@ -1003,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);
@@ -1043,7 +1079,8 @@ private void ProcessDefilteredScanline(
in frameControl,
scanlineSpan,
rowSpan,
- pngMetadata.TransparentColor);
+ pngMetadata.TransparentColor,
+ iccProfile);
break;
@@ -1054,7 +1091,8 @@ private void ProcessDefilteredScanline(
scanlineSpan,
rowSpan,
(uint)this.bytesPerPixel,
- (uint)this.bytesPerSample);
+ (uint)this.bytesPerSample,
+ iccProfile);
break;
@@ -1063,7 +1101,8 @@ private void ProcessDefilteredScanline(
in frameControl,
scanlineSpan,
rowSpan,
- pngMetadata.ColorTable);
+ pngMetadata.ColorTable,
+ iccProfile);
break;
@@ -1076,7 +1115,8 @@ private void ProcessDefilteredScanline(
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
- pngMetadata.TransparentColor);
+ pngMetadata.TransparentColor,
+ iccProfile);
break;
@@ -1088,7 +1128,8 @@ private void ProcessDefilteredScanline(
scanlineSpan,
rowSpan,
this.bytesPerPixel,
- this.bytesPerSample);
+ this.bytesPerSample,
+ iccProfile);
break;
}
@@ -1115,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(
@@ -1123,6 +1165,7 @@ private void ProcessInterlacedDefilteredScanline(
Span destination,
PngMetadata pngMetadata,
Span blendRowBuffer,
+ IccProfile? iccProfile,
int pixelOffset = 0,
int increment = 1)
where TPixel : unmanaged, IPixel
@@ -1157,7 +1200,8 @@ private void ProcessInterlacedDefilteredScanline(
rowSpan,
(uint)pixelOffset,
(uint)increment,
- pngMetadata.TransparentColor);
+ pngMetadata.TransparentColor,
+ iccProfile);
break;
@@ -1170,7 +1214,8 @@ private void ProcessInterlacedDefilteredScanline(
(uint)pixelOffset,
(uint)increment,
(uint)this.bytesPerPixel,
- (uint)this.bytesPerSample);
+ (uint)this.bytesPerSample,
+ iccProfile);
break;
@@ -1181,7 +1226,8 @@ private void ProcessInterlacedDefilteredScanline(
rowSpan,
(uint)pixelOffset,
(uint)increment,
- pngMetadata.ColorTable);
+ pngMetadata.ColorTable,
+ iccProfile);
break;
@@ -1196,7 +1242,8 @@ private void ProcessInterlacedDefilteredScanline(
(uint)increment,
this.bytesPerPixel,
this.bytesPerSample,
- pngMetadata.TransparentColor);
+ pngMetadata.TransparentColor,
+ iccProfile);
break;
@@ -1210,7 +1257,8 @@ private void ProcessInterlacedDefilteredScanline(
(uint)pixelOffset,
(uint)increment,
this.bytesPerPixel,
- this.bytesPerSample);
+ this.bytesPerSample,
+ iccProfile);
break;
}
@@ -2153,4 +2201,37 @@ private static bool IsXmpTextData(ReadOnlySpan keywordBytes)
private void SwapScanlineBuffers()
=> (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline);
+
+ private static void ApplyRgbaCompatibleIccProfile(Image image, IccProfile sourceProfile, IccProfile destinationProfile)
+ where TPixel : unmanaged, IPixel
+ {
+ ColorConversionOptions options = new()
+ {
+ SourceIccProfile = sourceProfile,
+ TargetIccProfile = destinationProfile,
+ MemoryAllocator = image.Configuration.MemoryAllocator,
+ };
+
+ ColorProfileConverter converter = new(options);
+
+ image.Mutate(o => o.ProcessPixelRowsAsVector4(
+ (pixelsRow, _) =>
+ {
+ using IMemoryOwner rgbBuffer = image.Configuration.MemoryAllocator.Allocate(pixelsRow.Length);
+ Span rgbPacked = rgbBuffer.Memory.Span;
+
+ Rgb.FromScaledVector4(pixelsRow, rgbPacked);
+ converter.Convert(rgbPacked, rgbPacked);
+
+ Span pixelsRowAsFloats = MemoryMarshal.Cast(pixelsRow);
+ ref float pixelsRowAsFloatsRef = ref MemoryMarshal.GetReference(pixelsRowAsFloats);
+
+ int cIdx = 0;
+ for (int x = 0; x < pixelsRow.Length; x++, cIdx += 4)
+ {
+ Unsafe.As(ref Unsafe.Add(ref pixelsRowAsFloatsRef, cIdx)) = rgbPacked[x];
+ }
+ },
+ PixelConversionModifiers.Scale));
+ }
}
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 3589a25a2d..a58101a6bd 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -206,6 +206,22 @@ 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)]
+ [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
+ {
+ 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/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++;
+ }
}
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index bc699da88e..25bec12664 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -166,6 +166,15 @@ 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 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";
+ }
+
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/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/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
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