diff --git a/src/ImageSharp/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs index 7eb3f4516f..1f3cbbca64 100644 --- a/src/ImageSharp/Compression/Zlib/Adler32.cs +++ b/src/ImageSharp/Compression/Zlib/Adler32.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -31,6 +32,8 @@ namespace SixLabors.ImageSharp.Compression.Zlib #if SUPPORTS_RUNTIME_INTRINSICS private const int MinBufferSize = 64; + private const int BlockSize = 1 << 5; + // The C# compiler emits this as a compile-time constant embedded in the PE file. private static ReadOnlySpan Tap1Tap2 => new byte[] { @@ -63,6 +66,11 @@ namespace SixLabors.ImageSharp.Compression.Zlib } #if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && buffer.Length >= MinBufferSize) + { + return CalculateAvx2(adler, buffer); + } + if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) { return CalculateSse(adler, buffer); @@ -83,19 +91,15 @@ namespace SixLabors.ImageSharp.Compression.Zlib uint s2 = (adler >> 16) & 0xFFFF; // Process the data in blocks. - const int BLOCK_SIZE = 1 << 5; - uint length = (uint)buffer.Length; - uint blocks = length / BLOCK_SIZE; - length -= blocks * BLOCK_SIZE; + uint blocks = length / BlockSize; + length -= blocks * BlockSize; - int index = 0; - fixed (byte* bufferPtr = buffer) + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) { - fixed (byte* tapPtr = Tap1Tap2) + fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) { - index += (int)blocks * BLOCK_SIZE; - var localBufferPtr = bufferPtr; + byte* localBufferPtr = bufferPtr; // _mm_setr_epi8 on x86 Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); @@ -105,7 +109,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib while (blocks > 0) { - uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */ + uint n = NMAX / BlockSize; /* The NMAX constraint. */ if (n > blocks) { n = blocks; @@ -138,7 +142,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); - localBufferPtr += BLOCK_SIZE; + localBufferPtr += BlockSize; } while (--n > 0); @@ -164,45 +168,127 @@ namespace SixLabors.ImageSharp.Compression.Zlib if (length > 0) { - if (length >= 16) - { - s2 += s1 += localBufferPtr[0]; - s2 += s1 += localBufferPtr[1]; - s2 += s1 += localBufferPtr[2]; - s2 += s1 += localBufferPtr[3]; - s2 += s1 += localBufferPtr[4]; - s2 += s1 += localBufferPtr[5]; - s2 += s1 += localBufferPtr[6]; - s2 += s1 += localBufferPtr[7]; - s2 += s1 += localBufferPtr[8]; - s2 += s1 += localBufferPtr[9]; - s2 += s1 += localBufferPtr[10]; - s2 += s1 += localBufferPtr[11]; - s2 += s1 += localBufferPtr[12]; - s2 += s1 += localBufferPtr[13]; - s2 += s1 += localBufferPtr[14]; - s2 += s1 += localBufferPtr[15]; - - localBufferPtr += 16; - length -= 16; - } + HandleLeftOver(localBufferPtr, length, ref s1, ref s2); + } - while (length-- > 0) - { - s2 += s1 += *localBufferPtr++; - } + return s1 | (s2 << 16); + } + } + } - if (s1 >= BASE) - { - s1 -= BASE; - } + // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint length = (uint)buffer.Length; - s2 %= BASE; + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + byte* localBufferPtr = bufferPtr; + + Vector256 zero = Vector256.Zero; + var dot3v = Vector256.Create((short)1); + var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + + // Process n blocks of data. At most NMAX data bytes can be + // processed before s2 must be reduced modulo BASE. + var vs1 = Vector256.CreateScalar(s1); + var vs2 = Vector256.CreateScalar(s2); + + while (length >= 32) + { + int k = length < NMAX ? (int)length : (int)NMAX; + k -= k % 32; + length -= (uint)k; + + Vector256 vs10 = vs1; + Vector256 vs3 = Vector256.Zero; + + while (k >= 32) + { + // Load 32 input bytes. + Vector256 block = Avx.LoadVector256(localBufferPtr); + + // Sum of abs diff, resulting in 2 x int32's + Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); + + vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); + vs3 = Avx2.Add(vs3, vs10); + + // sum 32 uint8s to 16 shorts. + Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); + + // sum 16 shorts to 8 uint32s. + Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); + + vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); + vs10 = vs1; + + localBufferPtr += BlockSize; + k -= 32; } - return s1 | (s2 << 16); + // Defer the multiplication with 32 to outside of the loop. + vs3 = Avx2.ShiftLeftLogical(vs3, 5); + vs2 = Avx2.Add(vs2, vs3); + + s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); + s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); + + s1 %= BASE; + s2 %= BASE; + + vs1 = Vector256.CreateScalar(s1); + vs2 = Vector256.CreateScalar(s2); } + + if (length > 0) + { + HandleLeftOver(localBufferPtr, length, ref s1, ref s2); + } + + return s1 | (s2 << 16); + } + } + + private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2) + { + if (length >= 16) + { + s2 += s1 += localBufferPtr[0]; + s2 += s1 += localBufferPtr[1]; + s2 += s1 += localBufferPtr[2]; + s2 += s1 += localBufferPtr[3]; + s2 += s1 += localBufferPtr[4]; + s2 += s1 += localBufferPtr[5]; + s2 += s1 += localBufferPtr[6]; + s2 += s1 += localBufferPtr[7]; + s2 += s1 += localBufferPtr[8]; + s2 += s1 += localBufferPtr[9]; + s2 += s1 += localBufferPtr[10]; + s2 += s1 += localBufferPtr[11]; + s2 += s1 += localBufferPtr[12]; + s2 += s1 += localBufferPtr[13]; + s2 += s1 += localBufferPtr[14]; + s2 += s1 += localBufferPtr[15]; + + localBufferPtr += 16; + length -= 16; } + + while (length-- > 0) + { + s2 += s1 += *localBufferPtr++; + } + + if (s1 >= BASE) + { + s1 -= BASE; + } + + s2 %= BASE; } #endif diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs index 506b0f2c1c..02fa5bf58d 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs @@ -483,18 +483,21 @@ namespace SixLabors.ImageSharp.Compression.Zlib int niceLength = Math.Min(this.niceLength, this.lookahead); int matchStrt = this.matchStart; - this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1); int matchLength = this.matchLen; + matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1); + this.matchLen = matchLength; - if (scan + matchLength > scanMax) + if (scan > scanMax - matchLength) { return false; } + int scanEndPosition = scan + matchLength; + byte* pinnedWindow = this.pinnedWindowPointer; int scanStart = this.strstart; - byte scanEnd1 = pinnedWindow[scan + matchLength - 1]; - byte scanEnd = pinnedWindow[scan + matchLength]; + byte scanEnd1 = pinnedWindow[scanEndPosition - 1]; + byte scanEnd = pinnedWindow[scanEndPosition]; // Do not waste too much time if we already have a good match: if (matchLength >= this.goodLength) @@ -508,8 +511,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib match = curMatch; scan = scanStart; - if (pinnedWindow[match + matchLength] != scanEnd - || pinnedWindow[match + matchLength - 1] != scanEnd1 + int matchEndPosition = match + matchLength; + if (pinnedWindow[matchEndPosition] != scanEnd + || pinnedWindow[matchEndPosition - 1] != scanEnd1 || pinnedWindow[match] != pinnedWindow[scan] || pinnedWindow[++match] != pinnedWindow[++scan]) { @@ -685,6 +689,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib return false; } + const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) { if (this.lookahead == 0) @@ -695,7 +700,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib return false; } - if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + if (this.strstart > windowLen) { // slide window, as FindLongestMatch needs this. // This should only happen when flushing and the window @@ -766,6 +771,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib return false; } + const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) { if (this.lookahead == 0) @@ -783,7 +789,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib return false; } - if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + if (this.strstart >= windowLen) { // slide window, as FindLongestMatch needs this. // This should only happen when flushing and the window diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 196d77ad77..c31a2c1c94 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 83c6389348..44a16f154e 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters internal static class AverageFilter { /// - /// Decodes the scanline + /// Decodes a scanline, which was filtered with the average filter. /// - /// The scanline to decode + /// The scanline to decode. /// The previous scanline. /// The bytes per pixel. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -30,11 +30,66 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + // The Avg filter predicts each pixel as the (truncated) average of a and b: + // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) + // With pixels positioned like this: + // prev: c b + // row: a d +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported && bytesPerPixel is 4) + { + DecodeSse2(scanline, previousScanline); + } + else +#endif + { + DecodeScalar(scanline, previousScanline, bytesPerPixel); + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeSse2(Span scanline, Span previousScanline) + { ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) - int x = 1; + Vector128 d = Vector128.Zero; + var ones = Vector128.Create((byte)1); + + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 a = d; + Vector128 b = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(); + d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); + + // PNG requires a truncating average, so we can't just use _mm_avg_epu8, + // but we can fix it up by subtracting off 1 if it rounded up. + Vector128 avg = Sse2.Average(a, b); + Vector128 xor = Sse2.Xor(a, b); + Vector128 and = Sse2.And(xor, ones); + avg = Sse2.Subtract(avg, and); + d = Sse2.Add(d, avg); + + // Store the result. + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); + + rb -= 4; + offset += 4; + } + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + + nint x = 1; for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) { ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); @@ -52,13 +107,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters } /// - /// Encodes the scanline + /// Encodes a scanline with the average filter applied. /// - /// The scanline to encode + /// The scanline to encode. /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. - /// The sum of the total variance of the filtered row + /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 6a89a1122a..0553eb46a9 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters internal static class PaethFilter { /// - /// Decodes the scanline + /// Decodes a scanline, which was filtered with the paeth filter. /// - /// The scanline to decode + /// The scanline to decode. /// The previous scanline. /// The bytes per pixel. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -32,6 +32,86 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + // Paeth tries to predict pixel d using the pixel to the left of it, a, + // and two pixels from the previous row, b and c: + // prev: c b + // row: a d + // The Paeth function predicts d to be whichever of a, b, or c is nearest to + // p = a + b - c. +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported && bytesPerPixel is 4) + { + DecodeSse41(scanline, previousScanline); + } + else +#endif + { + DecodeScalar(scanline, previousScanline, bytesPerPixel); + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeSse41(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + + Vector128 b = Vector128.Zero; + Vector128 d = Vector128.Zero; + + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + + // It's easiest to do this math (particularly, deal with pc) with 16-bit intermediates. + Vector128 c = b; + Vector128 a = d; + b = Sse2.UnpackLow( + Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(), + Vector128.Zero); + d = Sse2.UnpackLow( + Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(), + Vector128.Zero); + + // (p-a) == (a+b-c - a) == (b-c) + Vector128 pa = Sse2.Subtract(b.AsInt16(), c.AsInt16()); + + // (p-b) == (a+b-c - b) == (a-c) + Vector128 pb = Sse2.Subtract(a.AsInt16(), c.AsInt16()); + + // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) + Vector128 pc = Sse2.Add(pa.AsInt16(), pb.AsInt16()); + + pa = Ssse3.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */ + pb = Ssse3.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */ + pc = Ssse3.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */ + + Vector128 smallest = Sse2.Min(pc, Sse2.Min(pa, pb)); + + // Paeth breaks ties favoring a over b over c. + Vector128 mask = Sse41.BlendVariable(c, b, Sse2.CompareEqual(smallest, pb).AsByte()); + Vector128 nearest = Sse41.BlendVariable(mask, a, Sse2.CompareEqual(smallest, pa).AsByte()); + + // Note `_epi8`: we need addition to wrap modulo 255. + d = Sse2.Add(d, nearest); + + // Store the result. + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(Sse2.PackUnsignedSaturate(d.AsInt16(), d.AsInt16()).AsInt32()); + + rb -= 4; + offset += 4; + } + } + +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) + { ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -56,13 +136,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters } /// - /// Encodes the scanline + /// Encodes a scanline and applies the paeth filter. /// /// The scanline to encode /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. - /// The sum of the total variance of the filtered row + /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index c28b877e41..eaa4dc0344 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -21,17 +21,57 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters internal static class SubFilter { /// - /// Decodes the scanline + /// Decodes a scanline, which was filtered with the sub filter. /// - /// The scanline to decode + /// The scanline to decode. /// The bytes per pixel. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, int bytesPerPixel) + { + // The Sub filter predicts each pixel as the previous pixel. +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported && bytesPerPixel is 4) + { + DecodeSse2(scanline); + } + else +#endif + { + DecodeScalar(scanline, bytesPerPixel); + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + private static void DecodeSse2(Span scanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + + Vector128 d = Vector128.Zero; + + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 a = d; + d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); + + d = Sse2.Add(d, a); + + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); + + rb -= 4; + offset += 4; + } + } +#endif + + private static void DecodeScalar(Span scanline, int bytesPerPixel) { ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); // Sub(x) + Raw(x-bpp) - int x = bytesPerPixel + 1; + nint x = bytesPerPixel + 1; Unsafe.Add(ref scanBaseRef, x); for (; x < scanline.Length; ++x) { @@ -42,12 +82,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters } /// - /// Encodes the scanline + /// Encodes a scanline with the sup filter applied. /// - /// The scanline to encode + /// The scanline to encode. /// The filtered scanline result. /// The bytes per pixel. - /// The sum of the total variance of the filtered row + /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) { diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 7e0286991b..0d24d9c5d5 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters internal static class UpFilter { /// - /// Decodes the scanline + /// Decodes a scanline, which was filtered with the up filter. /// /// The scanline to decode /// The previous scanline. @@ -30,6 +30,91 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + DecodeAvx2(scanline, previousScanline); + } + else if (Sse2.IsSupported) + { + DecodeSse2(scanline, previousScanline); + } + else +#endif + { + DecodeScalar(scanline, previousScanline); + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + private static void DecodeAvx2(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + + // Up(x) + Prior(x) + int rb = scanline.Length; + nint offset = 1; + const int bytesPerBatch = 32; + while (rb >= bytesPerBatch) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector256 current = Unsafe.As>(ref scanRef); + Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); + + Vector256 sum = Avx2.Add(up, current); + Unsafe.As>(ref scanRef) = sum; + + offset += bytesPerBatch; + rb -= bytesPerBatch; + } + + // Handle left over. + for (nint i = offset; i < scanline.Length; i++) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); + byte above = Unsafe.Add(ref prevBaseRef, offset); + scan = (byte)(scan + above); + offset++; + } + } + + private static void DecodeSse2(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + + // Up(x) + Prior(x) + int rb = scanline.Length; + nint offset = 1; + const int bytesPerBatch = 16; + while (rb >= bytesPerBatch) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 current = Unsafe.As>(ref scanRef); + Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); + + Vector128 sum = Sse2.Add(up, current); + Unsafe.As>(ref scanRef) = sum; + + offset += bytesPerBatch; + rb -= bytesPerBatch; + } + + // Handle left over. + for (nint i = offset; i < scanline.Length; i++) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); + byte above = Unsafe.Add(ref prevBaseRef, offset); + scan = (byte)(scan + above); + offset++; + } + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline) + { ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -43,12 +128,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters } /// - /// Encodes the scanline + /// Encodes a scanline with the up filter applied. /// - /// The scanline to encode + /// The scanline to encode. /// The previous scanline. /// The filtered scanline result. - /// The sum of the total variance of the filtered row + /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 60437e015e..497dc39674 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -336,6 +336,14 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.End: goto EOF; + + default: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + } + + break; } } finally diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs new file mode 100644 index 0000000000..5b793c35de --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Spectral converter for gray TIFF's which use the JPEG compression. + /// + /// The type of the pixel. + internal sealed class GrayJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public GrayJpegSpectralConverter(Configuration configuration) + : base(configuration) + { + } + + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, frame.Precision); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index ce7820ccf9..cfbc32f4f6 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors @@ -55,17 +54,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); - // If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space. - // There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB). - using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? - new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); - var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); - jpegDecoder.LoadTables(this.jpegTables, scanDecoder); - scanDecoder.ResetInterval = 0; - jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); + switch (this.photometricInterpretation) + { + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + { + using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration); + var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None); + jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); + jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None); - // TODO: Should we pass through the CancellationToken from the tiff decoder? - CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); + // TODO: Should we pass through the CancellationToken from the tiff decoder? + CopyImageBytesToBuffer(buffer, spectralConverterGray.GetPixelBuffer(CancellationToken.None)); + break; + } + + // If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space. + // There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB). + case TiffPhotometricInterpretation.YCbCr: + case TiffPhotometricInterpretation.Rgb: + { + using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? + new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); + var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); + jpegDecoder.LoadTables(this.jpegTables, scanDecoder); + jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); + + // TODO: Should we pass through the CancellationToken from the tiff decoder? + CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); + break; + } + + default: + TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported"); + break; + } } else { @@ -86,6 +109,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } } + private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + { + int offset = 0; + for (int y = 0; y < pixelBuffer.Height; y++) + { + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); + Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); + rgbBytes.CopyTo(buffer.Slice(offset)); + offset += rgbBytes.Length; + } + } + /// protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index 001480542f..a83518064d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs index 5372384397..d6d1bb8a4c 100644 --- a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Metadata; + namespace SixLabors.ImageSharp.Formats.Tiff { /// @@ -12,5 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// bool IgnoreMetadata { get; } + + /// + /// Gets the decoding mode for multi-frame images. + /// + FrameDecodingMode DecodingMode { get; } } } diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 488701e318..51e84ef558 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -25,7 +25,7 @@ ## Implementation Status -- The Decoder currently only supports a single frame per image. +- 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) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 9d52e34dfe..b4d7520199 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -18,6 +19,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public bool IgnoreMetadata { get; set; } + /// + /// Gets or sets the decoding mode for multi-frame images. + /// + public FrameDecodingMode DecodingMode { get; set; } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 05c5358f59..cd06282f18 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -13,7 +13,6 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -33,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// private readonly bool ignoreMetadata; + /// + /// Gets the decoding mode for multi-frame images + /// + private FrameDecodingMode decodingMode; + /// /// The stream to decode from. /// @@ -59,6 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.Configuration = configuration ?? Configuration.Default; this.ignoreMetadata = options.IgnoreMetadata; + this.decodingMode = options.DecodingMode; this.memoryAllocator = this.Configuration.MemoryAllocator; } @@ -160,11 +165,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff 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); - // TODO: Tiff frames can have different sizes + // TODO: Tiff frames can have different sizes. ImageFrame root = frames[0]; this.Dimensions = root.Size(); foreach (ImageFrame frame in frames) diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs index 8c3edcadfa..ff374a9ac4 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing @@ -34,9 +34,10 @@ namespace SixLabors.ImageSharp.Processing Size = new Size(width, height), Mode = ResizeMode.BoxPad, Sampler = KnownResamplers.NearestNeighbor, + PadColor = color }; - return color.Equals(default) ? source.Resize(options) : source.Resize(options).BackgroundColor(color); + return source.Resize(options); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index cc53299528..5c5344ab80 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -176,9 +176,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Rgba32 rgba = default; color.ToRgba32(ref rgba); - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); + const int shift = 8 - IndexBits; + int r = rgba.R >> shift; + int g = rgba.G >> shift; + int b = rgba.B >> shift; int a = rgba.A >> (8 - IndexAlphaBits); ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); @@ -413,6 +414,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span momentSpan = this.momentsOwner.GetSpan(); Span volumeSpan = volume.GetSpan(); Span areaSpan = area.GetSpan(); + const int indexBits2 = IndexBits * 2; + const int indexAndAlphaBits = IndexBits + IndexAlphaBits; + const int indexBitsAndAlphaBits1 = IndexBits + IndexAlphaBits + 1; int baseIndex = GetPaletteIndex(1, 0, 0, 0); for (int r = 1; r < IndexCount; r++) @@ -421,9 +425,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations // in the form of ind1* manually. - int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + - (r << (IndexBits + IndexAlphaBits + 1)) + - (r << (IndexBits * 2)) + + int ind1R = (r << (indexBits2 + IndexAlphaBits)) + + (r << indexBitsAndAlphaBits1) + + (r << indexBits2) + (r << (IndexBits + 1)) + r; @@ -432,7 +436,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int g = 1; g < IndexCount; g++) { int ind1G = ind1R + - (g << (IndexBits + IndexAlphaBits)) + + (g << indexAndAlphaBits) + (g << IndexBits) + g; int r_g = r + g; @@ -446,7 +450,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization b; Moment line = default; - + int bIndexAlphaOffset = b * IndexAlphaCount; for (int a = 1; a < IndexAlphaCount; a++) { int ind1 = ind1B + a; @@ -455,7 +459,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization areaSpan[a] += line; - int inv = (b * IndexAlphaCount) + a; + int inv = bIndexAlphaOffset + a; volumeSpan[inv] += areaSpan[a]; int ind2 = ind1 - baseIndex; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 3d69006836..ef6a15fc9f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -21,19 +21,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); - this.Sampler = options.Sampler; + this.Options = options; this.DestinationWidth = size.Width; this.DestinationHeight = size.Height; this.DestinationRectangle = rectangle; - this.Compand = options.Compand; - this.PremultiplyAlpha = options.PremultiplyAlpha; } - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - /// /// Gets the destination width. /// @@ -50,14 +43,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Rectangle DestinationRectangle { get; } /// - /// Gets a value indicating whether to compress or expand individual pixel color values on processing. - /// - public bool Compand { get; } - - /// - /// Gets a value indicating whether to premultiply the alpha (if it exists) during the resize operation. + /// Gets the resize options. /// - public bool PremultiplyAlpha { get; } + public ResizeOptions Options { get; } /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index b486e42258..c0bf9291e0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -17,12 +16,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor where TPixel : unmanaged, IPixel { + private readonly ResizeOptions options; private readonly int destinationWidth; private readonly int destinationHeight; private readonly IResampler resampler; private readonly Rectangle destinationRectangle; - private readonly bool compand; - private readonly bool premultiplyAlpha; private Image destination; public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) @@ -31,13 +29,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destinationWidth = definition.DestinationWidth; this.destinationHeight = definition.DestinationHeight; this.destinationRectangle = definition.DestinationRectangle; - this.resampler = definition.Sampler; - this.premultiplyAlpha = definition.PremultiplyAlpha; - this.compand = definition.Compand; + this.options = definition.Options; + this.resampler = definition.Options.Sampler; } /// - protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight); + protected override Size GetDestinationSize() => new(this.destinationWidth, this.destinationHeight); /// protected override void BeforeImageApply(Image destination) @@ -62,8 +59,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Image destination = this.destination; Rectangle sourceRectangle = this.SourceRectangle; Rectangle destinationRectangle = this.destinationRectangle; - bool compand = this.compand; - bool premultiplyAlpha = this.premultiplyAlpha; + bool compand = this.options.Compand; + bool premultiplyAlpha = this.options.PremultiplyAlpha; + bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) + && this.options.PadColor != default; + TPixel fillColor = this.options.PadColor.ToPixel(); // Handle resize dimensions identical to the original if (source.Width == destination.Width @@ -91,6 +91,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ImageFrame sourceFrame = source.Frames[i]; ImageFrame destinationFrame = destination.Frames[i]; + if (shouldFill) + { + destinationFrame.Clear(fillColor); + } + ApplyNNResizeFrameTransform( configuration, sourceFrame, @@ -123,6 +128,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ImageFrame sourceFrame = source.Frames[i]; ImageFrame destinationFrame = destination.Frames[i]; + if (shouldFill) + { + destinationFrame.Clear(fillColor); + } + ApplyResizeFrameTransform( configuration, sourceFrame, diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index 4b31998da4..62cf8ab239 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -51,5 +51,10 @@ namespace SixLabors.ImageSharp.Processing /// the alpha (if it exists) during the resize operation. /// public bool PremultiplyAlpha { get; set; } = true; + + /// + /// Gets or sets the color to use as a background when padding an image. + /// + public Color PadColor { get; set; } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs index 6517bf3c48..5f91a050ee 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs @@ -16,16 +16,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private byte[] filter1; private byte[] filter2; private byte[] filter3; - private byte[] filter4; + private byte[] averageFilter3bpp; + private byte[] averageFilter4bpp; [GlobalSetup] public void ReadImages() { this.filter0 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter0)); - this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter1)); - this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter2)); - this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter3)); - this.filter4 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter4)); + this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.SubFilter3BytesPerPixel)); + this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.UpFilter)); + this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.PaethFilter3BytesPerPixel)); + this.averageFilter3bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter3BytesPerPixel)); + this.averageFilter4bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter4BytesPerPixel)); } [Benchmark(Baseline = true, Description = "None-filtered PNG file")] @@ -40,13 +42,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public Size PngFilter2() => LoadPng(this.filter2); - [Benchmark(Description = "Average-filtered PNG file")] - public Size PngFilter3() - => LoadPng(this.filter3); + [Benchmark(Description = "Average-filtered PNG file (3bpp)")] + public Size PngAvgFilter1() + => LoadPng(this.averageFilter3bpp); + + [Benchmark(Description = "Average-filtered PNG file (4bpp)")] + public Size PngAvgFilter2() + => LoadPng(this.averageFilter4bpp); [Benchmark(Description = "Paeth-filtered PNG file")] public Size PngFilter4() - => LoadPng(this.filter4); + => LoadPng(this.filter3); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Size LoadPng(byte[] bytes) diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs index 0886bd84dc..77f2b76634 100644 --- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; @@ -15,10 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [InlineData(0)] [InlineData(1)] [InlineData(2)] - public void ReturnsCorrectWhenEmpty(uint input) - { - Assert.Equal(input, Adler32.Calculate(input, default)); - } + public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default)); [Theory] [InlineData(0)] @@ -28,24 +26,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [InlineData(1024 + 15)] [InlineData(2034)] [InlineData(4096)] - public void MatchesReference(int length) + public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length); + + private static void CalculateAdlerAndCompareToReference(int length) { - var data = GetBuffer(length); + // arrange + byte[] data = GetBuffer(length); var adler = new SharpAdler32(); adler.Update(data); - long expected = adler.Value; + + // act long actual = Adler32.Calculate(data); + // assert Assert.Equal(expected, actual); } private static byte[] GetBuffer(int length) { - var data = new byte[length]; + byte[] data = new byte[length]; new Random(1).NextBytes(data); return data; } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll); + + [Fact] + public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + + [Fact] + public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic); + + private static void RunCalculateAdlerTest() + { + int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 }; + for (int i = 0; i < testData.Length; i++) + { + CalculateAdlerAndCompareToReference(testData[i]); + } + } +#endif } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs new file mode 100644 index 0000000000..edfff19a4b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs @@ -0,0 +1,179 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png.Filters; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + [Trait("Format", "Png")] + public class PngDecoderFilterTests + { + private static void RunAverageFilterTest() + { + // arrange + byte[] scanline = + { + 3, 39, 39, 39, 0, 4, 4, 4, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 4, 4, 4, + 0, 2, 2, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 2, 2, 2, 0, + 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 254, 254, 254, + 0, 6, 6, 6, 14, 71, 71, 71, 157, 254, 254, 254, 28, 251, 251, 251, 0, 4, 4, 4, 0, 2, 2, 2, 0, 11, + 11, 11, 0, 226, 226, 226, 0, 255, 128, 234 + }; + + byte[] previousScanline = + { + 3, 74, 74, 74, 0, 73, 73, 73, 0, 73, 73, 73, 0, 74, 74, 74, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, + 72, 0, 72, 72, 72, 0, 73, 73, 73, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, 72, 0, 72, 72, 72, 0, 74, + 74, 74, 0, 72, 72, 72, 0, 73, 73, 73, 0, 75, 75, 75, 0, 73, 73, 73, 0, 74, 74, 74, 0, 72, 72, 72, 0, + 73, 73, 73, 0, 73, 73, 73, 0, 72, 72, 72, 0, 74, 74, 74, 0, 61, 61, 61, 0, 101, 101, 101, 78, 197, + 197, 197, 251, 152, 152, 152, 255, 155, 155, 155, 255, 162, 162, 162, 255, 175, 175, 175, 255, 160, + 160, 160, 255, 139, 128, 134 + }; + + byte[] expected = + { + 3, 76, 76, 76, 0, 78, 78, 78, 0, 76, 76, 76, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, 76, + 76, 0, 78, 78, 78, 0, 77, 77, 77, 0, 78, 78, 78, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, + 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 77, 77, 77, 0, 78, 78, 78, 0, 77, 77, 77, 0, 77, 77, 77, 0, + 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 73, 73, 73, 0, 73, 73, 73, 14, 158, 158, 158, 203, 175, + 175, 175, 255, 158, 158, 158, 255, 160, 160, 160, 255, 163, 163, 163, 255, 180, 180, 180, 255, 140, + 140, 140, 255, 138, 6, 115 + }; + + // act + AverageFilter.Decode(scanline, previousScanline, 4); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunUpFilterTest() + { + // arrange + byte[] scanline = + { + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; + + byte[] previousScanline = + { + 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, + 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, + 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 + }; + + byte[] expected = + { + 62, 126, 65, 176, 51, 183, 83, 227, 72, 248, 20, 185, 151, 46, 246, 163, 240, 81, 250, 16, 8, 214, + 134, 85, 107, 139, 90, 218, 246, 126, 144, 43, 221, 71, 45, 56, 49, 182, 240, 142, 147, 48, 178, + 119, 100, 122, 137, 166, 28, 41, 135, 81, 24, 62, 34, 62, 248, 234, 68, 166, 93, 121, 237, 200 + }; + + // act + UpFilter.Decode(scanline, previousScanline); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunSubFilterTest() + { + // arrange + byte[] scanline = + { + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; + + byte[] expected = + { + 62, 23, 186, 150, 174, 27, 135, 209, 71, 161, 37, 39, 55, 78, 228, 97, 166, 5, 49, 134, 251, 28, + 142, 82, 105, 167, 151, 102, 192, 65, 71, 156, 143, 23, 111, 167, 66, 222, 118, 130, 240, 208, 230, + 94, 133, 213, 239, 204, 236, 64, 214, 189, 249, 134, 174, 228, 179, 115, 213, 6, 174, 44, 185, 4 + }; + + // act + SubFilter.Decode(scanline, 4); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunPaethFilterTest() + { + // arrange + byte[] scanline = + { + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; + + byte[] previousScanline = + { + 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, + 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, + 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 + }; + + byte[] expected = + { + 62, 126, 65, 176, 51, 183, 14, 235, 30, 248, 172, 254, 14, 165, 53, 56, 125, 92, 250, 16, 8, 177, + 10, 220, 118, 139, 50, 240, 205, 126, 144, 43, 221, 71, 45, 54, 144, 182, 240, 142, 147, 48, 178, + 106, 40, 122, 187, 166, 143, 41, 162, 151, 24, 111, 34, 135, 248, 92, 68, 169, 243, 21, 1, 200 + }; + + // act + PaethFilter.Decode(scanline, previousScanline, 4); + + // assert + Assert.Equal(expected, scanline); + } + + [Fact] + public void AverageFilter_Works() => RunAverageFilterTest(); + + [Fact] + public void UpFilter_Works() => RunUpFilterTest(); + + [Fact] + public void SubFilter_Works() => RunSubFilterTest(); + + [Fact] + public void PaethFilter_Works() => RunPaethFilterTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void AverageFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.AllowAll); + + [Fact] + public void AverageFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void UpFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.AllowAll); + + [Fact] + public void UpFilter_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableAVX2); + + [Fact] + public void UpFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void SubFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.AllowAll); + + [Fact] + public void SubFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void PaethFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.AllowAll); + + [Fact] + public void PaethFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); +#endif + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 0af5d99950..752036126f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -111,6 +111,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png image.CompareToOriginal(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithAverageFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + + [Theory] + [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithSubFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + + [Theory] + [WithFile(TestImages.Png.UpFilter, PixelTypes.Rgba32)] + public void Decode_WithUpFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + + [Theory] + [WithFile(TestImages.Png.PaethFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PaethFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithPaethFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + [Theory] [WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs similarity index 98% rename from tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs rename to tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs index 9b6119380a..11e3fbb230 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs @@ -13,7 +13,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Png { [Trait("Format", "Png")] - public partial class PngFilterTests : MeasureFixture + public class PngEncoderFilterTests : MeasureFixture { #if BENCHMARKING public const int Times = 1000000; @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public const int Times = 1; #endif - public PngFilterTests(ITestOutputHelper output) + public PngEncoderFilterTests(ITestOutputHelper output) : base(output) { } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index ea0544acf4..e4f4d26b85 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -4,6 +4,7 @@ // ReSharper disable InconsistentNaming using System; using System.IO; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -365,11 +366,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_PackBitsCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void CanDecodeJustOneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new TiffDecoder() { DecodingMode = FrameDecodingMode.First })) + { + Assert.Equal(1, image.Frames.Count); + } + } + [Theory] [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] [WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)] [WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] + [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index b1441d1093..780758c2b4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -20,41 +20,37 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void ImageShouldPad(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); - image.DebugSave(provider); + using Image image = provider.GetImage(); + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); + image.DebugSave(provider); - // Check pixels are empty - for (int y = 0; y < 25; y++) + // Check pixels are empty + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) { - for (int x = 0; x < 25; x++) - { - Assert.Equal(default, image[x, y]); - } + Assert.Equal(default, image[x, y]); } } } [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) where TPixel : unmanaged, IPixel { - var color = Color.Red; + Color color = Color.Red; TPixel expected = color.ToPixel(); - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); - image.DebugSave(provider); + using Image image = provider.GetImage(); + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); + image.DebugSave(provider); - // Check pixels are filled - for (int y = 0; y < 25; y++) + // Check pixels are filled + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) { - for (int x = 0; x < 25; x++) - { - Assert.Equal(expected, image[x, y]); - } + Assert.Equal(expected, image[x, y]); } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 227e470d4e..3e6726ba06 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(width, resizeProcessor.DestinationWidth); Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 60f7aaa0ba..a29f4c0353 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(width, resizeProcessor.DestinationWidth); Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); } [Fact] @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(width, resizeProcessor.DestinationWidth); Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Sampler); - Assert.Equal(compand, resizeProcessor.Compand); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); + Assert.Equal(compand, resizeProcessor.Options.Compand); } [Fact] @@ -78,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(width, resizeProcessor.DestinationWidth); Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Sampler); - Assert.Equal(compand, resizeProcessor.Compand); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); + Assert.Equal(compand, resizeProcessor.Options.Compand); // Ensure options are not altered. Assert.Equal(width, resizeOptions.Size.Width); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f16407ee8e..17ad0aca5c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -66,10 +66,13 @@ namespace SixLabors.ImageSharp.Tests // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; - public const string Filter1 = "Png/filter1.png"; - public const string Filter2 = "Png/filter2.png"; - public const string Filter3 = "Png/filter3.png"; - public const string Filter4 = "Png/filter4.png"; + public const string SubFilter3BytesPerPixel = "Png/filter1.png"; + public const string SubFilter4BytesPerPixel = "Png/SubFilter4Bpp.png"; + public const string UpFilter = "Png/filter2.png"; + public const string AverageFilter3BytesPerPixel = "Png/filter3.png"; + public const string AverageFilter4BytesPerPixel = "Png/AverageFilter4Bpp.png"; + public const string PaethFilter3BytesPerPixel = "Png/filter4.png"; + public const string PaethFilter4BytesPerPixel = "Png/PaethFilter4Bpp.png"; // Paletted images also from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string PalettedTwoColor = "Png/basn3p01.png"; @@ -154,15 +157,6 @@ namespace SixLabors.ImageSharp.Tests public const string ColorTypeOne = "Png/xc1n0g08.png"; public const string ColorTypeNine = "Png/xc9n2c08.png"; } - - public static readonly string[] All = - { - P1, Pd, Blur, Splash, Cross, - Powerpoint, SplashInterlaced, Interlaced, - Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar, VimImage1, VimImage2, VersioningImage1, - VersioningImage2, Ratio4x1, Ratio1x4 - }; } public static class Jpeg @@ -765,6 +759,7 @@ namespace SixLabors.ImageSharp.Tests public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; + public const string GrayscaleJpegCompressed = "Tiff/JpegCompressedGray.tiff"; public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff"; public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff"; public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; @@ -866,6 +861,7 @@ namespace SixLabors.ImageSharp.Tests public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff"; public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; + public const string MultiFrameMipMap = "Tiff/SKC1H3.tiff"; public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; diff --git a/tests/Images/Input/Png/AverageFilter4Bpp.png b/tests/Images/Input/Png/AverageFilter4Bpp.png new file mode 100644 index 0000000000..728b6cfaff --- /dev/null +++ b/tests/Images/Input/Png/AverageFilter4Bpp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7add6fba794bc76ccea2ee3a311b4050cf17f4f78b69a50785f7739b8b35919e +size 181108 diff --git a/tests/Images/Input/Png/PaethFilter4Bpp.png b/tests/Images/Input/Png/PaethFilter4Bpp.png new file mode 100644 index 0000000000..64c9f96ec8 --- /dev/null +++ b/tests/Images/Input/Png/PaethFilter4Bpp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b2b0a1190854577d5181fe40af61c421d615a1a2727cf9be5ebe727eaafd00d +size 8624 diff --git a/tests/Images/Input/Png/SubFilter4Bpp.png b/tests/Images/Input/Png/SubFilter4Bpp.png new file mode 100644 index 0000000000..d9f2c7fa25 --- /dev/null +++ b/tests/Images/Input/Png/SubFilter4Bpp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053fac72ff62c66dacb41a6251efa249d5b31567e0222efbf5b1bef912c0bf77 +size 13013 diff --git a/tests/Images/Input/Tiff/JpegCompressedGray.tiff b/tests/Images/Input/Tiff/JpegCompressedGray.tiff new file mode 100644 index 0000000000..e7feed15a8 --- /dev/null +++ b/tests/Images/Input/Tiff/JpegCompressedGray.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:868afd018d025ed7636f1155c1b1f64ba8a36153b56c7598e8dee18ce770cd5a +size 539660 diff --git a/tests/Images/Input/Tiff/SKC1H3.tiff b/tests/Images/Input/Tiff/SKC1H3.tiff new file mode 100644 index 0000000000..9f9a50fdd6 --- /dev/null +++ b/tests/Images/Input/Tiff/SKC1H3.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:938bbf1c0f8bdbea0c632bb8d51c1150f757f88b3779d7fa18c296a3a3f61e9b +size 13720193