Skip to content
44 changes: 44 additions & 0 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -323,6 +327,11 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
PngThrowHelper.ThrowNoData();
}

if (this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
{
ApplyRgbaCompatibleIccProfile(image, iccProfile, CompactSrgbV4Profile.Profile);
}

return image;
}
catch
Expand Down Expand Up @@ -2153,4 +2162,39 @@ private static bool IsXmpTextData(ReadOnlySpan<byte> keywordBytes)

private void SwapScanlineBuffers()
=> (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline);

private static void ApplyRgbaCompatibleIccProfile<TPixel>(Image<TPixel> image, IccProfile sourceProfile, IccProfile destinationProfile)
where TPixel : unmanaged, IPixel<TPixel>
{
ColorConversionOptions options = new()
{
SourceIccProfile = sourceProfile,
TargetIccProfile = destinationProfile,
};

ColorProfileConverter converter = new(options);

image.Mutate(o => o.ProcessPixelRowsAsVector4((pixelsRow, _) =>
{
using IMemoryOwner<Rgb> rgbBuffer = image.Configuration.MemoryAllocator.Allocate<Rgb>(pixelsRow.Length);
Span<Rgb> rgbPacked = rgbBuffer.Memory.Span;
ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked);

for (int x = 0; x < pixelsRow.Length; x++)
{
Unsafe.Add(ref rgbPackedRef, x) = Rgb.FromScaledVector4(pixelsRow[x]);
}

converter.Convert<Rgb, Rgb>(rgbPacked, rgbPacked);

Span<float> pixelsRowAsFloats = MemoryMarshal.Cast<Vector4, float>(pixelsRow);
ref float pixelsRowAsFloatsRef = ref MemoryMarshal.GetReference(pixelsRowAsFloats);

int cIdx = 0;
for (int x = 0; x < pixelsRow.Length; x++, cIdx += 4)
{
Unsafe.As<float, Rgb>(ref Unsafe.Add(ref pixelsRowAsFloatsRef, cIdx)) = rgbPacked[x];
}
}));
}
}
14 changes: 14 additions & 0 deletions tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,20 @@ public void Decode_WithAverageFilter<TPixel>(TestImageProvider<TPixel> 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)]
public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> 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)]
Expand Down
12 changes: 9 additions & 3 deletions tests/ImageSharp.Tests/MemoryAllocatorValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ private static void MemoryDiagnostics_MemoryReleased()
TestMemoryDiagnostics backing = LocalInstance.Value;
if (backing != null)
{
backing.TotalRemainingAllocated--;
lock (backing)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if moving the color converter inside the loop action removes this requirement.

We process in chunks so not concerned about initialization overhead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I would do that anyway so that MemoryAllocator options property can be set using the same one as the image configuration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, there is an hiden allocator in the ColorConversionOptions, yeah it should probably use the same as the image configuration (I'll change that)

Moving it inside the loop does not fix the problem

{
backing.TotalRemainingAllocated--;
}
}
}

Expand All @@ -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++;
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions tests/ImageSharp.Tests/TestImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ 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 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";
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tests/Images/Input/Png/icc-profiles/Perceptual.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tests/Images/Input/Png/icc-profiles/sRGB_Gray.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading