diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 62a8bf2b4..ec9258883 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Feature Request - url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas + url: https://github.com/SixLabors/ImageSharp/discussions/categories/ideas about: Share ideas for new features for this project. diff --git a/README.md b/README.md index 2492041db..6c669fb78 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standa ## License - ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0) -- An alternative Commercial Support License can be purchased **for projects and applications requiring support**. +- An alternative Six Labors License can be purchased **for projects and applications requiring developer support**. Please visit https://sixlabors.com/pricing for details. ## Support Six Labors @@ -43,7 +43,8 @@ Support the efforts of the development of the Six Labors projects. ## Questions -- Do you have questions? We are happy to help! Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/category_choices), or ask them on [Stack Overflow](https://stackoverflow.com) using the `ImageSharp` tag. Please do not open issues for questions. +- Do you have questions? We are happy to help! Simply purchase a [Six Labors License](https://sixlabors.com/pricing) for developer support. Please do not open issues for questions or misuse our [Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions). +- For feature ideas please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/categories/ideas) and we'll be happy to discuss. - Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening issues or pull requests! ## Code of Conduct diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 82a146dc7..2323b5ba7 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -287,8 +287,7 @@ namespace SixLabors.ImageSharp.Advanced where TPixel : unmanaged, IPixel where TDecoder : class, IImageDecoder { - default(TDecoder).Decode(default, default); - default(TDecoder).DecodeAsync(default, default, default); + default(TDecoder).Decode(default, default, default); } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 129b3a1aa..e76448938 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -3,9 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -31,48 +28,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new BmpDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - return new BmpDecoderCore(configuration, this).Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); + return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index c31a2c1c9..6d6cfc079 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -26,48 +24,24 @@ namespace SixLabors.ImageSharp.Formats.Gif public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var decoder = new GifDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new GifDecoderCore(configuration, this); - - using var bufferedStream = new BufferedReadStream(configuration, stream); - return decoder.Identify(bufferedStream, default); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index b55f1119b..db7f64ee2 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -13,26 +12,6 @@ namespace SixLabors.ImageSharp.Formats /// public interface IImageDecoder { - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The configuration for the image. - /// The containing image data. - /// The . - // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an . - /// - /// The configuration for the image. - /// The containing image data. - /// The . - // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream); - /// /// Decodes the image from the specified stream to an of a specific pixel type. /// @@ -42,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// @@ -53,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 6f5fc2333..c6377edd0 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats { @@ -12,14 +11,6 @@ namespace SixLabors.ImageSharp.Formats /// public interface IImageInfoDetector { - /// - /// Reads the raw image information from the specified stream. - /// - /// The configuration for the image. - /// The containing image data. - /// The object - IImageInfo Identify(Configuration configuration, Stream stream); - /// /// Reads the raw image information from the specified stream. /// @@ -27,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats /// The containing image data. /// The token to monitor for cancellation requests. /// The object - Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); + IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 5d77fb0c8..71ecda893 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -13,153 +12,45 @@ namespace SixLabors.ImageSharp.Formats { internal static class ImageDecoderUtilities { - /// - /// Reads the raw image information from the specified stream. - /// - /// The decoder. - /// /// The configuration for the image. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// is null. - /// A representing the asynchronous operation. - public static Task IdentifyAsync( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - => decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - - /// - /// Reads the raw image information from the specified stream. - /// - /// The decoder. - /// The configuration for the image. - /// The containing image data. - /// Factory method to handle as . - /// The token to monitor for cancellation requests. - /// is null. - /// A representing the asynchronous operation. - public static Task IdentifyAsync( + public static IImageInfo Identify( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - Func tooLargeImageExceptionFactory, CancellationToken cancellationToken) { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + try { - using var bufferedReadStream = new BufferedReadStream(configuration, stream); - IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); - return Task.FromResult(imageInfo); + return decoder.Identify(bufferedReadStream, cancellationToken); } catch (InvalidMemoryOperationException ex) { - InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions); - return Task.FromException(invalidImageContentException); - } - catch (OperationCanceledException) - { - return Task.FromCanceled(cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); + throw new InvalidImageContentException(decoder.Dimensions, ex); } } - /// - /// Decodes the image from the specified stream. - /// - /// The pixel format. - /// The decoder. - /// The configuration for the image. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task> DecodeAsync( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel => - decoder.DecodeAsync( - configuration, - stream, - DefaultLargeImageExceptionFactory, - cancellationToken); - - /// - /// Decodes the image from the specified stream. - /// - /// The pixel format. - /// The decoder. - /// The configuration for the image. - /// The containing image data. - /// Factory method to handle as . - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task> DecodeAsync( + public static Image Decode( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - Func largeImageExceptionFactory, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - { - try - { - using var bufferedReadStream = new BufferedReadStream(configuration, stream); - Image image = decoder.Decode(bufferedReadStream, cancellationToken); - return Task.FromResult(image); - } - catch (InvalidMemoryOperationException ex) - { - InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions); - return Task.FromException>(invalidImageContentException); - } - catch (OperationCanceledException) - { - return Task.FromCanceled>(cancellationToken); - } - catch (Exception ex) - { - return Task.FromException>(ex); - } - } - - public static IImageInfo Identify( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream) - { - using var bufferedReadStream = new BufferedReadStream(configuration, stream); - - try - { - return decoder.Identify(bufferedReadStream, default); - } - catch (InvalidMemoryOperationException ex) - { - throw new InvalidImageContentException(decoder.Dimensions, ex); - } - } - - public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); public static Image Decode( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - Func largeImageExceptionFactory) + Func largeImageExceptionFactory, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream); try { - return decoder.Decode(bufferedReadStream, default); + return decoder.Decode(bufferedReadStream, cancellationToken); } catch (InvalidMemoryOperationException ex) { @@ -170,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats private static InvalidImageContentException DefaultLargeImageExceptionFactory( InvalidMemoryOperationException memoryOperationException, Size dimensions) => - new InvalidImageContentException(dimensions, memoryOperationException); + new(dimensions, memoryOperationException); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs index 6c402fcfd..e87f2fc57 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs @@ -2,8 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -74,6 +79,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Block8x8F yBlock = ref this.Y; ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span); + if (RgbToYCbCrConverterVectorized.IsSupported) + { + ConvertAvx(ref l8Start, ref yBlock); + } + else + { + ConvertScalar(ref l8Start, ref yBlock); + } + } + + /// + /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. + /// + /// Start of span of L8 pixels with size of 64 + /// 8x8 destination matrix of Luminance(Y) converted data + private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock) + { + Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS + ref Vector128 l8ByteSpan = ref Unsafe.As>(ref l8Start); + ref Vector256 destRef = ref yBlock.V0; + + const int bytesPerL8Stride = 8; + for (nint i = 0; i < 8; i++) + { + Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i))); + } +#endif + } + + /// + /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats. + /// + /// Start of span of L8 pixels with size of 64 + /// 8x8 destination matrix of Luminance(Y) converted data + private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock) + { for (int i = 0; i < Block8x8F.Size; i++) { ref L8 c = ref Unsafe.Add(ref l8Start, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs index 789277d7d..e2d12916c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs @@ -2,8 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -94,10 +99,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Block8x8F greenBlock = ref this.G; ref Block8x8F blueBlock = ref this.B; - CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); + if (RgbToYCbCrConverterVectorized.IsSupported) + { + ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); + } + else + { + ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); + } + } + + /// + /// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. + /// + /// Span of Rgb24 pixels with size of 64 + /// 8x8 destination matrix of Red converted data + /// 8x8 destination matrix of Blue converted data + /// 8x8 destination matrix of Green converted data + private static void ConvertAvx(Span rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock) + { + Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); + ref Vector256 redRef = ref rBlock.V0; + ref Vector256 greenRef = ref gBlock.V0; + ref Vector256 blueRef = ref bBlock.V0; + var zero = Vector256.Create(0).AsByte(); + + var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes)); + var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb)); + Vector256 rgb, rg, bx; + + const int bytesPerRgbStride = 24; + for (nint i = 0; i < 8; i++) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); + + Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); + } +#endif } - private static void CopyToBlock(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock) + private static void ConvertScalar(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock) { ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs index 9566ee862..d7542d7a5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -60,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #if SUPPORTS_RUNTIME_INTRINSICS - private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] + internal static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; - private static ReadOnlySpan ExtractRgb => new byte[] + internal static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 18212ffc7..22a9801b8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -17,56 +16,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - using var decoder = new JpegDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Identify(configuration, stream); - } - - /// - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - // The introduction of a local variable that refers to an object the implements - // IDisposable means you must use async/await, where the compiler generates the - // state machine and a continuation. - using (var decoder = new JpegDecoderCore(configuration, this)) - { - return await decoder.IdentifyAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - } + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 023928f37..ef4e3ffac 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Processes the App1 marker retrieving any stored metadata + /// Processes the App1 marker retrieving any stored metadata. /// /// The input stream. /// The remaining bytes in the segment block. @@ -687,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int XmpMarkerLength = 29; if (remaining < ExifMarkerLength || this.IgnoreMetadata) { - // Skip the application header length + // Skip the application header length. stream.Skip(remaining); return; } @@ -697,12 +697,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - // XMP marker is the longest, so read at least that many bytes into temp. + // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes. stream.Read(this.temp, 0, ExifMarkerLength); + remaining -= ExifMarkerLength; if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { - remaining -= ExifMarkerLength; this.hasExif = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); @@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers + // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers. this.ExtendProfile(ref this.exifData, profile); } @@ -722,9 +722,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { - stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength); - remaining -= XmpMarkerLength; - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength))) + int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; + stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes); + remaining -= remainingXmpMarkerBytes; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) { this.hasXmp = true; byte[] profile = new byte[remaining]; @@ -736,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers. this.ExtendProfile(ref this.xmpData, profile); } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 2eebbb1d9..97a9cb7d7 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm @@ -30,50 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Pbm public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new PbmDecoderCore(configuration); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new PbmDecoderCore(configuration); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new PbmDecoderCore(configuration); - return decoder.Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new PbmDecoderCore(configuration); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 04e70c51d..0b233848a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -17,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Png public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { PngDecoderCore decoder = new(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) { PngDecoderCore decoder = new(configuration, true); - IImageInfo info = decoder.Identify(configuration, stream); + IImageInfo info = decoder.Identify(configuration, stream, cancellationToken); stream.Position = 0; PngMetadata meta = info.Metadata.GetPngMetadata(); @@ -40,118 +39,49 @@ namespace SixLabors.ImageSharp.Formats.Png if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.Rgb: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.Palette: - return this.Decode(configuration, stream); + return this.Decode(configuration, stream, cancellationToken); case PngColorType.GrayscaleWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.RgbWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); default: - return this.Decode(configuration, stream); + return this.Decode(configuration, stream, cancellationToken); } } /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - PngDecoderCore decoder = new(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - PngDecoderCore decoder = new(configuration, true); - IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - stream.Position = 0; - - PngMetadata meta = info.Metadata.GetPngMetadata(); - PngColorType color = meta.ColorType.GetValueOrDefault(); - PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); - switch (color) - { - case PngColorType.Grayscale: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.Rgb: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.Palette: - return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.GrayscaleWithAlpha: - return (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.RgbWithAlpha: - return (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - default: - return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - } - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - PngDecoderCore decoder = new(configuration, this); - return decoder.Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { PngDecoderCore decoder = new(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index e06a0ee88..bb0a0d548 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -3,9 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tga @@ -16,48 +13,25 @@ namespace SixLabors.ImageSharp.Formats.Tga public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new TgaDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - return new TgaDecoderCore(configuration, this).Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); + return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index e2dbc6ca9..97bce2b2d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// Buffer with decompressed pixel data. /// The width of the image or strip. /// The color type of the pixel data. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. + /// If set to true decodes the pixel data as big endian, otherwise as little endian. public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian) { switch (colorType) @@ -43,12 +43,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffColorType.Rgb888: UndoRgb24Bit(pixelBytes, width); break; + case TiffColorType.Rgba8888: + UndoRgba32Bit(pixelBytes, width); + break; case TiffColorType.Rgb161616: UndoRgb48Bit(pixelBytes, width, isBigEndian); break; + case TiffColorType.Rgba16161616: + UndoRgba64Bit(pixelBytes, width, isBigEndian); + break; case TiffColorType.Rgb323232: UndoRgb96Bit(pixelBytes, width, isBigEndian); break; + case TiffColorType.Rgba32323232: + UndoRgba128Bit(pixelBytes, width, isBigEndian); + break; } } @@ -243,6 +252,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } + private static void UndoRgba32Bit(Span pixelBytes, int width) + { + int rowBytesCount = width * 4; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) + { + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + Span rowRgb = MemoryMarshal.Cast(rowBytes).Slice(0, width); + ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); + byte r = rowRgbBase.R; + byte g = rowRgbBase.G; + byte b = rowRgbBase.B; + byte a = rowRgbBase.A; + + for (int x = 1; x < rowRgb.Length; x++) + { + ref Rgba32 pixel = ref rowRgb[x]; + r += pixel.R; + g += pixel.G; + b += pixel.B; + a += pixel.A; + var rgb = new Rgba32(r, g, b, a); + pixel.FromRgba32(rgb); + } + } + } + private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian) { int rowBytesCount = width * 6; @@ -319,6 +355,98 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } + private static void UndoRgba64Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 8; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a); + offset += 2; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a); + offset += 2; + } + } + } + } + private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian) { int rowBytesCount = width * 12; @@ -394,5 +522,97 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } } + + private static void UndoRgba128Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 16; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a); + offset += 4; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a); + offset += 4; + } + } + } + } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 985ffeb18..4130ee1a2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -30,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); int offset = 0; - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index ac4435db6..e37fff1e7 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs new file mode 100644 index 000000000..7fd8d9879 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel. + /// + internal class Rgba16161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian) + { + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + + int offset = 0; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + else + { + int byteCount = pixelRow.Length * 8; + PixelOperations.Instance.FromRgba64Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs new file mode 100644 index 000000000..705010ce9 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit. + /// + internal class Rgba16PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); + + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); + + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs new file mode 100644 index 000000000..71e1f7abd --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel. + /// + internal class Rgba24242424TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + int offset = 0; + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + Span bufferSpan = buffer.Slice(bufferStartIdx); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs new file mode 100644 index 000000000..03b78c3f8 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit. + /// + internal class Rgba24PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + Span bufferSpan = buffer.Slice(bufferStartIdx); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs new file mode 100644 index 000000000..fbd18ca3e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// + internal class Rgba32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + int offset = 0; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs new file mode 100644 index 000000000..e23111159 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit. + /// + internal class Rgba32PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs new file mode 100644 index 000000000..491a42fb7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel. + /// + internal class Rgba8888TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly Configuration configuration; + + public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + int offset = 0; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + int byteCount = pixelRow.Length * 4; + PixelOperations.Instance.FromRgba32Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs new file mode 100644 index 000000000..4fb0797dc --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs @@ -0,0 +1,97 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// + internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + int offset = 0; + byte[] buffer = new byte[4]; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, a); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, a); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs new file mode 100644 index 000000000..f2ccf2ec8 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths). + /// + internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly float aFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + private readonly ushort bitsPerSampleA; + + public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + } + + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var rBitReader = new BitReader(data[0].GetSpan()); + var gBitReader = new BitReader(data[1].GetSpan()); + var bBitReader = new BitReader(data[2].GetSpan()); + var aBitReader = new BitReader(data[3].GetSpan()); + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; + + color.FromVector4(new Vector4(r, g, b, a)); + pixelRow[x] = color; + } + + rBitReader.NextRow(); + gBitReader.NextRow(); + bBitReader.NextRow(); + aBitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs new file mode 100644 index 000000000..8bec7da89 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths). + /// + internal class RgbaTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly float aFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + private readonly ushort bitsPerSampleA; + + public RgbaTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; + + color.FromVector4(new Vector4(r, g, b, a)); + pixelRow[x] = color; + } + + bitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index a8c4cef4e..1c608e303 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -116,6 +116,38 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba2222: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 2 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba3333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb444: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -126,6 +158,59 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb444TiffColor(); + case TiffColorType.Rgba4444: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 4 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba5555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 5 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba6666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 6 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb888: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -136,6 +221,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb888TiffColor(configuration); + case TiffColorType.Rgba8888: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 8 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba8888TiffColor(configuration); + case TiffColorType.Rgb101010: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -146,6 +242,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba10101010: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 10 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb121212: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -156,6 +263,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba12121212: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 12 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb141414: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -166,6 +284,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba14141414: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 14 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb161616: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -176,6 +305,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba16161616: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 16 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -186,6 +326,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba24242424: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 24 + && bitsPerSample.Channel2 == 24 + && bitsPerSample.Channel1 == 24 + && bitsPerSample.Channel0 == 24, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -196,6 +347,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.RgbFloat323232: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -206,6 +368,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbFloat323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.RgbaFloat32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); return new PaletteTiffColor(bitsPerSample, colorMap); @@ -233,6 +406,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbPlanarTiffColor(bitsPerSample); + case TiffColorType.Rgba8888Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaPlanarTiffColor(bitsPerSample); + case TiffColorType.YCbCrPlanar: return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); @@ -240,14 +417,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba16161616Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba24242424Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba32323232Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + default: throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 8caefaed5..6b39224fb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -103,71 +103,171 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// Rgb222, + /// + /// RGBA color image with 2 bits for each channel. + /// + Rgba2222, + + /// + /// RGB color image with 3 bits for each channel. + /// + Rgb333, + + /// + /// RGBA color image with 3 bits for each channel. + /// + Rgba3333, + /// /// RGB color image with 4 bits for each channel. /// Rgb444, + /// + /// RGBA color image with 4 bits for each channel. + /// + Rgba4444, + + /// + /// RGB color image with 5 bits for each channel. + /// + Rgb555, + + /// + /// RGBA color image with 5 bits for each channel. + /// + Rgba5555, + + /// + /// RGB color image with 6 bits for each channel. + /// + Rgb666, + + /// + /// RGBA color image with 6 bits for each channel. + /// + Rgba6666, + /// /// RGB Full Color. Optimized implementation for 8-bit images. /// Rgb888, + /// + /// RGBA Full Color with 8-bit for each channel. + /// + Rgba8888, + /// /// RGB color image with 10 bits for each channel. /// Rgb101010, + /// + /// RGBA color image with 10 bits for each channel. + /// + Rgba10101010, + /// /// RGB color image with 12 bits for each channel. /// Rgb121212, + /// + /// RGBA color image with 12 bits for each channel. + /// + Rgba12121212, + /// /// RGB color image with 14 bits for each channel. /// Rgb141414, + /// + /// RGBA color image with 14 bits for each channel. + /// + Rgba14141414, + /// /// RGB color image with 16 bits for each channel. /// Rgb161616, + /// + /// RGBA color image with 16 bits for each channel. + /// + Rgba16161616, + /// /// RGB color image with 24 bits for each channel. /// Rgb242424, + /// + /// RGBA color image with 24 bits for each channel. + /// + Rgba24242424, + /// /// RGB color image with 32 bits for each channel. /// Rgb323232, + /// + /// RGBA color image with 32 bits for each channel. + /// + Rgba32323232, + /// /// RGB color image with 32 bits floats for each channel. /// RgbFloat323232, + /// + /// RGBA color image with 32 bits floats for each channel. + /// + RgbaFloat32323232, + /// /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. /// Rgb888Planar, + /// + /// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel. + /// + Rgba8888Planar, + /// /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. /// Rgb161616Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel. + /// + Rgba16161616Planar, + /// /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. /// Rgb242424Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel. + /// + Rgba24242424Planar, + /// /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. /// Rgb323232Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel. + /// + Rgba32323232Planar, + /// /// The pixels are stored in YCbCr format. /// diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 51e84ef55..aa960b373 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -105,7 +105,7 @@ |Artist | Y | Y | | |HostComputer | Y | Y | | |ColorMap | Y | Y | | -|ExtraSamples | | - | | +|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far | |Copyright | Y | Y | | ### Extension TIFF Tags diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index 8fd26ac13..9348a6883 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public readonly ushort Channel2; + /// + /// The bits for the alpha channel. + /// + public readonly ushort Channel3; + /// /// The number of channels. /// @@ -36,16 +41,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The bits for the channel 0. /// The bits for the channel 1. /// The bits for the channel 2. - public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) + /// The bits for the channel 3. + public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0) { this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32); this.Channels = 0; this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0); } /// @@ -62,11 +70,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff return false; } + ushort c3 = 0; ushort c2; ushort c1; ushort c0; switch (value.Length) { + case 4: + c3 = value[3]; + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + case 3: c2 = value[2]; c1 = value[1]; @@ -84,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } - sample = new TiffBitsPerSample(c0, c1, c2); + sample = new TiffBitsPerSample(c0, c1, c2, c3); return true; } @@ -96,11 +112,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff public bool Equals(TiffBitsPerSample other) => this.Channel0 == other.Channel0 && this.Channel1 == other.Channel1 - && this.Channel2 == other.Channel2; + && this.Channel2 == other.Channel2 + && this.Channel3 == other.Channel3; /// public override int GetHashCode() - => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2); + => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3); /// /// Converts the bits per sample struct to an ushort array. @@ -118,7 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new[] { this.Channel0, this.Channel1 }; } - return new[] { this.Channel0, this.Channel1, this.Channel2 }; + if (this.Channel3 == 0) + { + return new[] { this.Channel0, this.Channel1, this.Channel2 }; + } + + return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 }; } /// @@ -127,12 +149,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Bits per pixel. public TiffBitsPerPixel BitsPerPixel() { - int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2; + int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3; return (TiffBitsPerPixel)bitsPerPixel; } /// public override string ToString() - => $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"; + => this.Channel3 is 0 ? + $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})" + : $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})"; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index b4d752019..c4a9f3b22 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -25,49 +24,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff public FrameDecodingMode DecodingMode { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, "stream"); - - var decoder = new TiffDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); - } - - /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new TiffDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new TiffDecoderCore(configuration, this); - return decoder.Identify(configuration, stream); - } + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new TiffDecoderCore(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index cd06282f1..e5b810738 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Gets the decoding mode for multi-frame images /// - private FrameDecodingMode decodingMode; + private readonly FrameDecodingMode decodingMode; /// /// The stream to decode from. @@ -117,6 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public TiffFillOrder FillOrder { get; set; } + /// + /// Gets or sets the extra samples, which can contain the alpha channel data. + /// + public TiffExtraSampleType? ExtraSamples { get; set; } + /// /// Gets or sets the JPEG tables when jpeg compression is used. /// @@ -275,12 +280,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff return memory; } - else - { - DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); - span = (ulong[])array; - return null; - } + + DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); + span = (ulong[])array; + return null; } /// @@ -314,8 +317,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff case 2: bitsPerPixel = this.BitsPerSample.Channel2; break; + case 3: + bitsPerPixel = this.BitsPerSample.Channel2; + break; default: - TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported"); + TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); break; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index a187d444a..23bc5f15f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -29,9 +29,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); } - if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null) + IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples); + if (extraSamplesExifValue is not null) { - TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); + short[] extraSamples = (short[])extraSamplesExifValue.GetValue(); + if (extraSamples.Length != 1) + { + TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data."); + } + + var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; + options.ExtraSamples = extraSamplesType; + if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData) + { + TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData."); + } } TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; @@ -52,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff sampleFormat = sampleFormats[0]; foreach (TiffSampleFormat format in sampleFormats) { - if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float) + if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float) { TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); } @@ -252,12 +264,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.Rgb: { TiffBitsPerSample bitsPerSample = options.BitsPerSample; - if (bitsPerSample.Channels != 3) + if (bitsPerSample.Channels is not (3 or 4)) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) + if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) || + (bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3))) { TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); } @@ -270,41 +283,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff case 32: if (options.SampleFormat == TiffSampleFormat.Float) { - options.ColorType = TiffColorType.RgbFloat323232; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232; return; } - options.ColorType = TiffColorType.Rgb323232; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232; break; case 24: - options.ColorType = TiffColorType.Rgb242424; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424; break; case 16: - options.ColorType = TiffColorType.Rgb161616; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616; break; case 14: - options.ColorType = TiffColorType.Rgb141414; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414; break; case 12: - options.ColorType = TiffColorType.Rgb121212; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212; break; case 10: - options.ColorType = TiffColorType.Rgb101010; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010; break; case 8: - options.ColorType = TiffColorType.Rgb888; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888; + break; + case 6: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666; + break; + case 5: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555; break; case 4: - options.ColorType = TiffColorType.Rgb444; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444; + break; + case 3: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333; break; case 2: - options.ColorType = TiffColorType.Rgb222; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222; break; default: TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); @@ -317,16 +339,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff switch (bitsPerChannel) { case 32: - options.ColorType = TiffColorType.Rgb323232Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar; break; case 24: - options.ColorType = TiffColorType.Rgb242424Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar; break; case 16: - options.ColorType = TiffColorType.Rgb161616Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar; break; default: - options.ColorType = TiffColorType.Rgb888Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar; break; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs new file mode 100644 index 000000000..5fbc29177 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Description of extra components. + /// + internal enum TiffExtraSampleType + { + /// + /// The data is unspecified, not supported. + /// + UnspecifiedData = 0, + + /// + /// The extra data is associated alpha data (with pre-multiplied color). + /// + AssociatedAlphaData = 1, + + /// + /// The extra data is unassociated alpha data is transparency information that logically exists independent of an image; + /// it is commonly called a soft matte. + /// + UnassociatedAlphaData = 2 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 4f71fa35c..b7d4b6e7c 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils private const float Scale32Bit = 1.0f / 0xFFFFFFFF; - public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); + public static Vector4 Vector4Default { get; } = new(0.0f, 0.0f, 0.0f, 0.0f); - public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0); + public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0); - public static L16 L16Default { get; } = new L16(0); + public static L16 L16Default { get; } = new(0); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); @@ -54,6 +54,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); + color.FromRgba64(rgba); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel @@ -63,6 +72,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel @@ -72,6 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index 71b4e4f23..2f6b593ee 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -37,39 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Webp try { - return decoder.Decode(configuration, stream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - } - } - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - return new WebpDecoderCore(configuration, this).Identify(configuration, stream); - } - - /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - using var decoder = new WebpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(configuration, stream); - return decoder.DecodeAsync(configuration, bufferedStream, cancellationToken); + return decoder.Decode(configuration, stream, cancellationToken); } catch (InvalidMemoryOperationException ex) { @@ -80,16 +46,15 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + /// + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(configuration, stream); - return new WebpDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken); + return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index e39930def..a1ea2b760 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -16,19 +16,17 @@ namespace SixLabors.ImageSharp.IO /// internal sealed class ChunkedMemoryStream : Stream { - /// - /// The default length in bytes of each buffer chunk. - /// - public const int DefaultBufferLength = 128 * 1024; - // The memory allocator. private readonly MemoryAllocator allocator; // Data private MemoryChunk memoryChunk; - // The length of each buffer chunk - private readonly int chunkLength; + // The total number of allocated chunks + private int chunkCount; + + // The length of the largest contiguous buffer that can be handled by the allocator. + private readonly int allocatorCapacity; // Has the stream been disposed. private bool isDisposed; @@ -49,21 +47,10 @@ namespace SixLabors.ImageSharp.IO /// Initializes a new instance of the class. /// public ChunkedMemoryStream(MemoryAllocator allocator) - : this(DefaultBufferLength, allocator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The length, in bytes of each buffer chunk. - /// The memory allocator. - public ChunkedMemoryStream(int bufferLength, MemoryAllocator allocator) { - Guard.MustBeGreaterThan(bufferLength, 0, nameof(bufferLength)); Guard.NotNull(allocator, nameof(allocator)); - this.chunkLength = bufferLength; + this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); this.allocator = allocator; } @@ -191,6 +178,9 @@ namespace SixLabors.ImageSharp.IO case SeekOrigin.End: this.Position = this.Length + offset; break; + default: + ThrowInvalidSeek(); + break; } return this.Position; @@ -219,6 +209,7 @@ namespace SixLabors.ImageSharp.IO this.memoryChunk = null; this.writeChunk = null; this.readChunk = null; + this.chunkCount = 0; } finally { @@ -519,17 +510,22 @@ namespace SixLabors.ImageSharp.IO } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() - => throw new ObjectDisposedException(null, "The stream is closed."); + private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed."); [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRange(string value) - => throw new ArgumentOutOfRangeException(value); + private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin."); [MethodImpl(MethodImplOptions.AggressiveInlining)] private MemoryChunk AllocateMemoryChunk() { - IMemoryOwner buffer = this.allocator.Allocate(this.chunkLength); + // Tweak our buffer sizes to take the minimum of the provided buffer sizes + // or the allocator buffer capacity which provides us with the largest + // available contiguous buffer size. + IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); + return new MemoryChunk { Buffer = buffer, @@ -547,6 +543,18 @@ namespace SixLabors.ImageSharp.IO } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetChunkSize(int i) + { + // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator. + // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 +#pragma warning disable IDE1006 // Naming Styles + const int _128K = 1 << 17; + const int _4M = 1 << 22; + return i < 16 ? _128K * (1 << (i / 4)) : _4M; +#pragma warning restore IDE1006 // Naming Styles + } + private sealed class MemoryChunk : IDisposable { private bool isDisposed; diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index c892b82ba..3c2b6d67f 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; -using System.Linq; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp /// The image might be filled with memory garbage. /// /// The pixel type - /// The + /// The /// The width of the image /// The height of the image /// The @@ -98,20 +95,6 @@ namespace SixLabors.ImageSharp return format; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The image stream to read the header from. - /// The configuration. - /// The mime type or null if none found. - private static Task InternalDetectFormatAsync(Stream stream, Configuration config) - { - // We are going to cheat here because we know that by this point we have been wrapped in a - // seekable stream then we are free to use sync APIs this is potentially brittle and may - // need a better fix in the future. - return Task.FromResult(InternalDetectFormat(stream, config)); - } - /// /// By reading the header on the provided stream this calculates the images format. /// @@ -128,33 +111,17 @@ namespace SixLabors.ImageSharp : null; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The image stream to read the header from. - /// The configuration. - /// The decoder and the image format or null if none found. - private static async Task<(IImageDecoder Decoder, IImageFormat Format)> DiscoverDecoderAsync(Stream stream, Configuration config) - { - IImageFormat format = await InternalDetectFormatAsync(stream, config).ConfigureAwait(false); - - IImageDecoder decoder = format != null - ? config.ImageFormatsManager.FindDecoder(format) - : null; - - return (decoder, format); - } - /// /// Decodes the image stream to the current image. /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// The pixel format. /// /// A new . /// - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -163,37 +130,11 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = decoder.Decode(config, stream); - return (img, format); - } - - /// - /// Decodes the image stream to the current image. - /// - /// The stream. - /// the configuration. - /// The token to monitor for cancellation requests. - /// The pixel format. - /// A representing the asynchronous operation. - private static async Task<(Image Image, IImageFormat Format)> DecodeAsync( - Stream stream, - Configuration config, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) - .ConfigureAwait(false); - if (decoder is null) - { - return (null, null); - } - - Image img = await decoder.DecodeAsync(config, stream, cancellationToken) - .ConfigureAwait(false); + Image img = decoder.Decode(config, stream, cancellationToken); return (img, format); } - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -201,19 +142,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = decoder.Decode(config, stream); - return (img, format); - } - - private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) - { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); - if (decoder is null) - { - return (null, null); - } - - Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false); + Image img = decoder.Decode(config, stream, cancellationToken); return (img, format); } @@ -222,47 +151,20 @@ namespace SixLabors.ImageSharp /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// /// The or null if a suitable info detector is not found. /// - private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); - if (!(decoder is IImageInfoDetector detector)) - { - return (null, null); - } - - IImageInfo info = detector?.Identify(config, stream); - return (info, format); - } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The stream. - /// the configuration. - /// The token to monitor for cancellation requests. - /// - /// A representing the asynchronous operation with the - /// property of the returned type set to null if a suitable detector - /// is not found. - private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config, CancellationToken cancellationToken) - { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); - - if (!(decoder is IImageInfoDetector detector)) + if (decoder is not IImageInfoDetector detector) { return (null, null); } - if (detector is null) - { - return (null, format); - } - - IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false); + IImageInfo info = detector?.Identify(config, stream, cancellationToken); return (info, format); } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index fce0835fb..345a5e6c8 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync( + public static async Task LoadAsync( Configuration configuration, string path, IImageDecoder decoder, @@ -308,7 +308,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(path, nameof(path)); using Stream stream = configuration.FileSystem.OpenRead(path); - return LoadAsync(configuration, stream, decoder, cancellationToken); + return await LoadAsync(configuration, stream, decoder, cancellationToken) + .ConfigureAwait(false); } /// @@ -326,7 +327,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync( + public static async Task> LoadAsync( Configuration configuration, string path, IImageDecoder decoder, @@ -337,7 +338,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(path, nameof(path)); using Stream stream = configuration.FileSystem.OpenRead(path); - return LoadAsync(configuration, stream, decoder, cancellationToken); + return await LoadAsync(configuration, stream, decoder, cancellationToken) + .ConfigureAwait(false); } /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index f5e32d8ce..e10d7fe3d 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, _) => InternalDetectFormatAsync(s, configuration), + (s, _) => InternalDetectFormat(s, configuration), cancellationToken); /// @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), + (s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct), cancellationToken); /// @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) { Guard.NotNull(decoder, nameof(decoder)); - return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); } /// @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp return WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s, ct), + (s, ct) => decoder.Decode(configuration, s, ct), cancellationToken); } @@ -432,9 +432,9 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => await LoadWithFormatAsync(Configuration.Default, stream, cancellationToken).ConfigureAwait(false); + => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); + => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s, default)); /// /// Create a new instance of the class from the given stream. @@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( Configuration.Default, stream, - (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct), + (s, ct) => decoder.Decode(Configuration.Default, s, ct), cancellationToken); /// @@ -486,7 +486,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); /// /// Create a new instance of the class from the given stream. @@ -511,7 +511,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s, ct), + (s, ct) => decoder.Decode(configuration, s, ct), cancellationToken); /// @@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, - async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false), + (s, ct) => Decode(s, configuration, ct), cancellationToken) .ConfigureAwait(false); @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp await WithSeekableStreamAsync( configuration, stream, - (s, ct) => DecodeAsync(s, configuration, ct), + (s, ct) => Decode(s, configuration, ct), cancellationToken) .ConfigureAwait(false); @@ -759,7 +759,7 @@ namespace SixLabors.ImageSharp private static async Task WithSeekableStreamAsync( Configuration configuration, Stream stream, - Func> action, + Func action, CancellationToken cancellationToken) { Guard.NotNull(configuration, nameof(configuration)); @@ -770,10 +770,6 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } - // To make sure we don't trigger anything with aspnetcore then we just need to make sure we are - // seekable and we make the copy using CopyToAsync if the stream is seekable then we aren't using - // one of the aspnetcore wrapped streams that error on sync api calls and we can use it without - // having to further wrap if (stream.CanSeek) { if (configuration.ReadOrigin == ReadOrigin.Begin) @@ -781,14 +777,16 @@ namespace SixLabors.ImageSharp stream.Position = 0; } - return await action(stream, cancellationToken).ConfigureAwait(false); + // NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that + // would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the + // code below to copy the stream to an in-memory buffer before invoking the action. } using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator); await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - return await action(memoryStream, cancellationToken).ConfigureAwait(false); + return action(memoryStream, cancellationToken); } } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index fe037003e..716004f39 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -362,10 +362,8 @@ namespace SixLabors.ImageSharp return; } - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => - { - PixelOperations.Instance.To(this.GetConfiguration(), s, d); - }); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) + => PixelOperations.Instance.To(this.GetConfiguration(), s, d)); } /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs index 3fc353211..286b31786 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// Gets the MDFileUnits exif tag. /// - public static ExifTag MDFileUnits => new ExifTag(ExifTagValue.MDFileUnits); + public static ExifTag MDFileUnits { get; } = new ExifTag(ExifTagValue.MDFileUnits); /// /// Gets the SEMInfo exif tag. diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs index 314333919..82bd6ad2e 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { // All array types are value types so Clone() is sufficient here. var array = (Array)other.GetValue(); - this.TrySetValue(array.Clone()); + this.TrySetValue(array?.Clone()); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index c66526bbe..e78a644af 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.PixelFormats internal class PixelOperations : PixelOperations { private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); /// public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.PixelFormats { Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors); + PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); } /// diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index f748a4b57..710eb9c08 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.PixelFormats public partial class PixelOperations where TPixel : unmanaged, IPixel { - private static readonly Lazy LazyInfo = new Lazy(() => PixelTypeInfo.Create(), true); + private static readonly Lazy LazyInfo = new(() => PixelTypeInfo.Create(), true); /// /// Gets the global instance for the pixel type @@ -116,29 +116,29 @@ namespace SixLabors.ImageSharp.PixelFormats Span destinationPixels) where TSourcePixel : unmanaged, IPixel { - const int SliceLength = 1024; - int numberOfSlices = sourcePixels.Length / SliceLength; + const int sliceLength = 1024; + int numberOfSlices = sourcePixels.Length / sliceLength; - using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(SliceLength); + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); Span vectorSpan = tempVectors.GetSpan(); for (int i = 0; i < numberOfSlices; i++) { - int start = i * SliceLength; - ReadOnlySpan s = sourcePixels.Slice(start, SliceLength); - Span d = destinationPixels.Slice(start, SliceLength); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan); - this.FromVector4Destructive(configuration, vectorSpan, d); + int start = i * sliceLength; + ReadOnlySpan s = sourcePixels.Slice(start, sliceLength); + Span d = destinationPixels.Slice(start, sliceLength); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } - int endOfCompleteSlices = numberOfSlices * SliceLength; + int endOfCompleteSlices = numberOfSlices * sliceLength; int remainder = sourcePixels.Length - endOfCompleteSlices; if (remainder > 0) { ReadOnlySpan s = sourcePixels.Slice(endOfCompleteSlices); Span d = destinationPixels.Slice(endOfCompleteSlices); vectorSpan = vectorSpan.Slice(0, remainder); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan); - this.FromVector4Destructive(configuration, vectorSpan, d); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs index ff374a9ac..5e9e42032 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Processing { /// @@ -29,9 +31,11 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) { + Size size = source.GetCurrentSize(); var options = new ResizeOptions { - Size = new Size(width, height), + // Prevent downsizing. + Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)), Mode = ResizeMode.BoxPad, Sampler = KnownResamplers.NearestNeighbor, PadColor = color diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index c9dda5f6b..f4bda8bf4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(nint destIdx) => ref this.kernels[destIdx]; /// /// Computes the weights to apply at each pixel when resizing. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index c0bf9291e..b34675896 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -61,9 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle destinationRectangle = this.destinationRectangle; bool compand = this.options.Compand; bool premultiplyAlpha = this.options.PremultiplyAlpha; + TPixel fillColor = this.options.PadColor.ToPixel(); bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) && this.options.PadColor != default; - TPixel fillColor = this.options.PadColor.ToPixel(); // Handle resize dimensions identical to the original if (source.Width == destination.Width @@ -209,21 +209,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // To reintroduce parallel processing, we would launch multiple workers // for different row intervals of the image. - using (var worker = new ResizeWorker( + using var worker = new ResizeWorker( configuration, sourceRegion, conversionModifiers, horizontalKernelMap, verticalKernelMap, - destination.Width, interest, - destinationRectangle.Location)) - { - worker.Initialize(); + destinationRectangle.Location); + worker.Initialize(); - var workingInterval = new RowInterval(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); - } + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); } private readonly struct NNRowOperation : IRowOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 90cbf8bda..45f35b7d9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernelMap verticalKernelMap; - private readonly int destWidth; - private readonly Rectangle targetWorkingRect; private readonly Point targetOrigin; @@ -57,7 +55,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms PixelConversionModifiers conversionModifiers, ResizeKernelMap horizontalKernelMap, ResizeKernelMap verticalKernelMap, - int destWidth, Rectangle targetWorkingRect, Point targetOrigin) { @@ -67,7 +64,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.conversionModifiers = conversionModifiers; this.horizontalKernelMap = horizontalKernelMap; this.verticalKernelMap = verticalKernelMap; - this.destWidth = destWidth; this.targetWorkingRect = targetWorkingRect; this.targetOrigin = targetOrigin; @@ -80,19 +76,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, - destWidth, + targetWorkingRect.Width, workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( this.workerHeight, - destWidth, + targetWorkingRect.Width, preferContiguosImageBuffers: true, options: AllocationOptions.Clean); this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); - this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(destWidth); + this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(targetWorkingRect.Width); this.currentWindow = new RowInterval(0, this.workerHeight); } @@ -118,6 +114,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // When creating transposedFirstPassBuffer, we made sure it's contiguous: Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; + int width = this.targetWorkingRect.Width; for (int y = rowInterval.Min; y < rowInterval.Max; y++) { // Ensure offsets are normalized for cropping and padding. @@ -131,9 +130,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); int top = kernel.StartIndex - this.currentWindow.Min; + ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; - for (int x = 0; x < this.destWidth; x++) + for (nint x = 0; x < (right - left); x++) { ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.DangerousGetRowSpan(y); + Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } @@ -170,6 +170,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempRowSpan = this.tempRowBuffer.GetSpan(); Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; + int targetOriginX = this.targetOrigin.X; for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); @@ -184,13 +187,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) + for (nint x = left, z = 0; x < right; x++, z++) { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); // optimization for: // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 9665ca42d..d64eb15ae 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private void GenericBechmark() { this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream); + using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default); } [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs deleted file mode 100644 index 0e9bed1d9..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing.Imaging; -using System.IO; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg -{ - public class EncodeJpeg - { - [Params(75, 90, 100)] - public int Quality; - - private const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr; - - // System.Drawing - private SDImage bmpDrawing; - private Stream bmpStream; - private ImageCodecInfo jpegCodec; - private EncoderParameters encoderParameters; - - // ImageSharp - private Image bmpCore; - private JpegEncoder encoder420; - private JpegEncoder encoder444; - - private MemoryStream destinationStream; - - [GlobalSetup] - public void ReadImages() - { - if (this.bmpStream == null) - { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - - this.bmpCore = Image.Load(this.bmpStream); - this.bmpCore.Metadata.ExifProfile = null; - this.encoder420 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 }; - this.encoder444 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio444 }; - - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - this.jpegCodec = GetEncoder(ImageFormat.Jpeg); - this.encoderParameters = new EncoderParameters(1); - - // Quality cast to long is necessary -#pragma warning disable IDE0004 // Remove Unnecessary Cast - this.encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)this.Quality); -#pragma warning restore IDE0004 // Remove Unnecessary Cast - - this.destinationStream = new MemoryStream(); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - - this.encoderParameters.Dispose(); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Jpeg 4:2:0")] - public void JpegSystemDrawing() - { - this.bmpDrawing.Save(this.destinationStream, this.jpegCodec, this.encoderParameters); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } - - [Benchmark(Description = "ImageSharp Jpeg 4:2:0")] - public void JpegCore420() - { - this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder420); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } - - [Benchmark(Description = "ImageSharp Jpeg 4:4:4")] - public void JpegCore444() - { - this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder444); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } - - // https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparameter?redirectedfrom=MSDN&view=net-5.0 - private static ImageCodecInfo GetEncoder(ImageFormat format) - { - ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); - foreach (ImageCodecInfo codec in codecs) - { - if (codec.FormatID == format.Guid) - { - return codec; - } - } - - return null; - } - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - - -| Method | Quality | Mean | Error | StdDev | Ratio | -|---------------------------- |-------- |---------:|---------:|---------:|------:| -| 'System.Drawing Jpeg 4:2:0' | 75 | 30.04 ms | 0.540 ms | 0.479 ms | 1.00 | -| 'ImageSharp Jpeg 4:2:0' | 75 | 19.32 ms | 0.290 ms | 0.257 ms | 0.64 | -| 'ImageSharp Jpeg 4:4:4' | 75 | 26.76 ms | 0.332 ms | 0.294 ms | 0.89 | -| | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 90 | 32.82 ms | 0.184 ms | 0.163 ms | 1.00 | -| 'ImageSharp Jpeg 4:2:0' | 90 | 25.00 ms | 0.408 ms | 0.361 ms | 0.76 | -| 'ImageSharp Jpeg 4:4:4' | 90 | 31.83 ms | 0.636 ms | 0.595 ms | 0.97 | -| | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 100 | 39.30 ms | 0.359 ms | 0.318 ms | 1.00 | -| 'ImageSharp Jpeg 4:2:0' | 100 | 34.49 ms | 0.265 ms | 0.235 ms | 0.88 | -| 'ImageSharp Jpeg 4:4:4' | 100 | 56.40 ms | 0.565 ms | 0.501 ms | 1.44 | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs new file mode 100644 index 000000000..2c4686edd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SkiaSharp; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + /// + /// Benchmark for performance comparison between other codecs. + /// + /// + /// This benchmarks tests baseline 4:2:0 chroma sampling path. + /// + public class EncodeJpegComparison + { + // Big enough, 4:4:4 chroma sampling + private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; + + // Change/add parameters for extra benchmarks + [Params(75, 90, 100)] + public int Quality; + + private MemoryStream destinationStream; + + // ImageSharp + private Image imageImageSharp; + private JpegEncoder encoderImageSharp; + + // SkiaSharp + private SKBitmap imageSkiaSharp; + + [GlobalSetup(Target = nameof(BenchmarkImageSharp))] + public void SetupImageSharp() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + + this.imageImageSharp = Image.Load(imageBinaryStream); + this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 }; + + this.destinationStream = new MemoryStream(); + } + + [GlobalCleanup(Target = nameof(BenchmarkImageSharp))] + public void CleanupImageSharp() + { + this.imageImageSharp.Dispose(); + this.imageImageSharp = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + } + + [Benchmark(Description = "ImageSharp")] + public void BenchmarkImageSharp() + { + this.imageImageSharp.SaveAsJpeg(this.destinationStream, this.encoderImageSharp); + this.destinationStream.Seek(0, SeekOrigin.Begin); + } + + [GlobalSetup(Target = nameof(BenchmarkSkiaSharp))] + public void SetupSkiaSharp() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + + this.imageSkiaSharp = SKBitmap.Decode(imageBinaryStream); + + this.destinationStream = new MemoryStream(); + } + + [GlobalCleanup(Target = nameof(BenchmarkSkiaSharp))] + public void CleanupSkiaSharp() + { + this.imageSkiaSharp.Dispose(); + this.imageSkiaSharp = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + } + + [Benchmark(Description = "SkiaSharp")] + public void BenchmarkSkiaSharp() + { + this.imageSkiaSharp.Encode(SKEncodedImageFormat.Jpeg, this.Quality).SaveTo(this.destinationStream); + this.destinationStream.Seek(0, SeekOrigin.Begin); + } + } +} + +/* +BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 +Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores +.NET SDK=6.0.100-preview.3.21202.5 + [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT + DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT + + +| Method | Quality | Mean | Error | StdDev | +|----------- |-------- |----------:|----------:|----------:| +| ImageSharp | 75 | 6.820 ms | 0.0374 ms | 0.0312 ms | +| SkiaSharp | 75 | 16.417 ms | 0.3238 ms | 0.4747 ms | +| ImageSharp | 90 | 7.849 ms | 0.1565 ms | 0.3126 ms | +| SkiaSharp | 90 | 16.893 ms | 0.2200 ms | 0.2058 ms | +| ImageSharp | 100 | 11.016 ms | 0.2087 ms | 0.1850 ms | +| SkiaSharp | 100 | 20.410 ms | 0.2583 ms | 0.2290 ms | +*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs new file mode 100644 index 000000000..83fb556d5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + /// + /// Benchmark for all available encoding features of the Jpeg file type. + /// + /// + /// This benchmark does NOT compare ImageSharp to any other jpeg codecs. + /// + public class EncodeJpegFeatures + { + // Big enough, 4:4:4 chroma sampling + // No metadata + private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; + + public static IEnumerable ColorSpaceValues => + new[] { JpegColorType.Luminance, JpegColorType.Rgb, JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio444 }; + + [Params(75, 90, 100)] + public int Quality; + + [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] + public JpegColorType TargetColorSpace; + + private Image bmpCore; + private JpegEncoder encoder; + + private MemoryStream destinationStream; + + [GlobalSetup] + public void Setup() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + this.bmpCore = Image.Load(imageBinaryStream); + this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace }; + this.destinationStream = new MemoryStream(); + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpCore.Dispose(); + this.bmpCore = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + } + + [Benchmark] + public void Benchmark() + { + this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder); + this.destinationStream.Seek(0, SeekOrigin.Begin); + } + } +} + +/* +BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 +Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores +.NET SDK=6.0.100-preview.3.21202.5 + [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT + DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT + + +| Method | TargetColorSpace | Quality | Mean | Error | StdDev | +|---------- |----------------- |-------- |----------:|----------:|----------:| +| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms | +| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms | +| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms | +| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms | +| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms | +| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms | +| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms | +| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms | +| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms | +| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms | +| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms | +| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms | +*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs deleted file mode 100644 index 71276597a..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg -{ - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded - { - protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; - - protected override IEnumerable SearchPatterns => new[] { "*.bmp", "*.jpg" }; - - [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] - public void EncodeJpegImageSharp() - => this.ForEachImageSharpImage((img, ms) => - { - img.Save(ms, new JpegEncoder()); - return null; - }); - - [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] - public void EncodeJpegSystemDrawing() - => this.ForEachSystemDrawingImage((img, ms) => - { - img.Save(ms, ImageFormat.Jpeg); - return null; - }); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index aae144ce0..7bb147cf9 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using var memoryStream = new MemoryStream(this.jpegBytes); var decoder = new JpegDecoder(); - return decoder.Identify(Configuration.Default, memoryStream); + return decoder.Identify(Configuration.Default, memoryStream, default); } } } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index eda054968..81a95cd1e 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -9,6 +10,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; @@ -124,6 +126,32 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, action); + public Task ForEachImageParallelAsync(Func action) + { + int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0 + ? this.MaxDegreeOfParallelism + : Environment.ProcessorCount; + int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism); + + List tasks = new(); + for (int i = 0; i < this.Images.Length; i += partitionSize) + { + int end = Math.Min(i + partitionSize, this.Images.Length); + Task task = RunPartition(i, end); + tasks.Add(task); + } + + return Task.WhenAll(tasks); + + Task RunPartition(int start, int end) => Task.Run(async () => + { + for (int i = start; i < end; i++) + { + await action(this.Images[i]); + } + }); + } + private void LogImageProcessed(int width, int height) { this.LastProcessedImageSize = new Size(width, height); @@ -197,6 +225,26 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave image.Save(output, this.imageSharpJpegEncoder); } + public async Task ImageSharpResizeAsync(string input) + { + using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); + // Resize it to fit a 150x150 square + using var image = await ImageSharpImage.LoadAsync(input); + this.LogImageProcessed(image.Width, image.Height); + + image.Mutate(i => i.Resize(new ResizeOptions + { + Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize), + Mode = ResizeMode.Max + })); + + // Reduce the size of the file + image.Metadata.ExifProfile = null; + + // Save the results + await image.SaveAsync(output, this.imageSharpJpegEncoder); + } + public void MagickResize(string input) { using var image = new MagickImage(input); diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index c7484daa0..95e64b153 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); Stopwatch timer; - if (options == null || !options.ImageSharp) + if (options == null || !(options.ImageSharp || options.AsyncImageSharp)) { RunBenchmarkSwitcher(lrs, out timer); } @@ -74,7 +74,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { for (int i = 0; i < options.RepeatCount; i++) { - lrs.ImageSharpBenchmarkParallel(); + if (options.AsyncImageSharp) + { + lrs.ImageSharpBenchmarkParallelAsync(); + } + else + { + lrs.ImageSharpBenchmarkParallel(); + } + + Console.WriteLine("OK"); } } catch (Exception ex) @@ -221,6 +230,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private class CommandLineOptions { + [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] + public bool AsyncImageSharp { get; set; } + [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] public bool ImageSharp { get; set; } @@ -277,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; public MemoryAllocator CreateMemoryAllocator() { @@ -330,11 +342,17 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } }); + private void ImageSharpBenchmarkParallelAsync() => + this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f)) + .GetAwaiter() + .GetResult(); + private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); + private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); diff --git a/tests/ImageSharp.Tests.ruleset b/tests/ImageSharp.Tests.ruleset index 673f00517..50c275cd7 100644 --- a/tests/ImageSharp.Tests.ruleset +++ b/tests/ImageSharp.Tests.ruleset @@ -1,6 +1,9 @@  - + + + + diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index f85bc78fc..71d0ab34a 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -557,7 +557,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new BmpDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index e3db8a7ea..42bdb3ba9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - using (Image image = GifDecoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream, default)) { Assert.Equal((200, 200), (image.Width, image.Height)); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 5699b4741..efabed5b2 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif input.Save(memoryStream, new GifEncoder()); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(2, metadata.Comments.Count); @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); } @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 840cc9f68..9864d62b8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Runtime.CompilerServices; -using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; @@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -102,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -118,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } @@ -131,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - using (Image image = JpegDecoder.Decode(Configuration.Default, stream)) + using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default)) { JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); @@ -153,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream); + IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType); } @@ -181,8 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { IImageInfo imageInfo = useIdentify - ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) - : decoder.Decode(Configuration.Default, stream); + ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default) + : decoder.Decode(Configuration.Default, stream, default); test(imageInfo); } @@ -302,6 +301,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Null(ex); } + [Theory] + [WithFile(TestImages.Jpeg.Issues.ExifNullArrayTag, PixelTypes.Rgba32)] + public void Clone_WithNullRationalArrayTag_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(JpegDecoder); + var clone = image.Metadata.ExifProfile.DeepClone(); + }); + Assert.Null(ex); + } + [Fact] public void EncodedStringTags_WriteAndRead() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index ccb3e3fb9..faf40ccf7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } - private static JpegDecoder JpegDecoder => new JpegDecoder(); + private static JpegDecoder JpegDecoder => new(); [Fact] public void ParseStream_BasicPropertiesAreCorrect() @@ -139,27 +139,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.IsType(ex.InnerException); } - [Theory] - [InlineData(0)] - [InlineData(0.5)] - [InlineData(0.9)] - public async Task DecodeAsync_IsCancellable(int percentageOfStreamReadToCancel) + [Fact] + public async Task DecodeAsync_IsCancellable() { var cts = new CancellationTokenSource(); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); pausedStream.OnWaiting(s => { - if (s.Position >= s.Length * percentageOfStreamReadToCancel) - { - cts.Cancel(); - pausedStream.Release(); - } - else - { - // allows this/next wait to unblock - pausedStream.Next(); - } + cts.Cancel(); + pausedStream.Release(); }); var config = Configuration.CreateDefaultInstance(); @@ -213,6 +202,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + // https://github.com/SixLabors/ImageSharp/issues/2057 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)] + public void Issue2057_DecodeWorks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(JpegDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 8b6432ac3..8f3217a58 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(Configuration.Default, memStream)); + Assert.Throws(() => decoder.Decode(Configuration.Default, memStream, default)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 3cc879d6b..c9b0b3c55 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); - Image image = decoder.Decode(Configuration.Default, stream); + Image image = decoder.Decode(Configuration.Default, stream, default); PngMetadata metadata = image.Metadata.GetPngMetadata(); Assert.Equal(pngColorType, metadata.ColorType); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 8db1d1aaf..fd39a828f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png input.Save(memoryStream, new PngEncoder()); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); VerifyTextDataIsPresent(meta); @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.Contains(meta.TextData, m => m.Equals(expectedText)); @@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new PngDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index e4f4d26b8..bd300f799 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -78,9 +78,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)] [WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)] - public void TiffDecoder_Planar(TestImageProvider provider) + public void TiffDecoder_CanDecode_Planar(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_64Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_96Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + + [Theory] + [WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_128Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] @@ -123,6 +156,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower10BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) @@ -139,11 +182,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower14BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)] @@ -158,6 +211,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_18Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] @@ -207,6 +285,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] + [WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] @@ -223,6 +312,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] @@ -238,12 +333,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] @@ -270,6 +377,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] @@ -305,6 +423,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 00a178c8f..6bcb7e7ad 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -18,19 +18,19 @@ namespace SixLabors.ImageSharp.Tests.IO /// public class ChunkedMemoryStreamTests { - private readonly MemoryAllocator allocator; + /// + /// The default length in bytes of each buffer chunk when allocating large buffers. + /// + private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb - public ChunkedMemoryStreamTests() - { - this.allocator = Configuration.Default.MemoryAllocator; - } + /// + /// The default length in bytes of each buffer chunk when allocating small buffers. + /// + private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb - [Fact] - public void MemoryStream_Ctor_InvalidCapacities() - { - Assert.Throws(() => new ChunkedMemoryStream(int.MinValue, this.allocator)); - Assert.Throws(() => new ChunkedMemoryStream(0, this.allocator)); - } + private readonly MemoryAllocator allocator; + + public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; [Fact] public void MemoryStream_GetPositionTest_Negative() @@ -61,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); + byte[] expected = ms.ToArray(); for (int i = 0; i < expected.Length; i++) { @@ -82,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -94,8 +94,8 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); - var buffer = new byte[2]; + byte[] expected = ms.ToArray(); + byte[] buffer = new byte[2]; for (int i = 0; i < expected.Length; i += 2) { cms.Read(buffer); @@ -105,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferSpanTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); + byte[] expected = ms.ToArray(); Span buffer = new byte[2]; for (int i = 0; i < expected.Length; i += 2) { @@ -257,24 +257,24 @@ namespace SixLabors.ImageSharp.Tests.IO public void MemoryStream_CopyTo_Invalid() { ChunkedMemoryStream memoryStream; - const string BufferSize = "bufferSize"; + const string bufferSize = nameof(bufferSize); using (memoryStream = new ChunkedMemoryStream(this.allocator)) { - const string Destination = "destination"; - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null)); + const string destination = nameof(destination); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null)); // Validate the destination parameter first. - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); // Then bufferSize. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); } // After the Stream is disposed, we should fail on all CopyTos. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); ChunkedMemoryStream disposedStream = memoryStream; @@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Tests.IO private MemoryStream CreateTestStream(int length) { - var buffer = new byte[length]; + byte[] buffer = new byte[length]; var random = new Random(); random.NextBytes(buffer); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 44d7daa74..9992c3055 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -63,12 +63,11 @@ namespace SixLabors.ImageSharp.Tests this.localImageFormatMock = new Mock(); var detector = new Mock(); - detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); - detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); this.localDecoder = detector.As(); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((c, s) => + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => { using (var ms = new MemoryStream()) { @@ -78,8 +77,8 @@ namespace SixLabors.ImageSharp.Tests }) .Returns(this.localStreamReturnImageRgba32); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((c, s) => + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => { using (var ms = new MemoryStream()) { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 72477a832..0d5eead4b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); } [Fact] @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs index 17b557f83..b08a82523 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); } [Fact] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 2a89c1d39..3e1b22e85 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -320,30 +320,51 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan())); } - public static readonly TheoryData Generic_To_Data = new TheoryData + public static readonly TheoryData Generic_To_Data = new() { + new TestPixel(), new TestPixel(), - new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), new TestPixel(), - new TestPixel(), - new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), new TestPixel(), - new TestPixel() + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), }; [Theory] [MemberData(nameof(Generic_To_Data))] - public void Generic_To(TestPixel dummy) + public void Generic_To(TestPixel _) where TDestPixel : unmanaged, IPixel { - const int Count = 2134; - TPixel[] source = CreatePixelTestData(Count); - var expected = new TDestPixel[Count]; + const int count = 2134; + TPixel[] source = CreatePixelTestData(count); + var expected = new TDestPixel[count]; PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan())); + TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); } [Theory] @@ -1234,23 +1255,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } // TODO: We really need a PixelTypeInfo.BitsPerComponent property!! - private static bool IsComplexPixel() + private static bool IsComplexPixel() => default(TDest) switch { - switch (default(TDest)) - { - case HalfSingle _: - case HalfVector2 _: - case L16 _: - case La32 _: - case NormalizedShort2 _: - case Rg32 _: - case Short2 _: - return true; - - default: - return Unsafe.SizeOf() > sizeof(int); - } - } + HalfSingle or HalfVector2 or L16 or La32 or NormalizedShort2 or Rg32 or Short2 => true, + _ => Unsafe.SizeOf() > sizeof(int), + }; } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index 438d5f2ed..bfd901b95 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -189,5 +189,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // assert Assert.Equal(expected, rgba.ToScaledVector4()); } + + [Fact] + public void Issue2048() + { + // https://github.com/SixLabors/ImageSharp/issues/2048 + RgbaVector green = Color.Green.ToPixel(); + using Image source = new(Configuration.Default, 1, 1, green); + using Image clone = source.CloneAs(); + + Rgba32 srcColor = default; + Rgba32 cloneColor = default; + source[0, 0].ToRgba32(ref srcColor); + clone[0, 0].ToRgba32(ref cloneColor); + + Assert.Equal(srcColor, cloneColor); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 6e275b824..b1ff3df08 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -472,7 +472,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad + Mode = ResizeMode.BoxPad, + PadColor = Color.HotPink }; image.Mutate(x => x.Resize(options)); @@ -580,7 +581,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad + Mode = ResizeMode.Pad, + PadColor = Color.Lavender }; image.Mutate(x => x.Resize(options)); diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 6c2b97eb6..efe958552 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -212,19 +212,16 @@ namespace SixLabors.ImageSharp.Tests public int HeaderSize => this.testFormat.HeaderSize; - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream, default).GetAwaiter().GetResult(); + => this.DecodeImpl(configuration, stream); - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream, cancellationToken); - private async Task> DecodeImpl(Configuration config, Stream stream, CancellationToken cancellationToken) + private Image DecodeImpl(Configuration config, Stream stream) where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); - await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken); + stream.CopyTo(ms, config.StreamProcessingBufferSize); byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { @@ -239,16 +236,10 @@ namespace SixLabors.ImageSharp.Tests public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); - - public IImageInfo Identify(Configuration configuration, Stream stream) => - this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult(); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeImpl(configuration, stream, cancellationToken); + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) => + this.DecodeImpl(configuration, stream); } public class TestEncoder : IImageEncoder diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 17ad0aca5..0550978fd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -262,6 +262,8 @@ namespace SixLabors.ImageSharp.Tests public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; + public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; + public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg"; public static class Fuzz { @@ -824,6 +826,9 @@ namespace SixLabors.ImageSharp.Tests public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff"; + public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff"; + public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff"; public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; @@ -848,6 +853,38 @@ namespace SixLabors.ImageSharp.Tests public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; + // Images with alpha channel. + public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; + public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; + public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; + public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; + public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; + public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; + public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff"; + public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; + public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; + public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; + public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; + public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; + public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; + public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; + public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; + public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff"; + public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index f0834dc00..8160118aa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; @@ -59,11 +58,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - => Task.FromResult(this.Decode(configuration, stream)); - - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { var bmpReadDefines = new BmpReadDefines @@ -84,13 +79,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth == 8 || magicFrame.Depth == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12) + if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); FromRgba32Bytes(configuration, data, framePixels); } - else if (magicFrame.Depth == 16 || magicFrame.Depth == 14) + else if (magicFrame.Depth is 16 or 14) { ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); @@ -105,9 +100,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new Image(configuration, new ImageMetadata(), framesList); } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 1eb1328ef..110814480 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -15,11 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => Task.FromResult(this.Decode(configuration, stream)); - - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) @@ -49,10 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => Task.FromResult(this.Identify(configuration, stream)); - - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) { @@ -61,9 +52,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index 7265e29c3..104c54cee 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; @@ -16,6 +17,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities public TestPixel(float red, float green, float blue, float alpha) { + Guard.MustBeBetweenOrEqualTo(red, 0F, 1F, nameof(red)); + Guard.MustBeBetweenOrEqualTo(green, 0F, 1F, nameof(green)); + Guard.MustBeBetweenOrEqualTo(blue, 0F, 1F, nameof(blue)); + Guard.MustBeBetweenOrEqualTo(alpha, 0F, 1F, nameof(alpha)); + this.Red = red; this.Green = green; this.Blue = blue; @@ -33,14 +39,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities public TPixel AsPixel() { var pix = default(TPixel); - pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + pix.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); return pix; } - internal Span AsSpan() - { - return new Span(new[] { this.AsPixel() }); - } + internal Span AsSpan() => new(new[] { this.AsPixel() }); public void Deserialize(IXunitSerializationInfo info) { @@ -58,9 +61,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities info.AddValue("alpha", this.Alpha); } - public override string ToString() - { - return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}"; - } + public override string ToString() => $"{typeof(TPixel).Name}{this.AsPixel()}"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 129d17f4d..4542257c6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Concurrent; using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -350,9 +349,6 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InvocationCountsAsync = - new ConcurrentDictionary(); - private static readonly object Monitor = new object(); private string callerName; @@ -365,35 +361,22 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCountsAsync[this.callerName]++; - return Task.FromResult(new Image(42, 42)); - } - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; - internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; - internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; - InvocationCountsAsync[name] = 0; } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } private class TestDecoderWithParameters : IImageDecoder @@ -401,9 +384,6 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InvocationCountsAsync = - new ConcurrentDictionary(); - private static readonly object Monitor = new object(); private string callerName; @@ -420,35 +400,22 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCountsAsync[this.callerName]++; - return Task.FromResult(new Image(42, 42)); - } - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; - internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; - internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; - InvocationCountsAsync[name] = 0; } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } } } diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png index 35d633c86..804c5dcf9 100644 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1642520a9d4491f55af5a83280423929529e3d528637538d148b9dc2ecdd6d2d -size 365839 +oid sha256:77086032bab11b91c68bc1686179063edbadc9d453574e4f087b2bbd677b4c8e +size 402367 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png index 690c2ad46..bfa048f82 100644 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:060826324dcd4baa1df1b075707a9bd9527cf87c1d156e3beb3d8abb499bdcd5 -size 361829 +oid sha256:5c0653aa2b726574fbea4cc308c269ff5e534d38bb48c0e77470c11042a395fd +size 400267 diff --git a/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg b/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg new file mode 100644 index 000000000..d1ffe4ac9 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7048dee15946bf981e5b0d2481ffcb8a64684fddca07172275b13a05f01b6b63 +size 1631109 diff --git a/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg b/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg new file mode 100644 index 000000000..9b5bc8303 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c52500be37a8ea1ee1caeb78c79e44b02e10912df4f6db65313c6745573c8ee +size 250451 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff new file mode 100644 index 000000000..f4a1a3f6b --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f78ef11e4044d13ea3bf699e33472a708df3a5cc817dc41edb4df184f127f2b +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff new file mode 100644 index 000000000..06beb72f3 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9c2d6f4e16677d9fdfb38cc2bfb7df05eedbb8dc0e3c26a6dba9b427c2c698a +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff new file mode 100644 index 000000000..94a944705 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53e9ff25da2a2a7a613328cfaf33799df51fe150586fb8de52070e8cc8830d97 +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff new file mode 100644 index 000000000..26d911272 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caff76e01bc39b7a295f01a11e3787a6487ac002af5586dd956166a9c91eb048 +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff new file mode 100644 index 000000000..f1bd4c405 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9193b6a194be970b2cfb26369fa487fd6ec2f1656af11df2e48f1d6b0971bbf8 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff new file mode 100644 index 000000000..b098877e0 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:888bc84af8dffc4565b215412a8a2bb56f0c78211a082b893d87595cd9f555c1 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff new file mode 100644 index 000000000..cfb5082f1 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eae92c012ad56c084929e0a2aff7c93091224d9f8ab7f52f71b845792d6b763 +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff new file mode 100644 index 000000000..5cd4d9c45 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbab54f221956215266c35bfd26fdfb123e092e3836e2401b9f24e1c5b23516e +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff new file mode 100644 index 000000000..c0f54d865 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd9fa514619604275cede0b4747291db2f8e5ad02095565c891ace2b537d6336 +size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff new file mode 100644 index 000000000..33f7bee0f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:915ca9bbda952fc9ac78b44be07dab603948d51fb1a274935905e73cfe5bb0b9 +size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff new file mode 100644 index 000000000..cfbc05832 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f650c49faed4fd19b5527a0771489110090948e4ed33daa53b42c1776e288d89 +size 59078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff new file mode 100644 index 000000000..0e11ef26c --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cad4c5f42d77539ce1f67efa7e0ed1fa4f5dd32b3269e5862453d878c6b18d7 +size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff new file mode 100644 index 000000000..cfb8beb2b --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8874322776b8620573c26a3c84b8c7c9bf0aeaa7d68a7fef009f8838d14dca5b +size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff new file mode 100644 index 000000000..aed59c331 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0d89ddcda8525799b90c1cb4a15f3ea1cf399c2259017f219b1d09876161587 +size 88478 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff new file mode 100644 index 000000000..7176c382f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be006e56c2c2f34686293e8a5f4397a7bf873ff927d4dd0808cac90310569254 +size 117878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff new file mode 100644 index 000000000..aad945b8e --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2928f06ef146625f5c696272901a63644699e3410dc155b7e4e470009a7f6dfc +size 147278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff new file mode 100644 index 000000000..9909afe71 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514ce84d3506aab7360b24f63aa1da1ea66abd9b1e534a12487d03486a7e593b +size 176678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff new file mode 100644 index 000000000..47270d98d --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:234401d70156cc748a67992919f8780bb855bc5e87e404b573f61b5eb4817dcf +size 266816 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff new file mode 100644 index 000000000..a24d74c19 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bc4325ce7be8a16d23c559300c79c3228c2f5a4c266844ba49763a32d29f10e +size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff new file mode 100644 index 000000000..b7393cb22 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08f8c284f7a9a6f362c09a418d85a94a1fe09bfc3f4cfe6859a82d6814718091 +size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff new file mode 100644 index 000000000..fd4171158 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba4da7d63931f4462113e00bdee9e66e333ca42a47a33f62057c248bf4696ef +size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff new file mode 100644 index 000000000..7aa6beeaa --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:862ec9153cc755aa3ece8965a9d001a630ff756dfb018a9474661730482964cb +size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff new file mode 100644 index 000000000..fb5dd1dcc --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc4f67dacd3262418769831aeabf99c9a88a9674fabf9a08c8b3d3e47ac6d07a +size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff new file mode 100644 index 000000000..6c45e96fa --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a98a0176f5db7af5b22c74f4a518c2df1055b5ca7e732f63426b3df8090fc313 +size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff new file mode 100644 index 000000000..f853cbc62 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2185ef9a84701bcff104d4f2fe40171ed853e5d02a2049b209ee2a4c65105ea9 +size 235502 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff new file mode 100644 index 000000000..9d985a33a --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:874ef7a59491ba68364312b7bc27b6620d15ce8b1d5b780f57c6e6d8b919ef1f +size 73922 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff new file mode 100644 index 000000000..b195cc15a --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4faa8617d10ea5f79225c528c0a6d5c36f73d315e46150703df5ca5008ea1bd +size 73922 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff new file mode 100644 index 000000000..492a8fae8 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a17791068b9c3eb40db3157a9103892aaf4a5a74072c93006bfa702ba5545e5 +size 80428 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff new file mode 100644 index 000000000..43075dc21 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63fef29d79f8d707c74b6e083de6bb2ad41dde1d9b1aea5bd7729a2f7399132e +size 80344 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff new file mode 100644 index 000000000..557b6216a --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d91f0740d6df983b5e5fe904c22fe86c2a7ffd86673fb078092d80c96359fc1 +size 53666 diff --git a/tests/Images/Input/Tiff/flower-rgb-3bit.tiff b/tests/Images/Input/Tiff/flower-rgb-3bit.tiff new file mode 100644 index 000000000..db5f7916c --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-3bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b778a97467b1475c47c71b5f029c23b0962309095b8702cfc81c8fbaf4b8edb +size 3886 diff --git a/tests/Images/Input/Tiff/flower-rgb-5bit.tiff b/tests/Images/Input/Tiff/flower-rgb-5bit.tiff new file mode 100644 index 000000000..af1bc3921 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-5bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a53ffce2bfd3f1010a1fe232c8010708458880230f11b75ea3ef16b5164ce1 +size 6208 diff --git a/tests/Images/Input/Tiff/flower-rgb-6bit.tiff b/tests/Images/Input/Tiff/flower-rgb-6bit.tiff new file mode 100644 index 000000000..b0399487d --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-6bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d1db5b448aa9d61dd38dfb86e91e04fd0779a9c68cc2073913bcee3c635b5fe +size 7412