diff --git a/README.md b/README.md index 6b2fa5d0f5..14352bd80e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To clone ImageSharp locally, click the "Clone in [YOUR_OS]" button above or run git clone https://github.com/SixLabors/ImageSharp ``` -If working with Windows please ensure that you have enabled log file paths in git (run as Administrator). +If working with Windows please ensure that you have enabled long file paths in git (run as Administrator). ```bash git config --system core.longpaths true @@ -130,4 +130,4 @@ Become a bronze sponsor with a monthly donation of $100 and get your logo (small - \ No newline at end of file + diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index ddbac2d072..56afae68c7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// The float value at the specified index public float this[int idx] { - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { GuardBlockIndex(idx); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return Unsafe.Add(ref selfRef, idx); } - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { GuardBlockIndex(idx); diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index f35bb44682..16d24cf814 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public void Update(Buffer2D buffer, int startY) { // We don't actually have to assign values outside of the diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 422c7bd7ec..d26fbb936d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -315,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The packed bits. /// The number of bits /// The reference to the emitBuffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private void Emit(uint bits, uint count, ref byte emitBufferBase) { count += this.bitCount; @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The index of the Huffman encoder /// The value to encode. /// The reference to the emit buffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private void EmitHuff(HuffIndex index, int value, ref byte emitBufferBase) { uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; @@ -370,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The number of copies to encode. /// The value to encode. /// The reference to the emit buffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private void EmitHuffRLE(HuffIndex index, int runLength, int value, ref byte emitBufferBase) { int a = value; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs index f6a74c166a..0e73b9fb46 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs @@ -258,7 +258,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors { this.children.GetSpan().Fill(0); this.siblings.GetSpan().Fill(0); - this.bitsPerCode = MinBits; this.maxCode = MaxValue(this.bitsPerCode); this.nextValidCode = EoiCode + 1; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs new file mode 100644 index 0000000000..ebe7319413 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors +{ + /// + /// Represents a lzw string with a code word and a code length. + /// + public class LzwString + { + private static readonly LzwString Empty = new LzwString(0, 0, 0, null); + + private readonly LzwString previous; + private readonly byte value; + + /// + /// Initializes a new instance of the class. + /// + /// The code word. + public LzwString(byte code) + : this(code, code, 1, null) + { + } + + private LzwString(byte value, byte firstChar, int length, LzwString previous) + { + this.value = value; + this.FirstChar = firstChar; + this.Length = length; + this.previous = previous; + } + + /// + /// Gets the code length; + /// + public int Length { get; } + + /// + /// Gets the first character of the codeword. + /// + public byte FirstChar { get; } + + /// + /// Concatenates two code words. + /// + /// The code word to concatenate. + /// A concatenated lzw string. + public LzwString Concatenate(byte other) + { + if (this == Empty) + { + return new LzwString(other); + } + + return new LzwString(other, this.FirstChar, this.Length + 1, this); + } + + /// + /// Writes decoded pixel to buffer at a given position. + /// + /// The buffer to write to. + /// The position to write to. + /// The number of bytes written. + public int WriteTo(Span buffer, int offset) + { + if (this.Length == 0) + { + return 0; + } + + if (this.Length == 1) + { + buffer[offset] = this.value; + return 1; + } + + LzwString e = this; + var endIdx = this.Length - 1; + if (endIdx >= buffer.Length) + { + TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); + } + + for (int i = endIdx; i >= 0; i--) + { + buffer[offset + i] = e.value; + e = e.previous; + } + + return this.Length; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 82640dfed3..b85aa3a22b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { - var decoder = new TiffLzwDecoder(stream, this.Allocator); - decoder.DecodePixels(buffer.Length, 8, buffer); + var decoder = new TiffLzwDecoder(stream); + decoder.DecodePixels(buffer); if (this.Predictor == TiffPredictor.Horizontal) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs index d8150bea77..2f7ff0ee36 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs @@ -2,193 +2,254 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors { + /* + This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /// - /// Decompresses and decodes data using the dynamic LZW algorithms. + /// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13. /// - /// - /// This code is based on the used for GIF decoding. There is potential - /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW - /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is - /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial - /// byte indicating the length of the sub-block. In TIFF the data is written as a single block - /// with no length indicator (this can be determined from the 'StripByteCounts' entry). - /// internal sealed class TiffLzwDecoder { /// - /// The max decoder pixel stack size. + /// The stream to decode. /// - private const int MaxStackSize = 4096; + private readonly Stream stream; /// - /// The null code. + /// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode. + /// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again. /// - private const int NullCode = -1; + private const int ClearCode = 256; /// - /// The stream to decode. + /// End of Information. /// - private readonly Stream stream; + private const int EoiCode = 257; /// - /// The memory allocator. + /// Minimum code length of 9 bits. /// - private readonly MemoryAllocator allocator; + private const int MinBits = 9; + + /// + /// Maximum code length of 12 bits. + /// + private const int MaxBits = 12; + + /// + /// Maximum table size of 4096. + /// + private const int TableSize = 1 << MaxBits; + + private readonly LzwString[] table; + + private int tableLength; + private int bitsPerCode; + private int oldCode = ClearCode; + private int maxCode; + private int bitMask; + private int maxString; + private bool eofReached; + private int nextData; + private int nextBits; /// /// Initializes a new instance of the class /// and sets the stream, where the compressed data should be read from. /// /// The stream to read from. - /// The memory allocator. /// is null. - public TiffLzwDecoder(Stream stream, MemoryAllocator allocator) + public TiffLzwDecoder(Stream stream) { Guard.NotNull(stream, nameof(stream)); this.stream = stream; - this.allocator = allocator; + this.table = new LzwString[TableSize]; + for (int i = 0; i < 256; i++) + { + this.table[i] = new LzwString((byte)i); + } + + this.Init(); + } + + private void Init() + { + // Table length is 256 + 2, because of special clear code and end of information code. + this.tableLength = 258; + this.bitsPerCode = MinBits; + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + this.maxString = 1; } /// /// Decodes and decompresses all pixel indices from the stream. /// - /// The length of the compressed data. - /// Size of the data. /// The pixel array to decode to. - public void DecodePixels(int length, int dataSize, Span pixels) + public void DecodePixels(Span pixels) { - Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); - - // Initialize buffers - using IMemoryOwner prefixMemory = this.allocator.Allocate(MaxStackSize, AllocationOptions.Clean); - using IMemoryOwner suffixMemory = this.allocator.Allocate(MaxStackSize, AllocationOptions.Clean); - using IMemoryOwner pixelStackMemory = this.allocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); - - Span prefix = prefixMemory.GetSpan(); - Span suffix = suffixMemory.GetSpan(); - Span pixelStack = pixelStackMemory.GetSpan(); - - // Calculate the clear code. The value of the clear code is 2 ^ dataSize - int clearCode = 1 << dataSize; - - int codeSize = dataSize + 1; - - // Calculate the end code - int endCode = clearCode + 1; - - // Calculate the available code. - int availableCode = clearCode + 2; - - // Jillzhangs Code see: http://giflib.codeplex.com/ - // Adapted from John Cristy's ImageMagick. + // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. + // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ int code; - int oldCode = NullCode; - int codeMask = (1 << codeSize) - 1; - - int inputByte = 0; - int bits = 0; - - int top = 0; - int xyz = 0; - - int first = 0; + int offset = 0; - for (code = 0; code < clearCode; code++) + while ((code = this.GetNextCode()) != EoiCode) { - prefix[code] = 0; - suffix[code] = (byte)code; - } - - // Decoding process - while (xyz < length) - { - if (top == 0) + if (code == ClearCode) { - // Get the next code - int data = inputByte & ((1 << bits) - 1); + this.Init(); + code = this.GetNextCode(); - while (bits < codeSize) - { - inputByte = this.stream.ReadByte(); - data = (data << 8) | inputByte; - bits += 8; - } - - data >>= bits - codeSize; - bits -= codeSize; - code = data & codeMask; - - // Interpret the code - if (code > availableCode || code == endCode) + if (code == EoiCode) { break; } - if (code == clearCode) + if (this.table[code] == null) { - // Reset the decoder - codeSize = dataSize + 1; - codeMask = (1 << codeSize) - 1; - availableCode = clearCode + 2; - oldCode = NullCode; - continue; + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); } - if (oldCode == NullCode) + offset += this.table[code].WriteTo(pixels, offset); + } + else + { + if (this.table[this.oldCode] == null) { - pixelStack[top++] = suffix[code]; - oldCode = code; - first = code; - continue; + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); } - int inCode = code; - if (code == availableCode) + if (this.IsInTable(code)) { - pixelStack[top++] = (byte)first; + offset += this.table[code].WriteTo(pixels, offset); - code = oldCode; + this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); } - - while (code > clearCode) + else { - pixelStack[top++] = suffix[code]; - code = prefix[code]; + LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); + + offset += outString.WriteTo(pixels, offset); + this.AddStringToTable(outString); } + } - first = suffix[code]; + this.oldCode = code; - pixelStack[top++] = suffix[code]; + if (offset >= pixels.Length) + { + break; + } + } + } - if (availableCode < MaxStackSize) - { - prefix[availableCode] = oldCode; - suffix[availableCode] = first; - availableCode++; - if (availableCode > codeMask - 1 && availableCode < MaxStackSize) - { - codeSize++; - codeMask = (1 << codeSize) - 1; - } - } + private void AddStringToTable(LzwString lzwString) + { + if (this.tableLength > this.table.Length) + { + TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); + } + + this.table[this.tableLength++] = lzwString; + + if (this.tableLength > this.maxCode) + { + this.bitsPerCode++; - oldCode = inCode; + if (this.bitsPerCode > MaxBits) + { + // Continue reading MaxBits (12 bit) length codes. + this.bitsPerCode = MaxBits; } - // Pop a pixel off the pixel stack. - top--; + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + } + + if (lzwString.Length > this.maxString) + { + this.maxString = lzwString.Length; + } + } + + private int GetNextCode() + { + if (this.eofReached) + { + return EoiCode; + } - // Clear missing pixels - pixels[xyz++] = (byte)pixelStack[top]; + int read = this.stream.ReadByte(); + if (read < 0) + { + this.eofReached = true; + return EoiCode; } + + this.nextData = (this.nextData << 8) | read; + this.nextBits += 8; + + if (this.nextBits < this.bitsPerCode) + { + read = this.stream.ReadByte(); + if (read < 0) + { + this.eofReached = true; + return EoiCode; + } + + this.nextData = (this.nextData << 8) | read; + this.nextBits += 8; + } + + var code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; + this.nextBits -= this.bitsPerCode; + + return code; } + + private bool IsInTable(int code) => code < this.tableLength; + + private int MaxCode() => this.bitMask - 1; + + private static int BitmaskFor(int bits) => (1 << bits) - 1; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 979206ad5c..66f885f23a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Vector4 ConvolveCore(ref Vector4 rowStartRef) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Fma.IsSupported) + if (Avx2.IsSupported && Fma.IsSupported) { float* bufferStart = this.bufferPtr; float* bufferEnd = bufferStart + (this.Length & ~3); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index 81a5604f1e..5a9ceea946 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -16,17 +16,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private Stream bmpStream; private SDImage bmpDrawing; private Image bmpCore; + private MemoryStream destinationStream; [GlobalSetup] public void ReadImages() { if (this.bmpStream == null) { - const string TestImage = TestImages.Bmp.NegHeight; + const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr; this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.bmpCore = Image.Load(this.bmpStream); + this.bmpCore.Metadata.ExifProfile = null; this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); + this.destinationStream = new MemoryStream(); } } @@ -42,15 +45,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { - using var stream = new MemoryStream(); - this.bmpDrawing.Save(stream, ImageFormat.Jpeg); + this.bmpDrawing.Save(this.destinationStream, ImageFormat.Jpeg); + this.destinationStream.Seek(0, SeekOrigin.Begin); } [Benchmark(Description = "ImageSharp Jpeg")] public void JpegCore() { - using var stream = new MemoryStream(); - this.bmpCore.SaveAsJpeg(stream); + this.bmpCore.SaveAsJpeg(this.destinationStream); + this.destinationStream.Seek(0, SeekOrigin.Begin); } } } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 5504a99784..50a930b6f1 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -31,14 +31,21 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { + RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); - RunDecodeJpegProfilingTests(); + // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); Console.ReadLine(); } + private static void RunJpegEncoderProfilingTests() + { + var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); + benchmarks.EncodeJpeg_SingleMidSize(); + } + private static void RunJpegColorProfilingTests() { new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index fcce507d8d..9108cdaf8a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression [Theory] [InlineData(new byte[] { })] [InlineData(new byte[] { 42 })] // One byte + [InlineData(new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 })] [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index b9368e4ca6..6efe92b232 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -196,79 +196,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.None }; - - this.TiffEncoderPaletteTest(provider, encoder); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate }; - - this.TiffEncoderPaletteTest(provider, encoder); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate, UseHorizontalPredictor = true }; - - this.TiffEncoderPaletteTest(provider, encoder); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, usePredictor: true, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works_Bug(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Lzw }; - - this.TiffEncoderPaletteTest(provider, encoder); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works_Bug(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Lzw, UseHorizontalPredictor = true }; - - Assert.Throws(() => this.TiffEncoderPaletteTest(provider, encoder)); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, usePredictor: true, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.PackBits }; - - this.TiffEncoderPaletteTest(provider, encoder); - } - - private void TiffEncoderPaletteTest(TestImageProvider provider, TiffEncoder encoder) - where TPixel : unmanaged, IPixel - { - // Because a quantizer is used to create the palette (and therefore changes to the original are expected), - // we do not compare the encoded image against the original: - // Instead we load the encoded image with a reference decoder and compare against that image. - using Image image = provider.GetImage(); - using var memStream = new MemoryStream(); - - image.Save(memStream, encoder); - memStream.Position = 0; - - using var encodedImage = (Image)Image.Load(Configuration, memStream); - var encodedImagePath = provider.Utility.SaveTestOutputFile(encodedImage, "tiff", encoder); - TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage); - } + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index bf412739d9..5f426083c2 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms @@ -85,5 +87,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(compand, resizeOptions.Compand); Assert.Equal(mode, resizeOptions.Mode); } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void HwIntrinsics_Resize() + { + static void RunTest() + { + using var image = new Image(50, 50); + image.Mutate(img => img.Resize(25, 25)); + + Assert.Equal(25, image.Width); + Assert.Equal(25, image.Height); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA); + } +#endif } } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index 78fb99802e..9de3fc8dfe 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -75,6 +75,21 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks #pragma warning restore SA1515 // Single-line comment should be preceded by blank line } + [Fact(Skip = ProfilingSetup.SkipProfilingTests)] + public void EncodeJpeg_SingleMidSize() + { + string path = TestFile.GetInputFileFullPath(TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr); + using var image = Image.Load(path); + image.Metadata.ExifProfile = null; + + using var ms = new MemoryStream(); + for (int i = 0; i < 30; i++) + { + image.SaveAsJpeg(ms); + ms.Seek(0, SeekOrigin.Begin); + } + } + // Benchmark, enable manually! [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [InlineData(1, 75, JpegSubsample.Ratio420)] diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs index 4cbbefe686..a2f36c85a8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs @@ -25,9 +25,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [MemberData(nameof(Intrinsics))] - public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedItrinsics, string[] expectedValues) + public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues) { - Dictionary features = expectedItrinsics.ToFeatureKeyValueCollection(); + Dictionary features = expectedIntrinsics.ToFeatureKeyValueCollection(); HwIntrinsics[] keys = features.Keys.ToArray(); HwIntrinsics actualIntrinsics = keys[0]; @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests actualIntrinsics |= keys[i]; } - Assert.Equal(expectedItrinsics, actualIntrinsics); + Assert.Equal(expectedIntrinsics, actualIntrinsics); IEnumerable actualValues = features.Select(x => x.Value); Assert.Equal(expectedValues, actualValues);