diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 99ca63fc39..637373e804 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -69,12 +69,6 @@ jobs: sdk-preview: true runtime: -x64 codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net10.0 - sdk: 10.0.x - sdk-preview: true - runtime: -x64 - codecov: false - os: macos-26 framework: net10.0 sdk: 10.0.x @@ -99,11 +93,6 @@ jobs: sdk: 8.0.x runtime: -x64 codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net8.0 - sdk: 8.0.x - runtime: -x64 - codecov: false - os: macos-26 framework: net8.0 sdk: 8.0.x diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index 3ddbf93b58..fd99fb4467 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -39,6 +39,24 @@ internal static class ColorProfileConverterExtensionsIcc 0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, 0.0033717495F, 0.0034852044F, 0.0028800198F, 0F]; + /// + /// Converts a color value from one ICC color profile to another using the specified color profile converter. + /// + /// + /// This method performs color conversion using ICC profiles, ensuring accurate color mapping + /// between different color spaces. Both the source and target ICC profiles must be provided in the converter's + /// options. The method supports perceptual adjustments when required by the profiles. + /// + /// The type representing the source color profile. Must implement . + /// The type representing the destination color profile. Must implement . + /// The color profile converter configured with source and target ICC profiles. + /// The color value to convert, defined in the source color profile. + /// + /// A color value in the target color profile, resulting from the ICC profile-based conversion of the source value. + /// + /// + /// Thrown if either the source or target ICC profile is missing from the converter options. + /// internal static TTo ConvertUsingIccProfile(this ColorProfileConverter converter, in TFrom source) where TFrom : struct, IColorProfile where TTo : struct, IColorProfile @@ -81,6 +99,29 @@ internal static class ColorProfileConverterExtensionsIcc return TTo.FromScaledVector4(targetParams.Converter.Calculate(targetPcs)); } + /// + /// Converts a span of color values from a source color profile to a destination color profile using ICC profiles. + /// + /// + /// This method performs color conversion by transforming the input values through the Profile + /// Connection Space (PCS) as defined by the provided ICC profiles. Perceptual adjustments are applied as required + /// by the profiles. The method does not support absolute colorimetric intent and will not perform such + /// conversions. + /// + /// The type representing the source color profile. Must implement . + /// The type representing the destination color profile. Must implement . + /// The color profile converter that provides conversion options and ICC profiles. + /// + /// A read-only span containing the source color values to convert. The values must conform to the source color + /// profile. + /// + /// + /// A span to receive the converted color values in the destination color profile. Must be at least as large as the + /// source span. + /// + /// + /// Thrown if the source or target ICC profile is missing from the converter options. + /// internal static void ConvertUsingIccProfile(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) where TFrom : struct, IColorProfile where TTo : struct, IColorProfile diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsPixelCompatible.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsPixelCompatible.cs new file mode 100644 index 0000000000..2780f04bae --- /dev/null +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsPixelCompatible.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.ColorProfiles; + +internal static class ColorProfileConverterExtensionsPixelCompatible +{ + /// + /// Converts the pixel data of the specified image from the source color profile to the target color profile using + /// the provided color profile converter. + /// + /// + /// This method modifies the source image in place by converting its pixel data according to the + /// color profiles specified in the converter. The method does not verify whether the profiles are RGB compatible; + /// if they are not, the conversion may produce incorrect results. Ensure that both the source and target ICC + /// profiles are set on the converter before calling this method. + /// + /// The pixel format. + /// The color profile converter configured with source and target ICC profiles. + /// + /// The image whose pixel data will be converted. The conversion is performed in place, modifying the original + /// image. + /// + /// + /// Thrown if the converter's source or target ICC profile is not specified. + /// + public static void Convert(this ColorProfileConverter converter, Image source) + where TPixel : unmanaged, IPixel + { + // These checks actually take place within the converter, but we want to fail fast here. + // Note. we do not check to see whether the profiles themselves are RGB compatible, + // if they are not, then the converter will simply produce incorrect results. + if (converter.Options.SourceIccProfile is null) + { + throw new InvalidOperationException("Source ICC profile is missing."); + } + + if (converter.Options.TargetIccProfile is null) + { + throw new InvalidOperationException("Target ICC profile is missing."); + } + + // Process the rows in parallel chunks, the converter itself is thread safe. + source.Mutate(o => o.ProcessPixelRowsAsVector4( + row => + { + // Gather and convert the pixels in the row to Rgb. + using IMemoryOwner rgbBuffer = converter.Options.MemoryAllocator.Allocate(row.Length); + Span rgbSpan = rgbBuffer.Memory.Span; + Rgb.FromScaledVector4(row, rgbSpan); + + // Perform the actual color conversion. + converter.ConvertUsingIccProfile(rgbSpan, rgbSpan); + + // Copy the converted Rgb pixels back to the row as TPixel. + ref Vector4 rowRef = ref MemoryMarshal.GetReference(row); + for (int i = 0; i < rgbSpan.Length; i++) + { + Vector3 rgb = rgbSpan[i].AsVector3Unsafe(); + Unsafe.As(ref Unsafe.Add(ref rowRef, (uint)i)) = rgb; + } + }, + PixelConversionModifiers.Scale)); + } +} diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs index adf0107da0..da50a1abec 100644 --- a/src/ImageSharp/Formats/ImageDecoderCore.cs +++ b/src/ImageSharp/Formats/ImageDecoderCore.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats; @@ -124,4 +127,41 @@ internal abstract class ImageDecoderCore /// protected abstract Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; + + /// + /// Converts the ICC color profile of the specified image to the compact sRGB v4 profile if a source profile is + /// available. + /// + /// + /// 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). + ///
+ /// 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. + ///
+ /// The pixel format. + /// The image whose ICC profile will be converted to the compact sRGB v4 profile. + /// + /// if the conversion was performed; otherwise, . + /// + protected bool TryConvertIccProfile(Image image) + where TPixel : unmanaged, IPixel + { + if (!this.Options.TryGetIccProfileForColorConversion(image.Metadata.IccProfile, out IccProfile? profile)) + { + return false; + } + + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + MemoryAllocator = image.Configuration.MemoryAllocator, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(image); + return true; + } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 38f964d37b..bff4d30ee5 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -212,6 +212,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: + { if (frameCount >= this.maxFrames) { goto EOF; @@ -246,7 +247,10 @@ internal sealed class PngDecoderCore : ImageDecoderCore } break; + } + case PngChunkType.Data: + { pngMetadata.AnimateRootFrame = currentFrameControl != null; currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height); if (image is null) @@ -276,6 +280,8 @@ internal sealed class PngDecoderCore : ImageDecoderCore } break; + } + case PngChunkType.Palette: this.palette = chunk.Data.GetSpan().ToArray(); break; @@ -323,6 +329,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore PngThrowHelper.ThrowNoData(); } + _ = this.TryConvertIccProfile(image); return image; } catch diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index cecdf88c9d..1cf4bb6ed6 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -111,7 +111,7 @@ public class PngMetadata : IFormatMetadata color = PngColorType.Rgb; break; default: - if (colorType.HasFlag(PixelColorType.Luminance)) + if (colorType.HasFlag(PixelColorType.Luminance | PixelColorType.Alpha)) { color = PngColorType.GrayscaleWithAlpha; break; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs index 9847f45b54..086aa3b4e1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -1,5 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. + #nullable disable using System.Buffers; @@ -48,31 +49,53 @@ internal class Rgba16161616TiffColor : TiffBaseColorDecoder using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : []; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + if (this.isBigEndian) + { + if (hasAssociatedAlpha) { - for (int x = 0; x < pixelRow.Length; x++) + for (int y = top; y < top + height; y++) { - ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort a = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a) - : TPixel.FromRgba64(new Rgba64(r, g, b, a)); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + for (int x = 0; x < pixelRow.Length; x++) + { + ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); + ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 2, 2)); + ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 4, 2)); + ushort a = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 6, 2)); + offset += 8; + + pixelRow[x] = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + } } } else { + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + for (int x = 0; x < pixelRow.Length; x++) + { + ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); + ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 2, 2)); + ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 4, 2)); + ushort a = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset + 6, 2)); + offset += 8; + + pixelRow[x] = TPixel.FromRgba64(new Rgba64(r, g, b, a)); + } + } + } + } + else + { + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length * 8; + PixelOperations.Instance.FromRgba64Bytes( this.configuration, data.Slice(offset, byteCount), diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs index e30765b1f4..b7d412f3c8 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs @@ -45,7 +45,12 @@ internal static class TiffUtilities return TPixel.FromRgba64(default); } - return TPixel.FromRgba64(new Rgba64((ushort)(r / a), (ushort)(g / a), (ushort)(b / a), a)); + float scale = 65535f / a; + ushort ur = (ushort)Math.Min(r * scale, 65535); + ushort ug = (ushort)Math.Min(g * scale, 65535); + ushort ub = (ushort)Math.Min(b * scale, 65535); + + return TPixel.FromRgba64(new Rgba64(ur, ug, ub, a)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs index 491f716500..7cec7c1db8 100644 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs +++ b/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs @@ -123,7 +123,10 @@ internal readonly struct WebpVp8X : IEquatable long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Vp8X); stream.WriteByte(flags); - stream.Position += 3; // Reserved bytes + + Span reserved = stackalloc byte[3]; + stream.Write(reserved); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1); diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index 6b31cadf4f..632e1bec04 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -39,13 +39,13 @@ internal struct UnmanagedMemoryHandle : IEquatable Interlocked.Increment(ref totalOutstandingHandles); } - public IntPtr Handle => this.handle; + public readonly IntPtr Handle => this.handle; - public bool IsInvalid => this.Handle == IntPtr.Zero; + public readonly bool IsInvalid => this.Handle == IntPtr.Zero; - public bool IsValid => this.Handle != IntPtr.Zero; + public readonly bool IsValid => this.Handle != IntPtr.Zero; - public unsafe void* Pointer => (void*)this.Handle; + public readonly unsafe void* Pointer => (void*)this.Handle; /// /// Gets the total outstanding handle allocations for testing purposes. @@ -121,9 +121,9 @@ internal struct UnmanagedMemoryHandle : IEquatable this.lengthInBytes = 0; } - public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + public readonly bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); - public override bool Equals(object? obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + public override readonly bool Equals(object? obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); - public override int GetHashCode() => this.handle.GetHashCode(); + public override readonly int GetHashCode() => this.handle.GetHashCode(); } 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 partial class PngDecoderTests 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/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 5cbc27611a..a0c552a221 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -336,4 +336,29 @@ public class PngMetadataTests Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); } + + + [Theory] + [InlineData(PixelColorType.Binary, PngColorType.Palette)] + [InlineData(PixelColorType.Indexed, PngColorType.Palette)] + [InlineData(PixelColorType.Luminance, PngColorType.Grayscale)] + [InlineData(PixelColorType.RGB, PngColorType.Rgb)] + [InlineData(PixelColorType.BGR, PngColorType.Rgb)] + [InlineData(PixelColorType.YCbCr, PngColorType.RgbWithAlpha)] + [InlineData(PixelColorType.CMYK, PngColorType.RgbWithAlpha)] + [InlineData(PixelColorType.YCCK, PngColorType.RgbWithAlpha)] + public void FromFormatConnectingMetadata_ConvertColorTypeAsExpected(PixelColorType pixelColorType, PngColorType expectedPngColorType) + { + FormatConnectingMetadata formatConnectingMetadata = new() + { + PixelTypeInfo = new PixelTypeInfo(24) + { + ColorType = pixelColorType, + }, + }; + + PngMetadata actual = PngMetadata.FromFormatConnectingMetadata(formatConnectingMetadata); + + Assert.Equal(expectedPngColorType, actual.ColorType); + } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index d850c67a51..5096d93bd7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -365,6 +365,19 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider); } + [Theory] + [WithFile(Issues3031, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaBigEndian, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaLittleEndian, PixelTypes.Rgba64)] + public void TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + image.DebugSave(provider); + + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + [Theory] [WithFile(Issues2454_A, PixelTypes.Rgba32)] [WithFile(Issues2454_B, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs new file mode 100644 index 0000000000..6483b63c52 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils; + +[Trait("Format", "Tiff")] +public class TiffUtilitiesTest +{ + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(42, 84, 128, 0)] + [InlineData(65535, 65535, 65535, 0)] + public void ColorFromRgba64Premultiplied_WithZeroAlpha_ReturnsDefaultPixel(ushort r, ushort g, ushort b, ushort a) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(default, actual); + } + + [Theory] + [InlineData(65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)] + [InlineData(32767, 0, 0, 65535, 32767, 0, 0, 65535)] + [InlineData(0, 32767, 0, 65535, 0, 32767, 0, 65535)] + [InlineData(0, 0, 32767, 65535, 0, 0, 32767, 65535)] + public void ColorFromRgba64Premultiplied_WithNoAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } + + [Theory] + [InlineData(32766, 0, 0, 32766, 65535, 0, 0, 32766)] // Red, 50% Alpha + [InlineData(0, 32766, 0, 32766, 0, 65535, 0, 32766)] // Green, 50% Alpha + [InlineData(0, 0, 32766, 32766, 0, 0, 65535, 32766)] // Blue, 50% Alpha + [InlineData(8191, 0, 0, 16383, 32765, 0, 0, 16383)] // Red, 25% Alpha + [InlineData(0, 8191, 0, 16383, 0, 32765, 0, 16383)] // Green, 25% Alpha + [InlineData(0, 0, 8191, 16383, 0, 0, 32765, 16383)] // Blue, 25% Alpha + [InlineData(8191, 0, 0, 0, 0, 0, 0, 0)] // Red, 0% Alpha + [InlineData(0, 8191, 0, 0, 0, 0, 0, 0)] // Green, 0% Alpha + [InlineData(0, 0, 8191, 0, 0, 0, 0, 0)] // Blue, 0% Alpha + public void ColorFromRgba64Premultiplied_WithAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs new file mode 100644 index 0000000000..78be0bf9fd --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Webp.Chunks; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP; + +[Trait("Format", "Webp")] +public class WebpVp8XTests +{ + [Fact] + public void WebpVp8X_WriteTo_Writes_Reserved_Bytes() + { + // arrange + WebpVp8X header = new(false, false, false, false, false, 10, 40); + MemoryStream ms = new(); + byte[] expected = [86, 80, 56, 88, 10, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 39, 0, 0]; + + // act + header.WriteTo(ms); + + // assert + byte[] actual = ms.ToArray(); + Assert.Equal(expected, actual); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 6e7e9fea73..c1f5b44bf7 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; @@ -273,67 +274,75 @@ public class UniformUnmanagedPoolMemoryAllocatorTests [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + RemoteExecutor.Invoke(RunTest, length.ToString(CultureInfo.InvariantCulture)).Dispose(); static void RunTest(string lengthStr) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - + int lengthInner = int.Parse(lengthStr, CultureInfo.InvariantCulture); + + // We want to verify that a leaked (not disposed) `MemoryGroup` still returns its + // unmanaged handles into the pool when it is finalized. + // + // We intentionally do NOT validate this by checking the contents of the re-rented memory + // (contents are not guaranteed to be preserved) nor by comparing pointer values + // (the pool may return a different handle while still correctly pooling). + // + // Instead, we validate that after a forced GC+finalization cycle, a subsequent allocation + // of the same size does not cause the number of outstanding unmanaged handles to *increase* + // compared to a known baseline. + + // Establish a baseline: create one allocation and dispose it so the pool is initialized. + // (This ensures subsequent observations are not biased by first-time pool growth.) + allocator.AllocateGroup(lengthInner, 100).Dispose(); + int baselineHandles = UnmanagedMemoryHandle.TotalOutstandingHandles; + + // Leak one allocation and force finalization. AllocateGroupAndForget(allocator, lengthInner); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); - AllocateGroupAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); - Assert.Equal(42, g.First().Span[0]); + // Allocate again. If the leaked group was finalized correctly and returned to the pool, + // this should not require additional unmanaged allocations (ie, the handle count must not grow). + allocator.AllocateGroup(lengthInner, 100).Dispose(); + + // Note: we use "<=" instead of "==" here. + // + // After we record the baseline, the pool is allowed to legitimately *decrease* + // `UnmanagedMemoryHandle.TotalOutstandingHandles` by trimming retained buffers + // (eg. via the pool's trim timer/GC callbacks/high-pressure logic). + // + // What must not happen is the opposite: the leaked (non-disposed) group should be finalized + // and its handles returned to the pool such that allocating again does NOT require creating + // additional unmanaged handles. Therefore the only invariant we can reliably assert here is + // "no growth" relative to the baseline. + Assert.True(UnmanagedMemoryHandle.TotalOutstandingHandles <= baselineHandles); } } - private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length) { + // Allocate a group and drop the reference without disposing. + // The test relies on the group's finalizer to return the rented memory to the pool. MemoryGroup g = allocator.AllocateGroup(length, 100); - if (check) - { - Assert.Equal(42, g.First().Span[0]); - } - g.First().Span[0] = 42; + // Touch the memory to ensure the buffer is actually materialized/usable. + g[0].Span[0] = 42; if (length < 512) { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. + // For ArrayPool.Shared, the first rented array may be stored in TLS on the finalizer thread. + // Repeat rental to increase the chance that per-core buckets are involved when length + // is small and allocations go through ArrayPool. MemoryGroup g1 = allocator.AllocateGroup(length, 100); - g1.First().Span[0] = 42; + g1[0].Span[0] = 42; + g1 = null; } + + g = null; } [Theory] @@ -341,69 +350,63 @@ public class UniformUnmanagedPoolMemoryAllocatorTests [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + RemoteExecutor.Invoke(RunTest, length.ToString(CultureInfo.InvariantCulture)).Dispose(); static void RunTest(string lengthStr) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - + int lengthInner = int.Parse(lengthStr, CultureInfo.InvariantCulture); + + // This test verifies pooling behavior when an `IMemoryOwner` is leaked (not disposed) + // and must be returned to the pool by finalization. + // + // We do NOT use a sentinel byte value to prove reuse because the contents of pooled buffers + // are not required to be preserved across rentals. + // + // Instead, we assert that after forcing GC+finalization, renting the same size again does not + // increase `UnmanagedMemoryHandle.TotalOutstandingHandles` above a baseline. + + // Establish a baseline: allocate+dispose once so the pool has a chance to materialize/retain buffers. + allocator.Allocate(lengthInner).Dispose(); + int baselineHandles = UnmanagedMemoryHandle.TotalOutstandingHandles; + + // Leak one allocation and force finalization. AllocateSingleAndForget(allocator, lengthInner); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); - AllocateSingleAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); + // Allocate again. If the leaked owner was finalized correctly and returned to the pool, + // this should not require additional unmanaged allocations (ie, the handle count must not grow). + allocator.Allocate(lengthInner).Dispose(); - using IMemoryOwner g = allocator.Allocate(lengthInner); - Assert.Equal(42, g.GetSpan()[0]); - GC.KeepAlive(allocator); + // Note: we use "<=" rather than "==". The pool may legitimately trim and free retained buffers, + // reducing the handle count between baseline and check. The invariant is "no growth". + Assert.True(UnmanagedMemoryHandle.TotalOutstandingHandles <= baselineHandles); } } [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length) { + // Allocate and intentionally do not dispose. IMemoryOwner g = allocator.Allocate(length); - if (check) - { - Assert.Equal(42, g.GetSpan()[0]); - } + // Touch the memory to ensure the buffer is actually materialized/usable. g.GetSpan()[0] = 42; if (length < 512) { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. + // For ArrayPool.Shared, the first rented array may be stored in TLS on the finalizer thread. + // Repeat rental to increase the chance that per-core buckets are involved when length + // is small and allocations go through ArrayPool. IMemoryOwner g1 = allocator.Allocate(length); g1.GetSpan()[0] = 42; + g1 = null; } + + g = null; } [Fact] diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs index 395dfd455f..6c8dd2618c 100644 --- a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs +++ b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs @@ -20,20 +20,13 @@ public static class MemoryAllocatorValidator private static void MemoryDiagnostics_MemoryReleased() { TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalRemainingAllocated--; - } + backing?.OnReleased(); } private static void MemoryDiagnostics_MemoryAllocated() { TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalAllocated++; - backing.TotalRemainingAllocated++; - } + backing?.OnAllocated(); } public static TestMemoryDiagnostics MonitorAllocations() @@ -48,11 +41,23 @@ public static class MemoryAllocatorValidator public static void ValidateAllocations(int expectedAllocationCount = 0) => LocalInstance.Value?.Validate(expectedAllocationCount); - public class TestMemoryDiagnostics : IDisposable + public sealed class TestMemoryDiagnostics : IDisposable { - public int TotalAllocated { get; set; } + private int totalAllocated; + private int totalRemainingAllocated; + + public int TotalAllocated => Volatile.Read(ref this.totalAllocated); + + public int TotalRemainingAllocated => Volatile.Read(ref this.totalRemainingAllocated); + + internal void OnAllocated() + { + Interlocked.Increment(ref this.totalAllocated); + Interlocked.Increment(ref this.totalRemainingAllocated); + } - public int TotalRemainingAllocated { get; set; } + internal void OnReleased() + => Interlocked.Decrement(ref this.totalRemainingAllocated); public void Validate(int expectedAllocationCount) { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index bc699da88e..f6cd776e47 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -166,6 +166,15 @@ public static class TestImages // 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"; @@ -1142,6 +1151,7 @@ public static class TestImages public const string Issues2435 = "Tiff/Issues/Issue2435.tiff"; public const string Issues2454_A = "Tiff/Issues/Issue2454_A.tif"; public const string Issues2454_B = "Tiff/Issues/Issue2454_B.tif"; + public const string Issues3031 = "Tiff/Issues/Issue3031.tiff"; public const string Issues2587 = "Tiff/Issues/Issue2587.tiff"; public const string Issues2679 = "Tiff/Issues/Issue2679.tiff"; public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff"; 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/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png new file mode 100644 index 0000000000..d5a017475f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png new file mode 100644 index 0000000000..a3317a6b48 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50dfa103459d21642df5e1ca760081fbdfe3b7244624d9d87d8a20f45b51bbe +size 117953 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png new file mode 100644 index 0000000000..5b72c3732c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3333503012aa29e23f0d0e52993ad3499514251208fb3318e2bb1560d54650fa +size 117956 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png new file mode 100644 index 0000000000..d5a017475f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 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 diff --git a/tests/Images/Input/Tiff/Issues/Issue3031.tiff b/tests/Images/Input/Tiff/Issues/Issue3031.tiff new file mode 100644 index 0000000000..bc3ef7d7cf --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue3031.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4d2db56a1b7fdea09ed65eab1d10a02952821f6662ca77caa44b8c51b30310 +size 416