diff --git a/src/Directory.Build.props b/src/Directory.Build.props index d211992a94..faa29865f2 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -27,6 +27,7 @@ + diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs index d83e737f12..89f18cff61 100644 --- a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs +++ b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Threading; namespace SixLabors.ImageSharp.Diagnostics @@ -47,6 +48,16 @@ namespace SixLabors.ImageSharp.Diagnostics } } + /// + /// Fires when ImageSharp allocates memory from a MemoryAllocator + /// + internal static event Action MemoryAllocated; + + /// + /// Fires when ImageSharp releases memory allocated from a MemoryAllocator + /// + internal static event Action MemoryReleased; + /// /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. /// @@ -54,11 +65,17 @@ namespace SixLabors.ImageSharp.Diagnostics internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; - internal static void IncrementTotalUndisposedAllocationCount() => + internal static void IncrementTotalUndisposedAllocationCount() + { Interlocked.Increment(ref totalUndisposedAllocationCount); + MemoryAllocated?.Invoke(); + } - internal static void DecrementTotalUndisposedAllocationCount() => + internal static void DecrementTotalUndisposedAllocationCount() + { Interlocked.Decrement(ref totalUndisposedAllocationCount); + MemoryReleased?.Invoke(); + } internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 41adc1cfff..ee0a312803 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -122,11 +122,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + Image image = null; try { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - var image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); + image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -193,8 +194,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp } catch (IndexOutOfRangeException e) { + image?.Dispose(); throw new ImageFormatException("Bitmap does not have a valid format.", e); } + catch + { + image?.Dispose(); + throw; + } } /// @@ -323,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); break; case RleSkippedPixelHandling.Transparent: - color.FromVector4(Vector4.Zero); + color.FromScaledVector4(Vector4.Zero); break; // Default handling for skipped pixels is black (which is what System.Drawing is also doing). default: - color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); break; } } @@ -395,12 +402,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); break; case RleSkippedPixelHandling.Transparent: - color.FromVector4(Vector4.Zero); + color.FromScaledVector4(Vector4.Zero); break; // Default handling for skipped pixels is black (which is what System.Drawing is also doing). default: - color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); break; } } @@ -1127,7 +1134,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp g * invMaxValueGreen, b * invMaxValueBlue, alpha); - color.FromVector4(vector4); + color.FromScaledVector4(vector4); } else { diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 16dca3324f..d17e89cd45 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private void ReadGraphicalControlExtension() { - this.stream.Read(this.buffer, 0, 6); + int bytesRead = this.stream.Read(this.buffer, 0, 6); + if (bytesRead != 6) + { + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); + } this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); } @@ -231,7 +235,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private void ReadImageDescriptor() { - this.stream.Read(this.buffer, 0, 9); + int bytesRead = this.stream.Read(this.buffer, 0, 9); + if (bytesRead != 9) + { + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); + } this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) @@ -245,7 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private void ReadLogicalScreenDescriptor() { - this.stream.Read(this.buffer, 0, 7); + int bytesRead = this.stream.Read(this.buffer, 0, 7); + if (bytesRead != 7) + { + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); + } this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 430adeb21d..532892e060 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -95,7 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - return this.pixelBuffer; + var buffer = this.pixelBuffer; + this.pixelBuffer = null; + return buffer; } /// @@ -210,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.rgbBuffer?.Dispose(); this.paddedProxyPixelRow?.Dispose(); + this.pixelBuffer?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ef4e3ffac2..533ffa719c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -228,11 +228,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Metadata = new ImageMetadata(); this.QuantizationTables = new Block8x8F[4]; this.scanDecoder = huffmanScanDecoder; + + if (tableBytes.Length < 4) + { + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); + } + using var ms = new MemoryStream(tableBytes); using var stream = new BufferedReadStream(this.Configuration, ms); // Check for the Start Of Image marker. - stream.Read(this.markerBuffer, 0, 2); + int bytesRead = stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { @@ -240,16 +246,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } // Read next marker. - stream.Read(this.markerBuffer, 0, 2); - byte marker = this.markerBuffer[1]; - fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); + bytesRead = stream.Read(this.markerBuffer, 0, 2); + fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2); while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { if (!fileMarker.Invalid) { // Get the marker length. - int remaining = this.ReadUint16(stream) - 2; + int markerContentByteSize = this.ReadUint16(stream) - 2; + + // Check whether stream actually has enought bytes to read + // markerContentByteSize is always positive so we cast + // to uint to avoid sign extension + if (stream.RemainingBytes < (uint)markerContentByteSize) + { + JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); + } switch (fileMarker.Marker) { @@ -259,13 +272,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.RST7: break; case JpegConstants.Markers.DHT: - this.ProcessDefineHuffmanTablesMarker(stream, remaining); + this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, remaining); + this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.DRI: - this.ProcessDefineRestartIntervalMarker(stream, remaining); + this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.EOI: return; @@ -273,7 +286,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } // Read next marker. - stream.Read(this.markerBuffer, 0, 2); + bytesRead = stream.Read(this.markerBuffer, 0, 2); + if (bytesRead != 2) + { + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); + } + fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); } } @@ -315,14 +333,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (!fileMarker.Invalid) { // Get the marker length. - int remaining = this.ReadUint16(stream) - 2; + int markerContentByteSize = this.ReadUint16(stream) - 2; + + // Check whether stream actually has enought bytes to read + // markerContentByteSize is always positive so we cast + // to uint to avoid sign extension + if (stream.RemainingBytes < (uint)markerContentByteSize) + { + JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); + } switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); + this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly); break; case JpegConstants.Markers.SOF5: @@ -350,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(stream, remaining); + this.ProcessStartOfScanMarker(stream, markerContentByteSize); break; } else @@ -364,41 +390,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (metadataOnly) { - stream.Skip(remaining); + stream.Skip(markerContentByteSize); } else { - this.ProcessDefineHuffmanTablesMarker(stream, remaining); + this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); } break; case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, remaining); + this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.DRI: if (metadataOnly) { - stream.Skip(remaining); + stream.Skip(markerContentByteSize); } else { - this.ProcessDefineRestartIntervalMarker(stream, remaining); + this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); } break; case JpegConstants.Markers.APP0: - this.ProcessApplicationHeaderMarker(stream, remaining); + this.ProcessApplicationHeaderMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(stream, remaining); + this.ProcessApp1Marker(stream, markerContentByteSize); break; case JpegConstants.Markers.APP2: - this.ProcessApp2Marker(stream, remaining); + this.ProcessApp2Marker(stream, markerContentByteSize); break; case JpegConstants.Markers.APP3: @@ -411,20 +437,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: - stream.Skip(remaining); + stream.Skip(markerContentByteSize); break; case JpegConstants.Markers.APP13: - this.ProcessApp13Marker(stream, remaining); + this.ProcessApp13Marker(stream, markerContentByteSize); break; case JpegConstants.Markers.APP14: - this.ProcessApp14Marker(stream, remaining); + this.ProcessApp14Marker(stream, markerContentByteSize); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: - stream.Skip(remaining); + stream.Skip(markerContentByteSize); break; case JpegConstants.Markers.DAC: @@ -722,7 +748,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { - int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; + const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; + if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata) + { + // Skip the application header length. + stream.Skip(remaining); + return; + } + stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes); remaining -= remainingXmpMarkerBytes; if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) @@ -1260,7 +1293,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int selectorsBytes = selectorsCount * 2; if (remaining != 4 + selectorsBytes) { - JpegThrowHelper.ThrowBadMarker("SOS", remaining); + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining); } // selectorsCount*2 bytes: component index + huffman tables indices @@ -1312,8 +1345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg component.ACHuffmanTableId = acTableIndex; } - // 3 bytes: Progressive scan decoding data - stream.Read(this.temp, 0, 3); + // 3 bytes: Progressive scan decoding data. + int bytesRead = stream.Read(this.temp, 0, 3); + if (bytesRead != 3) + { + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data"); + } int spectralStart = this.temp[0]; this.scanDecoder.SpectralStart = spectralStart; @@ -1336,7 +1373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ShortMethod)] private ushort ReadUint16(BufferedReadStream stream) { - stream.Read(this.markerBuffer, 0, 2); + int bytesRead = stream.Read(this.markerBuffer, 0, 2); + if (bytesRead != 2) + { + JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort."); + } + return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index b238e45ef3..1073ffff78 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ColdPath)] public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker."); + [MethodImpl(InliningOptions.ColdPath)] public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 497dc39674..12770bc521 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -227,10 +227,16 @@ namespace SixLabors.ImageSharp.Formats.Png return image; } + catch + { + image?.Dispose(); + throw; + } finally { this.scanline?.Dispose(); this.previousScanline?.Dispose(); + this.nextChunk?.Data?.Dispose(); } } @@ -472,6 +478,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } + this.previousScanline?.Dispose(); + this.scanline?.Dispose(); this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); this.scanline = this.Configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); } @@ -1359,6 +1367,7 @@ namespace SixLabors.ImageSharp.Formats.Png { if (chunk.Type == PngChunkType.Data) { + chunk.Data?.Dispose(); return chunk.Length; } @@ -1453,6 +1462,9 @@ namespace SixLabors.ImageSharp.Formats.Png if (validCrc != inputCrc) { string chunkTypeName = Encoding.ASCII.GetString(chunkType); + + // ensure when throwing we dispose the data back to the memory allocator + chunk.Data?.Dispose(); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index cfbc32f4f6..4e788c76af 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -65,7 +65,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None); // TODO: Should we pass through the CancellationToken from the tiff decoder? - CopyImageBytesToBuffer(buffer, spectralConverterGray.GetPixelBuffer(CancellationToken.None)); + using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None); + CopyImageBytesToBuffer(buffer, decompressedBuffer); break; } @@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); // TODO: Should we pass through the CancellationToken from the tiff decoder? - CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); + using var decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None); + CopyImageBytesToBuffer(buffer, decompressedBuffer); break; } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs index 0baf4c89c5..765f2c237d 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// Ccitt1D = 2, - /// - /// PackBits compression - /// - PackBits = 32773, - /// /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). /// @@ -65,27 +60,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants Deflate = 8, /// - /// Deflate compression - old. + /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// if this is chosen. /// - OldDeflate = 32946, + ItuTRecT82 = 9, /// - /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). + /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). /// /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// if this is chosen. /// - ItuTRecT82 = 9, + ItuTRecT43 = 10, /// - /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). + /// NeXT 2-bit Grey Scale compression algorithm. /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + NeXT = 32766, + + /// + /// PackBits compression. + /// + PackBits = 32773, + + /// + /// ThunderScan 4-bit compression. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, /// if this is chosen. /// - ItuTRecT43 = 10 + ThunderScan = 32809, + + /// + /// Deflate compression - old. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + OldDeflate = 32946, } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 5d910d16e7..e605629122 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 L16 l16 = TiffUtils.L16Default; var color = default(TPixel); - color.FromVector4(TiffUtils.Vector4Default); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index 4cda954804..7d230dfd5e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs index ee9bf8a9ce..c43b121caf 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); byte[] buffer = new byte[4]; int offset = 0; @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index 7367a78e34..00e4caef79 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index a40fa76675..59be3f8918 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int value = bitReader.ReadBits(this.bitsPerSample0); float intensity = value / this.factor; - color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); pixelRow[x] = color; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 29e03c6c6a..ad5793084b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation float r = colorMap[rOffset + i] / 65535F; float g = colorMap[gOffset + i] / 65535F; float b = colorMap[bOffset + i] / 65535F; - palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); + palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f)); } return palette; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index a4d725bcf4..6093690117 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } } else diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 1c61b0991c..76fed3c93e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } } else @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 4130ee1a29..addf576e95 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index e37fff1e74..2eda3b5af7 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs index bf1e65e1c7..02319bfa66 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs index cdc6942bd7..26f75bfcf8 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index 4dc3295a44..7fd98dd504 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; byte[] buffer = new byte[4]; @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(r, g, b, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(r, g, b, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index 54466e05bc..f1ff91382e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - color.FromVector4(new Vector4(r, g, b, 1.0f)); + color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); pixelRow[x] = color; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 4a887c426f..9d037cec74 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - color.FromVector4(new Vector4(r, g, b, 1.0f)); + color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); pixelRow[x] = color; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs index 7fd8d9879b..0340438cbf 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -2,6 +2,8 @@ // 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; @@ -18,15 +20,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly Configuration configuration; + private readonly MemoryAllocator memoryAllocator; + + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// /// The configuration. + /// The memory allocator. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian) + /// The type of the extra samples. + public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian) { this.configuration = configuration; this.isBigEndian = isBigEndian; + this.memoryAllocator = memoryAllocator; + this.extraSamplesType = extraSamplesType; } /// @@ -36,10 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; + using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; + Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); @@ -57,7 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); } } else @@ -69,6 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation pixelRow, pixelRow.Length); + if (hasAssociatedAlpha) + { + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); + } + offset += byteCount; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs index 705010ce90..856d810d31 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs @@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// 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; + /// The extra samples type. + /// If set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } /// public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) @@ -30,13 +37,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); Span alphaData = data[3].GetSpan(); + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; for (int y = top; y < top + height; y++) { @@ -52,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); } } else @@ -62,11 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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)); + ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2)); offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + 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 index 71e1f7abd6..2ce30252f5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// + /// The type of the extra samples. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) @@ -28,8 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); + + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; @@ -58,7 +68,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } else @@ -81,7 +93,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + 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 index 03b78c3f87..89172cfe72 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// + /// The extra samples type. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } /// public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) @@ -29,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; @@ -39,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation Span alphaData = data[3].GetSpan(); Span bufferSpan = buffer.Slice(bufferStartIdx); + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; for (int y = top; y < top + height; y++) { @@ -58,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } else @@ -76,7 +86,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + 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 index fbd18ca3e0..8ee9eb0bf9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs @@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// + /// The type of the extra samples. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) @@ -28,7 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); + + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; for (int y = top; y < top + height; y++) @@ -51,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } } else @@ -70,7 +81,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + 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 index e231111590..c98ac1cf00 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs @@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly TiffExtraSampleType? extraSamplesType; + /// /// Initializes a new instance of the class. /// + /// The extra samples type. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } /// public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) @@ -29,13 +36,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); Span alphaData = data[3].GetSpan(); + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; int offset = 0; for (int y = top; y < top + height; y++) { @@ -51,7 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } } else @@ -65,7 +75,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + 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 index 491a42fb75..967a68ad0c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs @@ -2,6 +2,9 @@ // 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; @@ -15,13 +18,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly Configuration configuration; - public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration; + private readonly MemoryAllocator memoryAllocator; + + private readonly TiffExtraSampleType? extraSamplesType; + + public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType) + { + this.configuration = configuration; + this.memoryAllocator = memoryAllocator; + this.extraSamplesType = extraSamplesType; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { int offset = 0; + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + var color = default(TPixel); + color.FromScaledVector4(TiffUtils.Vector4Default); + using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; + Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); @@ -32,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation pixelRow, pixelRow.Length); + if (hasAssociatedAlpha) + { + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); + } + offset += byteCount; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs index 4fb0797dc5..f95045ec5a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; byte[] buffer = new byte[4]; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(r, g, b, a); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(r, g, b, a); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs index f2ccf2ec81..e2dbdfb00a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -32,7 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly ushort bitsPerSampleA; - public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample) + private readonly TiffExtraSampleType? extraSampleType; + + public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) { this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleG = bitsPerSample.Channel1; @@ -43,6 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + + this.extraSampleType = extraSampleType; } /// @@ -57,6 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) { var color = default(TPixel); + bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData; var rBitReader = new BitReader(data[0].GetSpan()); var gBitReader = new BitReader(data[1].GetSpan()); @@ -73,7 +78,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; - color.FromVector4(new Vector4(r, g, b, a)); + var vec = new Vector4(r, g, b, a); + if (hasAssociatedAlpha) + { + color = TiffUtils.UnPremultiply(ref vec, color); + } + else + { + color.FromScaledVector4(vec); + } + pixelRow[x] = color; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs index 8bec7da89d..74b816dbc9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -31,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly ushort bitsPerSampleA; - public RgbaTiffColor(TiffBitsPerSample bitsPerSample) + private readonly TiffExtraSampleType? extraSamplesType; + + public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) { this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleG = bitsPerSample.Channel1; @@ -42,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + + this.extraSamplesType = extraSampleType; } /// @@ -51,6 +55,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var bitReader = new BitReader(data); + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); @@ -61,8 +67,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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; + var vec = new Vector4(r, g, b, a); + if (hasAssociatedAlpha) + { + pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color); + } + else + { + color.FromScaledVector4(vec); + 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 1c608e3033..c95d039461 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation MemoryAllocator memoryAllocator, TiffColorType colorType, TiffBitsPerSample bitsPerSample, + TiffExtraSampleType? extraSampleType, ushort[] colorMap, Rational[] referenceBlackAndWhite, Rational[] ycbcrCoefficients, @@ -125,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 2, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb333: DebugGuard.IsTrue( @@ -146,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 3, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb444: DebugGuard.IsTrue( @@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 4, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb555: DebugGuard.IsTrue( @@ -188,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 5, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb666: DebugGuard.IsTrue( @@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 6, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb888: DebugGuard.IsTrue( @@ -230,7 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba8888TiffColor(configuration); + return new Rgba8888TiffColor(configuration, memoryAllocator, extraSampleType); case TiffColorType.Rgb101010: DebugGuard.IsTrue( @@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 10, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb121212: DebugGuard.IsTrue( @@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 12, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb141414: DebugGuard.IsTrue( @@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 14, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(bitsPerSample); + return new RgbaTiffColor(extraSampleType, bitsPerSample); case TiffColorType.Rgb161616: DebugGuard.IsTrue( @@ -314,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + return new Rgba16161616TiffColor(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.Rgb242424: DebugGuard.IsTrue( @@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 24, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + return new Rgba24242424TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.Rgb323232: DebugGuard.IsTrue( @@ -356,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 32, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + return new Rgba32323232TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.RgbFloat323232: DebugGuard.IsTrue( @@ -394,6 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation public static TiffBasePlanarColorDecoder CreatePlanar( TiffColorType colorType, TiffBitsPerSample bitsPerSample, + TiffExtraSampleType? extraSampleType, ushort[] colorMap, Rational[] referenceBlackAndWhite, Rational[] ycbcrCoefficients, @@ -408,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation case TiffColorType.Rgba8888Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaPlanarTiffColor(bitsPerSample); + return new RgbaPlanarTiffColor(extraSampleType, bitsPerSample); case TiffColorType.YCbCrPlanar: return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); @@ -419,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation case TiffColorType.Rgba16161616Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + return new Rgba16PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); case TiffColorType.Rgb242424Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); @@ -427,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation case TiffColorType.Rgba24242424Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + return new Rgba24PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); case TiffColorType.Rgb323232Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); @@ -435,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation case TiffColorType.Rgba32323232Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + return new Rgba32PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); default: throw TiffThrowHelper.InvalidColorType(colorType.ToString()); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 038281c998..d509776d7d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 L16 l16 = TiffUtils.L16Default; var color = default(TPixel); - color.FromVector4(TiffUtils.Vector4Default); + color.FromScaledVector4(TiffUtils.Vector4Default); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index 807023b6bf..fbf8130789 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; const uint maxValue = 0xFFFFFF; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs index 71323c7bae..40d1541c51 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); byte[] buffer = new byte[4]; int offset = 0; @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); pixelRow[x] = color; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs index e433956f09..fd908c1e9f 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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); + color.FromScaledVector4(TiffUtils.Vector4Default); const uint maxValue = 0xFFFFFFFF; int offset = 0; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index d692fc7897..e1e2ba9838 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int value = bitReader.ReadBits(this.bitsPerSample0); float intensity = 1.0f - (value / this.factor); - color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); pixelRow[x] = color; } diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index aa960b373c..8cb327a7b0 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -28,15 +28,6 @@ - The Decoder currently only supports decoding multiframe images, which have the same dimensions. - Some compression formats are not yet supported. See the list below. -### Deviations from the TIFF spec (to be fixed) - -- Decoder - - A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels) - - NB: Need to handle this for both planar and chunky data - - If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this - - Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows) - - Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB? - ### Compression Formats | |Encoder|Decoder|Comments | @@ -87,7 +78,7 @@ |Model | Y | Y | | |StripOffsets | Y | Y | | |Orientation | | - | Ignore. Many readers ignore this tag. | -|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | +|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample. | |RowsPerStrip | Y | Y | | |StripByteCounts | Y | Y | | |MinSampleValue | | | | @@ -105,7 +96,7 @@ |Artist | Y | Y | | |HostComputer | Y | Y | | |ColorMap | Y | Y | | -|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far | +|ExtraSamples | | Y | Unspecified alpha data is not supported. | |Copyright | Y | Y | | ### Extension TIFF Tags diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index e5b810738a..1cd3d2c0c1 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -118,9 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff public TiffFillOrder FillOrder { get; set; } /// - /// Gets or sets the extra samples, which can contain the alpha channel data. + /// Gets or sets the extra samples type. /// - public TiffExtraSampleType? ExtraSamples { get; set; } + public TiffExtraSampleType? ExtraSamplesType { get; set; } /// /// Gets or sets the JPEG tables when jpeg compression is used. @@ -157,40 +157,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.inputStream = stream; - var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); - - IEnumerable directories = reader.Read(); - this.byteOrder = reader.ByteOrder; - this.isBigTiff = reader.IsBigTiff; - var frames = new List>(); - foreach (ExifProfile ifd in directories) + try { - cancellationToken.ThrowIfCancellationRequested(); - ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); - frames.Add(frame); + this.inputStream = stream; + var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); + + IEnumerable directories = reader.Read(); + this.byteOrder = reader.ByteOrder; + this.isBigTiff = reader.IsBigTiff; - if (this.decodingMode is FrameDecodingMode.First) + foreach (ExifProfile ifd in directories) { - break; + cancellationToken.ThrowIfCancellationRequested(); + ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); + frames.Add(frame); + + if (this.decodingMode is FrameDecodingMode.First) + { + break; + } } - } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); - // TODO: Tiff frames can have different sizes. - ImageFrame root = frames[0]; - this.Dimensions = root.Size(); - foreach (ImageFrame frame in frames) - { - if (frame.Size() != root.Size()) + // TODO: Tiff frames can have different sizes. + ImageFrame root = frames[0]; + this.Dimensions = root.Size(); + foreach (ImageFrame frame in frames) { - TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); + if (frame.Size() != root.Size()) + { + TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); + } } + + return new Image(this.Configuration, metadata, frames); } + catch + { + foreach (ImageFrame f in frames) + { + f.Dispose(); + } - return new Image(this.Configuration, metadata, frames); + throw; + } } /// @@ -240,8 +252,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); - IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); - IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); + using IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); + using IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { @@ -262,8 +274,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff cancellationToken); } - stripOffsetsMemory?.Dispose(); - stripByteCountsMemory?.Dispose(); return frame; } @@ -375,6 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar( this.ColorType, this.BitsPerSample, + this.ExtraSamplesType, this.ColorMap, this.ReferenceBlackAndWhite, this.YcbcrCoefficients, @@ -456,6 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.memoryAllocator, this.ColorType, this.BitsPerSample, + this.ExtraSamplesType, this.ColorMap, this.ReferenceBlackAndWhite, this.YcbcrCoefficients, diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 23bc5f15f2..6baf71466a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -39,10 +39,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff } var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; - options.ExtraSamples = extraSamplesType; - if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData) + options.ExtraSamplesType = extraSamplesType; + if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData)) { - TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData."); + TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData."); } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index b7d4b6e7cc..532423c4f1 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) + public static TPixel ColorFromRgb64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel { rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); @@ -63,12 +63,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64Premultiplied(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); + var vec = rgba.ToVector4(); + return UnPremultiply(ref vec, color); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel { var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); return color; } @@ -76,17 +85,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils 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); + Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; + color.FromScaledVector4(colorVector); return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; + return UnPremultiply(ref colorVector, color); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel { var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); return color; } @@ -94,11 +111,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils 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); + Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; + color.FromScaledVector4(colorVector); return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; + return UnPremultiply(ref colorVector, color); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel @@ -113,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils where TPixel : unmanaged, IPixel { var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); return color; } @@ -122,7 +147,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils where TPixel : unmanaged, IPixel { var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); - color.FromVector4(colorVector); + color.FromScaledVector4(colorVector); + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel UnPremultiply(ref Vector4 vector, TPixel color) + where TPixel : unmanaged, IPixel + { + Numerics.UnPremultiply(ref vector); + color.FromScaledVector4(vector); + return color; } @@ -139,8 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return 0; } - int padding = subSampling - (valueToRoundUp % subSampling); - return padding; + return subSampling - (valueToRoundUp % subSampling); } } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 9d18e5d821..0e00f037ca 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -82,38 +82,47 @@ namespace SixLabors.ImageSharp.Formats.Webp public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.Metadata = new ImageMetadata(); - this.currentStream = stream; + Image image = null; + try + { + this.Metadata = new ImageMetadata(); + this.currentStream = stream; - uint fileSize = this.ReadImageHeader(); + uint fileSize = this.ReadImageHeader(); - using (this.webImageInfo = this.ReadVp8Info()) - { - if (this.webImageInfo.Features is { Animation: true }) + using (this.webImageInfo = this.ReadVp8Info()) { - WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); - } + if (this.webImageInfo.Features is { Animation: true }) + { + WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); + } - var image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (this.webImageInfo.IsLossless) - { - var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); - losslessDecoder.Decode(pixels, image.Width, image.Height); - } - else - { - var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); - lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); - } + image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (this.webImageInfo.IsLossless) + { + var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); + losslessDecoder.Decode(pixels, image.Width, image.Height); + } + else + { + var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); + } - // There can be optional chunks after the image data, like EXIF and XMP. - if (this.webImageInfo.Features != null) - { - this.ParseOptionalChunks(this.webImageInfo.Features); - } + // There can be optional chunks after the image data, like EXIF and XMP. + if (this.webImageInfo.Features != null) + { + this.ParseOptionalChunks(this.webImageInfo.Features); + } - return image; + return image; + } + } + catch + { + image?.Dispose(); + throw; } } @@ -190,7 +199,11 @@ namespace SixLabors.ImageSharp.Formats.Webp uint fileSize = this.ReadChunkSize(); // The first byte contains information about the image features used. - byte imageFeatures = (byte)this.currentStream.ReadByte(); + int imageFeatures = this.currentStream.ReadByte(); + if (imageFeatures == -1) + { + WebpThrowHelper.ThrowInvalidImageContentException("VP8X header doe not contain enough data"); + } // The first two bit of it are reserved and should be 0. if (imageFeatures >> 6 != 0) @@ -214,19 +227,34 @@ namespace SixLabors.ImageSharp.Formats.Webp features.Animation = (imageFeatures & (1 << 1)) != 0; // 3 reserved bytes should follow which are supposed to be zero. - this.currentStream.Read(this.buffer, 0, 3); + int bytesRead = this.currentStream.Read(this.buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("VP8X header does not contain enough data"); + } + if (this.buffer[0] != 0 || this.buffer[1] != 0 || this.buffer[2] != 0) { WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); } // 3 bytes for the width. - this.currentStream.Read(this.buffer, 0, 3); + bytesRead = this.currentStream.Read(this.buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the width"); + } + this.buffer[3] = 0; uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; // 3 bytes for the height. - this.currentStream.Read(this.buffer, 0, 3); + bytesRead = this.currentStream.Read(this.buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the height"); + } + this.buffer[3] = 0; uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; @@ -272,7 +300,12 @@ namespace SixLabors.ImageSharp.Formats.Webp this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; // VP8 data size (not including this 4 bytes). - this.currentStream.Read(this.buffer, 0, 4); + int bytesRead = this.currentStream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 data size"); + } + uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); // remaining counts the available image data payload. @@ -284,7 +317,12 @@ namespace SixLabors.ImageSharp.Formats.Webp // - A 3-bit version number. // - A 1-bit show_frame flag. // - A 19-bit field containing the size of the first data partition in bytes. - this.currentStream.Read(this.buffer, 0, 3); + bytesRead = this.currentStream.Read(this.buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 frame tag"); + } + uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); remaining -= 3; bool isNoKeyFrame = (frameTag & 0x1) == 1; @@ -312,13 +350,23 @@ namespace SixLabors.ImageSharp.Formats.Webp } // Check for VP8 magic bytes. - this.currentStream.Read(this.buffer, 0, 3); + bytesRead = this.currentStream.Read(this.buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes"); + } + if (!this.buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) { WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); } - this.currentStream.Read(this.buffer, 0, 4); + bytesRead = this.currentStream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the image width and height"); + } + uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer); uint width = tmp & 0x3fff; sbyte xScale = (sbyte)(tmp >> 6); @@ -429,54 +477,15 @@ namespace SixLabors.ImageSharp.Formats.Webp switch (chunkType) { case WebpChunkType.Iccp: - uint iccpChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) - { - this.currentStream.Skip((int)iccpChunkSize); - } - else - { - byte[] iccpData = new byte[iccpChunkSize]; - this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); - var profile = new IccProfile(iccpData); - if (profile.CheckIsValid()) - { - this.Metadata.IccProfile = profile; - } - } - + this.ReadIccProfile(); break; case WebpChunkType.Exif: - uint exifChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) - { - this.currentStream.Skip((int)exifChunkSize); - } - else - { - byte[] exifData = new byte[exifChunkSize]; - this.currentStream.Read(exifData, 0, (int)exifChunkSize); - var profile = new ExifProfile(exifData); - this.Metadata.ExifProfile = profile; - } - + this.ReadExifProfile(); break; case WebpChunkType.Xmp: - uint xmpChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) - { - this.currentStream.Skip((int)xmpChunkSize); - } - else - { - byte[] xmpData = new byte[xmpChunkSize]; - this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); - var profile = new XmpProfile(xmpData); - this.Metadata.XmpProfile = profile; - } - + this.ReadXmpProfile(); break; case WebpChunkType.Animation: @@ -488,7 +497,12 @@ namespace SixLabors.ImageSharp.Formats.Webp features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); int alphaDataSize = (int)(alphaChunkSize - 1); features.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); - this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); + int bytesRead = this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); + if (bytesRead != alphaDataSize) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha chunk"); + } + break; default: WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); @@ -514,22 +528,100 @@ namespace SixLabors.ImageSharp.Formats.Webp { // Read chunk header. WebpChunkType chunkType = this.ReadChunkType(); - uint chunkLength = this.ReadChunkSize(); - if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) { - byte[] exifData = new byte[chunkLength]; - this.currentStream.Read(exifData, 0, (int)chunkLength); - this.Metadata.ExifProfile = new ExifProfile(exifData); + this.ReadExifProfile(); + } + else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null) + { + this.ReadXmpProfile(); } else { - // Skip XMP chunk data or any duplicate EXIF chunk. + // Skip duplicate XMP or EXIF chunk. + uint chunkLength = this.ReadChunkSize(); this.currentStream.Skip((int)chunkLength); } } } + /// + /// Reads the EXIF profile from the stream. + /// + private void ReadExifProfile() + { + uint exifChunkSize = this.ReadChunkSize(); + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)exifChunkSize); + } + else + { + byte[] exifData = new byte[exifChunkSize]; + int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize); + if (bytesRead != exifChunkSize) + { + // Ignore invalid chunk. + return; + } + + var profile = new ExifProfile(exifData); + this.Metadata.ExifProfile = profile; + } + } + + /// + /// Reads the XMP profile the stream. + /// + private void ReadXmpProfile() + { + uint xmpChunkSize = this.ReadChunkSize(); + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)xmpChunkSize); + } + else + { + byte[] xmpData = new byte[xmpChunkSize]; + int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); + if (bytesRead != xmpChunkSize) + { + // Ignore invalid chunk. + return; + } + + var profile = new XmpProfile(xmpData); + this.Metadata.XmpProfile = profile; + } + } + + /// + /// Reads the ICCP chunk from the stream. + /// + private void ReadIccProfile() + { + uint iccpChunkSize = this.ReadChunkSize(); + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)iccpChunkSize); + } + else + { + byte[] iccpData = new byte[iccpChunkSize]; + int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); + if (bytesRead != iccpChunkSize) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); + } + + var profile = new IccProfile(iccpData); + if (profile.CheckIsValid()) + { + this.Metadata.IccProfile = profile; + } + } + } + /// /// Identifies the chunk type from the chunk. /// diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs index fffdd34101..5f063fd11a 100644 --- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs +++ b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs @@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Webp { internal static class WebpThrowHelper { + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + /// /// Cold path optimization for throwing -s /// diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 4ab7f312b2..2823b8ed6f 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO /// public override bool CanWrite { get; } = false; + /// + /// Gets remaining byte count available to read. + /// + public long RemainingBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.Length - this.Position; + } + /// /// Gets the underlying stream. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs index 5fd613b1f0..4ec9b3267e 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs @@ -80,7 +80,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } public static unsafe int Write(Encoding encoding, string value, Span destination) +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER || NET + => encoding.GetBytes(value.AsSpan(), destination); +#else { + if (value.Length == 0) + { + return 0; + } + fixed (char* c = value) { fixed (byte* b = destination) @@ -89,6 +97,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } } +#endif private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index e0ab8ecf95..665e4753a7 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -121,6 +121,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// + /// The . + /// + public Image CreateThumbnail() => this.CreateThumbnail(); + /// /// Returns the thumbnail in the EXIF profile when available. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 9b5e098c83..abfe835b16 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadValues64(List values, ulong offset) { - DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .Net Stream.Length can Int64.MaxValue."); + DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue."); this.Seek(offset); ulong count = this.ReadUInt64(); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index f6fadca33e..f74cf59493 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -187,12 +187,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && RuntimeEnvironment.IsNetCore) { // There's something wrong with the JIT in .NET Core 3.1 on certain - // MacOSX machines so we have to use different pipelines. + // macOS machines so we have to use different pipelines. // It's: // - Not reproducable locally // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // https://github.com/SixLabors/ImageSharp/pull/1591 - this.InvokeMacOSX(in rows, span); + this.InvokeMacOS(in rows, span); return; } @@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [ExcludeFromCodeCoverage] [MethodImpl(InliningOptions.ShortMethod)] - private void InvokeMacOSX(in RowInterval rows, Span span) + private void InvokeMacOS(in RowInterval rows, Span span) { Matrix3x2 matrix = this.matrix; TResampler sampler = this.sampler; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 26b970d8b5..9bb9c113d4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -186,12 +186,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && RuntimeEnvironment.IsNetCore) { // There's something wrong with the JIT in .NET Core 3.1 on certain - // MacOSX machines so we have to use different pipelines. + // macOS machines so we have to use different pipelines. // It's: // - Not reproducable locally // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // https://github.com/SixLabors/ImageSharp/pull/1591 - this.InvokeMacOSX(in rows, span); + this.InvokeMacOS(in rows, span); return; } @@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [ExcludeFromCodeCoverage] [MethodImpl(InliningOptions.ShortMethod)] - public void InvokeMacOSX(in RowInterval rows, Span span) + public void InvokeMacOS(in RowInterval rows, Span span) { Matrix4x4 matrix = this.matrix; TResampler sampler = this.sampler; diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 71d0ab34a4..43ec45a34f 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -20,6 +20,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; namespace SixLabors.ImageSharp.Tests.Formats.Bmp { [Trait("Format", "Bmp")] + [ValidateDisposedMemoryAllocations] public class BmpDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 1922b161f2..7a5241c5a8 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -18,6 +18,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Gif { [Trait("Format", "Gif")] + [ValidateDisposedMemoryAllocations] public class GifDecoderTests { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 70cbc3af72..d5e0f081bf 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -105,6 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, + TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085, }; private static readonly Dictionary CustomToleranceValues = new() diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 9864d62b8f..d9915f17d6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -179,11 +179,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - IImageInfo imageInfo = useIdentify - ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default) - : decoder.Decode(Configuration.Default, stream, default); - - test(imageInfo); + if (useIdentify) + { + IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default); + test(imageInfo); + } + else + { + using var img = decoder.Decode(Configuration.Default, stream, default); + test(img); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index faf40ccf7d..1faa6f0f4c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes [Trait("Format", "Jpg")] + [ValidateDisposedMemoryAllocations] public partial class JpegDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs new file mode 100644 index 0000000000..2bbce6cb1b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public partial class JpegEncoderTests + { + [Theory] + [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] + public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + var encoder = new JpegEncoder(); + var stream = new MemoryStream(); + + using Image image = provider.GetImage(JpegDecoder); + image.Save(stream, encoder); + }); + + Assert.Null(ex); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 18eae9fbd3..d860836e08 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -20,7 +20,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { [Trait("Format", "Jpg")] - public class JpegEncoderTests + public partial class JpegEncoderTests { private static JpegEncoder JpegEncoder => new(); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 97237bca59..eb3bc8c9a5 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -11,6 +11,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Pbm; namespace SixLabors.ImageSharp.Tests.Formats.Pbm { [Trait("Format", "Pbm")] + [ValidateDisposedMemoryAllocations] public class PbmDecoderTests { [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 752036126f..a4fcf63baf 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -19,6 +19,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { [Trait("Format", "Png")] + [ValidateDisposedMemoryAllocations] public partial class PngDecoderTests { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 55dc2ecdd8..e83c5a98cc 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -16,6 +16,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; namespace SixLabors.ImageSharp.Tests.Formats.Tga { [Trait("Format", "Tga")] + [ValidateDisposedMemoryAllocations] public class TgaDecoderTests { private static TgaDecoder TgaDecoder => new(); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs index aeadae2557..7e45edb6d1 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { public abstract class TiffDecoderBaseTester { - protected static TiffDecoder TiffDecoder => new TiffDecoder(); + protected static TiffDecoder TiffDecoder => new(); - protected static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + protected static MagickReferenceDecoder ReferenceDecoder => new(); protected static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index bd300f799a..9460f3a351 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -14,6 +14,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Trait("Format", "Tiff")] + [ValidateDisposedMemoryAllocations] public class TiffDecoderTests : TiffDecoderBaseTester { public static readonly string[] MultiframeTestImages = Multiframes; @@ -187,6 +188,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } + [Theory] [WithFile(Flower14BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) @@ -226,6 +244,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider) + + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } + [Theory] [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) @@ -236,6 +272,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } + [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] @@ -261,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel { // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.Net to our format with YCbCr? + // converting the pixel data from Magick.NET to our format with YCbCr? using Image image = provider.GetImage(); image.DebugSave(provider); } @@ -296,6 +349,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F); + } + [Theory] [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] @@ -318,6 +388,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } + [Theory] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] @@ -339,6 +427,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); + } + [Theory] [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] @@ -351,6 +457,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; + } + + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); + } + [Theory] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 1c92fdf335..f29fa5d793 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -13,6 +13,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] + [ValidateDisposedMemoryAllocations] public class WebpDecoderTests { private static WebpDecoder WebpDecoder => new(); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index eaa7fb5646..456b9a3f52 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Webp; @@ -150,5 +151,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.NotNull(actualExif); Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); } + + [Theory] + [WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)] + public void WebpDecoder_IgnoresInvalidExifChunk(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(); + }); + Assert.Null(ex); + } } } diff --git a/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs b/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs index 8074b8b150..5246f8f911 100644 --- a/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux)); } - else if (TestEnvironment.IsOSX) + else if (TestEnvironment.IsMacOS) { Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index a4044b906c..7683ee6889 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] + [ValidateDisposedMemoryAllocations] public void FromPixels(bool useSpan) { Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 357d02be4b..ce1f902e59 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -55,6 +55,8 @@ namespace SixLabors.ImageSharp.Tests static void RunTest(string formatInner) { + using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations(); + Configuration configuration = Configuration.Default.Clone(); configuration.PreferContiguousImageBuffers = true; IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index f748b7feb4..d1a8e25bb5 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Collection(nameof(NonParallelCollection))] public class NonParallel { - public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; + public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - // TODO: Investigate failures on MacOS. All handles are released after GC. + // TODO: Investigate failures on macOS. All handles are released after GC. // (It seems to happen more consistently on .NET 6.) - [ConditionalFact(nameof(IsNotMacOs))] + [ConditionalFact(nameof(IsNotMacOS))] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { if (!TestEnvironment.RunsOnCI) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index 7d98eff611..2976bf7fde 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -245,9 +245,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators cleanup.Register(b1); } - public static readonly bool IsNotMacOS = !TestEnvironment.IsOSX; + public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - // TODO: Investigate MacOS failures + // TODO: Investigate macOS failures [ConditionalTheory(nameof(IsNotMacOS))] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 414f991f70..0520c3c1fe 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -259,9 +259,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsOSX) + if (TestEnvironment.IsMacOS) { - // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 return; } @@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsOSX) + if (TestEnvironment.IsMacOS) { - // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 return; } diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs new file mode 100644 index 0000000000..13664ee9b2 --- /dev/null +++ b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading; +using SixLabors.ImageSharp.Diagnostics; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public static class MemoryAllocatorValidator + { + private static readonly AsyncLocal LocalInstance = new(); + + public static bool MonitoringAllocations => LocalInstance.Value != null; + + static MemoryAllocatorValidator() + { + MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated; + MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased; + } + + private static void MemoryDiagnostics_MemoryReleased() + { + TestMemoryDiagnostics backing = LocalInstance.Value; + if (backing != null) + { + backing.TotalRemainingAllocated--; + } + } + + private static void MemoryDiagnostics_MemoryAllocated() + { + TestMemoryDiagnostics backing = LocalInstance.Value; + if (backing != null) + { + backing.TotalAllocated++; + backing.TotalRemainingAllocated++; + } + } + + public static TestMemoryDiagnostics MonitorAllocations() + { + var diag = new TestMemoryDiagnostics(); + LocalInstance.Value = diag; + return diag; + } + + public static void StopMonitoringAllocations() => LocalInstance.Value = null; + + public static void ValidateAllocations(int expectedAllocationCount = 0) + => LocalInstance.Value?.Validate(expectedAllocationCount); + + public class TestMemoryDiagnostics : IDisposable + { + public int TotalAllocated { get; set; } + + public int TotalRemainingAllocated { get; set; } + + public void Validate(int expectedAllocationCount) + { + var count = this.TotalRemainingAllocated; + var pass = expectedAllocationCount == count; + Assert.True(pass, $"Expected a {expectedAllocationCount} undisposed buffers but found {count}"); + } + + public void Dispose() + { + this.Validate(0); + if (LocalInstance.Value == this) + { + StopMonitoringAllocations(); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index a859852279..1668b7a125 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -354,10 +354,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif TestProfile(profile); - using Image thumbnail = profile.CreateThumbnail(); + using Image thumbnail = profile.CreateThumbnail(); Assert.NotNull(thumbnail); Assert.Equal(256, thumbnail.Width); Assert.Equal(170, thumbnail.Height); + + using Image genericThumbnail = profile.CreateThumbnail(); + Assert.NotNull(genericThumbnail); + Assert.Equal(256, genericThumbnail.Width); + Assert.Equal(170, genericThumbnail.Height); } [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index b1ff3df08c..9d766e0585 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms }; private static readonly ImageComparer ValidatorComparer = - ImageComparer.TolerantPercentage(TestEnvironment.IsOSX && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); + ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); [Fact] public void Resize_PixelAgnostic() diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 2009d981dc..310056fb35 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -264,6 +264,7 @@ namespace SixLabors.ImageSharp.Tests 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 const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg"; public static class Fuzz { @@ -292,6 +293,7 @@ namespace SixLabors.ImageSharp.Tests public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; + public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg"; } } @@ -635,6 +637,7 @@ namespace SixLabors.ImageSharp.Tests { public const string Earth = "Webp/earth_lossy.webp"; public const string WithExif = "Webp/exif_lossy.webp"; + public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp"; public const string WithIccp = "Webp/lossy_with_iccp.webp"; public const string WithXmp = "Webp/xmp_lossy.webp"; public const string BikeSmall = "Webp/bike_lossless_small.webp"; @@ -863,20 +866,33 @@ namespace SixLabors.ImageSharp.Tests // Images with alpha channel. public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; + public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff"; public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; + public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff"; public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; + public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff"; public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; + public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff"; public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; + public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.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 Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff"; public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; + public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff"; public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; + public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff"; public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; + public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_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"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index e1cbf12ac9..220b78e48d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); - // TODO: We should add OSX. + // TODO: We should add macOS. sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); sb.Append(Environment.NewLine); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index 1801d6b590..fa25846748 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } var testFile = TestFile.Create(path); - Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); + using Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); if (useExactComparer) { ImageComparer.Exact.VerifySimilarity(magickImage, image); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index e2e7d73bc6..63c5ce31a2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; +using SixLabors.ImageSharp.Diagnostics; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -158,8 +159,13 @@ namespace SixLabors.ImageSharp.Tests return this.LoadImage(decoder); } - var key = new Key(this.PixelType, this.FilePath, decoder); + // do not cache so we can track allocation correctly when validating memory + if (MemoryAllocatorValidator.MonitoringAllocations) + { + return this.LoadImage(decoder); + } + var key = new Key(this.PixelType, this.FilePath, decoder); Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); return cachedImage.Clone(this.Configuration); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 3ccaf2ba37..df584173ee 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - internal static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 diff --git a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs new file mode 100644 index 0000000000..65ed990dd7 --- /dev/null +++ b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Reflection; +using Xunit.Sdk; + +namespace SixLabors.ImageSharp.Tests +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute + { + private readonly int expected = 0; + + public ValidateDisposedMemoryAllocationsAttribute() + : this(0) + { + } + + public ValidateDisposedMemoryAllocationsAttribute(int expected) + => this.expected = expected; + + public override void Before(MethodInfo methodUnderTest) + => MemoryAllocatorValidator.MonitorAllocations(); + + public override void After(MethodInfo methodUnderTest) + { + MemoryAllocatorValidator.ValidateAllocations(this.expected); + MemoryAllocatorValidator.StopMonitoringAllocations(); + } + } +} diff --git a/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg b/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg new file mode 100644 index 0000000000..e95ef7a73d --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d41a41180a3371d0c4a724b40a4c86f6f975dab6be9da96964a484818770394 +size 30715 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg new file mode 100644 index 0000000000..8a680ff6a6 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d478beff34179fda26238a44434607c276f55438ee96824c5af8c0188d358d8d +size 234 diff --git a/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff new file mode 100644 index 0000000000..d0d9f2d1ea --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8bc77670ea12cd163fbf21fa7dd6d6c10e3b8c1afc83f26618f92303d0cbfcf +size 235478 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff new file mode 100644 index 0000000000..01ff24afc0 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a9878183ebe84506810ea2edc6dc95cd76780f3847ac3a614ce2dcfb355ea9f +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff new file mode 100644 index 0000000000..a0eabbea2b --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b95334e1e4b78f5106cacd66d6dc9ad15d023c5449e9562b7489982c5336715 +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff new file mode 100644 index 0000000000..9348957077 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b1aa42b51bd5474cbf333d9a0b89634198250d3eb5de4cf3af2a8d846395bf0 +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff new file mode 100644 index 0000000000..50e9d9c83d --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a146f0bfcac158dfda1ba307737b39d472a95926ff172bc8c798557f2dd1e1b +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff new file mode 100644 index 0000000000..b58f097c1f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3190269edb721175e8f3788528bd84aee3a12b2c7fb970c02c98822452fc81b0 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff new file mode 100644 index 0000000000..cfe66071da --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d65b9b1172d125fece197c2bf332bff9074db2b443e74d1973a14dbe45865e8 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff new file mode 100644 index 0000000000..a6199845a5 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ee223455c3e2f748d4dc9a1446047adb3c547878b815504c785497d8c059d34 +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff new file mode 100644 index 0000000000..3bc030c509 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea0206508fe23dfb8653ac1706894e54a4ad980b3bf75b68e5deb9f2838a1de +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff new file mode 100644 index 0000000000..7a87e5ef8c --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03d1050a606b6fa9e909f54991ed12b1aa96b739e5f69f4b1bae7c903b1236ef +size 88478 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff new file mode 100644 index 0000000000..2679e58298 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41481c59d9b63ca946d9d2cf2ccc599d1e48ef6fdfa00926c1e6d5cdfd35eb47 +size 117878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff new file mode 100644 index 0000000000..1cdb3d475f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bc3b2b8031593ff9a79d7caccd2ed614cf8d788dedbefbd27d0a4f88399be4e +size 147278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff new file mode 100644 index 0000000000..3184bd90b3 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9c1c64ce9a002337c3f6d332b4a5bb3016718b672f9c95d8792b013545553c2 +size 176678 diff --git a/tests/Images/Input/Webp/exif_lossy.webp b/tests/Images/Input/Webp/exif_lossy.webp index 35e454b96f..5d6db3800f 100644 --- a/tests/Images/Input/Webp/exif_lossy.webp +++ b/tests/Images/Input/Webp/exif_lossy.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b -size 40765 +oid sha256:5c53967bfefcfece8cd4411740c1c394e75864ca61a7a9751df3b28e727c0205 +size 68646 diff --git a/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp b/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp new file mode 100644 index 0000000000..35e454b96f --- /dev/null +++ b/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b +size 40765