diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
index a2dd5d9ced..4d94f583ab 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieLabCieLab
+///
+/// Allows conversion between two color profiles based on the CIE Lab color space.
+///
+public static class ColorProfileConverterExtensionsCieLabCieLab
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -34,6 +50,20 @@ internal static class ColorProfileConverterExtensionsCieLabCieLab
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
index 096622564c..1de4510bc9 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieLabCieXyz
+///
+/// Allows conversion between two color profiles based on the CIE Lab and CIE XYZ color spaces.
+///
+public static class ColorProfileConverterExtensionsCieLabCieXyz
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -33,6 +49,20 @@ internal static class ColorProfileConverterExtensionsCieLabCieXyz
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
index 51be13799c..4f0d470806 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieLabRgb
+///
+/// Allows conversion between two color profiles based on the CIE Lab and RGB color spaces.
+///
+public static class ColorProfileConverterExtensionsCieLabRgb
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -34,6 +50,20 @@ internal static class ColorProfileConverterExtensionsCieLabRgb
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
index 3bab4e7b16..3bb1b2d4f8 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieXyzCieLab
+///
+/// Allows conversion between two color profiles based on the CIE XYZ and CIE Lab color spaces.
+///
+public static class ColorProfileConverterExtensionsCieXyzCieLab
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -33,6 +49,20 @@ internal static class ColorProfileConverterExtensionsCieXyzCieLab
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
index 5188511476..dabca45793 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieXyzCieXyz
+///
+/// Allows conversion between two color profiles based on the CIE XYZ color space.
+///
+public static class ColorProfileConverterExtensionsCieXyzCieXyz
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -30,6 +46,20 @@ internal static class ColorProfileConverterExtensionsCieXyzCieXyz
return TTo.FromProfileConnectingSpace(options, in pcsFrom);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
index c56bf214b9..1803c0839c 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsCieXyzRgb
+///
+/// Allows conversion between two color profiles based on the CIE XYZ and RGB color spaces.
+///
+public static class ColorProfileConverterExtensionsCieXyzRgb
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -33,6 +49,20 @@ internal static class ColorProfileConverterExtensionsCieXyzRgb
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
index c33f40001a..3ddbf93b58 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
@@ -60,8 +60,8 @@ internal static class ColorProfileConverterExtensionsIcc
ColorProfileConverter pcsConverter = new(new ColorConversionOptions
{
MemoryAllocator = converter.Options.MemoryAllocator,
- SourceWhitePoint = new CieXyz(converter.Options.SourceIccProfile.Header.PcsIlluminant),
- TargetWhitePoint = new CieXyz(converter.Options.TargetIccProfile.Header.PcsIlluminant),
+ SourceWhitePoint = KnownIlluminants.D50Icc,
+ TargetWhitePoint = KnownIlluminants.D50Icc
});
// Normalize the source, then convert to the PCS space.
@@ -104,8 +104,8 @@ internal static class ColorProfileConverterExtensionsIcc
ColorProfileConverter pcsConverter = new(new ColorConversionOptions
{
MemoryAllocator = converter.Options.MemoryAllocator,
- SourceWhitePoint = new CieXyz(converter.Options.SourceIccProfile.Header.PcsIlluminant),
- TargetWhitePoint = new CieXyz(converter.Options.TargetIccProfile.Header.PcsIlluminant),
+ SourceWhitePoint = KnownIlluminants.D50Icc,
+ TargetWhitePoint = KnownIlluminants.D50Icc
});
using IMemoryOwner pcsBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
index badbcc6831..c2ed9a5918 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsRgbCieLab
+///
+/// Allows conversion between two color profiles based on the RGB and CIE Lab color spaces.
+///
+public static class ColorProfileConverterExtensionsRgbCieLab
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -34,6 +50,20 @@ internal static class ColorProfileConverterExtensionsRgbCieLab
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
index cd7d5e4d65..9cf7ec70d9 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsRgbCieXyz
+///
+/// Allows conversion between two color profiles based on the RGB and CIE XYZ color spaces.
+///
+public static class ColorProfileConverterExtensionsRgbCieXyz
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -33,6 +49,20 @@ internal static class ColorProfileConverterExtensionsRgbCieXyz
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
index 2a4b64b1ca..34f3f7f191 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
@@ -6,8 +6,24 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
-internal static class ColorProfileConverterExtensionsRgbRgb
+///
+/// Allows conversion between two color profiles based on the RGB color space.
+///
+public static class ColorProfileConverterExtensionsRgbRgb
{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
@@ -34,6 +50,20 @@ internal static class ColorProfileConverterExtensionsRgbRgb
return TTo.FromProfileConnectingSpace(options, in pcsTo);
}
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
diff --git a/src/ImageSharp/ColorProfiles/KnownIlluminants.cs b/src/ImageSharp/ColorProfiles/KnownIlluminants.cs
index b9236497fe..20ba445ecc 100644
--- a/src/ImageSharp/ColorProfiles/KnownIlluminants.cs
+++ b/src/ImageSharp/ColorProfiles/KnownIlluminants.cs
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
///
///
/// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+/// and https://color.org/specification/ICC.1-2022-05.pdf
///
/// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant
///
@@ -30,10 +31,15 @@ public static class KnownIlluminants
public static CieXyz C { get; } = new(0.98074F, 1F, 1.18232F);
///
- /// Gets the Horizon Light. ICC profile PCS illuminant.
+ /// Gets the Horizon Light.
///
public static CieXyz D50 { get; } = new(0.96422F, 1F, 0.82521F);
+ ///
+ /// Gets the D50 illuminant used in the ICC profile specification.
+ ///
+ public static CieXyz D50Icc { get; } = new(0.9642F, 1F, 0.8249F);
+
///
/// Gets the Mid-morning / Mid-afternoon Daylight illuminant.
///
diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs
index 2511cffdbb..bb6c2a2825 100644
--- a/src/ImageSharp/Formats/DecoderOptions.cs
+++ b/src/ImageSharp/Formats/DecoderOptions.cs
@@ -78,7 +78,7 @@ public sealed class DecoderOptions
return false;
}
- if (IccProfileHeader.IsLikelySrgb(profile.Header))
+ if (profile.IsCanonicalSrgbMatrixTrc())
{
return false;
}
@@ -99,7 +99,7 @@ public sealed class DecoderOptions
return false;
}
- if (this.ColorProfileHandling == ColorProfileHandling.Compact && IccProfileHeader.IsLikelySrgb(profile.Header))
+ if (this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc())
{
return true;
}
diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
index 701b9af053..7e0b56beb3 100644
--- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
@@ -32,7 +32,7 @@ public class PngFrameMetadata : IFormatFrameMetadata
///
/// Gets or sets the frame delay for animated images.
- /// If not 0, when utilized in Png animation, this field specifies the number of hundredths (1/100) of a second to
+ /// If not 0, when utilized in Png animation, this field specifies the number of seconds to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
///
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
index 03cd639ad3..86b5c19d21 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
@@ -28,20 +28,20 @@ internal abstract class TiffBaseDecompressor : TiffBaseCompression
/// Decompresses image data into the supplied buffer.
///
/// The to read image data from.
- /// The strip offset of stream.
- /// The number of bytes to read from the input stream.
+ /// The data offset within the stream.
+ /// The number of bytes to read from the input stream.
/// The height of the strip.
/// The output buffer for uncompressed data.
/// The token to monitor cancellation.
- public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken)
+ public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span buffer, CancellationToken cancellationToken)
{
- DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
- DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
+ DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset));
+ DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count));
- stream.Seek((long)stripOffset, SeekOrigin.Begin);
- this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken);
+ stream.Seek((long)offset, SeekOrigin.Begin);
+ this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken);
- if ((long)stripOffset + (long)stripByteCount < stream.Position)
+ if ((long)offset + (long)count < stream.Position)
{
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index fbff352970..8a4a27946f 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -5,6 +5,7 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
+using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO;
@@ -441,8 +442,14 @@ internal class TiffDecoderCore : ImageDecoderCore
{
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
- int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
- stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize);
+ ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
+
+ if (uncompressedStripSize > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ stripBuffers[stripIndex] = this.memoryAllocator.Allocate((int)uncompressedStripSize);
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata);
@@ -507,15 +514,83 @@ internal class TiffDecoderCore : ImageDecoderCore
rowsPerStrip = height;
}
- int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
+ ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
int bitsPerPixel = this.BitsPerPixel;
- using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean);
- Span stripBufferSpan = stripBuffer.GetSpan();
- Buffer2D pixels = frame.PixelBuffer;
-
using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata);
TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder();
+ Buffer2D pixels = frame.PixelBuffer;
+
+ // There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue.
+ // We can read them, but we cannot allocate a buffer that large to hold the uncompressed data.
+ // In this scenario we fall back to reading and decoding one row at a time.
+ //
+ // The NoneTiffCompression decompressor can be used to read individual rows since we have
+ // a guarantee that each row required the same number of bytes.
+ if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue)
+ {
+ ulong bytesPerRowU = this.CalculateStripBufferSize(width, 1);
+
+ // This should never happen, but we check just to be sure.
+ if (bytesPerRowU > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ int bytesPerRow = (int)bytesPerRowU;
+ using IMemoryOwner rowBufferOwner = this.memoryAllocator.Allocate(bytesPerRow, AllocationOptions.Clean);
+ Span rowBuffer = rowBufferOwner.GetSpan();
+ for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
+ ? rowsPerStrip
+ : height % rowsPerStrip;
+
+ int top = rowsPerStrip * stripIndex;
+ if (top + stripHeight > height)
+ {
+ break;
+ }
+
+ ulong baseOffset = stripOffsets[stripIndex];
+ ulong available = stripByteCounts[stripIndex];
+ ulong required = (ulong)bytesPerRow * (ulong)stripHeight;
+ if (available < required)
+ {
+ break;
+ }
+
+ for (int r = 0; r < stripHeight; r++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow);
+
+ // Use the NoneTiffCompression decompressor to read exactly one row.
+ none.Decompress(
+ this.inputStream,
+ rowOffset,
+ (ulong)bytesPerRow,
+ 1,
+ rowBuffer,
+ cancellationToken);
+
+ colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1);
+ }
+ }
+
+ return;
+ }
+
+ if (uncompressedStripSize > int.MaxValue)
+ {
+ TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
+ }
+
+ using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate((int)uncompressedStripSize, AllocationOptions.Clean);
+ Span stripBufferSpan = stripBuffer.GetSpan();
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
@@ -808,7 +883,7 @@ internal class TiffDecoderCore : ImageDecoderCore
/// The height for the desired pixel buffer.
/// The index of the plane for planar image configuration (or zero for chunky).
/// The size (in bytes) of the required pixel buffer.
- private int CalculateStripBufferSize(int width, int height, int plane = -1)
+ private ulong CalculateStripBufferSize(int width, int height, int plane = -1)
{
DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
@@ -841,8 +916,8 @@ internal class TiffDecoderCore : ImageDecoderCore
}
}
- int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
- return bytesPerRow * height;
+ ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8;
+ return bytesPerRow * (ulong)height;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index 173d9436dd..86489cd363 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -32,6 +32,11 @@ internal class WebpAnimationDecoder : IDisposable
///
private readonly uint maxFrames;
+ ///
+ /// Whether to skip metadata.
+ ///
+ private readonly bool skipMetadata;
+
///
/// The area to restore.
///
@@ -57,19 +62,97 @@ internal class WebpAnimationDecoder : IDisposable
///
private readonly BackgroundColorHandling backgroundColorHandling;
+ ///
+ /// How to handle validation of errors in different segments of encoded image files.
+ ///
+ private readonly SegmentIntegrityHandling segmentIntegrityHandling;
+
///
/// Initializes a new instance of the class.
///
/// The memory allocator.
/// The global configuration.
/// The maximum number of frames to decode. Inclusive.
+ /// Whether to skip metadata.
/// The flag to decide how to handle the background color in the Animation Chunk.
- public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames, BackgroundColorHandling backgroundColorHandling)
+ /// How to handle validation of errors in different segments of encoded image files.
+ public WebpAnimationDecoder(
+ MemoryAllocator memoryAllocator,
+ Configuration configuration,
+ uint maxFrames,
+ bool skipMetadata,
+ BackgroundColorHandling backgroundColorHandling,
+ SegmentIntegrityHandling segmentIntegrityHandling)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.maxFrames = maxFrames;
+ this.skipMetadata = skipMetadata;
this.backgroundColorHandling = backgroundColorHandling;
+ this.segmentIntegrityHandling = segmentIntegrityHandling;
+ }
+
+ ///
+ /// Reads the animated webp image information from the specified stream.
+ ///
+ /// The stream, where the image should be decoded from. Cannot be null.
+ /// The webp features.
+ /// The width of the image.
+ /// The height of the image.
+ /// The size of the image data in bytes.
+ public ImageInfo Identify(
+ BufferedReadStream stream,
+ WebpFeatures features,
+ uint width,
+ uint height,
+ uint completeDataSize)
+ {
+ List framesMetadata = [];
+ this.metadata = new ImageMetadata();
+ this.webpMetadata = this.metadata.GetWebpMetadata();
+ this.webpMetadata.RepeatCount = features.AnimationLoopCount;
+
+ Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
+ ? Color.Transparent
+ : features.AnimationBackgroundColor!.Value;
+
+ this.webpMetadata.BackgroundColor = backgroundColor;
+
+ Span buffer = stackalloc byte[4];
+ uint frameCount = 0;
+ int remainingBytes = (int)completeDataSize;
+ while (remainingBytes > 0)
+ {
+ WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
+ remainingBytes -= 4;
+ switch (chunkType)
+ {
+ case WebpChunkType.FrameData:
+
+ ImageFrameMetadata frameMetadata = new();
+ uint dataSize = ReadFrameInfo(stream, ref frameMetadata);
+ framesMetadata.Add(frameMetadata);
+
+ remainingBytes -= (int)dataSize;
+ break;
+ case WebpChunkType.Xmp:
+ case WebpChunkType.Exif:
+ WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, this.metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
+ break;
+ default:
+
+ // Specification explicitly states to ignore unknown chunks.
+ // We do not support writing these chunks at present.
+ break;
+ }
+
+ if (stream.Position == stream.Length || ++frameCount == this.maxFrames)
+ {
+ break;
+ }
+ }
+
+ return new ImageInfo(new Size((int)width, (int)height), this.metadata, framesMetadata);
}
///
@@ -128,10 +211,12 @@ internal class WebpAnimationDecoder : IDisposable
break;
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
- WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, buffer);
+ WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
break;
default:
- WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
+
+ // Specification explicitly states to ignore unknown chunks.
+ // We do not support writing these chunks at present.
break;
}
@@ -144,6 +229,26 @@ internal class WebpAnimationDecoder : IDisposable
return image!;
}
+ ///
+ /// Reads frame information from the specified stream and updates the provided frame metadata.
+ ///
+ /// The stream from which to read the frame information. Must support reading and seeking.
+ /// A reference to the structure that will be updated with the parsed frame metadata.
+ /// The number of bytes read from the stream while parsing the frame information.
+ private static uint ReadFrameInfo(BufferedReadStream stream, ref ImageFrameMetadata frameMetadata)
+ {
+ WebpFrameData frameData = WebpFrameData.Parse(stream);
+ SetFrameMetadata(frameMetadata, frameData);
+
+ // Size of the frame header chunk.
+ const int chunkHeaderSize = 16;
+
+ uint remaining = frameData.DataSize - chunkHeaderSize;
+ stream.Skip((int)remaining);
+
+ return remaining;
+ }
+
///
/// Reads an individual webp frame.
///
@@ -155,6 +260,7 @@ internal class WebpAnimationDecoder : IDisposable
/// The width of the image.
/// The height of the image.
/// The default background color of the canvas in.
+ /// The number of bytes read from the stream while parsing the frame information.
private uint ReadFrame(
BufferedReadStream stream,
ref Image? image,
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index 8df159dbff..dc95ca0443 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
+using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
@@ -120,6 +121,7 @@ internal static class WebpChunkParsingUtils
return new WebpImageInfo
{
+ DataSize = dataSize,
Width = width,
Height = height,
XScale = xScale,
@@ -178,6 +180,7 @@ internal static class WebpChunkParsingUtils
return new WebpImageInfo
{
+ DataSize = imageDataSize,
Width = width,
Height = height,
BitsPerPixel = features.Alpha ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24,
@@ -333,7 +336,13 @@ internal static class WebpChunkParsingUtils
/// If there are more such chunks, readers MAY ignore all except the first one.
/// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
///
- public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, Span buffer)
+ public static void ParseOptionalChunks(
+ BufferedReadStream stream,
+ WebpChunkType chunkType,
+ ImageMetadata metadata,
+ bool ignoreMetaData,
+ SegmentIntegrityHandling segmentIntegrityHandling,
+ Span buffer)
{
long streamLength = stream.Length;
while (stream.Position < streamLength)
@@ -353,12 +362,30 @@ internal static class WebpChunkParsingUtils
bytesRead = stream.Read(exifData, 0, (int)chunkLength);
if (bytesRead != chunkLength)
{
- WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
+ if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
+ {
+ WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
+ }
+
+ return;
}
- if (metadata.ExifProfile != null)
+ if (metadata.ExifProfile == null)
{
- metadata.ExifProfile = new ExifProfile(exifData);
+ ExifProfile exifProfile = new(exifData);
+
+ // Set the resolution from the metadata.
+ double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
+ double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);
+
+ if (horizontalValue > 0 && verticalValue > 0)
+ {
+ metadata.HorizontalResolution = horizontalValue;
+ metadata.VerticalResolution = verticalValue;
+ metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
+ }
+
+ metadata.ExifProfile = exifProfile;
}
break;
@@ -367,14 +394,16 @@ internal static class WebpChunkParsingUtils
bytesRead = stream.Read(xmpData, 0, (int)chunkLength);
if (bytesRead != chunkLength)
{
- WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
- }
+ if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
+ {
+ WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
+ }
- if (metadata.XmpProfile != null)
- {
- metadata.XmpProfile = new XmpProfile(xmpData);
+ return;
}
+ metadata.XmpProfile ??= new XmpProfile(xmpData);
+
break;
default:
stream.Skip((int)chunkLength);
@@ -383,6 +412,16 @@ internal static class WebpChunkParsingUtils
}
}
+ private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag tag)
+ {
+ if (exifProfile.TryGetValue(tag, out IExifValue? resolution))
+ {
+ return resolution.Value.ToDouble();
+ }
+
+ return 0;
+ }
+
///
/// Determines if the chunk type is an optional VP8X chunk.
///
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 51379a32ae..0e9888adb2 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -56,6 +56,8 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
///
private readonly BackgroundColorHandling backgroundColorHandling;
+ private readonly SegmentIntegrityHandling segmentIntegrityHandling;
+
///
/// Initializes a new instance of the class.
///
@@ -64,6 +66,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
: base(options.GeneralOptions)
{
this.backgroundColorHandling = options.BackgroundColorHandling;
+ this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.maxFrames = options.GeneralOptions.MaxFrames;
@@ -89,7 +92,10 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
this.memoryAllocator,
this.configuration,
this.maxFrames,
- this.backgroundColorHandling);
+ this.skipMetadata,
+ this.backgroundColorHandling,
+ this.segmentIntegrityHandling);
+
return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
@@ -101,6 +107,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
this.webImageInfo.Vp8LBitReader,
this.memoryAllocator,
this.configuration);
+
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
@@ -109,6 +116,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
this.webImageInfo.Vp8BitReader,
this.memoryAllocator,
this.configuration);
+
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
}
@@ -131,11 +139,29 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
///
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
- ReadImageHeader(stream, stackalloc byte[4]);
-
+ uint fileSize = ReadImageHeader(stream, stackalloc byte[4]);
ImageMetadata metadata = new();
+
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{
+ if (this.webImageInfo.Features is { Animation: true })
+ {
+ using WebpAnimationDecoder animationDecoder = new(
+ this.memoryAllocator,
+ this.configuration,
+ this.maxFrames,
+ this.skipMetadata,
+ this.backgroundColorHandling,
+ this.segmentIntegrityHandling);
+
+ return animationDecoder.Identify(
+ stream,
+ this.webImageInfo.Features,
+ this.webImageInfo.Width,
+ this.webImageInfo.Height,
+ fileSize);
+ }
+
return new ImageInfo(
new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height),
metadata);
@@ -211,6 +237,8 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
}
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{
+ // ANIM chunks appear before EXIF and XMP chunks.
+ // Return after parsing an ANIM chunk - The animated decoder will handle the rest.
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer);
if (isAnimationChunk)
{
@@ -273,7 +301,9 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
this.ReadAlphaData(stream, features, ignoreAlpha, buffer);
break;
default:
- WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
+
+ // Specification explicitly states to ignore unknown chunks.
+ // We do not support writing these chunks at present.
break;
}
@@ -335,7 +365,11 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
- // Ignore invalid chunk.
+ if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
+ {
+ WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
+ }
+
return;
}
@@ -385,7 +419,11 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
- // Ignore invalid chunk.
+ if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
+ {
+ WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
+ }
+
return;
}
diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
index 3428ce199a..e0993145f0 100644
--- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
+++ b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
@@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Webp;
internal class WebpImageInfo : IDisposable
{
+ ///
+ /// Gets or sets the size of the encoded image data in bytes.
+ ///
+ public uint DataSize { get; set; }
+
///
/// Gets or sets the bitmap width in pixels.
///
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs
new file mode 100644
index 0000000000..bfa4ab9bdb
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs
@@ -0,0 +1,346 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using SixLabors.ImageSharp.ColorProfiles;
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+///
+/// Provides logic for identifying canonical IEC 61966-2-1 (sRGB) matrix-TRC ICC profiles,
+/// distinguishing them from appearance or device-specific variants.
+///
+public sealed partial class IccProfile
+{
+ // sRGB v2 Preference
+ private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE);
+
+ // sRGB v4 Preference
+ private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D);
+
+ ///
+ /// Detects canonical sRGB matrix+TRC profiles quickly and safely.
+ /// Rules:
+ /// 1) Accept known IEC sRGB v2 and v4 by profile ID.
+ /// 2) Require RGB, PCS=XYZ, ICC v2 or v4, and no A2B*/B2A* LUTs.
+ /// 3) Require rTRC, gTRC, bTRC to exist and be identical by parameters or sampled shape.
+ /// 4) Accept if rXYZ/gXYZ/bXYZ already match the D50-adapted sRGB colorants within tolerance.
+ /// 5) If white point ≈ D65, adapt only the colorant columns to D50 using Bradford
+ /// via and then compare.
+ /// This rejects channel-swapped and appearance profiles while allowing real sRGB.
+ ///
+ ///
+ /// Reference D50-adapted sRGB colorants from Bruce Lindbloom:
+ ///
+ /// R=(0.4360747, 0.2225045, 0.0139322)
+ /// G=(0.3850649, 0.7168786, 0.0971045)
+ /// B=(0.1430804, 0.0606169, 0.7141733)
+ ///
+ internal bool IsCanonicalSrgbMatrixTrc()
+ {
+ IccProfileHeader h = this.Header;
+
+ // Fast path for known IEC sRGB profile IDs
+ if (h.Id == StandardRgbV2 || h.Id == StandardRgbV4)
+ {
+ return true;
+ }
+
+ // Header gating to avoid parsing work for obvious non-matches
+ if (h.FileSignature != "acsp")
+ {
+ return false;
+ }
+
+ if (h.DataColorSpace != IccColorSpaceType.Rgb)
+ {
+ return false;
+ }
+
+ if (h.ProfileConnectionSpace != IccColorSpaceType.CieXyz)
+ {
+ return false;
+ }
+
+ if (h.Version.Major is not 2 and not 4)
+ {
+ return false;
+ }
+
+ this.InitializeEntries();
+ IccTagDataEntry[] entries = this.entries;
+
+ // Reject device/display LUT profiles. We only accept matrix+TRC encodings.
+ if (Has(entries, IccProfileTag.AToB0) || Has(entries, IccProfileTag.AToB1) || Has(entries, IccProfileTag.AToB2) ||
+ Has(entries, IccProfileTag.BToA0) || Has(entries, IccProfileTag.BToA1) || Has(entries, IccProfileTag.BToA2))
+ {
+ return false;
+ }
+
+ // Required matrix+TRC tags
+ if (!TryGetXyz(entries, IccProfileTag.MediaWhitePoint, out Vector3 wtpt))
+ {
+ return false;
+ }
+
+ if (!TryGetXyz(entries, IccProfileTag.RedMatrixColumn, out Vector3 rXYZ))
+ {
+ return false;
+ }
+
+ if (!TryGetXyz(entries, IccProfileTag.GreenMatrixColumn, out Vector3 gXYZ))
+ {
+ return false;
+ }
+
+ if (!TryGetXyz(entries, IccProfileTag.BlueMatrixColumn, out Vector3 bXYZ))
+ {
+ return false;
+ }
+
+ // TRCs must exist and be identical across channels. This filters many trick profiles.
+ if (!TryGetTrc(entries, IccProfileTag.RedTrc, out Trc tR))
+ {
+ return false;
+ }
+
+ if (!TryGetTrc(entries, IccProfileTag.GreenTrc, out Trc tG))
+ {
+ return false;
+ }
+
+ if (!TryGetTrc(entries, IccProfileTag.BlueTrc, out Trc tB))
+ {
+ return false;
+ }
+
+ if (!tR.Equals(tG) || !tR.Equals(tB))
+ {
+ return false;
+ }
+
+ // D50-adapted sRGB colorants (compare as columns: r,g,b), tight epsilon
+ const float eps = 2e-3F;
+ Vector3 rRef = new(0.4360747F, 0.2225045F, 0.0139322F);
+ Vector3 gRef = new(0.3850649F, 0.7168786F, 0.0971045F);
+ Vector3 bRef = new(0.1430804F, 0.0606169F, 0.7141733F);
+
+ // First, accept if the stored colorants are already the D50 sRGB primaries.
+ // Many v2 sRGB profiles store D50-adapted colorants while declaring wtpt≈D65.
+ if (Near(rXYZ, rRef, eps) && Near(gXYZ, gRef, eps) && Near(bXYZ, bRef, eps))
+ {
+ return true;
+ }
+
+ // If the profile declares a D65 white, adapt the colorant columns to D50 and compare again.
+ // We never adapt when they already match, to avoid compounding rounding.
+ if (Near(wtpt, KnownIlluminants.D65.AsVector3Unsafe(), 2e-3F))
+ {
+ CieXyz fromWp = new(wtpt); // Declared white
+ CieXyz toWp = KnownIlluminants.D50; // PCS white
+ Matrix4x4 matrix = KnownChromaticAdaptationMatrices.Bradford;
+
+ rXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(rXYZ), (fromWp, toWp), matrix).AsVector3Unsafe();
+ gXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(gXYZ), (fromWp, toWp), matrix).AsVector3Unsafe();
+ bXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(bXYZ), (fromWp, toWp), matrix).AsVector3Unsafe();
+ }
+
+ // Require identity mapping of primaries, no permutation
+ if (!Near(rXYZ, rRef, eps) || !Near(gXYZ, gRef, eps) || !Near(bXYZ, bRef, eps))
+ {
+ return false;
+ }
+
+ return true;
+
+ static bool Has(ReadOnlySpan span, IccProfileTag tag)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (span[i].TagSignature == tag)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static bool TryGetXyz(ReadOnlySpan span, IccProfileTag tag, out Vector3 xyz)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ IccTagDataEntry e = span[i];
+ if (e.TagSignature != tag)
+ {
+ continue;
+ }
+
+ if (e is IccXyzTagDataEntry x && x.Data is { Length: >= 1 })
+ {
+ xyz = x.Data[0];
+ return true;
+ }
+
+ break;
+ }
+
+ xyz = default;
+ return false;
+ }
+
+ static bool TryGetTrc(ReadOnlySpan span, IccProfileTag tag, out Trc trc)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ IccTagDataEntry e = span[i];
+ if (e.TagSignature != tag)
+ {
+ continue;
+ }
+
+ if (e is IccParametricCurveTagDataEntry p)
+ {
+ trc = Trc.FromParametric(p.Curve);
+ return true;
+ }
+
+ if (e is IccCurveTagDataEntry c)
+ {
+ trc = Trc.FromCurveLut(c.CurveData);
+ return true;
+ }
+
+ break;
+ }
+
+ trc = default;
+ return false;
+ }
+
+ static bool Near(in Vector3 a, in Vector3 b, float tol)
+ => MathF.Abs(a.X - b.X) <= tol &&
+ MathF.Abs(a.Y - b.Y) <= tol &&
+ MathF.Abs(a.Z - b.Z) <= tol;
+ }
+
+ ///
+ /// Compact, allocation-free descriptor of a TRC for equality and optional sRGB check.
+ ///
+ private readonly struct Trc : IEquatable
+ {
+ private readonly byte kind; // 0 = none, 1 = parametric, 2 = sampled
+ private readonly float g; // parametric payload or downsampled hash
+ private readonly float a;
+ private readonly float b;
+ private readonly float c;
+ private readonly float d;
+ private readonly float e;
+ private readonly float f;
+ private readonly int n; // for sampled, length or a small signature
+
+ private Trc(byte kind, float g, float a, float b, float c, float d, float e, float f, int n)
+ {
+ this.kind = kind;
+ this.g = g;
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ this.n = n;
+ }
+
+ public static Trc FromParametric(IccParametricCurve c)
+
+ // Normalize by curve type to a stable tuple
+ // The types map to piecewise forms, but equality across channels is the key requirement here
+ => new(1, c.G, c.A, c.B, c.C, c.D, c.E, c.F, (int)c.Type);
+
+ public static Trc FromCurveLut(float[] data)
+ {
+ // Exact sequence equality is enforced by the calling code using the same Trc construction
+ // Record a short signature to compare cheaply, avoid copying
+ if (data == null)
+ {
+ return default;
+ }
+
+ int n = data.Length;
+ if (n == 0)
+ {
+ return default;
+ }
+
+ // Downsample a few points to a robust fingerprint
+ // Use fixed indices to avoid allocations
+ float s0 = data[0];
+ float s1 = data[n >> 2];
+ float s2 = data[n >> 1];
+ float s3 = data[(n * 3) >> 2];
+ float s4 = data[n - 1];
+
+ return new Trc(
+ 2,
+ s0,
+ s1,
+ s2,
+ s3,
+ s4,
+ 0F,
+ 0F,
+ n);
+ }
+
+ public override bool Equals(object? obj) => obj is Trc trc && this.Equals(trc);
+
+ public bool Equals(Trc other)
+ {
+ if (this.kind != other.kind)
+ {
+ return false;
+ }
+
+ if (this.kind == 0)
+ {
+ return false;
+ }
+
+ if (this.kind == 1)
+ {
+ // parametric: exact parameter match and type match
+ return this.n == other.n &&
+ this.g == other.g && this.a == other.a &&
+ this.b == other.b && this.c == other.c &&
+ this.d == other.d && this.e == other.e && this.f == other.f;
+ }
+
+ // sampled: same length and same 5-point fingerprint
+ return this.n == other.n &&
+ this.g == other.g && this.a == other.a &&
+ this.b == other.b && this.c == other.c && this.d == other.d;
+ }
+
+ // Optional stricter sRGB check if you need it later
+ public bool IsSrgbLike()
+ {
+ if (this.kind == 1)
+ {
+ // Accept common sRGB parametric encodings where type and parameters match
+ // IEC 61966-2-1 maps to Type4 or Type5 forms in practice
+ // Tighten only if you must exclude gamma~2.2 profiles that share primaries
+ return true;
+ }
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ int a = HashCode.Combine(this.kind, this.g, this.a, this.b, this.c, this.d, this.e);
+ int b = HashCode.Combine(this.f, this.n);
+ return HashCode.Combine(a, b);
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
index 392ccb3062..05be3eb5dd 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
/// Represents an ICC profile
///
-public sealed class IccProfile : IDeepCloneable
+public sealed partial class IccProfile : IDeepCloneable
{
///
/// The byte array to read the ICC profile from
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs
index b50885d025..959668aaf9 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs
@@ -11,17 +11,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
public sealed class IccProfileHeader
{
- private static readonly Vector3 TruncatedD50 = new(0.9642029F, 1F, 0.8249054F);
-
- // sRGB v2 Preference
- private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE);
-
- // sRGB v4 Preference
- private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D);
-
- // sRGB v4 Appearance
- private static readonly IccProfileId StandardRgbV4A = new(0xDF1132A1, 0x746E97B0, 0xAD85719, 0xBE711E08);
-
///
/// Gets or sets the profile size in bytes (will be ignored when writing a profile).
///
@@ -108,31 +97,4 @@ public sealed class IccProfileHeader
/// Gets or sets the profile ID (hash).
///
public IccProfileId Id { get; set; }
-
- internal static bool IsLikelySrgb(IccProfileHeader header)
- {
- // Reject known perceptual-appearance profile
- // This profile employs perceptual rendering intents to maintain color appearance across different
- // devices and media, which can lead to variations from standard sRGB representations.
- if (header.Id == StandardRgbV4A)
- {
- return false;
- }
-
- // Accept known sRGB profile IDs
- if (header.Id == StandardRgbV2 || header.Id == StandardRgbV4)
- {
- return true;
- }
-
- // Fallback: best-guess heuristic
- return
- header.FileSignature == "acsp" &&
- header.DataColorSpace == IccColorSpaceType.Rgb &&
- (header.ProfileConnectionSpace == IccColorSpaceType.CieXyz || header.ProfileConnectionSpace == IccColorSpaceType.CieLab) &&
- (header.Class == IccProfileClass.DisplayDevice || header.Class == IccProfileClass.ColorSpace) &&
- header.PcsIlluminant == TruncatedD50 &&
- (header.Version.Major == 2 || header.Version.Major == 4) &&
- !string.Equals(header.CmmType, "ADBE", StringComparison.Ordinal);
- }
}
diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs
index 6c56dc682d..cb349af96a 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs
@@ -42,7 +42,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper)
[InlineData(TestIccProfiles.RommRgb, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 16-bit vs 8-bit)
[InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation
[InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B)
- public void CanConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.00005)
+ public void CanConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005)
{
List actual = Inputs.ConvertAll(input => GetActualTargetValues(input, sourceProfile, targetProfile));
AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper);
@@ -63,7 +63,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper)
[InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation
[InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B)
[InlineData(TestIccProfiles.Issue129, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> -> XYZ -> RGB
- public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.00005)
+ public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005)
{
List actual = GetBulkActualTargetValues(Inputs, sourceProfile, targetProfile);
AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 2856abe5c1..71753bf9ca 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -402,6 +402,9 @@ public partial class JpegDecoderTests
[WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Jpeg.ICC.SRgbGray, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Jpeg.ICC.Perceptual, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Jpeg.ICC.PerceptualcLUTOnly, PixelTypes.Rgba32)]
public void Decode_RGB_ICC_Jpeg(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
index 111544f7f5..c0abed214b 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
@@ -314,6 +314,21 @@ public class WebpDecoderTests
Assert.Equal(12, image.Frames.Count);
}
+ [Theory]
+ [InlineData(Lossless.Animated)]
+ public void Info_AnimatedLossless_VerifyAllFrames(string imagePath)
+ {
+ TestFile testFile = TestFile.Create(imagePath);
+ using MemoryStream stream = new(testFile.Bytes, false);
+ ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream);
+ WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
+ WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata();
+
+ Assert.Equal(0, webpMetaData.RepeatCount);
+ Assert.Equal(150U, frameMetaData.FrameDelay);
+ Assert.Equal(12, image.FrameCount);
+ }
+
[Theory]
[WithFile(Lossy.Animated, PixelTypes.Rgba32)]
public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider)
@@ -331,6 +346,21 @@ public class WebpDecoderTests
Assert.Equal(12, image.Frames.Count);
}
+ [Theory]
+ [InlineData(Lossy.Animated)]
+ public void Info_AnimatedLossy_VerifyAllFrames(string imagePath)
+ {
+ TestFile testFile = TestFile.Create(imagePath);
+ using MemoryStream stream = new(testFile.Bytes, false);
+ ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream);
+ WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata();
+ WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata();
+
+ Assert.Equal(0, webpMetaData.RepeatCount);
+ Assert.Equal(150U, frameMetaData.FrameDelay);
+ Assert.Equal(12, image.FrameCount);
+ }
+
[Theory]
[WithFile(Lossless.Animated, PixelTypes.Rgba32)]
public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 3e5b3b7120..af6148c873 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -216,6 +216,9 @@ public static class TestImages
public const string AppleRGB = "Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg";
public const string CMYK = "Jpg/icc-profiles/issue-129.jpg";
public const string YCCK = "Jpg/icc-profiles/issue_2723.jpg";
+ public const string SRgbGray = "Jpg/icc-profiles/sRGB_Gray.jpg";
+ public const string Perceptual = "Jpg/icc-profiles/Perceptual.jpg";
+ public const string PerceptualcLUTOnly = "Jpg/icc-profiles/Perceptual-cLUT-only.jpg";
}
public static class Progressive
diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png
new file mode 100644
index 0000000000..a0b73d299f
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fe06798b92c9b476c167407e752b4379d50f1b1ad6329eceb368c8c36097b401
+size 95103
diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png
new file mode 100644
index 0000000000..99ae53f93e
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21f8d54d4b789b783f3020402d4c1b91bb541de6565e2960976b569f60694631
+size 99385
diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png
new file mode 100644
index 0000000000..759b26a60c
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:18ad361f79b4ab26d452d5cc7ada4c121dfbf45d20da7c23a58f71a9497d17a2
+size 5341
diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg
new file mode 100644
index 0000000000..7b2e57f659
--- /dev/null
+++ b/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:04e552f0bd68bddb40f35c456034b1bf1e590f37e990a28b2fe2e94753bbe685
+size 276191
diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg
new file mode 100644
index 0000000000..879fd05ad3
--- /dev/null
+++ b/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:74a0931e320ca938d7dc94c4ab7b27a15880732fc139718629a7234f34bdafba
+size 297456
diff --git a/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg b/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg
new file mode 100644
index 0000000000..2abd976863
--- /dev/null
+++ b/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:22892d1b7965d973c7d8925ad7d749988c6a36b333b264a55d389f1e4faa0245
+size 36854