Browse Source

Add RGB support.

pull/3054/head
James Jackson-South 1 week ago
parent
commit
7e22a01469
  1. 8
      src/ImageSharp/Formats/DecoderOptions.cs
  2. 16
      src/ImageSharp/Formats/ImageDecoder.cs
  3. 53
      src/ImageSharp/Formats/ImageDecoderCore.cs
  4. 1
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  5. 15
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  7. 2
      tests/ImageSharp.Tests/TestImages.cs
  8. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png
  9. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png
  10. 3
      tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff
  11. 3
      tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff

8
src/ImageSharp/Formats/DecoderOptions.cs

@ -78,12 +78,12 @@ public sealed class DecoderOptions
return false;
}
if (profile.IsCanonicalSrgbMatrixTrc())
if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
{
return false;
}
if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
if (profile.IsCanonicalSrgbMatrixTrc())
{
return false;
}
@ -99,11 +99,11 @@ public sealed class DecoderOptions
return false;
}
if (this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc())
if (this.ColorProfileHandling == ColorProfileHandling.Convert)
{
return true;
}
return this.ColorProfileHandling == ColorProfileHandling.Convert;
return this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc();
}
}

16
src/ImageSharp/Formats/ImageDecoder.cs

@ -328,6 +328,14 @@ public abstract class ImageDecoder : IImageDecoder
{
image.Metadata.IccProfile = null;
}
foreach (ImageFrame frame in image.Frames)
{
if (options.CanRemoveIccProfile(frame.Metadata.IccProfile))
{
frame.Metadata.IccProfile = null;
}
}
}
private static void HandleIccProfile(DecoderOptions options, ImageInfo image)
@ -336,5 +344,13 @@ public abstract class ImageDecoder : IImageDecoder
{
image.Metadata.IccProfile = null;
}
foreach (ImageFrameMetadata frame in image.FrameMetadataCollection)
{
if (options.CanRemoveIccProfile(frame.IccProfile))
{
frame.IccProfile = null;
}
}
}
}

53
src/ImageSharp/Formats/ImageDecoderCore.cs

@ -5,6 +5,7 @@ using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@ -164,4 +165,56 @@ internal abstract class ImageDecoderCore
converter.Convert(image);
return true;
}
/// <summary>
/// Converts the ICC color profile of the specified image frame to the compact sRGB v4 profile if a source profile is
/// available.
/// </summary>
/// <remarks>
/// This method should only be used by decoders that gurantee that the encoded image data is in a color space
/// compatible with sRGB (e.g. standard RGB, Adobe RGB, ProPhoto RGB).
/// <br/>
/// If the image does not have a valid ICC profile for color conversion, no changes are made.
/// This operation may affect the color appearance of the image to ensure consistency with the sRGB color
/// space.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame whose ICC profile will be converted to the compact sRGB v4 profile.</param>
/// <returns>
/// <see langword="true"/> if the conversion was performed; otherwise, <see langword="false"/>.
/// </returns>
protected bool TryConvertIccProfile<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
if (!this.Options.TryGetIccProfileForColorConversion(frame.Metadata.IccProfile, out IccProfile? profile))
{
return false;
}
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
MemoryAllocator = frame.Configuration.MemoryAllocator,
};
ColorProfileConverter converter = new(options);
ImageMetadata metadata = new()
{
IccProfile = frame.Metadata.IccProfile
};
IMemoryGroup<TPixel> m = frame.PixelBuffer.MemoryGroup;
// Safe: ToArray only materializes the Memory<TPixel> segment list, not the underlying pixel buffers,
// and Wrap(Memory<T>[]) creates a Consumed MemoryGroup that does not own the buffers (Dispose just
// invalidates the view). This means no pixel data is cloned and disposing the temporary image will
// not dispose or leak the frame's pixel buffer.
MemoryGroup<TPixel> memorySource = MemoryGroup<TPixel>.Wrap(m.ToArray());
using Image<TPixel> image = new(frame.Configuration, memorySource, frame.Width, frame.Height, metadata);
converter.Convert(image);
return true;
}
}

1
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -429,7 +429,6 @@ internal static class TiffColorDecoderFactory<TPixel>
}
}
#pragma warning disable IDE0060 // Remove unused parameter
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
ImageFrameMetadata metadata,
DecoderOptions options,

15
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -52,11 +52,6 @@ internal class TiffDecoderCore : ImageDecoderCore
/// </summary>
private ByteOrder byteOrder;
/// <summary>
/// Indicating whether is BigTiff format.
/// </summary>
private bool isBigTiff;
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
@ -167,7 +162,6 @@ internal class TiffDecoderCore : ImageDecoderCore
IList<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
Size? size = null;
uint frameCount = 0;
@ -273,6 +267,15 @@ internal class TiffDecoderCore : ImageDecoderCore
this.DecodeImageWithStrips(tags, frame, width, height, cancellationToken);
}
// Only RGB-compatible color types can be converted here because the TPixel-based ICC profile conversion
// expects RGB-like pixel data; other photometric interpretations (YCbCr, CMYK, Lab, etc.) would require
// dedicated transforms. We do this once at the frame level to avoid duplicating conversion logic
// across all color decoders and to keep their decode paths focused on raw pixel unpacking.
if (this.ColorType is >= TiffColorType.PaletteColor and <= TiffColorType.Rgba32323232Planar)
{
_ = this.TryConvertIccProfile(frame);
}
return frame;
}

4
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -23,7 +23,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public static readonly string[] MultiframeTestImages = Multiframes;
[Theory]
// [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
[WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
[WithFile(Cmyk64BitDeflate, PixelTypes.Rgba32)]
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder.Instance));
@ -355,6 +355,8 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Icc.PerceptualCmyk, PixelTypes.Rgba32)]
[WithFile(Icc.PerceptualCieLab, PixelTypes.Rgba32)]
[WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)]
[WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)]
public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{

2
tests/ImageSharp.Tests/TestImages.cs

@ -1199,6 +1199,8 @@ public static class TestImages
{
public const string PerceptualCmyk = "Tiff/icc-profiles/Perceptual_CMYK.tiff";
public const string PerceptualCieLab = "Tiff/icc-profiles/Perceptual_CIELAB.tiff";
public const string PerceptualRgb8 = "Tiff/icc-profiles/Perceptual_RGB8.tiff";
public const string PerceptualRgb16 = "Tiff/icc-profiles/Perceptual_RGB16.tiff";
}
}

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c
size 386

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c
size 386

3
tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3b0e89ac971c23c12c9edff6f9e9dd066fe99f36e28e89972343d50562dd7dbd
size 65260

3
tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18b0e5ee58c9f53bf5414fc8244e98d65aa0bc6c3397e201399baff0cd378b23
size 35236
Loading…
Cancel
Save