From 4b7b1df08826f0e38e8e3921aa15b23d5979981b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Jun 2021 23:16:47 +1000 Subject: [PATCH 001/119] Migrate png decoder --- src/ImageSharp/Formats/Png/PngChunk.cs | 6 +-- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 53 ++++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index fd11ba1b6..7b5f390f1 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Memory; +using System.Buffers; namespace SixLabors.ImageSharp.Formats.Png { @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal readonly struct PngChunk { - public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) + public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) { this.Length = length; this.Type = type; @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets the data bytes appropriate to the chunk type, if any. /// This field can be of zero length or null. /// - public IManagedByteBuffer Data { get; } + public IMemoryOwner Data { get; } /// /// Gets a value indicating whether the given chunk is critical to decoding diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index c2c336c03..a80cea7f9 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; @@ -84,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Previous scanline processed. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The current scanline that is being processed. /// - private IManagedByteBuffer scanline; + private IMemoryOwner scanline; /// /// The index of the current scanline being processed. @@ -149,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); @@ -168,29 +169,29 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.Palette: var pal = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(pal); this.palette = pal; break; case PngChunkType.Transparency: var alpha = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); break; case PngChunkType.Text: - this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { var exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -239,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); @@ -251,19 +252,19 @@ namespace SixLabors.ImageSharp.Formats.Png this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Text: - this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (!this.ignoreMetadata) { var exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -312,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The number of bits per value. /// The new array. /// The resulting array. - private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IMemoryOwner buffer) { if (bits >= 8) { @@ -320,9 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Png return false; } - buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); + buffer = this.memoryAllocator.Allocate(bytesPerScanline * 8 / bits, AllocationOptions.Clean); ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref buffer.Array[0]; + ref byte resultRef = ref buffer.GetReference(); int mask = 0xFF >> (8 - bits); int resultOffset = 0; @@ -504,7 +505,8 @@ namespace SixLabors.ImageSharp.Formats.Png { while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + Span scanlineSpan = this.scanline.GetSpan(); + int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < this.bytesPerScanline) { @@ -512,7 +514,6 @@ namespace SixLabors.ImageSharp.Formats.Png } this.currentRowBytesRead = 0; - Span scanlineSpan = this.scanline.GetSpan(); switch ((FilterType)scanlineSpan[0]) { @@ -576,7 +577,7 @@ namespace SixLabors.ImageSharp.Formats.Png while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < bytesPerInterlaceScanline) { @@ -653,7 +654,7 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer) + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IMemoryOwner buffer) ? buffer.GetSpan() : trimmed; @@ -735,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IMemoryOwner buffer) ? buffer.GetSpan() : trimmed; @@ -1189,12 +1190,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The length of the chunk data to read. [MethodImpl(InliningOptions.ShortMethod)] - private IManagedByteBuffer ReadChunkData(int length) + private IMemoryOwner ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() - IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); + IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); - this.currentStream.Read(buffer.Array, 0, length); + this.currentStream.Read(buffer.GetSpan(), 0, length); return buffer; } @@ -1274,7 +1275,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void SwapBuffers() { - IManagedByteBuffer temp = this.previousScanline; + IMemoryOwner temp = this.previousScanline; this.previousScanline = this.scanline; this.scanline = temp; } From 5bc3850d093a533f7bfaccd0ea43ad63ee2b9dc6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Jul 2021 22:24:33 +1000 Subject: [PATCH 002/119] Migrate png encoder --- .../Common/Extensions/StreamExtensions.cs | 1 - src/ImageSharp/Formats/Png/PngEncoderCore.cs | 95 +++++++++---------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 47a0e0bbf..1193eccee 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.IO; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7a285eb70..9814d3444 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -80,32 +80,32 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The raw data of previous scanline. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The raw data of current scanline. /// - private IManagedByteBuffer currentScanline; + private IMemoryOwner currentScanline; /// /// The common buffer for the filters. /// - private IManagedByteBuffer filterBuffer; + private IMemoryOwner filterBuffer; /// /// The ext buffer for the sub filter, . /// - private IManagedByteBuffer subFilter; + private IMemoryOwner subFilter; /// /// The ext buffer for the average filter, . /// - private IManagedByteBuffer averageFilter; + private IMemoryOwner averageFilter; /// /// The ext buffer for the Paeth filter, . /// - private IManagedByteBuffer paethFilter; + private IMemoryOwner paethFilter; /// /// Initializes a new instance of the class. @@ -278,21 +278,17 @@ namespace SixLabors.ImageSharp.Formats.Png else { // 1, 2, and 4 bit grayscale - using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer( - rowSpan.Length, - AllocationOptions.Clean)) - { - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); - Span tempSpan = temp.GetSpan(); - - // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - tempSpan, - rowSpan.Length); - PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); - } + using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + + // We need to first create an array of luminance bytes then scale them down to the correct bit depth. + PixelOperations.Instance.ToL8Bytes( + this.configuration, + rowSpan, + tempSpan, + rowSpan.Length); + PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); } } } @@ -453,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Apply filter for the raw scanline. /// - private IManagedByteBuffer FilterPixelBytes() + private IMemoryOwner FilterPixelBytes() { switch (this.options.FilterMethod) { @@ -490,8 +486,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The quantized pixels. Can be null. /// The row. - /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + /// The + private IMemoryOwner EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); @@ -502,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// /// The row span. - private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + private IMemoryOwner EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) { // CollectPixelBytes if (this.bitDepth < 8) @@ -522,7 +518,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// to be most compressible, using lowest total variation as proxy for compressibility. /// /// The - private IManagedByteBuffer GetOptimalFilteredScanline() + private IMemoryOwner GetOptimalFilteredScanline() { // Palette images don't compress well with adaptive filtering. if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) @@ -543,7 +539,7 @@ namespace SixLabors.ImageSharp.Formats.Png // That way the above comment would actually be true. It used to be anyway... // If we could use SIMD for none branching filters we could really speed it up. int lowestSum = currentSum; - IManagedByteBuffer actualResult = this.filterBuffer; + IMemoryOwner actualResult = this.filterBuffer; PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); @@ -612,8 +608,8 @@ namespace SixLabors.ImageSharp.Formats.Png int colorTableLength = paletteLength * Unsafe.SizeOf(); bool hasAlpha = false; - using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); - using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); + using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); @@ -640,12 +636,12 @@ namespace SixLabors.ImageSharp.Formats.Png Unsafe.Add(ref alphaTableRef, i) = alpha; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); // Write the transparency data if (hasAlpha) { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); } } @@ -938,9 +934,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); this.filterBuffer?.Dispose(); - this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + this.filterBuffer = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); } /// @@ -952,9 +948,9 @@ namespace SixLabors.ImageSharp.Formats.Png { int resultLength = this.filterBuffer.Length(); - this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.subFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); + this.averageFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); + this.paethFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); } } @@ -974,10 +970,10 @@ namespace SixLabors.ImageSharp.Formats.Png for (int y = 0; y < this.height; y++) { - IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); - deflateStream.Write(r.Array, 0, resultLength); + IMemoryOwner r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); + deflateStream.Write(r.GetSpan(), 0, resultLength); - IManagedByteBuffer temp = this.currentScanline; + IMemoryOwner temp = this.currentScanline; this.currentScanline = this.previousScanline; this.previousScanline = temp; } @@ -1027,10 +1023,10 @@ namespace SixLabors.ImageSharp.Formats.Png // encode data // note: quantized parameter not used // note: row parameter not used - IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); - deflateStream.Write(r.Array, 0, resultLength); + IMemoryOwner r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); + deflateStream.Write(r.GetSpan(), 0, resultLength); - IManagedByteBuffer temp = this.currentScanline; + IMemoryOwner temp = this.currentScanline; this.currentScanline = this.previousScanline; this.previousScanline = temp; } @@ -1080,10 +1076,10 @@ namespace SixLabors.ImageSharp.Formats.Png } // encode data - IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan); - deflateStream.Write(r.Array, 0, resultLength); + IMemoryOwner r = this.EncodeAdam7IndexedPixelRow(destSpan); + deflateStream.Write(r.GetSpan(), 0, resultLength); - IManagedByteBuffer temp = this.currentScanline; + IMemoryOwner temp = this.currentScanline; this.currentScanline = this.previousScanline; this.previousScanline = temp; } @@ -1103,7 +1099,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to write to. /// The type of chunk to write. /// The containing data. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); + private void WriteChunk(Stream stream, PngChunkType type, Span data) + => this.WriteChunk(stream, type, data, 0, data.Length); /// /// Writes a chunk of a specified length to the stream at the given offset. @@ -1113,7 +1110,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing data. /// The position to offset the data at. /// The of the data to write. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length) + private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length) { BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); @@ -1126,7 +1123,7 @@ namespace SixLabors.ImageSharp.Formats.Png { stream.Write(data, offset, length); - crc = Crc32.Calculate(crc, data.AsSpan(offset, length)); + crc = Crc32.Calculate(crc, data.Slice(offset, length)); } BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); From 043427ac7f6b4818787a189e3b75ce0db4e9cdd9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Jul 2021 16:08:14 +1000 Subject: [PATCH 003/119] Refactor to avoid all the weird allocations --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 4 +- .../Formats/Png/Filters/AverageFilter.cs | 4 +- .../Formats/Png/Filters/PaethFilter.cs | 4 +- .../Formats/Png/Filters/SubFilter.cs | 2 +- .../Formats/Png/Filters/UpFilter.cs | 4 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 122 +++---- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 304 ++++++++---------- .../Formats/Png/ReferenceImplementations.cs | 10 +- 8 files changed, 215 insertions(+), 239 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 9ef7c01c6..f56cb37a8 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -37,7 +37,7 @@ namespace SixLabors /// has a different size than /// [Conditional("DEBUG")] - public static void MustBeSameSized(Span target, Span other, string parameterName) + public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) where T : struct { if (target.Length != other.Length) @@ -57,7 +57,7 @@ namespace SixLabors /// has less items than /// [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(Span target, Span minSpan, string parameterName) + public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) where T : struct { if (target.Length < minSpan.Length) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 0ab141397..83c638934 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index e8e0aa704..6a89a1122 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 116154836..c28b877e4 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index e0f35293a..7e0286991 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index a80cea7f9..80ce5e6bd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -543,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow++; } } @@ -618,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan = image.GetPixelRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow += Adam7.RowIncrement[pass]; } @@ -654,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IMemoryOwner buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline - 1, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; + + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; + } + } + finally + { + buffer?.Dispose(); } - - buffer?.Dispose(); } /// @@ -1273,7 +1283,7 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } - private void SwapBuffers() + private void SwapScanlineBuffers() { IMemoryOwner temp = this.previousScanline; this.previousScanline = this.scanline; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 9814d3444..4f6fb7356 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -87,26 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// private IMemoryOwner currentScanline; - /// - /// The common buffer for the filters. - /// - private IMemoryOwner filterBuffer; - - /// - /// The ext buffer for the sub filter, . - /// - private IMemoryOwner subFilter; - - /// - /// The ext buffer for the average filter, . - /// - private IMemoryOwner averageFilter; - - /// - /// The ext buffer for the Paeth filter, . - /// - private IMemoryOwner paethFilter; - /// /// Initializes a new instance of the class. /// @@ -173,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png { this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = null; this.currentScanline = null; - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.filterBuffer = null; } /// @@ -440,6 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.GrayscaleWithAlpha: this.CollectGrayscaleBytes(rowSpan); break; + case PngColorType.Rgb: + case PngColorType.RgbWithAlpha: default: this.CollectTPixelBytes(rowSpan); break; @@ -447,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Apply filter for the raw scanline. + /// Apply the line filter for the raw scanline to enable better compression. /// - private IMemoryOwner FilterPixelBytes() + private void FilterPixelBytes(ref Span filter, ref Span attempt) { switch (this.options.FilterMethod) { case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; - + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + break; case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); - return this.filterBuffer; + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); + break; case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; - + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + case PngFilterMethod.Adaptive: default: - return this.GetOptimalFilteredScanline(); + this.ApplyOptimalFilteredScanline(ref filter, ref attempt); + break; } } /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. + /// Collects the pixel data line by line for compressing. + /// Each scanline is filtered in the most optimal manner to improve compression. /// /// The pixel format. /// The row span. - /// The quantized pixels. Can be null. - /// The row. - /// The - private IMemoryOwner EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + /// The filtered buffer. + /// Used for attempting optimized filtering. + /// The quantized pixels. Can be . + /// The row number. + private void CollectAndFilterPixelRow( + ReadOnlySpan rowSpan, + ref Span filter, + ref Span attempt, + IndexedImageFrame quantized, + int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// - /// The row span. - private IMemoryOwner EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void EncodeAdam7IndexedPixelRow( + ReadOnlySpan row, + ref Span filter, + ref Span attempt) { // CollectPixelBytes if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); } else { - rowSpan.CopyTo(this.currentScanline.GetSpan()); + row.CopyTo(this.currentScanline.GetSpan()); } - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// - /// The - private IMemoryOwner GetOptimalFilteredScanline() + private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) { // Palette images don't compress well with adaptive filtering. - if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) + // Nor do images comprising a single row. + if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) { - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + return; } - this.AllocateExtBuffers(); - Span scanSpan = this.currentScanline.GetSpan(); - Span prevSpan = this.previousScanline.GetSpan(); - - // This order, while different to the enumerated order is more likely to produce a smaller sum - // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum); + Span current = this.currentScanline.GetSpan(); + Span previous = this.previousScanline.GetSpan(); - // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. - // That way the above comment would actually be true. It used to be anyway... - // If we could use SIMD for none branching filters we could really speed it up. - int lowestSum = currentSum; - IMemoryOwner actualResult = this.filterBuffer; - - PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + int min = int.MaxValue; + SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.paethFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + UpFilter.Encode(current, previous, attempt, out sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.subFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) { - actualResult = this.averageFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - return actualResult; + PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) + { + SwapSpans(ref filter, ref attempt); + } } /// @@ -920,38 +896,13 @@ namespace SixLabors.ImageSharp.Formats.Png /// Allocates the buffers for each scanline. /// /// The bytes per scanline. - /// Length of the result. - private void AllocateBuffers(int bytesPerScanline, int resultLength) + private void AllocateScanlineBuffers(int bytesPerScanline) { // Clean up from any potential previous runs. - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.filterBuffer?.Dispose(); this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); - this.filterBuffer = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - } - - /// - /// Allocates the ext buffers for adaptive filter. - /// - private void AllocateExtBuffers() - { - if (this.subFilter == null) - { - int resultLength = this.filterBuffer.Length(); - - this.subFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - this.averageFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - this.paethFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - } } /// @@ -965,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); - int resultLength = bytesPerScanline + 1; - this.AllocateBuffers(bytesPerScanline, resultLength); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < this.height; y++) { - IMemoryOwner r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); - deflateStream.Write(r.GetSpan(), 0, resultLength); - - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; + this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); } } @@ -1000,36 +953,33 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); - this.AllocateBuffers(bytesPerScanline, resultLength); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect pixel data + Span srcRow = pixels.GetPixelRowSpan(row); + for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { - // collect data - Span srcRow = pixels.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - // note: quantized parameter not used - // note: row parameter not used - IMemoryOwner r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); - deflateStream.Write(r.GetSpan(), 0, resultLength); + // Encode data + // Note: quantized parameter not used + // Note: row parameter not used + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + deflateStream.Write(filter); - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1055,34 +1005,36 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; + + this.AllocateScanlineBuffers(bytesPerScanline); - this.AllocateBuffers(bytesPerScanline, resultLength); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect data + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) { - // collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - IMemoryOwner r = this.EncodeAdam7IndexedPixelRow(destSpan); - deflateStream.Write(r.GetSpan(), 0, resultLength); + // Encode data + this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); + deflateStream.Write(filter); - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1151,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png return scanlineLength / mod; } + + private void SwapScanlineBuffers() + { + IMemoryOwner temp = this.previousScanline; + this.previousScanline = this.currentScanline; + this.currentScanline = temp; + } + + private static void SwapSpans(ref Span a, ref Span b) + { + Span t = b; + b = a; + a = t; + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs index a9b53e16e..be9883a70 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs @@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(Span scanline, Span previousScanline, Span result, out int sum) + public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); From 270ce9b1b3eca94e40b96f15e38beec890cd990b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Jul 2021 16:09:49 +1000 Subject: [PATCH 004/119] Update PngDecoderCore.cs --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 132 ++++++++++--------- 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 80ce5e6bd..36d700103 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -746,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IMemoryOwner buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; - break; + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); + break; - break; + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - case PngColorType.Palette: - PngScanlineProcessor.ProcessInterlacedPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.palette, - this.paletteAlpha); + break; - break; + case PngColorType.Palette: + PngScanlineProcessor.ProcessInterlacedPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.palette, + this.paletteAlpha); - case PngColorType.Rgb: - PngScanlineProcessor.ProcessInterlacedRgbScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + break; - break; + case PngColorType.Rgb: + PngScanlineProcessor.ProcessInterlacedRgbScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessInterlacedRgbaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); + break; - break; - } + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessInterlacedRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - buffer?.Dispose(); + break; + } + } + finally + { + buffer?.Dispose(); + } } /// From b78582fb7552f082002eb11df10aadd4266fcf92 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Jul 2021 22:26:53 +1000 Subject: [PATCH 005/119] Migrate deflater --- src/ImageSharp/Compression/Zlib/Deflater.cs | 2 +- .../Compression/Zlib/DeflaterEngine.cs | 41 +++++++++++-------- .../Compression/Zlib/DeflaterHuffman.cs | 28 ++++++------- .../Compression/Zlib/DeflaterOutputStream.cs | 27 +++++------- .../Compression/Zlib/DeflaterPendingBuffer.cs | 31 +++++++++----- 5 files changed, 71 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Compression/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs index 800c96703..7ff8342aa 100644 --- a/src/ImageSharp/Compression/Zlib/Deflater.cs +++ b/src/ImageSharp/Compression/Zlib/Deflater.cs @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// The number of compressed bytes added to the output, or 0 if either /// or returns true or length is zero. /// - public int Deflate(byte[] output, int offset, int length) + public int Deflate(Span output, int offset, int length) { int origLength = length; diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs index d3cfa7c3d..506b0f2c1 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs @@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// This array contains the part of the uncompressed stream that /// is of relevance. The current character is indexed by strstart. /// - private IManagedByteBuffer windowMemoryOwner; + private IMemoryOwner windowMemoryOwner; private MemoryHandle windowMemoryHandle; - private readonly byte[] window; + private readonly Memory window; private readonly byte* pinnedWindowPointer; private int maxChain; @@ -153,19 +153,19 @@ namespace SixLabors.ImageSharp.Compression.Zlib // Create pinned pointers to the various buffers to allow indexing // without bounds checks. - this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); - this.window = this.windowMemoryOwner.Array; - this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); + this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Memory; + this.windowMemoryHandle = this.window.Pin(); this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); this.head = this.headMemoryOwner.Memory; - this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); + this.headMemoryHandle = this.head.Pin(); this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); this.prev = this.prevMemoryOwner.Memory; - this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin(); + this.prevMemoryHandle = this.prev.Pin(); this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; // We start at index 1, to avoid an implementation deficiency, that @@ -303,7 +303,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib case DeflaterConstants.DEFLATE_STORED: if (this.strstart > this.blockStart) { - this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib case DeflaterConstants.DEFLATE_FAST: if (this.strstart > this.blockStart) { - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib if (this.strstart > this.blockStart) { - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); this.blockStart = this.strstart; } @@ -362,7 +362,10 @@ namespace SixLabors.ImageSharp.Compression.Zlib more = this.inputEnd - this.inputOff; } - Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more); + Unsafe.CopyBlockUnaligned( + ref this.window.Span[this.strstart + this.lookahead], + ref this.inputBuf[this.inputOff], + unchecked((uint)more)); this.inputOff += more; this.lookahead += more; @@ -426,7 +429,11 @@ namespace SixLabors.ImageSharp.Compression.Zlib private void SlideWindow() { - Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE); + Unsafe.CopyBlockUnaligned( + ref this.window.Span[0], + ref this.window.Span[DeflaterConstants.WSIZE], + DeflaterConstants.WSIZE); + this.matchStart -= DeflaterConstants.WSIZE; this.strstart -= DeflaterConstants.WSIZE; this.blockStart -= DeflaterConstants.WSIZE; @@ -663,7 +670,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib lastBlock = false; } - this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock); + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock); this.blockStart += storedLength; return !(lastBlock || storedLength == 0); } @@ -683,7 +690,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib if (this.lookahead == 0) { // We are flushing everything - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); this.blockStart = this.strstart; return false; } @@ -743,7 +750,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib if (this.huffman.IsFull()) { bool lastBlock = finish && (this.lookahead == 0); - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock); this.blockStart = this.strstart; return !lastBlock; } @@ -771,7 +778,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib this.prevAvailable = false; // We are flushing everything - this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); this.blockStart = this.strstart; return false; } @@ -846,7 +853,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib } bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; - this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock); + this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock); this.blockStart += len; return !lastBlock; } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs index d6892dfd2..27a8d5671 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs @@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Compression.Zlib private Tree blTree; // Buffer for distances - private readonly IMemoryOwner distanceManagedBuffer; + private readonly IMemoryOwner distanceMemoryOwner; private readonly short* pinnedDistanceBuffer; private MemoryHandle distanceBufferHandle; - private readonly IMemoryOwner literalManagedBuffer; + private readonly IMemoryOwner literalMemoryOwner; private readonly short* pinnedLiteralBuffer; private MemoryHandle literalBufferHandle; @@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Compression.Zlib this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); - this.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize); - this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); + this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin(); this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; - this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize); - this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin(); + this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin(); this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// Count of bytes to write /// True if this is the last block [MethodImpl(InliningOptions.ShortMethod)] - public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) { this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); this.Pending.AlignToByte(); @@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// Index of first byte to flush /// Count of bytes to flush /// True if this is the last block - public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) { this.literalTree.Frequencies[EofSymbol]++; @@ -286,13 +286,13 @@ namespace SixLabors.ImageSharp.Compression.Zlib + this.extraBits; int static_len = this.extraBits; - ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); + ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); for (int i = 0; i < LiteralNumber; i++) { static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); } - ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); + ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); for (int i = 0; i < DistanceNumber; i++) { static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); @@ -419,9 +419,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib { this.Pending.Dispose(); this.distanceBufferHandle.Dispose(); - this.distanceManagedBuffer.Dispose(); + this.distanceMemoryOwner.Dispose(); this.literalBufferHandle.Dispose(); - this.literalManagedBuffer.Dispose(); + this.literalMemoryOwner.Dispose(); this.literalTree.Dispose(); this.blTree.Dispose(); @@ -484,7 +484,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib private IMemoryOwner frequenciesMemoryOwner; private MemoryHandle frequenciesMemoryHandle; - private IManagedByteBuffer lengthsMemoryOwner; + private IMemoryOwner lengthsMemoryOwner; private MemoryHandle lengthsMemoryHandle; public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) @@ -498,7 +498,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; - this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements); + this.lengthsMemoryOwner = memoryAllocator.Allocate(elements); this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); this.Length = (byte*)this.lengthsMemoryHandle.Pointer; diff --git a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs index cbbf7ea79..d949ddf38 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using SixLabors.ImageSharp.Memory; @@ -14,8 +15,8 @@ namespace SixLabors.ImageSharp.Compression.Zlib internal sealed class DeflaterOutputStream : Stream { private const int BufferLength = 512; - private IManagedByteBuffer memoryOwner; - private readonly byte[] buffer; + private IMemoryOwner memoryOwner; + private readonly Memory buffer; private Deflater deflater; private readonly Stream rawStream; private bool isDisposed; @@ -29,8 +30,8 @@ namespace SixLabors.ImageSharp.Compression.Zlib public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) { this.rawStream = rawStream; - this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength); - this.buffer = this.memoryOwner.Array; + this.memoryOwner = memoryAllocator.Allocate(BufferLength); + this.buffer = this.memoryOwner.Memory; this.deflater = new Deflater(memoryAllocator, compressionLevel); } @@ -49,15 +50,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// public override long Position { - get - { - return this.rawStream.Position; - } + get => this.rawStream.Position; - set - { - throw new NotSupportedException(); - } + set => throw new NotSupportedException(); } /// @@ -93,14 +88,14 @@ namespace SixLabors.ImageSharp.Compression.Zlib { while (flushing || !this.deflater.IsNeedingInput) { - int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength); + int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); if (deflateCount <= 0) { break; } - this.rawStream.Write(this.buffer, 0, deflateCount); + this.rawStream.Write(this.buffer.Span.Slice(0, deflateCount)); } if (!this.deflater.IsNeedingInput) @@ -114,13 +109,13 @@ namespace SixLabors.ImageSharp.Compression.Zlib this.deflater.Finish(); while (!this.deflater.IsFinished) { - int len = this.deflater.Deflate(this.buffer, 0, BufferLength); + int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); if (len <= 0) { break; } - this.rawStream.Write(this.buffer, 0, len); + this.rawStream.Write(this.buffer.Span.Slice(0, len)); } if (!this.deflater.IsFinished) diff --git a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs index 36dfd92da..8f2c8d398 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Compression.Zlib @@ -13,9 +14,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// internal sealed unsafe class DeflaterPendingBuffer : IDisposable { - private readonly byte[] buffer; + private readonly Memory buffer; private readonly byte* pinnedBuffer; - private IManagedByteBuffer bufferMemoryOwner; + private IMemoryOwner bufferMemoryOwner; private MemoryHandle bufferMemoryHandle; private int start; @@ -29,9 +30,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// The memory allocator to use for buffer allocations. public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) { - this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); - this.buffer = this.bufferMemoryOwner.Array; - this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin(); + this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE); + this.buffer = this.bufferMemoryOwner.Memory; + this.bufferMemoryHandle = this.buffer.Pin(); this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; } @@ -70,9 +71,13 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// The offset of first byte to write. /// The number of bytes to write. [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBlock(byte[] block, int offset, int length) + public void WriteBlock(ReadOnlySpan block, int offset, int length) { - Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref this.buffer.Span[this.end], + ref MemoryMarshal.GetReference(block.Slice(offset)), + unchecked((uint)length)); + this.end += length; } @@ -136,7 +141,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib /// The offset into output array. /// The maximum number of bytes to store. /// The number of bytes flushed. - public int Flush(byte[] output, int offset, int length) + public int Flush(Span output, int offset, int length) { if (this.BitCount >= 8) { @@ -149,13 +154,19 @@ namespace SixLabors.ImageSharp.Compression.Zlib { length = this.end - this.start; - Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); this.start = 0; this.end = 0; } else { - Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); this.start += length; } From bd4d2c0ab350e36f0d3acf153d9b10742a7a9f53 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Jul 2021 23:29:10 +1000 Subject: [PATCH 006/119] Final migrations --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 23 ++++++++--------- .../Formats/Jpeg/JpegDecoderCore.cs | 22 +++++++++------- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +-- .../Tiff/Writers/TiffBiColorWriter{TPixel}.cs | 2 +- .../Tiff/Writers/TiffPaletteWriter{TPixel}.cs | 4 +-- .../Memory/Allocators/MemoryAllocator.cs | 4 +-- .../RgbPlanarTiffColorTests.cs | 25 ++++++++++++++----- 7 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 7a18d847c..c6ca5b09d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -348,17 +348,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { bool isL8 = typeof(TPixel) == typeof(L8); - using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize8Bit, AllocationOptions.Clean); + Span colorPalette = colorPaletteBuffer.GetSpan(); + + if (isL8) { - Span colorPalette = colorPaletteBuffer.GetSpan(); - if (isL8) - { - this.Write8BitGray(stream, image, colorPalette); - } - else - { - this.Write8BitColor(stream, image, colorPalette); - } + this.Write8BitGray(stream, image, colorPalette); + } + else + { + this.Write8BitColor(stream, image, colorPalette); } } @@ -442,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp MaxColors = 16 }); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; @@ -486,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp MaxColors = 2 }); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 8571cf0ec..9f3966de2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -928,9 +929,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { int length = remaining; - using (IManagedByteBuffer huffmanData = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) { - ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); + Span huffmanDataSpan = huffmanData.GetSpan(); + ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan); for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -949,11 +951,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index."); } - stream.Read(huffmanData.Array, 0, 16); + stream.Read(huffmanDataSpan, 0, 16); - using (IManagedByteBuffer codeLengths = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) + using (IMemoryOwner codeLengths = this.Configuration.MemoryAllocator.Allocate(17, AllocationOptions.Clean)) { - ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); + Span codeLengthsSpan = codeLengths.GetSpan(); + ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan); int codeLengthSum = 0; for (int j = 1; j < 17; j++) @@ -968,17 +971,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } - using (IManagedByteBuffer huffmanValues = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) { - stream.Read(huffmanValues.Array, 0, codeLengthSum); + Span huffmanValuesSpan = huffmanValues.GetSpan(); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); i += 17 + codeLengthSum; this.BuildHuffmanTable( tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableIndex, - codeLengths.GetSpan(), - huffmanValues.GetSpan()); + codeLengthsSpan, + huffmanValuesSpan); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 36d700103..de70a9dff 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -393,8 +393,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.Configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); } /// diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index 662e729ef..6c96e4fc3 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers { // Write uncompressed image. int bytesPerStrip = this.BytesPerRow * height; - this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); + this.bitStrip ??= this.MemoryAllocator.Allocate(bytesPerStrip); this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index 61e24d652..e95236fd2 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers else { int stripPixels = width * height; - this.indexedPixelsBuffer ??= this.MemoryAllocator.AllocateManagedByteBuffer(stripPixels); + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(stripPixels); Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); int lastRow = y + height; int indexedPixelsRowIdx = 0; @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers private void AddColorMapTag() { - using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.colorPaletteBytes); + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index ff376a618..af56b99a0 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Memory protected internal abstract int GetBufferCapacityInBytes(); /// - /// Allocates an , holding a of length . + /// Allocates an , holding a of length . /// /// Type of the data stored in the buffer. /// Size of the buffer to allocate. diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs index e9c73a668..73862b852 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; @@ -242,19 +243,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [MemberData(nameof(Rgb4Data))] [MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb484_Data))] - public void Decode_WritesPixelData(byte[][] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => + public void Decode_WritesPixelData( + byte[][] inputData, + TiffBitsPerSample bitsPerSample, + int left, + int top, + int width, + int height, + Rgba32[][] expectedResult) + => AssertDecode( + expectedResult, + pixels => { - var buffers = new IManagedByteBuffer[inputData.Length]; + var buffers = new IMemoryOwner[inputData.Length]; for (int i = 0; i < buffers.Length; i++) { - buffers[i] = Configuration.Default.MemoryAllocator.AllocateManagedByteBuffer(inputData[i].Length); + buffers[i] = Configuration.Default.MemoryAllocator.Allocate(inputData[i].Length); ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); } new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); + + foreach (IMemoryOwner buffer in buffers) + { + buffer.Dispose(); + } }); - } } } From f37b46e56760fda5b761bf0e7cdd6404d0e66e12 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 13:23:39 +0200 Subject: [PATCH 007/119] initial import of beeees stress code --- tests/Directory.Build.targets | 7 + .../ImageSharp.Benchmarks.csproj | 8 +- .../LoadResizeSaveStressRunner.cs | 221 ++++++++++++++++++ .../LoadResizeSaveStress_NonParallel.cs | 52 +++++ .../LoadResizeSaveStress_Parallel.cs | 48 ++++ .../ImageSharp.Tests.ProfilingSandbox.csproj | 2 + .../LoadResizeSaveParallelMemoryStress.cs | 100 ++++++++ .../Program.cs | 4 +- 8 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs create mode 100644 tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 9c1788145..5ca7d2b93 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -29,6 +29,13 @@ + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 17f6068d4..30fbbbda9 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -9,7 +9,7 @@ false Debug;Release;Debug-InnerLoop;Release-InnerLoop - + 9 @@ -41,6 +41,12 @@ + + + + + + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs new file mode 100644 index 000000000..77585213f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -0,0 +1,221 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using FreeImageAPI; +using ImageMagick; +using PhotoSauce.MagicScaler; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +using SkiaSharp; +using ImageSharpImage = SixLabors.ImageSharp.Image; +using ImageSharpSize = SixLabors.ImageSharp.Size; +using NetVipsImage = NetVips.Image; +using SystemDrawingImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + public class LoadResizeSaveStressRunner + { + private const int ThumbnailSize = 150; + private const int Quality = 75; + private const string ImageSharp = nameof(ImageSharp); + private const string SystemDrawing = nameof(SystemDrawing); + private const string MagickNET = nameof(MagickNET); + private const string NetVips = nameof(NetVips); + private const string FreeImage = nameof(FreeImage); + private const string MagicScaler = nameof(MagicScaler); + private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); + private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); + + // Set the quality for ImagSharp + private readonly JpegEncoder imageSharpJpegEncoder = new () { Quality = Quality }; + private readonly ImageCodecInfo systemDrawingJpegCodec = + ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); + + public string[] Images { get; private set; } + + private string outputDirectory; + + public int ImageCount { get; set; } = int.MaxValue; + + public void Init() + { + if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) + { + // Workaround ImageMagick issue + OpenCL.IsEnabled = false; + } + + string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); + if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) + { + throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); + } + + // Get at most this.ImageCount images from there + this.Images = Directory.EnumerateFiles(imageDirectory).Take(this.ImageCount).ToArray(); + + // Create the output directory next to the images directory + this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + } + + private string OutputPath(string inputPath, string postfix) => + Path.Combine( + this.outputDirectory, + Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); + + private (int width, int height) ScaledSize(int inWidth, int inHeight, int outSize) + { + int width, height; + if (inWidth > inHeight) + { + width = outSize; + height = (int)Math.Round(inHeight * outSize / (double)inWidth); + } + else + { + width = (int)Math.Round(inWidth * outSize / (double)inHeight); + height = outSize; + } + + return (width, height); + } + + public void SystemDrawingResize(string input) + { + using var image = SystemDrawingImage.FromFile(input, true); + (int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); + var resized = new Bitmap(scaled.width, scaled.height); + using var graphics = Graphics.FromImage(resized); + using var attributes = new ImageAttributes(); + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.AssumeLinear; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + + // Save the results + using var encoderParams = new EncoderParameters(1); + using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); + encoderParams.Param[0] = qualityParam; + resized.Save(this.OutputPath(input, SystemDrawing), this.systemDrawingJpegCodec, encoderParams); + } + + public void ImageSharpResize(string input) + { + using FileStream output = File.Open(this.OutputPath(input, ImageSharp), FileMode.Create); + + // Resize it to fit a 150x150 square + using var image = ImageSharpImage.Load(input); + image.Mutate(i => i.Resize(new ResizeOptions + { + Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), + Mode = ResizeMode.Max + })); + + // Reduce the size of the file + image.Metadata.ExifProfile = null; + + // Save the results + image.Save(output, this.imageSharpJpegEncoder); + } + + public void MagickResize(string input) + { + using var image = new MagickImage(input); + + // Resize it to fit a 150x150 square + image.Resize(ThumbnailSize, ThumbnailSize); + + // Reduce the size of the file + image.Strip(); + + // Set the quality + image.Quality = Quality; + + // Save the results + image.Write(this.OutputPath(input, MagickNET)); + } + + public void FreeImageResize(string input) + { + using var original = FreeImageBitmap.FromFile(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + var resized = new FreeImageBitmap(original, scaled.width, scaled.height); + + // JPEG_QUALITYGOOD is 75 JPEG. + // JPEG_BASELINE strips metadata (EXIF, etc.) + resized.Save( + this.OutputPath(input, FreeImage), + FREE_IMAGE_FORMAT.FIF_JPEG, + FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYGOOD | FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE); + } + + public void MagicScalerResize(string input) + { + var settings = new ProcessImageSettings() + { + Width = ThumbnailSize, + Height = ThumbnailSize, + ResizeMode = CropScaleMode.Max, + SaveFormat = FileFormat.Jpeg, + JpegQuality = Quality, + JpegSubsampleMode = ChromaSubsampleMode.Subsample420 + }; + + using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); + MagicImageProcessor.ProcessImage(input, output, settings); + } + + public void SkiaCanvasResize(string input) + { + using var original = SKBitmap.Decode(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); + using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; + SKCanvas canvas = surface.Canvas; + canvas.Scale((float)scaled.width / original.Width); + canvas.DrawBitmap(original, 0, 0, paint); + canvas.Flush(); + + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpCanvas)); + surface.Snapshot() + .Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void SkiaBitmapResize(string input) + { + using var original = SKBitmap.Decode(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); + if (resized == null) + { + return; + } + + using var image = SKImage.FromBitmap(resized); + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpBitmap)); + image.Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void NetVipsResize(string input) + { + // Thumbnail to fit a 150x150 square + using var thumb = NetVipsImage.Thumbnail(input, ThumbnailSize, ThumbnailSize); + + // Save the results + thumb.Jpegsave(this.OutputPath(input, NetVips), q: Quality, strip: true); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs new file mode 100644 index 000000000..99aeaad5e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + public class LoadResizeSaveStress_NonParallel + { + private LoadResizeSaveStressRunner benchmarks; + + [GlobalSetup] + public void Setup() + { + this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; + this.benchmarks.Init(); + } + + private void ForEachImage(Action action) + { + foreach (string image in this.benchmarks.Images) + { + action(image); + } + } + + [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save")] + public void SystemDrawingBenchmark() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + [Benchmark(Description = "ImageSharp Load, Resize, Save")] + public void ImageSharpBenchmark() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + [Benchmark(Description = "ImageMagick Load, Resize, Save")] + public void MagickBenchmark() => this.ForEachImage(this.benchmarks.MagickResize); + + [Benchmark(Description = "ImageFree Load, Resize, Save")] + public void FreeImageBenchmark() => this.ForEachImage(this.benchmarks.FreeImageResize); + + [Benchmark(Description = "MagicScaler Load, Resize, Save")] + public void MagicScalerBenchmark() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save")] + public void SkiaCanvasBenchmark() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save")] + public void SkiaBitmapBenchmark() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + [Benchmark(Description = "NetVips Load, Resize, Save")] + public void NetVipsBenchmark() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs new file mode 100644 index 000000000..78f02b71e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + [MemoryDiagnoser] + public class LoadResizeSaveStress_Parallel + { + private LoadResizeSaveStressRunner benchmarks; + + [GlobalSetup] + public void Setup() + { + this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; + this.benchmarks.Init(); + } + + private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + + [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save - Parallel")] + public void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + [Benchmark(Description = "ImageSharp Load, Resize, Save - Parallel")] + public void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + [Benchmark(Description = "ImageMagick Load, Resize, Save - Parallel")] + public void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + + [Benchmark(Description = "ImageFree Load, Resize, Save - Parallel")] + public void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); + + [Benchmark(Description = "MagicScaler Load, Resize, Save - Parallel")] + public void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save - Parallel")] + public void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save - Parallel")] + public void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + [Benchmark(Description = "NetVips Load, Resize, Save - Parallel")] + public void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index a60ac604f..c4fd2bf70 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -14,6 +14,7 @@ false Debug;Release;Debug-InnerLoop;Release-InnerLoop false + 9 @@ -31,6 +32,7 @@ + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs new file mode 100644 index 000000000..61bdc33b3 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +{ + internal class LoadResizeSaveParallelMemoryStress + { + private readonly LoadResizeSaveStressRunner benchmarks; + + public LoadResizeSaveParallelMemoryStress() + { + this.benchmarks = new LoadResizeSaveStressRunner(); + this.benchmarks.Init(); + } + + public static void Run() + { + Console.WriteLine(@"Choose a library for image resizing stress test: + +1. System.Drawing +2. ImageSharp +3. MagicScaler +4. SkiaSharp +5. NetVips +6. ImageMagick +7. FreeImage +"); + + ConsoleKey key = Console.ReadKey().Key; + if (key < ConsoleKey.D1 || key > ConsoleKey.D7) + { + Console.WriteLine("Unrecognized command."); + return; + } + + try + { + var lrs = new LoadResizeSaveParallelMemoryStress(); + Console.WriteLine("\nRunning..."); + var timer = new Stopwatch(); + timer.Start(); + + switch (key) + { + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaCanvasBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.MagickBenchmarkParallel(); + break; + case ConsoleKey.D7: + lrs.FreeImageBenchmarkParallel(); + break; + } + + timer.Stop(); + Console.WriteLine($"Completed in {timer.ElapsedMilliseconds / 1000.0:f3}sec"); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + + private void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); + + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + private void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 50a930b6f..8e03fbbec 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -31,7 +32,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - RunJpegEncoderProfilingTests(); + LoadResizeSaveParallelMemoryStress.Run(); + // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); From a94aa889e8b2e16170b70f89804fd8ecba37e552 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 15:01:30 +0200 Subject: [PATCH 008/119] gitignore MemoryStress images --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 475d6e76b..769a40c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -221,4 +221,5 @@ artifacts/ # Tests **/Images/ActualOutput **/Images/ReferenceOutput +**/Images/Input/MemoryStress .DS_Store From 2e91fc836301384914b929f137beb999e4bd8cb3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 15:09:23 +0200 Subject: [PATCH 009/119] add a README --- tests/ImageSharp.Benchmarks/LoadResizeSave/README.md | 7 +++++++ .../LoadResizeSaveParallelMemoryStress.cs | 1 + tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/README.md diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md new file mode 100644 index 000000000..d21f2772b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md @@ -0,0 +1,7 @@ +The benchmarks have been adapted from the +[PhotoSauce's MemoryStress project](https://github.com/saucecontrol/core-imaging-playground/tree/beeees/MemoryStress). + +### Setup + +Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr + and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 61bdc33b3..54b09b72b 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { + // See ImageSharp.Benchmarks/LoadResizeSave/README.md internal class LoadResizeSaveParallelMemoryStress { private readonly LoadResizeSaveStressRunner benchmarks; diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 8e03fbbec..9dd7e4c82 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - Console.ReadLine(); + // Console.ReadLine(); } private static void RunJpegEncoderProfilingTests() From 7a91493ddb05332579c989be00df10e69cc3d353 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 18:31:03 +0200 Subject: [PATCH 010/119] unify parallel & non-parallel benchmarks --- .../LoadResizeSaveStressBenchmarks.cs | 72 +++++++++++++++++++ .../LoadResizeSaveStressRunner.cs | 26 +++++++ .../LoadResizeSaveStress_NonParallel.cs | 52 -------------- .../LoadResizeSaveStress_Parallel.cs | 48 ------------- .../LoadResizeSave/README.md | 2 + .../LoadResizeSaveParallelMemoryStress.cs | 59 +++++++++++++-- 6 files changed, 153 insertions(+), 106 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs delete mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs delete mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs new file mode 100644 index 000000000..add5a72ff --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + [MemoryDiagnoser] + [ShortRunJob] + public class LoadResizeSaveStressBenchmarks + { + private LoadResizeSaveStressRunner runner; + + [GlobalSetup] + public void Setup() + { + this.runner = new LoadResizeSaveStressRunner() { ImageCount = Environment.ProcessorCount }; + Console.WriteLine("ImageCount:" + this.runner.ImageCount); + this.runner.Init(); + } + + private void ForEachImage(Action action, int maxDegreeOfParallelism) + { + this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.runner.ForEachImageParallel(action); + } + + public int[] ParallelismValues { get; } = + { + Environment.ProcessorCount, + Environment.ProcessorCount / 2, + Environment.ProcessorCount / 4, + 1 + }; + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(ParallelismValues))] + public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void FreeImage(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.FreeImageResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaCanvas(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaCanvasResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index 77585213f..bb9b68d65 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -8,6 +8,7 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using FreeImageAPI; using ImageMagick; using PhotoSauce.MagicScaler; @@ -42,10 +43,14 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public string[] Images { get; private set; } + public double TotalProcessedMegapixels { get; private set; } + private string outputDirectory; public int ImageCount { get; set; } = int.MaxValue; + public int MaxDegreeOfParallelism { get; set; } = -1; + public void Init() { if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) @@ -67,6 +72,17 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); } + public void ForEachImageParallel(Action action) => Parallel.ForEach( + this.Images, + new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, + action); + + private void IncreaseTotalMegapixels(int width, int height) + { + double pixels = width * (double)height; + this.TotalProcessedMegapixels += pixels / 1_000_000.0; + } + private string OutputPath(string inputPath, string postfix) => Path.Combine( this.outputDirectory, @@ -92,6 +108,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SystemDrawingResize(string input) { using var image = SystemDrawingImage.FromFile(input, true); + this.IncreaseTotalMegapixels(image.Width, image.Height); + (int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); var resized = new Bitmap(scaled.width, scaled.height); using var graphics = Graphics.FromImage(resized); @@ -116,6 +134,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Resize it to fit a 150x150 square using var image = ImageSharpImage.Load(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); + image.Mutate(i => i.Resize(new ResizeOptions { Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), @@ -132,6 +152,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void MagickResize(string input) { using var image = new MagickImage(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); // Resize it to fit a 150x150 square image.Resize(ThumbnailSize, ThumbnailSize); @@ -149,6 +170,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void FreeImageResize(string input) { using var original = FreeImageBitmap.FromFile(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); var resized = new FreeImageBitmap(original, scaled.width, scaled.height); @@ -172,6 +195,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave JpegSubsampleMode = ChromaSubsampleMode.Subsample420 }; + // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); MagicImageProcessor.ProcessImage(input, output, settings); } @@ -179,6 +203,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaCanvasResize(string input) { using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; @@ -196,6 +221,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaBitmapResize(string input) { using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); if (resized == null) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs deleted file mode 100644 index 99aeaad5e..000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave -{ - public class LoadResizeSaveStress_NonParallel - { - private LoadResizeSaveStressRunner benchmarks; - - [GlobalSetup] - public void Setup() - { - this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; - this.benchmarks.Init(); - } - - private void ForEachImage(Action action) - { - foreach (string image in this.benchmarks.Images) - { - action(image); - } - } - - [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save")] - public void SystemDrawingBenchmark() => this.ForEachImage(this.benchmarks.SystemDrawingResize); - - [Benchmark(Description = "ImageSharp Load, Resize, Save")] - public void ImageSharpBenchmark() => this.ForEachImage(this.benchmarks.ImageSharpResize); - - [Benchmark(Description = "ImageMagick Load, Resize, Save")] - public void MagickBenchmark() => this.ForEachImage(this.benchmarks.MagickResize); - - [Benchmark(Description = "ImageFree Load, Resize, Save")] - public void FreeImageBenchmark() => this.ForEachImage(this.benchmarks.FreeImageResize); - - [Benchmark(Description = "MagicScaler Load, Resize, Save")] - public void MagicScalerBenchmark() => this.ForEachImage(this.benchmarks.MagicScalerResize); - - [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save")] - public void SkiaCanvasBenchmark() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - - [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save")] - public void SkiaBitmapBenchmark() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); - - [Benchmark(Description = "NetVips Load, Resize, Save")] - public void NetVipsBenchmark() => this.ForEachImage(this.benchmarks.NetVipsResize); - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs deleted file mode 100644 index 78f02b71e..000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave -{ - [MemoryDiagnoser] - public class LoadResizeSaveStress_Parallel - { - private LoadResizeSaveStressRunner benchmarks; - - [GlobalSetup] - public void Setup() - { - this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; - this.benchmarks.Init(); - } - - private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); - - [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save - Parallel")] - public void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); - - [Benchmark(Description = "ImageSharp Load, Resize, Save - Parallel")] - public void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); - - [Benchmark(Description = "ImageMagick Load, Resize, Save - Parallel")] - public void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); - - [Benchmark(Description = "ImageFree Load, Resize, Save - Parallel")] - public void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); - - [Benchmark(Description = "MagicScaler Load, Resize, Save - Parallel")] - public void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); - - [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save - Parallel")] - public void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - - [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save - Parallel")] - public void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); - - [Benchmark(Description = "NetVips Load, Resize, Save - Parallel")] - public void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md index d21f2772b..6cb48eb48 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md @@ -5,3 +5,5 @@ Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 54b09b72b..fdf686080 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Text; using System.Threading.Tasks; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; @@ -13,12 +14,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { private readonly LoadResizeSaveStressRunner benchmarks; - public LoadResizeSaveParallelMemoryStress() + private LoadResizeSaveParallelMemoryStress() { this.benchmarks = new LoadResizeSaveStressRunner(); this.benchmarks.Init(); } + private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; + public static void Run() { Console.WriteLine(@"Choose a library for image resizing stress test: @@ -42,9 +45,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox try { var lrs = new LoadResizeSaveParallelMemoryStress(); - Console.WriteLine("\nRunning..."); - var timer = new Stopwatch(); - timer.Start(); + lrs.benchmarks.MaxDegreeOfParallelism = 10; + + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); + var timer = Stopwatch.StartNew(); switch (key) { @@ -72,7 +77,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } timer.Stop(); - Console.WriteLine($"Completed in {timer.ElapsedMilliseconds / 1000.0:f3}sec"); + var stats = Stats.Create(timer, lrs.TotalProcessedMegapixels); + Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); + Console.WriteLine(stats.GetMarkdown()); } catch (Exception ex) { @@ -80,7 +87,39 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + record Stats(double TotalSeconds, double TotalMegapixels, double MegapixelsPerSec, double MegapixelsPerSecPerCpu) + { + public static Stats Create(Stopwatch sw, double totalMegapixels) + { + double totalSeconds = sw.ElapsedMilliseconds / 1000.0; + double megapixelsPerSec = totalMegapixels / totalSeconds; + double megapixelsPerSecPerCpu = megapixelsPerSec / Environment.ProcessorCount; + return new Stats(totalSeconds, totalMegapixels, megapixelsPerSec, megapixelsPerSecPerCpu); + } + + public string GetMarkdown() + { + var bld = new StringBuilder(); + bld.AppendLine($"| {nameof(TotalSeconds)} | {nameof(MegapixelsPerSec)} | {nameof(MegapixelsPerSecPerCpu)} |"); + bld.AppendLine( + $"| {L(nameof(TotalSeconds))} | {L(nameof(MegapixelsPerSec))} | {L(nameof(MegapixelsPerSecPerCpu))} |"); + + bld.Append("| "); + bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendLine(" |"); + + return bld.ToString(); + + static string L(string header) => new ('-', header.Length); + static string F(string column) => $"{{0,{column.Length}:f3}}"; + } + } + + private void ForEachImage(Action action) => this.benchmarks.ForEachImageParallel(action); private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); @@ -99,3 +138,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); } } + +// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} From 5a9826e85840f882fcd4bd20607b1bfd8ac7e6e0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 19:44:15 +0200 Subject: [PATCH 011/119] remove FreeImage --- tests/Directory.Build.targets | 1 - .../ImageSharp.Benchmarks.csproj | 4 ++-- .../LoadResizeSaveStressBenchmarks.cs | 11 ----------- .../LoadResizeSaveStressRunner.cs | 18 ------------------ .../LoadResizeSaveParallelMemoryStress.cs | 10 +--------- 5 files changed, 3 insertions(+), 41 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 5ca7d2b93..238046d75 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -30,7 +30,6 @@ - diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 30fbbbda9..248b14a9d 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -41,10 +41,10 @@ - - + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index add5a72ff..dc2f49b1e 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave @@ -49,18 +46,10 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave [ArgumentsSource(nameof(ParallelismValues))] public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void FreeImage(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.FreeImageResize, maxDegreeOfParallelism); - [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaCanvas(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaCanvasResize, maxDegreeOfParallelism); - [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index bb9b68d65..bd3a8e62d 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -9,7 +9,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; -using FreeImageAPI; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; @@ -31,7 +30,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave private const string SystemDrawing = nameof(SystemDrawing); private const string MagickNET = nameof(MagickNET); private const string NetVips = nameof(NetVips); - private const string FreeImage = nameof(FreeImage); private const string MagicScaler = nameof(MagicScaler); private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); @@ -167,22 +165,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave image.Write(this.OutputPath(input, MagickNET)); } - public void FreeImageResize(string input) - { - using var original = FreeImageBitmap.FromFile(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); - - (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); - var resized = new FreeImageBitmap(original, scaled.width, scaled.height); - - // JPEG_QUALITYGOOD is 75 JPEG. - // JPEG_BASELINE strips metadata (EXIF, etc.) - resized.Save( - this.OutputPath(input, FreeImage), - FREE_IMAGE_FORMAT.FIF_JPEG, - FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYGOOD | FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE); - } - public void MagicScalerResize(string input) { var settings = new ProcessImageSettings() diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index fdf686080..fac04e94d 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -32,7 +32,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 4. SkiaSharp 5. NetVips 6. ImageMagick -7. FreeImage "); ConsoleKey key = Console.ReadKey().Key; @@ -63,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox lrs.MagicScalerBenchmarkParallel(); break; case ConsoleKey.D4: - lrs.SkiaCanvasBenchmarkParallel(); + lrs.SkiaBitmapBenchmarkParallel(); break; case ConsoleKey.D5: lrs.NetVipsBenchmarkParallel(); @@ -71,9 +70,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox case ConsoleKey.D6: lrs.MagickBenchmarkParallel(); break; - case ConsoleKey.D7: - lrs.FreeImageBenchmarkParallel(); - break; } timer.Stop(); @@ -127,12 +123,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); - private void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); - private void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); From 19f3559b9261ee63bcd0b0ad319ede5a403faef1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:11:16 +0200 Subject: [PATCH 012/119] minor fix --- .../LoadResizeSave/LoadResizeSaveStressBenchmarks.cs | 1 + .../LoadResizeSaveParallelMemoryStress.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index dc2f49b1e..6dad6944e 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { + // See README.md for instructions about initialization. [MemoryDiagnoser] [ShortRunJob] public class LoadResizeSaveStressBenchmarks diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index fac04e94d..ecf76fb48 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox "); ConsoleKey key = Console.ReadKey().Key; - if (key < ConsoleKey.D1 || key > ConsoleKey.D7) + if (key < ConsoleKey.D1 || key > ConsoleKey.D6) { Console.WriteLine("Unrecognized command."); return; From 21544fdbbee6682fa837fbf96df7abc4896da41a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:32:23 +0200 Subject: [PATCH 013/119] fix StyleCop warnings --- .../LoadResizeSaveParallelMemoryStress.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index ecf76fb48..a4b27e0a5 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -4,7 +4,7 @@ using System; using System.Diagnostics; using System.Text; -using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } timer.Stop(); - var stats = Stats.Create(timer, lrs.TotalProcessedMegapixels); + var stats = new Stats(timer, lrs.TotalProcessedMegapixels); Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); Console.WriteLine(stats.GetMarkdown()); } @@ -83,22 +83,30 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - record Stats(double TotalSeconds, double TotalMegapixels, double MegapixelsPerSec, double MegapixelsPerSecPerCpu) + private struct Stats { - public static Stats Create(Stopwatch sw, double totalMegapixels) + public double TotalSeconds { get; } + + public double TotalMegapixels { get; } + + public double MegapixelsPerSec { get; } + + public double MegapixelsPerSecPerCpu { get; } + + public Stats(Stopwatch sw, double totalMegapixels) { - double totalSeconds = sw.ElapsedMilliseconds / 1000.0; - double megapixelsPerSec = totalMegapixels / totalSeconds; - double megapixelsPerSecPerCpu = megapixelsPerSec / Environment.ProcessorCount; - return new Stats(totalSeconds, totalMegapixels, megapixelsPerSec, megapixelsPerSecPerCpu); + this.TotalMegapixels = totalMegapixels; + this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; + this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; + this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; } public string GetMarkdown() { var bld = new StringBuilder(); - bld.AppendLine($"| {nameof(TotalSeconds)} | {nameof(MegapixelsPerSec)} | {nameof(MegapixelsPerSecPerCpu)} |"); + bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); bld.AppendLine( - $"| {L(nameof(TotalSeconds))} | {L(nameof(MegapixelsPerSec))} | {L(nameof(MegapixelsPerSecPerCpu))} |"); + $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); bld.Append("| "); bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); @@ -130,11 +138,3 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); } } - -// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit - { - } -} From 2566879ff5be41a573c6c22a04432576512dbad8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:45:19 +0200 Subject: [PATCH 014/119] workaround NuGet restore issues --- .../ImageSharp.Benchmarks.csproj | 11 ++++++----- .../LoadResizeSaveParallelMemoryStress.cs | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 248b14a9d..5888b3c04 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -41,12 +41,13 @@ - - - + + + + - + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index a4b27e0a5..5a1a242e2 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Text; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox From 17b0e3ed82c99d4fa921788fa360ff4eb3c5c53b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Jul 2021 13:11:13 +1000 Subject: [PATCH 015/119] Fix reference issues --- tests/Directory.Build.targets | 22 +++++++++---------- .../ImageSharp.Benchmarks.csproj | 12 +++++----- .../ImageSharp.Tests.ProfilingSandbox.csproj | 5 +++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 238046d75..53b4f9632 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -18,23 +18,21 @@ - - + + - + + + + - - - - - - - - - + + + + diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 5888b3c04..84b83ee14 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -38,16 +38,14 @@ + + + + + - - - - - - - diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index c4fd2bf70..10deb24c6 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -38,6 +38,11 @@ + + + + + From 69687da552679c6af89ca49b849ac587107aeecd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Jul 2021 13:21:14 +1000 Subject: [PATCH 016/119] Add missing ref for macOS --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index b8d44d0d1..30bd544fa 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -39,6 +39,7 @@ + From 0de8878a4045bf7df65f9f9b216b24afd5bd9976 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 09:32:42 +0200 Subject: [PATCH 017/119] Change DebugGuard min from 1 to 0 --- .../Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index f5ef77091..6d3620c62 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { public static void LoadAndStretchEdges(RowOctet source, Span dest, Point start, Size sampleSize, Size totalSize) { - DebugGuard.MustBeBetweenOrEqualTo(start.X, 1, totalSize.Width - 1, nameof(start.X)); - DebugGuard.MustBeBetweenOrEqualTo(start.Y, 1, totalSize.Height - 1, nameof(start.Y)); + DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X)); + DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y)); int width = Math.Min(sampleSize.Width, totalSize.Width - start.X); int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y); From 91ff89181502c897133578dea18dfcd412e219c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 09:36:16 +0200 Subject: [PATCH 018/119] Put decoder and encoder tests in one test collection, so those tests are executed serial --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs | 1 + 12 files changed, 12 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 2b42b65f0..e64d8452f 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -20,6 +20,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { + [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 90e6cf43f..f338c1aff 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -19,6 +19,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { + [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index c3250d72c..c0df1e400 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -17,6 +17,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { + [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 3a0f188ce..bd24e1a8d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -14,6 +14,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { + [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 67df6a881..d13a9696c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -22,6 +22,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes + [Collection("RunSerial")] [Trait("Format", "Jpg")] public partial class JpegDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 3c48865c7..8e12b04be 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -20,6 +20,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + [Collection("RunSerial")] [Trait("Format", "Jpg")] public class JpegEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 7147f82d6..9832aeb7b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -16,6 +16,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { + [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 58d733c4f..50bacfba4 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -15,6 +15,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { + [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 2a7aca882..ac94a8fc8 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -16,6 +16,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { + [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index d6eb333a2..1ad4f9a84 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -13,6 +13,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { + [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaEncoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 6b82f4281..a007cd3a9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -17,6 +17,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { + [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 09505692f..0286671ae 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -17,6 +17,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { + [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffEncoderTests { From 20040bd89a255fa9b0b6eb51d773be93640748b9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 5 Jul 2021 15:11:13 +0200 Subject: [PATCH 019/119] add ability to filter for Baseline/Progressive --- .../LoadResizeSaveStressBenchmarks.cs | 11 +++- .../LoadResizeSaveStressRunner.cs | 53 ++++++++++++++++++- .../LoadResizeSaveParallelMemoryStress.cs | 7 ++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index 6dad6944e..f1f7de3dc 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -13,11 +13,18 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { private LoadResizeSaveStressRunner runner; + // private const JpegKind Filter = JpegKind.Progressive; + private const JpegKind Filter = JpegKind.Any; + [GlobalSetup] public void Setup() { - this.runner = new LoadResizeSaveStressRunner() { ImageCount = Environment.ProcessorCount }; - Console.WriteLine("ImageCount:" + this.runner.ImageCount); + this.runner = new LoadResizeSaveStressRunner() + { + ImageCount = Environment.ProcessorCount, + Filter = Filter + }; + Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); this.runner.Init(); } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index bd3a8e62d..c15f641b4 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -22,6 +22,13 @@ using SystemDrawingImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { + public enum JpegKind + { + Baseline = 1, + Progressive = 2, + Any = Baseline | Progressive + } + public class LoadResizeSaveStressRunner { private const int ThumbnailSize = 150; @@ -49,6 +56,43 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int MaxDegreeOfParallelism { get; set; } = -1; + public JpegKind Filter { get; set; } + + private static readonly string[] ProgressiveFiles = + { + "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", + "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", + "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", + "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", + "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", + "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", + "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", + "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", + "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", + "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", + "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", + "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", + "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", + "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", + "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", + "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", + "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", + "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", + "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", + "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", + "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", + "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", + "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", + "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", + "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", + "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", + "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", + "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", + "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", + "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", + "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", + }; + public void Init() { if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) @@ -64,10 +108,17 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave } // Get at most this.ImageCount images from there - this.Images = Directory.EnumerateFiles(imageDirectory).Take(this.ImageCount).ToArray(); + bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); + + this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); // Create the output directory next to the images directory this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + + static JpegKind GetJpegType(string f) => + ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) + ? JpegKind.Progressive + : JpegKind.Baseline; } public void ForEachImageParallel(Action action) => Parallel.ForEach( diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 5a1a242e2..2aadf02eb 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -15,7 +15,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private LoadResizeSaveParallelMemoryStress() { - this.benchmarks = new LoadResizeSaveStressRunner(); + this.benchmarks = new LoadResizeSaveStressRunner() + { + // MaxDegreeOfParallelism = 10, + // Filter = JpegKind.Baseline + }; this.benchmarks.Init(); } @@ -43,7 +47,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox try { var lrs = new LoadResizeSaveParallelMemoryStress(); - lrs.benchmarks.MaxDegreeOfParallelism = 10; Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); From 6f6ee7337dcdf1a099a95fdd0a481fc582442c3d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 7 Jul 2021 23:38:48 +0300 Subject: [PATCH 020/119] Renamed pixel dimensions for JpegFrame --- .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 4 ++-- .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 8 ++++---- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 5c3ee6e28..dd43baa23 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -109,10 +109,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void Init() { this.WidthInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); + MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); this.HeightInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); + MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 827afe38d..13d6bc35e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -28,12 +28,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Gets or sets the number of scanlines within the frame. /// - public int Scanlines { get; set; } + public int PixelHeight { get; set; } /// /// Gets or sets the number of samples per scanline. /// - public int SamplesPerLine { get; set; } + public int PixelWidth { get; set; } /// /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. @@ -95,8 +95,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public void InitComponents() { - this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor); - this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor); + this.McusPerLine = (int)MathF.Ceiling(this.PixelWidth / 8F / this.MaxHorizontalFactor); + this.McusPerColumn = (int)MathF.Ceiling(this.PixelHeight / 8F / this.MaxVerticalFactor); for (int i = 0; i < this.ComponentCount; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9f3966de2..ad47f386d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -852,17 +852,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, Precision = this.temp[0], - Scanlines = (this.temp[1] << 8) | this.temp[2], - SamplesPerLine = (this.temp[3] << 8) | this.temp[4], + PixelHeight = (this.temp[1] << 8) | this.temp[2], + PixelWidth = (this.temp[3] << 8) | this.temp[4], ComponentCount = this.temp[5] }; - if (this.Frame.SamplesPerLine == 0 || this.Frame.Scanlines == 0) + if (this.Frame.PixelWidth == 0 || this.Frame.PixelHeight == 0) { - JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.SamplesPerLine, this.Frame.Scanlines); + JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight); } - this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines); + this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); this.ComponentCount = this.Frame.ComponentCount; if (!metadataOnly) From 925b3ad1389bf10c19fa867149ed5d1ce3201dcb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 7 Jul 2021 23:39:08 +0300 Subject: [PATCH 021/119] Added debug code to the sandbox --- .../Program.cs | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 9dd7e4c82..1a956dc9c 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -32,14 +33,76 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - LoadResizeSaveParallelMemoryStress.Run(); // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - // Console.ReadLine(); + //Test_Performance(20); + + //Test_DebugRun("chroma_444_16x16", true); + //Console.WriteLine(); + //Test_DebugRun("chroma_420_16x16", true); + //Console.WriteLine(); + //Test_DebugRun("444_14x14"); + //Console.WriteLine(); + //Test_DebugRun("baseline_4k_444", false); + //Console.WriteLine(); + //Test_DebugRun("progressive_4k_444", true); + //Console.WriteLine(); + //Test_DebugRun("baseline_4k_420", false); + //Console.WriteLine(); + //Test_DebugRun("cmyk_jpeg"); + //Console.WriteLine(); + //Test_DebugRun("Channel_digital_image_CMYK_color"); + //Console.WriteLine(); + + + //Test_DebugRun("test_baseline_4k_444", false); + //Console.WriteLine(); + //Test_DebugRun("test_progressive_4k_444", false); + //Console.WriteLine(); + //Test_DebugRun("test_baseline_4k_420", false); + //Console.WriteLine(); + + // Binary size of this must be ~2096kb + //Test_DebugRun("422", true); + + Test_DebugRun("baseline_s444_q100", true); + Test_DebugRun("progressive_s444_q100", true); + + Console.ReadLine(); + } + + public static void Test_Performance(int iterations) + { + using var stream = new MemoryStream(File.ReadAllBytes("C:\\Users\\pl4nu\\Downloads\\progressive_4k_444.jpg")); + //using var stream = new MemoryStream(File.ReadAllBytes("C:\\Users\\pl4nu\\Downloads\\baseline_4k_444.jpg")); + var sw = new Stopwatch(); + sw.Start(); + for (int i = 0; i < iterations; i++) + { + using var img = Image.Load(stream); + stream.Position = 0; + } + + sw.Stop(); + Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds}ms\nPer invocation: {sw.ElapsedMilliseconds / iterations}ms"); + } + + public static void Test_DebugRun(string name, bool save = false) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"img: {name}"); + Console.ResetColor(); + using var img = Image.Load($"C:\\Users\\pl4nu\\Downloads\\{name}.jpg"); + + if (save) + { + img.SaveAsJpeg($"C:\\Users\\pl4nu\\Downloads\\test_{name}.jpg", + new ImageSharp.Formats.Jpeg.JpegEncoder { Subsample = ImageSharp.Formats.Jpeg.JpegSubsample.Ratio444, Quality = 100 }); + } } private static void RunJpegEncoderProfilingTests() From 2f8d3c933bf0bf3c5b1b44c44a5f512f2ca6a52e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:03:11 +0300 Subject: [PATCH 022/119] Injected progressive scan parameters --- .../Components/Decoder/HuffmanScanDecoder.cs | 16 ++++------------ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 12 +++++++----- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 6424ee23a..0ff10e270 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -29,16 +29,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private readonly int componentsLength; // The spectral selection start. - private readonly int spectralStart; + public int spectralStart; // The spectral selection end. - private readonly int spectralEnd; + public int spectralEnd; // The successive approximation high bit end. - private readonly int successiveHigh; + public int successiveHigh; // The successive approximation low bit end. - private readonly int successiveLow; + public int successiveLow; // How many mcu's are left to do. private int todo; @@ -74,10 +74,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder HuffmanTable[] acHuffmanTables, int componentsLength, int restartInterval, - int spectralStart, - int spectralEnd, - int successiveHigh, - int successiveLow, CancellationToken cancellationToken) { this.dctZigZag = ZigZag.CreateUnzigTable(); @@ -90,10 +86,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.componentsLength = componentsLength; this.restartInterval = restartInterval; this.todo = restartInterval; - this.spectralStart = spectralStart; - this.spectralEnd = spectralEnd; - this.successiveHigh = successiveHigh; - this.successiveLow = successiveLow; this.cancellationToken = cancellationToken; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ad47f386d..b2eb18941 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1056,11 +1056,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.acHuffmanTables, selectorsCount, this.resetInterval, - spectralStart, - spectralEnd, - successiveApproximation >> 4, - successiveApproximation & 15, - cancellationToken); + cancellationToken) + { + spectralStart = spectralStart, + spectralEnd = spectralEnd, + successiveHigh = successiveApproximation >> 4, + successiveLow = successiveApproximation & 15 + }; sd.ParseEntropyCodedData(); } From 336c64aab675b5b3baec1f82b7031882412c207e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:04:34 +0300 Subject: [PATCH 023/119] Injected scan selectors count --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 4 +--- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 0ff10e270..34eaf1500 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private readonly int restartInterval; // The number of interleaved components. - private readonly int componentsLength; + public int componentsLength; // The spectral selection start. public int spectralStart; @@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder JpegFrame frame, HuffmanTable[] dcHuffmanTables, HuffmanTable[] acHuffmanTables, - int componentsLength, int restartInterval, CancellationToken cancellationToken) { @@ -83,7 +82,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.dcHuffmanTables = dcHuffmanTables; this.acHuffmanTables = acHuffmanTables; this.components = frame.Components; - this.componentsLength = componentsLength; this.restartInterval = restartInterval; this.todo = restartInterval; this.cancellationToken = cancellationToken; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index b2eb18941..9cae029fe 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1054,10 +1054,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame, this.dcHuffmanTables, this.acHuffmanTables, - selectorsCount, this.resetInterval, cancellationToken) { + componentsLength = selectorsCount, + spectralStart = spectralStart, spectralEnd = spectralEnd, successiveHigh = successiveApproximation >> 4, From 3b2d2d8c1de984af69bcac1e2f5bc7435b0f83bc Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:14:19 +0300 Subject: [PATCH 024/119] Injected frame & reset interval --- .../Components/Decoder/HuffmanScanDecoder.cs | 37 +++++++++++++------ .../Formats/Jpeg/JpegDecoderCore.cs | 6 ++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 34eaf1500..c189e4e28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -16,14 +16,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal class HuffmanScanDecoder { - private readonly JpegFrame frame; private readonly HuffmanTable[] dcHuffmanTables; private readonly HuffmanTable[] acHuffmanTables; private readonly BufferedReadStream stream; - private readonly JpegComponent[] components; + + // Frame related + private JpegFrame frame; + private JpegComponent[] components; + + public JpegFrame Frame + { + set + { + frame = value; + components = value.Components; + } + } // The restart interval. - private readonly int restartInterval; + private int restartInterval; + // How many mcu's are left to do. + private int todo; + + public int ResetInterval + { + set + { + restartInterval = value; + todo = value; + } + } // The number of interleaved components. public int componentsLength; @@ -40,9 +62,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // The successive approximation low bit end. public int successiveLow; - // How many mcu's are left to do. - private int todo; - // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. private int eobrun; @@ -69,21 +88,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The token to monitor cancellation. public HuffmanScanDecoder( BufferedReadStream stream, - JpegFrame frame, HuffmanTable[] dcHuffmanTables, HuffmanTable[] acHuffmanTables, - int restartInterval, CancellationToken cancellationToken) { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; this.scanBuffer = new HuffmanScanBuffer(stream); - this.frame = frame; this.dcHuffmanTables = dcHuffmanTables; this.acHuffmanTables = acHuffmanTables; - this.components = frame.Components; - this.restartInterval = restartInterval; - this.todo = restartInterval; this.cancellationToken = cancellationToken; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9cae029fe..5d7e12618 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1051,12 +1051,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var sd = new HuffmanScanDecoder( stream, - this.Frame, this.dcHuffmanTables, this.acHuffmanTables, - this.resetInterval, cancellationToken) { + Frame = this.Frame, + + ResetInterval = this.resetInterval, + componentsLength = selectorsCount, spectralStart = spectralStart, From 5d4450346c36f5fcb15f9504a9e16cd070345042 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:24:18 +0300 Subject: [PATCH 025/119] Injected huffman tables --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 10 ++++------ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 5 +++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index c189e4e28..d212340c1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -16,10 +16,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal class HuffmanScanDecoder { - private readonly HuffmanTable[] dcHuffmanTables; - private readonly HuffmanTable[] acHuffmanTables; private readonly BufferedReadStream stream; + // huffman tables + public HuffmanTable[] dcHuffmanTables; + public HuffmanTable[] acHuffmanTables; + // Frame related private JpegFrame frame; private JpegComponent[] components; @@ -88,15 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The token to monitor cancellation. public HuffmanScanDecoder( BufferedReadStream stream, - HuffmanTable[] dcHuffmanTables, - HuffmanTable[] acHuffmanTables, CancellationToken cancellationToken) { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; this.scanBuffer = new HuffmanScanBuffer(stream); - this.dcHuffmanTables = dcHuffmanTables; - this.acHuffmanTables = acHuffmanTables; this.cancellationToken = cancellationToken; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 5d7e12618..dda4e96ea 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1051,12 +1051,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var sd = new HuffmanScanDecoder( stream, - this.dcHuffmanTables, - this.acHuffmanTables, cancellationToken) { Frame = this.Frame, + dcHuffmanTables = this.dcHuffmanTables, + acHuffmanTables = this.acHuffmanTables, + ResetInterval = this.resetInterval, componentsLength = selectorsCount, From 442af2c5be2a4c552b627a4c0fc79d74cb7d3b63 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:32:31 +0300 Subject: [PATCH 026/119] Scan decoder is not a persistent state of the decoder core --- .../Components/Decoder/HuffmanScanDecoder.cs | 3 +- .../Formats/Jpeg/JpegDecoderCore.cs | 35 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index d212340c1..b1f371e0f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -94,7 +94,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; - this.scanBuffer = new HuffmanScanBuffer(stream); this.cancellationToken = cancellationToken; } @@ -105,6 +104,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); + this.scanBuffer = new HuffmanScanBuffer(this.stream); + if (!this.frame.Progressive) { this.ParseBaselineData(); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index dda4e96ea..a52ce3f9c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -172,6 +172,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Block8x8F[] QuantizationTables { get; private set; } + private HuffmanScanDecoder scanDecoder; + /// /// Finds the next file marker within the byte stream. /// @@ -213,6 +215,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.scanDecoder = new HuffmanScanDecoder(stream, cancellationToken); + this.ParseStream(stream, cancellationToken: cancellationToken); this.InitExifProfile(); this.InitIccProfile(); @@ -1049,26 +1053,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - var sd = new HuffmanScanDecoder( - stream, - cancellationToken) - { - Frame = this.Frame, - - dcHuffmanTables = this.dcHuffmanTables, - acHuffmanTables = this.acHuffmanTables, - - ResetInterval = this.resetInterval, - - componentsLength = selectorsCount, - - spectralStart = spectralStart, - spectralEnd = spectralEnd, - successiveHigh = successiveApproximation >> 4, - successiveLow = successiveApproximation & 15 - }; - - sd.ParseEntropyCodedData(); + this.scanDecoder.Frame = this.Frame; + this.scanDecoder.dcHuffmanTables = this.dcHuffmanTables; + this.scanDecoder.acHuffmanTables = this.acHuffmanTables; + this.scanDecoder.ResetInterval = this.resetInterval; + this.scanDecoder.componentsLength = selectorsCount; + this.scanDecoder.spectralStart = spectralStart; + this.scanDecoder.spectralEnd = spectralEnd; + this.scanDecoder.successiveHigh = successiveApproximation >> 4; + this.scanDecoder.successiveLow = successiveApproximation & 15; + + this.scanDecoder.ParseEntropyCodedData(); } /// From b7d54b10c825659b3d2cb4ef027c3aba185d82d0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:36:01 +0300 Subject: [PATCH 027/119] Added comments for future refactoring --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a52ce3f9c..54c3d0a5b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1053,11 +1053,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; + // This can be injected in SOF marker callback this.scanDecoder.Frame = this.Frame; + + // Huffman tables can be calculated directly in the scan decoder class this.scanDecoder.dcHuffmanTables = this.dcHuffmanTables; this.scanDecoder.acHuffmanTables = this.acHuffmanTables; + + // This can be injectd in DRI marker callback this.scanDecoder.ResetInterval = this.resetInterval; + + // This can be passed as ParseEntropyCodedData() parameter as it is used only there this.scanDecoder.componentsLength = selectorsCount; + + // This is okay to inject here, might be good to wrap it in a separate struct but not really necessary this.scanDecoder.spectralStart = spectralStart; this.scanDecoder.spectralEnd = spectralEnd; this.scanDecoder.successiveHigh = successiveApproximation >> 4; From 7044741601300960929b9b1de0d27d2b2cef5599 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 00:41:49 +0300 Subject: [PATCH 028/119] Added extra comment --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 54c3d0a5b..7455d2a60 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1053,6 +1053,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; + // All the comments below are for separate refactoring PR + // Main reason it's not fixed here is to make this commit less intrusive + // This can be injected in SOF marker callback this.scanDecoder.Frame = this.Frame; From 9b8172473b2c2c9afdc8e5574606bd87c8768268 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 06:48:39 +0300 Subject: [PATCH 029/119] Jpeg frame is now injected to the scan decoder at the SOF marker --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 7455d2a60..44bd05145 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -920,6 +920,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; this.Frame.InitComponents(); this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); + + // This can be injected in SOF marker callback + this.scanDecoder.Frame = this.Frame; } } @@ -1056,9 +1059,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // All the comments below are for separate refactoring PR // Main reason it's not fixed here is to make this commit less intrusive - // This can be injected in SOF marker callback - this.scanDecoder.Frame = this.Frame; - // Huffman tables can be calculated directly in the scan decoder class this.scanDecoder.dcHuffmanTables = this.dcHuffmanTables; this.scanDecoder.acHuffmanTables = this.acHuffmanTables; From 7e1bd5906834ed45cc7a5b81e327ff27ebe998cb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 15:10:07 +0300 Subject: [PATCH 030/119] Slight change to image post processor for better understanding --- .../Decoder/JpegImagePostProcessor.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 5b0331c85..fd3b9b431 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -132,28 +132,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Execute one step processing pixel rows into 'destination'. - /// - /// The pixel type - /// The destination image. - public void DoPostProcessorStep(ImageFrame destination) - where TPixel : unmanaged, IPixel - { - foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) - { - cpp.CopyBlocksToColorBuffer(); - } - - this.ConvertColorsInto(destination); - - this.PixelRowCounter += PixelRowsPerStep; - } - - /// /// Convert and copy row of colors into 'destination' starting at row . /// /// The pixel type /// The destination image - private void ConvertColorsInto(ImageFrame destination) + public void DoPostProcessorStep(ImageFrame destination) where TPixel : unmanaged, IPixel { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); @@ -161,6 +144,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var buffers = new Buffer2D[this.ComponentProcessors.Length]; for (int i = 0; i < this.ComponentProcessors.Length; i++) { + this.ComponentProcessors[i].CopyBlocksToColorBuffer(); buffers[i] = this.ComponentProcessors[i].ColorBuffer; } @@ -176,6 +160,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // TODO: Investigate if slicing is actually necessary PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } + + this.PixelRowCounter += PixelRowsPerStep; } } } From 5ba8763ade0ef953eb4d2ec6accffd8ba4a030c7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 8 Jul 2021 15:58:41 +0300 Subject: [PATCH 031/119] Replaced hardcoded values with actual calculated ones in postprocessor --- .../Decoder/JpegComponentPostProcessor.cs | 2 +- .../Components/Decoder/JpegImagePostProcessor.cs | 13 +++++++++---- tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 5 ++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index fc1ebaf92..0a0a2cd9e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder imagePostProcessor.PostProcessorBufferSize.Height, this.blockAreaSize.Height); - this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; + this.BlockRowsPerStep = imagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index fd3b9b431..5f3389e17 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// The number of block rows to be processed in one Step. /// - public const int BlockRowsPerStep = 4; + public int BlockRowsPerStep; /// /// The number of image pixel rows to be processed in one step. /// - public const int PixelRowsPerStep = 4 * 8; + public int PixelRowsPerStep; /// /// Temporal buffer to store a row of colors. @@ -56,8 +56,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.configuration = configuration; this.RawJpeg = rawJpeg; IJpegComponent c0 = rawJpeg.Components[0]; - this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; - this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); + + this.BlockRowsPerStep = c0.SamplingFactors.Height; + this.PixelRowsPerStep = this.BlockRowsPerStep * 8; + + this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.BlockRowsPerStep; + + this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 1a956dc9c..e0b7f31a7 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -58,7 +58,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox //Test_DebugRun("Channel_digital_image_CMYK_color"); //Console.WriteLine(); - //Test_DebugRun("test_baseline_4k_444", false); //Console.WriteLine(); //Test_DebugRun("test_progressive_4k_444", false); @@ -69,6 +68,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // Binary size of this must be ~2096kb //Test_DebugRun("422", true); + //Test_DebugRun("baseline_4k_420", false); + //Test_DebugRun("baseline_s444_q100", false); + //Test_DebugRun("progressive_s444_q100", false); + Test_DebugRun("baseline_4k_420", true); Test_DebugRun("baseline_s444_q100", true); Test_DebugRun("progressive_s444_q100", true); From 7a342a1e7611bdf8040a95f6352cf270878dcc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Pa=C5=BEourek?= Date: Fri, 9 Jul 2021 14:07:06 +0200 Subject: [PATCH 032/119] Updated Colourful from 2.0.5 to 3.0.0 --- tests/Directory.Build.targets | 2 +- .../Color/ColorspaceCieXyzToCieLabConvert.cs | 6 +++--- .../Color/ColorspaceCieXyzToHunterLabConvert.cs | 6 +++--- .../Color/ColorspaceCieXyzToLmsConvert.cs | 5 ++--- .../Color/ColorspaceCieXyzToRgbConvert.cs | 5 ++--- .../ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs | 9 ++++----- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 53b4f9632..ddceaff1f 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -20,7 +20,7 @@ - + diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index 914041e5b..fcb3daf3b 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -4,10 +4,10 @@ using BenchmarkDotNet.Attributes; using Colourful; -using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Illuminants = Colourful.Illuminants; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.D50).ToLab(Illuminants.D50).Build(); [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { - return ColourfulConverter.ToLab(XYZColor).L; + return ColourfulConverter.Convert(XYZColor).L; } [Benchmark(Description = "ImageSharp Convert")] diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index c6f4c0471..afba44e73 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -4,10 +4,10 @@ using BenchmarkDotNet.Attributes; using Colourful; -using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Illuminants = Colourful.Illuminants; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.C).ToHunterLab(Illuminants.C).Build(); [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { - return ColourfulConverter.ToHunterLab(XYZColor).L; + return ColourfulConverter.Convert(XYZColor).L; } [Benchmark(Description = "ImageSharp Convert")] diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index c7f78bb08..eddc1a680 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -4,7 +4,6 @@ using BenchmarkDotNet.Attributes; using Colourful; -using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; @@ -19,12 +18,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ().ToLMS().Build(); [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { - return ColourfulConverter.ToLMS(XYZColor).L; + return ColourfulConverter.Convert(XYZColor).L; } [Benchmark(Description = "ImageSharp Convert")] diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index 18494f3f6..b56e55b1e 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -4,7 +4,6 @@ using BenchmarkDotNet.Attributes; using Colourful; -using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; @@ -19,12 +18,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(RGBWorkingSpaces.sRGB.WhitePoint).ToRGB(RGBWorkingSpaces.sRGB).Build(); [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { - return ColourfulConverter.ToRGB(XYZColor).R; + return ColourfulConverter.Convert(XYZColor).R; } [Benchmark(Description = "ImageSharp Convert")] diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 21cf10bb7..d42b22ecb 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -4,7 +4,6 @@ using BenchmarkDotNet.Attributes; using Colourful; -using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; @@ -15,20 +14,20 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); - private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB); + private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717); private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }); - private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.WideGamutRGB).ToRGB(RGBWorkingSpaces.sRGB).Build(); [Benchmark(Baseline = true, Description = "Colourful Adapt")] public RGBColor ColourfulConvert() { - return ColourfulConverter.Adapt(RGBColor); + return ColourfulConverter.Convert(RGBColor); } [Benchmark(Description = "ImageSharp Adapt")] - internal Rgb ColorSpaceConvert() + public Rgb ColorSpaceConvert() { return ColorSpaceConverter.Adapt(Rgb); } From 22af24128c9054afca7cf0c996aa2762c4ed6444 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 18:03:33 +0300 Subject: [PATCH 033/119] WIP spectral converter --- .../Decoder/SpectralConverter{TPixel}.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs new file mode 100644 index 000000000..578a05e32 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Numerics; +using System.Text; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + internal abstract class SpectralConverter + { + public abstract void ConvertStride(); + } + + internal class SpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + private Configuration configuration; + + private JpegComponentPostProcessor[] componentProcessors; + + private JpegColorConverter colorConverter; + + private IMemoryOwner rgbaBuffer; + + private Buffer2D pixelBuffer; + + public JpegFrame Frame + { + set => this.InjectFrame(value); + } + + public SpectralConverter(Configuration configuration) + { + this.configuration = configuration; + } + + private void InjectFrame(JpegFrame frame) + { + MemoryAllocator allocator = this.configuration.MemoryAllocator; + + // pixel buffer for resulting image + this.pixelBuffer = allocator.Allocate2D(frame.PixelWidth, frame.PixelHeight, AllocationOptions.Clean); + + // component processors from spectral to Rgba32 + this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, this, frame.Components[i]); + } + + // single 'stride' rgba32 buffer for conversion between spectral and TPixel + this.rgbaBuffer = allocator.Allocate(frame.PixelWidth); + + // color converter from Rgba32 to TPixel + this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, frame.Precision); + } + + public override void ConvertStride() + { + int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + PixelRowsPerStep); + + var buffers = new Buffer2D[this.componentProcessors.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyBlocksToColorBuffer(); + buffers[i] = this.componentProcessors[i].ColorBuffer; + } + + for (int yy = this.PixelRowCounter; yy < maxY; yy++) + { + int y = yy - this.PixelRowCounter; + + var values = new JpegColorConverter.ComponentValues(buffers, y); + this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); + + Span destRow = destination.GetPixelRowSpan(yy); + + // TODO: Investigate if slicing is actually necessary + PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); + } + + this.PixelRowCounter += PixelRowsPerStep; + } + } +} From dbe4c4e870f645d5706001eaa8eb368458a633ca Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 18:11:36 +0300 Subject: [PATCH 034/119] Fixed iteration variables --- .../Decoder/SpectralConverter{TPixel}.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 578a05e32..3db47aa66 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -30,6 +30,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Buffer2D pixelBuffer; + + public int BlockRowsPerStep; + + private int PixelRowsPerStep; + + private int PixelRowCounter; + + public JpegFrame Frame { set => this.InjectFrame(value); @@ -59,11 +67,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // color converter from Rgba32 to TPixel this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, frame.Precision); + + // iteration data + IJpegComponent c0 = frame.Components[0]; + + const int blockPixelHeight = 8; + this.BlockRowsPerStep = c0.SamplingFactors.Height; + this.PixelRowsPerStep = this.BlockRowsPerStep * blockPixelHeight; } public override void ConvertStride() { - int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + PixelRowsPerStep); + int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + this.PixelRowsPerStep); var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) @@ -79,13 +94,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var values = new JpegColorConverter.ComponentValues(buffers, y); this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); - Span destRow = destination.GetPixelRowSpan(yy); + Span destRow = this.pixelBuffer.GetRowSpan(yy); // TODO: Investigate if slicing is actually necessary PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } - this.PixelRowCounter += PixelRowsPerStep; + this.PixelRowCounter += this.PixelRowsPerStep; } } } From 887c0ba4b9601e141ea8eb5907b88e062b2bdf61 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 18:15:14 +0300 Subject: [PATCH 035/119] Added todo(s) --- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 3db47aa66..8bab9f18a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void ConvertStride(); } + // TODO: componentProcessors must be disposed!!! + // TODO: rgbaBuffer must be disposed!!! internal class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { From 6b2f18952c0a802cc69065ec095157588dcc97bc Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 18:52:33 +0300 Subject: [PATCH 036/119] Decoupled image processor from component processor --- .../Decoder/JpegComponentPostProcessor.cs | 19 ++++++++----------- .../Decoder/JpegImagePostProcessor.cs | 11 ++--------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 0a0a2cd9e..83406e0d6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -27,23 +27,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Initializes a new instance of the class. /// - public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) + public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) { this.Component = component; - this.ImagePostProcessor = imagePostProcessor; + this.RawJpeg = rawJpeg; this.blockAreaSize = this.Component.SubSamplingDivisors * 8; this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( - imagePostProcessor.PostProcessorBufferSize.Width, - imagePostProcessor.PostProcessorBufferSize.Height, + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, this.blockAreaSize.Height); - this.BlockRowsPerStep = imagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; + this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height; } - /// - /// Gets the - /// - public JpegImagePostProcessor ImagePostProcessor { get; } + public IRawJpegData RawJpeg { get; } /// /// Gets the @@ -76,8 +73,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public void CopyBlocksToColorBuffer() { - var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); - float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); + float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1; int destAreaStride = this.ColorBuffer.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 5f3389e17..18b2bc6e8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -62,14 +62,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.BlockRowsPerStep; - this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); - + var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; for (int i = 0; i < rawJpeg.Components.Length; i++) { - this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]); + this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this.RawJpeg, postProcessorBufferSize, rawJpeg.Components[i]); } this.rgbaBuffer = memoryAllocator.Allocate(rawJpeg.ImageSizeInPixels.Width); @@ -91,11 +89,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public int NumberOfPostProcessorSteps { get; } - /// - /// Gets the size of the temporary buffers we need to allocate into . - /// - public Size PostProcessorBufferSize { get; } - /// /// Gets the value of the counter that grows by each step by . /// From dc4bc6e03f91f87bc2464ac5154f258a2ab7175a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 18:58:07 +0300 Subject: [PATCH 037/119] Fixed converter frame injection --- .../Decoder/SpectralConverter{TPixel}.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 8bab9f18a..6e6ba7ab8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Buffer2D pixelBuffer; + public int BlockRowsPerStep; private int PixelRowsPerStep; @@ -40,42 +41,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int PixelRowCounter; - public JpegFrame Frame - { - set => this.InjectFrame(value); - } public SpectralConverter(Configuration configuration) { this.configuration = configuration; } - private void InjectFrame(JpegFrame frame) + public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { MemoryAllocator allocator = this.configuration.MemoryAllocator; + // iteration data + IJpegComponent c0 = frame.Components[0]; + + const int blockPixelHeight = 8; + this.BlockRowsPerStep = c0.SamplingFactors.Height; + this.PixelRowsPerStep = this.BlockRowsPerStep * blockPixelHeight; + // pixel buffer for resulting image this.pixelBuffer = allocator.Allocate2D(frame.PixelWidth, frame.PixelHeight, AllocationOptions.Clean); // component processors from spectral to Rgba32 + var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { - this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, this, frame.Components[i]); + this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, jpegData, postProcessorBufferSize, frame.Components[i]); } // single 'stride' rgba32 buffer for conversion between spectral and TPixel this.rgbaBuffer = allocator.Allocate(frame.PixelWidth); // color converter from Rgba32 to TPixel - this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, frame.Precision); - - // iteration data - IJpegComponent c0 = frame.Components[0]; - - const int blockPixelHeight = 8; - this.BlockRowsPerStep = c0.SamplingFactors.Height; - this.PixelRowsPerStep = this.BlockRowsPerStep * blockPixelHeight; + this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } public override void ConvertStride() From d178c8c6725cb68bda2d2acdd820e3ea37ff23fe Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 19:02:44 +0300 Subject: [PATCH 038/119] Added getter which converts pixels to PixelBuffer property --- .../Decoder/SpectralConverter{TPixel}.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 6e6ba7ab8..1befbbf82 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Text; +using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -24,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { private Configuration configuration; + private CancellationToken cancellationToken; + + private JpegComponentPostProcessor[] componentProcessors; private JpegColorConverter colorConverter; @@ -41,10 +45,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int PixelRowCounter; + private bool converted; + + public Buffer2D PixelBuffer + { + get + { + if (!this.converted) + { + while (this.PixelRowCounter < this.pixelBuffer.Height) + { + this.cancellationToken.ThrowIfCancellationRequested(); + this.ConvertStride(); + } + + this.converted = true; + } + + return this.pixelBuffer; + } + } - public SpectralConverter(Configuration configuration) + public SpectralConverter(Configuration configuration, CancellationToken ct) { this.configuration = configuration; + this.cancellationToken = ct; } public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) From c86d02901cf91867cedf38db3875c8eecd9c3e80 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 19:04:05 +0300 Subject: [PATCH 039/119] Fixed sandbox code --- tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index e0b7f31a7..b51592b6d 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -70,12 +70,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox //Test_DebugRun("baseline_4k_420", false); //Test_DebugRun("baseline_s444_q100", false); - //Test_DebugRun("progressive_s444_q100", false); + //Test_DebugRun("progressive_s420_q100", false); Test_DebugRun("baseline_4k_420", true); Test_DebugRun("baseline_s444_q100", true); - Test_DebugRun("progressive_s444_q100", true); + Test_DebugRun("progressive_s420_q100", true); - Console.ReadLine(); + //Console.ReadLine(); } public static void Test_Performance(int iterations) From 1dbb16a7fdebf38c87be8efa453245a2157cdbb2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 20:39:22 +0300 Subject: [PATCH 040/119] Wired up converter & scan decoder --- .../Components/Decoder/HuffmanScanDecoder.cs | 21 +++++++++++-------- .../Decoder/SpectralConverter{TPixel}.cs | 4 +++- .../Formats/Jpeg/JpegDecoderCore.cs | 5 ++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index b1f371e0f..3980739e1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -26,15 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private JpegFrame frame; private JpegComponent[] components; - public JpegFrame Frame - { - set - { - frame = value; - components = value.Components; - } - } - // The restart interval. private int restartInterval; // How many mcu's are left to do. @@ -72,6 +63,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private HuffmanScanBuffer scanBuffer; + private SpectralConverter spectralConverter; + private CancellationToken cancellationToken; /// @@ -90,10 +83,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The token to monitor cancellation. public HuffmanScanDecoder( BufferedReadStream stream, + SpectralConverter converter, CancellationToken cancellationToken) { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; + this.spectralConverter = converter; this.cancellationToken = cancellationToken; } @@ -121,6 +116,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.frame = frame; + this.components = frame.Components; + + this.spectralConverter.InjectFrameData(frame, jpegData); + } + private void ParseBaselineData() { if (this.componentsLength == 1) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 1befbbf82..ae4a90337 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -15,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal abstract class SpectralConverter { + public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); + public abstract void ConvertStride(); } @@ -72,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken = ct; } - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { MemoryAllocator allocator = this.configuration.MemoryAllocator; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 44bd05145..168286071 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -215,13 +215,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.scanDecoder = new HuffmanScanDecoder(stream, cancellationToken); + SpectralConverter spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); + + this.scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); this.ParseStream(stream, cancellationToken: cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); this.InitDerivedMetadataProperties(); + return this.PostProcessIntoImage(cancellationToken); } From 1c10ec6d05fb7d75cc83a5a55f096f219c823a81 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 20:47:02 +0300 Subject: [PATCH 041/119] Added Buffer2D Image ctor, wired new post processor with decoder core --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 6 +++--- src/ImageSharp/Image{TPixel}.cs | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 168286071..eb4ed5b95 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - SpectralConverter spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); + var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); this.scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return this.PostProcessIntoImage(cancellationToken); + return new Image(this.Configuration, spectralConverter.PixelBuffer, this.Metadata); } /// @@ -925,7 +925,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); // This can be injected in SOF marker callback - this.scanDecoder.Frame = this.Frame; + this.scanDecoder.InjectFrameData(this.Frame, this); } } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index b43ff0422..669db2a97 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -87,6 +87,14 @@ namespace SixLabors.ImageSharp this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); } + internal Image( + Configuration configuration, + Buffer2D pixelBuffer, + ImageMetadata metadata) + : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) + { + } + /// /// Initializes a new instance of the class /// wrapping an external . From 1348ecfeaa2e3fdf5584f9afa7490b94f71cfde1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 20:53:44 +0300 Subject: [PATCH 042/119] Implemented disposable pattern for spectral converter --- .../Decoder/SpectralConverter{TPixel}.cs | 16 +++++++++++++--- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index ae4a90337..ec1c057b2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -13,15 +13,15 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - internal abstract class SpectralConverter + internal abstract class SpectralConverter : IDisposable { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); public abstract void ConvertStride(); + + public abstract void Dispose(); } - // TODO: componentProcessors must be disposed!!! - // TODO: rgbaBuffer must be disposed!!! internal class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { @@ -129,5 +129,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.PixelRowCounter += this.PixelRowsPerStep; } + + public override void Dispose() + { + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + + this.rgbaBuffer.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index eb4ed5b95..30024af95 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); + using var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); this.scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); From 1d4dd088283f57dcc256ceb944c1aebfb558e5c1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 9 Jul 2021 23:17:01 +0300 Subject: [PATCH 043/119] Implemented step-based iteration for spectralconverter --- .../Decoder/JpegComponentPostProcessor.cs | 10 ++++++-- .../Decoder/SpectralConverter{TPixel}.cs | 24 +++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 83406e0d6..9eafaea0a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -71,16 +71,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Invoke for block rows, copy the result into . /// - public void CopyBlocksToColorBuffer() + public void CopyBlocksToColorBuffer(int step) { var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1; int destAreaStride = this.ColorBuffer.Width; + int yBlockStart = step * this.BlockRowsPerStep; + for (int y = 0; y < this.BlockRowsPerStep; y++) { - int yBlock = this.currentComponentRowInBlocks + y; + int yBlock = yBlockStart + y; if (yBlock >= this.SizeInBlocks.Height) { @@ -104,7 +106,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); } } + } + public void CopyBlocksToColorBuffer() + { + this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks); this.currentComponentRowInBlocks += this.BlockRowsPerStep; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index ec1c057b2..16a55e95e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - public abstract void ConvertStride(); + public abstract void ConvertStride(int step); public abstract void Dispose(); } @@ -44,8 +44,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int PixelRowsPerStep; - private int PixelRowCounter; - private bool converted; @@ -55,10 +53,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (!this.converted) { - while (this.PixelRowCounter < this.pixelBuffer.Height) + int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.PixelRowsPerStep); + + for (int i = 0; i < steps; i++) { this.cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(); + this.ConvertStride(i); } this.converted = true; @@ -103,20 +103,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } - public override void ConvertStride() + public override void ConvertStride(int step) { - int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + this.PixelRowsPerStep); + int pixelRowStart = this.PixelRowsPerStep * step; + + int maxY = Math.Min(this.pixelBuffer.Height, pixelRowStart + this.PixelRowsPerStep); var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { - this.componentProcessors[i].CopyBlocksToColorBuffer(); + this.componentProcessors[i].CopyBlocksToColorBuffer(step); buffers[i] = this.componentProcessors[i].ColorBuffer; } - for (int yy = this.PixelRowCounter; yy < maxY; yy++) + for (int yy = pixelRowStart; yy < maxY; yy++) { - int y = yy - this.PixelRowCounter; + int y = yy - pixelRowStart; var values = new JpegColorConverter.ComponentValues(buffers, y); this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); @@ -126,8 +128,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // TODO: Investigate if slicing is actually necessary PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } - - this.PixelRowCounter += this.PixelRowsPerStep; } public override void Dispose() From fa0aaec88e8792f073f53dc691f59965f17905f7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 10 Jul 2021 15:05:31 +0300 Subject: [PATCH 044/119] Added separate step parameter for spectral data enumeration --- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 16a55e95e..11feaa189 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - public abstract void ConvertStride(int step); + public abstract void ConvertStride(int step, int spectralStep); public abstract void Dispose(); } @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int i = 0; i < steps; i++) { this.cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(i); + this.ConvertStride(i, i); } this.converted = true; @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } - public override void ConvertStride(int step) + public override void ConvertStride(int step, int spectralStep) { int pixelRowStart = this.PixelRowsPerStep * step; @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { - this.componentProcessors[i].CopyBlocksToColorBuffer(step); + this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); buffers[i] = this.componentProcessors[i].ColorBuffer; } From c0173571d31d5702fb8806cb81064eb69a3e51e7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 10 Jul 2021 15:23:44 +0300 Subject: [PATCH 045/119] Added external way to mark convesion finished --- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 11feaa189..580adb3ab 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -15,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal abstract class SpectralConverter : IDisposable { + public abstract void CommitConversion(); + public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); public abstract void ConvertStride(int step, int spectralStep); @@ -74,6 +76,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken = ct; } + public override void CommitConversion() => this.converted = true; + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { MemoryAllocator allocator = this.configuration.MemoryAllocator; From 3c59cd9c51c560c1465543b74145ea3522857a82 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 10 Jul 2021 15:41:33 +0300 Subject: [PATCH 046/119] Added initial support for the baseline interleaved stride conversion --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 3980739e1..914562831 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -133,6 +133,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder else { this.ParseBaselineDataInterleaved(); + + // this is the only path where conversion is done right after the scan + this.spectralConverter.CommitConversion(); } } @@ -160,6 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); + // decode from binary to spectral for (int i = 0; i < mcusPerLine; i++) { // Scan an interleaved mcu... process components in order @@ -207,6 +211,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder mcu++; this.HandleRestart(); } + + // convert from spectral to actual pixels via given converter + this.spectralConverter.ConvertStride(j, j); } } From 460b02c776600ba5696d2d5457f17ecb03f82f93 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 10 Jul 2021 18:31:06 +0300 Subject: [PATCH 047/119] Sandbox changes --- tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index b51592b6d..7876c7dc6 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -71,9 +71,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox //Test_DebugRun("baseline_4k_420", false); //Test_DebugRun("baseline_s444_q100", false); //Test_DebugRun("progressive_s420_q100", false); - Test_DebugRun("baseline_4k_420", true); + //Test_DebugRun("baseline_4k_420", true); Test_DebugRun("baseline_s444_q100", true); - Test_DebugRun("progressive_s420_q100", true); + //Test_DebugRun("progressive_s420_q100", true); //Console.ReadLine(); } From e39adf85f6c7add528325f1d557411e9751e7bcb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 00:01:37 +0300 Subject: [PATCH 048/119] Fixed invalid baseline jpeg decoding --- .../Components/Decoder/HuffmanScanDecoder.cs | 5 +++-- .../Decoder/JpegComponentPostProcessor.cs | 16 ++++++++++++++++ .../Decoder/SpectralConverter{TPixel}.cs | 10 ++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 914562831..0e13f75e4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < v; y++) { int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + Span blockSpan = component.SpectralBlocks.GetRowSpan(y); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -213,7 +213,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } // convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStride(j, j); + this.spectralConverter.ConvertStride(j, 0); + this.spectralConverter.ClearStride(0); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 9eafaea0a..6c25601c2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -108,6 +108,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + public void ClearSpectralStride(int step) + { + int yBlockStart = step * this.BlockRowsPerStep; + for (int y = 0; y < this.BlockRowsPerStep; y++) + { + int yBlock = yBlockStart + y; + + if (yBlock >= this.SizeInBlocks.Height) + { + break; + } + + this.Component.SpectralBlocks.GetRowSpan(yBlock).Clear(); + } + } + public void CopyBlocksToColorBuffer() { this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 580adb3ab..3499704ae 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void ConvertStride(int step, int spectralStep); + public abstract void ClearStride(int spectralStep); + public abstract void Dispose(); } @@ -134,6 +136,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + public override void ClearStride(int spectralStep) + { + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.ClearSpectralStride(spectralStep); + } + } + public override void Dispose() { foreach (JpegComponentPostProcessor cpp in this.componentProcessors) From 7afca199b769236cb12982f0412770ea98cfbb0e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 10:01:12 +0300 Subject: [PATCH 049/119] Rolled back to counter enumeration for spectral converter --- .../Decoder/SpectralConverter{TPixel}.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 3499704ae..706e4c0b7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -19,14 +19,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - public abstract void ConvertStride(int step, int spectralStep); + public abstract void ConvertStride(int spectralStep); public abstract void ClearStride(int spectralStep); public abstract void Dispose(); } - internal class SpectralConverter : SpectralConverter + internal sealed class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { private Configuration configuration; @@ -48,6 +48,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int PixelRowsPerStep; + private int PixelRowCounter; + private bool converted; @@ -62,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int i = 0; i < steps; i++) { this.cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(i, i); + this.ConvertStride(i); } this.converted = true; @@ -109,11 +111,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } - public override void ConvertStride(int step, int spectralStep) + public override void ConvertStride(int spectralStep) { - int pixelRowStart = this.PixelRowsPerStep * step; - - int maxY = Math.Min(this.pixelBuffer.Height, pixelRowStart + this.PixelRowsPerStep); + int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + this.PixelRowsPerStep); var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) @@ -122,9 +122,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder buffers[i] = this.componentProcessors[i].ColorBuffer; } - for (int yy = pixelRowStart; yy < maxY; yy++) + for (int yy = this.PixelRowCounter; yy < maxY; yy++) { - int y = yy - pixelRowStart; + int y = yy - this.PixelRowCounter; var values = new JpegColorConverter.ComponentValues(buffers, y); this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); @@ -134,13 +134,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // TODO: Investigate if slicing is actually necessary PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } + + this.PixelRowCounter += this.PixelRowsPerStep; } public override void ClearStride(int spectralStep) { foreach (JpegComponentPostProcessor cpp in this.componentProcessors) { - cpp.ClearSpectralStride(spectralStep); + cpp.ClearSpectralBuffers(spectralStep); } } From 4b5f0f69aad44bb5f5a36d84aa0489f194fe0d75 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 10:12:43 +0300 Subject: [PATCH 050/119] Refactored scan converter --- .../Components/Decoder/HuffmanScanDecoder.cs | 3 +- .../Decoder/SpectralConverter{TPixel}.cs | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 0e13f75e4..609672271 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -213,8 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } // convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStride(j, 0); - this.spectralConverter.ClearStride(0); + this.spectralConverter.ConvertStrideBaseline(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 706e4c0b7..9264039c4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - public abstract void ConvertStride(int spectralStep); + public abstract void ConvertStrideIncremental(); - public abstract void ClearStride(int spectralStep); + public abstract void ConvertStrideBaseline(); public abstract void Dispose(); } @@ -111,7 +111,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } - public override void ConvertStride(int spectralStep) + public override void ConvertStrideIncremental() => this.ConvertNextStride(this.PixelRowCounter / 8); + + public override void ConvertStrideBaseline() + { + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor + this.ConvertNextStride(spectralStep: 0); + + // Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride + // Which leads to decoding artifacts + // Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.ClearSpectralBuffers(); + } + } + + + private void ConvertNextStride(int spectralStep) { int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + this.PixelRowsPerStep); @@ -138,14 +156,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.PixelRowCounter += this.PixelRowsPerStep; } - public override void ClearStride(int spectralStep) - { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) - { - cpp.ClearSpectralBuffers(spectralStep); - } - } - public override void Dispose() { foreach (JpegComponentPostProcessor cpp in this.componentProcessors) From 74a7e90cf5e75eee5087d10b4f7fa4a1bab32add Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 10:29:10 +0300 Subject: [PATCH 051/119] Refactores post processor buffer clear --- .../Decoder/JpegComponentPostProcessor.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 6c25601c2..067eb47be 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -108,19 +108,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - public void ClearSpectralStride(int step) + // TODO: refactor this + public void ClearSpectralBuffers() { - int yBlockStart = step * this.BlockRowsPerStep; - for (int y = 0; y < this.BlockRowsPerStep; y++) + Buffer2D spectralBlocks = this.Component.SpectralBlocks; + for (int i = 0; i < spectralBlocks.Height; i++) { - int yBlock = yBlockStart + y; - - if (yBlock >= this.SizeInBlocks.Height) - { - break; - } - - this.Component.SpectralBlocks.GetRowSpan(yBlock).Clear(); + spectralBlocks.GetRowSpan(i).Clear(); } } From 4af7fd1dc76307a6feb77f4c56759634bf2d8990 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 10:29:19 +0300 Subject: [PATCH 052/119] Refactored spectral converter --- .../Decoder/SpectralConverter{TPixel}.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 9264039c4..c0e615137 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - public abstract void ConvertStrideIncremental(); - public abstract void ConvertStrideBaseline(); public abstract void Dispose(); @@ -61,10 +59,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.PixelRowsPerStep); - for (int i = 0; i < steps; i++) + for (int step = 0; step < steps; step++) { this.cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(i); + this.ConvertNextStride(step); } this.converted = true; @@ -111,8 +109,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } - public override void ConvertStrideIncremental() => this.ConvertNextStride(this.PixelRowCounter / 8); - public override void ConvertStrideBaseline() { // Convert next pixel stride using single spectral `stride' @@ -128,6 +124,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + public override void Dispose() + { + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + + this.rgbaBuffer.Dispose(); + } private void ConvertNextStride(int spectralStep) { @@ -155,15 +160,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.PixelRowCounter += this.PixelRowsPerStep; } - - public override void Dispose() - { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - - this.rgbaBuffer.Dispose(); - } } } From fae763edd3701de39953f4b31e66719e8a65c490 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 11:07:59 +0300 Subject: [PATCH 053/119] Final refactor of the converter --- .../Components/Decoder/HuffmanScanDecoder.cs | 3 --- .../Decoder/SpectralConverter{TPixel}.cs | 25 ++++++------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 609672271..34afa72b4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -133,9 +133,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder else { this.ParseBaselineDataInterleaved(); - - // this is the only path where conversion is done right after the scan - this.spectralConverter.CommitConversion(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index c0e615137..71e37ba8a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -14,9 +14,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal abstract class SpectralConverter : IDisposable - { - public abstract void CommitConversion(); - + { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); public abstract void ConvertStrideBaseline(); @@ -40,22 +38,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Buffer2D pixelBuffer; - - public int BlockRowsPerStep; private int PixelRowsPerStep; private int PixelRowCounter; + public SpectralConverter(Configuration configuration, CancellationToken ct) + { + this.configuration = configuration; + this.cancellationToken = ct; + } - private bool converted; + private bool Converted => this.PixelRowCounter >= this.pixelBuffer.Height; public Buffer2D PixelBuffer { get { - if (!this.converted) + if (!this.Converted) { int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.PixelRowsPerStep); @@ -64,22 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken.ThrowIfCancellationRequested(); this.ConvertNextStride(step); } - - this.converted = true; } return this.pixelBuffer; } } - public SpectralConverter(Configuration configuration, CancellationToken ct) - { - this.configuration = configuration; - this.cancellationToken = ct; - } - - public override void CommitConversion() => this.converted = true; - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { MemoryAllocator allocator = this.configuration.MemoryAllocator; From 243e2bd463ceabee2157bd2b69639feb4956e5de Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 11:27:58 +0300 Subject: [PATCH 054/119] Removed todo --- .../Jpeg/Components/Decoder/JpegComponentPostProcessor.cs | 1 - .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 067eb47be..7ad0e87d2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -108,7 +108,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - // TODO: refactor this public void ClearSpectralBuffers() { Buffer2D spectralBlocks = this.Component.SpectralBlocks; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 71e37ba8a..441745e53 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -14,7 +14,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal abstract class SpectralConverter : IDisposable - { + { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); public abstract void ConvertStrideBaseline(); From 639ed629a8594dbba1dc3e11f02985899ad0a9ec Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 12:21:03 +0300 Subject: [PATCH 055/119] Implemented new spectral buffers allocation --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 4 ++++ .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 12 ++++++++++++ .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 9 +++++++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 ++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 34afa72b4..447f2c643 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -128,10 +128,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (this.componentsLength == 1) { + this.frame.AllocateComponents(fullScan: true); this.ParseBaselineDataNonInterleaved(); } else { + // interleaved baseline is the only place where we can optimize memory footprint via reusing single spectral stride + this.frame.AllocateComponents(fullScan: false); this.ParseBaselineDataInterleaved(); } } @@ -305,6 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.CheckProgressiveData(); + this.frame.AllocateComponents(fullScan: true); if (this.componentsLength == 1) { this.ParseProgressiveDataNonInterleaved(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index dd43baa23..05f46aaba 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -125,6 +125,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { JpegThrowHelper.ThrowBadSampling(); } + } + + public void AllocateSpectral(bool fullScan) + { + if (this.SpectralBlocks != null) + { + // this method will be called each scan marker so we need to allocate only once + return; + } + + int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = fullScan ? this.Frame.McusPerColumn * this.VerticalSamplingFactor : this.VerticalSamplingFactor; int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); int width = this.WidthInBlocks + 1; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 13d6bc35e..595f39dbd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -104,5 +104,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder component.Init(); } } + + public void AllocateComponents(bool fullScan) + { + for (int i = 0; i < this.ComponentCount; i++) + { + JpegComponent component = this.Components[i]; + component.AllocateSpectral(fullScan); + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 30024af95..1289ff3bf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -921,9 +921,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.MaxVerticalFactor = maxV; this.ColorSpace = this.DeduceJpegColorSpace(); this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; - this.Frame.InitComponents(); this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); + this.Frame.InitComponents(); + // This can be injected in SOF marker callback this.scanDecoder.InjectFrameData(this.Frame, this); } From 27d7c3a7695a47bbe0785bb6234e44181fb2ba73 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 12:44:47 +0300 Subject: [PATCH 056/119] Rolled back to initial sandbox code --- .../Program.cs | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 7876c7dc6..9aa983ac5 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Diagnostics; -using System.IO; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -39,73 +37,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - //Test_Performance(20); - - //Test_DebugRun("chroma_444_16x16", true); - //Console.WriteLine(); - //Test_DebugRun("chroma_420_16x16", true); - //Console.WriteLine(); - //Test_DebugRun("444_14x14"); - //Console.WriteLine(); - //Test_DebugRun("baseline_4k_444", false); - //Console.WriteLine(); - //Test_DebugRun("progressive_4k_444", true); - //Console.WriteLine(); - //Test_DebugRun("baseline_4k_420", false); - //Console.WriteLine(); - //Test_DebugRun("cmyk_jpeg"); - //Console.WriteLine(); - //Test_DebugRun("Channel_digital_image_CMYK_color"); - //Console.WriteLine(); - - //Test_DebugRun("test_baseline_4k_444", false); - //Console.WriteLine(); - //Test_DebugRun("test_progressive_4k_444", false); - //Console.WriteLine(); - //Test_DebugRun("test_baseline_4k_420", false); - //Console.WriteLine(); - - // Binary size of this must be ~2096kb - //Test_DebugRun("422", true); - - //Test_DebugRun("baseline_4k_420", false); - //Test_DebugRun("baseline_s444_q100", false); - //Test_DebugRun("progressive_s420_q100", false); - //Test_DebugRun("baseline_4k_420", true); - Test_DebugRun("baseline_s444_q100", true); - //Test_DebugRun("progressive_s420_q100", true); - - //Console.ReadLine(); - } - - public static void Test_Performance(int iterations) - { - using var stream = new MemoryStream(File.ReadAllBytes("C:\\Users\\pl4nu\\Downloads\\progressive_4k_444.jpg")); - //using var stream = new MemoryStream(File.ReadAllBytes("C:\\Users\\pl4nu\\Downloads\\baseline_4k_444.jpg")); - var sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) - { - using var img = Image.Load(stream); - stream.Position = 0; - } - - sw.Stop(); - Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds}ms\nPer invocation: {sw.ElapsedMilliseconds / iterations}ms"); - } - - public static void Test_DebugRun(string name, bool save = false) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"img: {name}"); - Console.ResetColor(); - using var img = Image.Load($"C:\\Users\\pl4nu\\Downloads\\{name}.jpg"); - - if (save) - { - img.SaveAsJpeg($"C:\\Users\\pl4nu\\Downloads\\test_{name}.jpg", - new ImageSharp.Formats.Jpeg.JpegEncoder { Subsample = ImageSharp.Formats.Jpeg.JpegSubsample.Ratio444, Quality = 100 }); - } + Console.ReadLine(); } private static void RunJpegEncoderProfilingTests() From da7bca3786d56b54bd9ca526883a958dca56e35f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 13:07:08 +0300 Subject: [PATCH 057/119] Moved SpectralConverter to the separate file --- .../Jpeg/Components/Decoder/SpectralConverter.cs | 16 ++++++++++++++++ .../Decoder/SpectralConverter{TPixel}.cs | 9 --------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs new file mode 100644 index 000000000..35a879082 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + internal abstract class SpectralConverter : IDisposable + { + public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); + + public abstract void ConvertStrideBaseline(); + + public abstract void Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 441745e53..a25e756c7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -13,15 +13,6 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - internal abstract class SpectralConverter : IDisposable - { - public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - - public abstract void ConvertStrideBaseline(); - - public abstract void Dispose(); - } - internal sealed class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { From 86a7b462c4205302fab8443642651e27c63c548d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 13:07:46 +0300 Subject: [PATCH 058/119] Added docs --- src/ImageSharp/Image{TPixel}.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 669db2a97..fb1e6d92f 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -87,6 +87,13 @@ namespace SixLabors.ImageSharp this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); } + /// + /// Initializes a new instance of the class + /// wrapping an external + /// The configuration providing initialization code which allows extending the library. + /// Pixel buffer. + /// The images metadata. internal Image( Configuration configuration, Buffer2D pixelBuffer, From d325d06b5fa00806b0336e8427383f3d852c8b0e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 13:14:42 +0300 Subject: [PATCH 059/119] Fixed styling issues --- .../Components/Decoder/HuffmanScanDecoder.cs | 143 +++++++++--------- .../Decoder/JpegComponentPostProcessor.cs | 8 +- .../Decoder/JpegImagePostProcessor.cs | 27 ++-- .../Decoder/SpectralConverter{TPixel}.cs | 29 ++-- .../Formats/Jpeg/JpegDecoderCore.cs | 21 +-- 5 files changed, 108 insertions(+), 120 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 447f2c643..7a63ef7c6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -18,43 +18,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { private readonly BufferedReadStream stream; - // huffman tables - public HuffmanTable[] dcHuffmanTables; - public HuffmanTable[] acHuffmanTables; - // Frame related private JpegFrame frame; private JpegComponent[] components; // The restart interval. private int restartInterval; + // How many mcu's are left to do. private int todo; - public int ResetInterval - { - set - { - restartInterval = value; - todo = value; - } - } - - // The number of interleaved components. - public int componentsLength; - - // The spectral selection start. - public int spectralStart; - - // The spectral selection end. - public int spectralEnd; - - // The successive approximation high bit end. - public int successiveHigh; - - // The successive approximation low bit end. - public int successiveLow; - // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. private int eobrun; @@ -63,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private HuffmanScanBuffer scanBuffer; - private SpectralConverter spectralConverter; + private readonly SpectralConverter spectralConverter; private CancellationToken cancellationToken; @@ -71,15 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Initializes a new instance of the class. /// /// The input stream. - /// The image frame. - /// The DC Huffman tables. - /// The AC Huffman tables. - /// The length of the components. Different to the array length. - /// The reset interval. - /// The spectral selection start. - /// The spectral selection end. - /// The successive approximation bit high end. - /// The successive approximation bit low end. + /// Spectral to pixel converter. /// The token to monitor cancellation. public HuffmanScanDecoder( BufferedReadStream stream, @@ -92,6 +57,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken = cancellationToken; } + // huffman tables + public HuffmanTable[] DcHuffmanTables { get; set; } + + public HuffmanTable[] AcHuffmanTables { get; set; } + + // Reset interval + public int ResetInterval + { + set + { + this.restartInterval = value; + this.todo = value; + } + } + + // The number of interleaved components. + public int ComponentsLength { get; set; } + + // The spectral selection start. + public int SpectralStart { get; set; } + + // The spectral selection end. + public int SpectralEnd { get; set; } + + // The successive approximation high bit end. + public int SuccessiveHigh { get; set; } + + // The successive approximation low bit end. + public int SuccessiveLow { get; set; } + /// /// Decodes the entropy coded data. /// @@ -126,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineData() { - if (this.componentsLength == 1) + if (this.ComponentsLength == 1) { this.frame.AllocateComponents(fullScan: true); this.ParseBaselineDataNonInterleaved(); @@ -148,13 +143,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanScanBuffer buffer = ref this.scanBuffer; // Pre-derive the huffman table to avoid in-loop checks. - for (int i = 0; i < this.componentsLength; i++) + for (int i = 0; i < this.ComponentsLength; i++) { int order = this.frame.ComponentOrder[i]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; dcHuffmanTable.Configure(); acHuffmanTable.Configure(); } @@ -169,13 +164,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Scan an interleaved mcu... process components in order int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.componentsLength; k++) + for (int k = 0; k < this.ComponentsLength; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -225,8 +220,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; dcHuffmanTable.Configure(); acHuffmanTable.Configure(); @@ -260,9 +255,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Logic has been adapted from libjpeg. // See Table B.3 – Scan header parameter size and values. itu-t81.pdf bool invalid = false; - if (this.spectralStart == 0) + if (this.SpectralStart == 0) { - if (this.spectralEnd != 0) + if (this.SpectralEnd != 0) { invalid = true; } @@ -270,22 +265,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder else { // Need not check Ss/Se < 0 since they came from unsigned bytes. - if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63) + if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) { invalid = true; } // AC scans may have only one component. - if (this.componentsLength != 1) + if (this.ComponentsLength != 1) { invalid = true; } } - if (this.successiveHigh != 0) + if (this.SuccessiveHigh != 0) { // Successive approximation refinement scan: must have Al = Ah-1. - if (this.successiveHigh - 1 != this.successiveLow) + if (this.SuccessiveHigh - 1 != this.SuccessiveLow) { invalid = true; } @@ -293,14 +288,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // TODO: How does this affect 12bit jpegs. // According to libjpeg the range covers 8bit only? - if (this.successiveLow > 13) + if (this.SuccessiveLow > 13) { invalid = true; } if (invalid) { - JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow); + JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); } } @@ -309,7 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.CheckProgressiveData(); this.frame.AllocateComponents(fullScan: true); - if (this.componentsLength == 1) + if (this.ComponentsLength == 1) { this.ParseProgressiveDataNonInterleaved(); } @@ -328,11 +323,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanScanBuffer buffer = ref this.scanBuffer; // Pre-derive the huffman table to avoid in-loop checks. - for (int k = 0; k < this.componentsLength; k++) + for (int k = 0; k < this.ComponentsLength; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; dcHuffmanTable.Configure(); } @@ -343,11 +338,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Scan an interleaved mcu... process components in order int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.componentsLength; k++) + for (int k = 0; k < this.ComponentsLength; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -393,9 +388,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int w = component.WidthInBlocks; int h = component.HeightInBlocks; - if (this.spectralStart == 0) + if (this.SpectralStart == 0) { - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; dcHuffmanTable.Configure(); for (int j = 0; j < h; j++) @@ -423,7 +418,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } else { - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; acHuffmanTable.Configure(); for (int j = 0; j < h; j++) @@ -502,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref short blockDataRef = ref Unsafe.As(ref block); ref HuffmanScanBuffer buffer = ref this.scanBuffer; - if (this.successiveHigh == 0) + if (this.SuccessiveHigh == 0) { // First scan for DC coefficient, must be first int s = buffer.DecodeHuffman(ref dcTable); @@ -513,20 +508,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder s += component.DcPredictor; component.DcPredictor = s; - blockDataRef = (short)(s << this.successiveLow); + blockDataRef = (short)(s << this.SuccessiveLow); } else { // Refinement scan for DC coefficient buffer.CheckBits(); - blockDataRef |= (short)(buffer.GetBits(1) << this.successiveLow); + blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow); } } private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) { ref short blockDataRef = ref Unsafe.As(ref block); - if (this.successiveHigh == 0) + if (this.SuccessiveHigh == 0) { // MCU decoding for AC initial scan (either spectral selection, // or first pass of successive approximation). @@ -538,9 +533,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref ZigZag zigzag = ref this.dctZigZag; - int start = this.spectralStart; - int end = this.spectralEnd; - int low = this.successiveLow; + int start = this.SpectralStart; + int end = this.SpectralEnd; + int low = this.SuccessiveLow; for (int i = start; i <= end; ++i) { @@ -584,11 +579,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Refinement scan for these AC coefficients ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref ZigZag zigzag = ref this.dctZigZag; - int start = this.spectralStart; - int end = this.spectralEnd; + int start = this.SpectralStart; + int end = this.SpectralEnd; - int p1 = 1 << this.successiveLow; - int m1 = (-1) << this.successiveLow; + int p1 = 1 << this.SuccessiveLow; + int m1 = (-1) << this.SuccessiveLow; int k = start; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 7ad0e87d2..a853d06fd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -63,10 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public int BlockRowsPerStep { get; } /// - public void Dispose() - { - this.ColorBuffer.Dispose(); - } + public void Dispose() => this.ColorBuffer.Dispose(); /// /// Invoke for block rows, copy the result into . diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 18b2bc6e8..26a063524 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Numerics; using System.Threading; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// (3) Color conversion form one of the -s into a buffer of RGBA values
/// (4) Packing pixels from the buffer.
/// These operations are executed in steps. - /// image rows are converted in one step, + /// image rows are converted in one step, /// which means that size of the allocated memory is limited (does not depend on ). ///
internal class JpegImagePostProcessor : IDisposable @@ -29,12 +28,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// The number of block rows to be processed in one Step. /// - public int BlockRowsPerStep; + private readonly int blockRowsPerStep; /// /// The number of image pixel rows to be processed in one step. /// - public int PixelRowsPerStep; + private readonly int pixelRowsPerStep; /// /// Temporal buffer to store a row of colors. @@ -57,12 +56,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.RawJpeg = rawJpeg; IJpegComponent c0 = rawJpeg.Components[0]; - this.BlockRowsPerStep = c0.SamplingFactors.Height; - this.PixelRowsPerStep = this.BlockRowsPerStep * 8; + this.blockRowsPerStep = c0.SamplingFactors.Height; + this.pixelRowsPerStep = this.blockRowsPerStep * 8; - this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.BlockRowsPerStep; + this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.blockRowsPerStep; - var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); + var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; for (int i = 0; i < rawJpeg.Components.Length; i++) @@ -85,12 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public IRawJpegData RawJpeg { get; } /// - /// Gets the total number of post processor steps deduced from the height of the image and . + /// Gets the total number of post processor steps deduced from the height of the image and . /// public int NumberOfPostProcessorSteps { get; } /// - /// Gets the value of the counter that grows by each step by . + /// Gets the value of the counter that grows by each step by . /// public int PixelRowCounter { get; private set; } @@ -129,15 +128,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } /// - /// Execute one step processing pixel rows into 'destination'. - /// Convert and copy row of colors into 'destination' starting at row . + /// Execute one step processing pixel rows into 'destination'. + /// Convert and copy row of colors into 'destination' starting at row . /// /// The pixel type /// The destination image public void DoPostProcessorStep(ImageFrame destination) where TPixel : unmanaged, IPixel { - int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); + int maxY = Math.Min(destination.Height, this.PixelRowCounter + this.pixelRowsPerStep); var buffers = new Buffer2D[this.ComponentProcessors.Length]; for (int i = 0; i < this.ComponentProcessors.Length; i++) @@ -159,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } - this.PixelRowCounter += PixelRowsPerStep; + this.PixelRowCounter += this.pixelRowsPerStep; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index a25e756c7..1d1770aa7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -3,9 +3,7 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Numerics; -using System.Text; using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; @@ -16,11 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal sealed class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { - private Configuration configuration; + private readonly Configuration configuration; private CancellationToken cancellationToken; - private JpegComponentPostProcessor[] componentProcessors; private JpegColorConverter colorConverter; @@ -29,11 +26,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Buffer2D pixelBuffer; - public int BlockRowsPerStep; + private int blockRowsPerStep; - private int PixelRowsPerStep; + private int pixelRowsPerStep; - private int PixelRowCounter; + private int pixelRowCounter; public SpectralConverter(Configuration configuration, CancellationToken ct) { @@ -41,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken = ct; } - private bool Converted => this.PixelRowCounter >= this.pixelBuffer.Height; + private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height; public Buffer2D PixelBuffer { @@ -49,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (!this.Converted) { - int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.PixelRowsPerStep); + int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); for (int step = 0; step < steps; step++) { @@ -70,14 +67,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder IJpegComponent c0 = frame.Components[0]; const int blockPixelHeight = 8; - this.BlockRowsPerStep = c0.SamplingFactors.Height; - this.PixelRowsPerStep = this.BlockRowsPerStep * blockPixelHeight; + this.blockRowsPerStep = c0.SamplingFactors.Height; + this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight; // pixel buffer for resulting image this.pixelBuffer = allocator.Allocate2D(frame.PixelWidth, frame.PixelHeight, AllocationOptions.Clean); // component processors from spectral to Rgba32 - var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.PixelRowsPerStep); + var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { @@ -118,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ConvertNextStride(int spectralStep) { - int maxY = Math.Min(this.pixelBuffer.Height, this.PixelRowCounter + this.PixelRowsPerStep); + int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) @@ -127,9 +124,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder buffers[i] = this.componentProcessors[i].ColorBuffer; } - for (int yy = this.PixelRowCounter; yy < maxY; yy++) + for (int yy = this.pixelRowCounter; yy < maxY; yy++) { - int y = yy - this.PixelRowCounter; + int y = yy - this.pixelRowCounter; var values = new JpegColorConverter.ComponentValues(buffers, y); this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); @@ -140,7 +137,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); } - this.PixelRowCounter += this.PixelRowsPerStep; + this.pixelRowCounter += this.pixelRowsPerStep; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 1289ff3bf..08e6ae021 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -97,6 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private AdobeMarker adobe; + /// + /// Scan decoder. + /// + private HuffmanScanDecoder scanDecoder; + /// /// Initializes a new instance of the class. /// @@ -172,8 +177,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Block8x8F[] QuantizationTables { get; private set; } - private HuffmanScanDecoder scanDecoder; - /// /// Finds the next file marker within the byte stream. /// @@ -1064,20 +1067,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Main reason it's not fixed here is to make this commit less intrusive // Huffman tables can be calculated directly in the scan decoder class - this.scanDecoder.dcHuffmanTables = this.dcHuffmanTables; - this.scanDecoder.acHuffmanTables = this.acHuffmanTables; + this.scanDecoder.DcHuffmanTables = this.dcHuffmanTables; + this.scanDecoder.AcHuffmanTables = this.acHuffmanTables; // This can be injectd in DRI marker callback this.scanDecoder.ResetInterval = this.resetInterval; // This can be passed as ParseEntropyCodedData() parameter as it is used only there - this.scanDecoder.componentsLength = selectorsCount; + this.scanDecoder.ComponentsLength = selectorsCount; // This is okay to inject here, might be good to wrap it in a separate struct but not really necessary - this.scanDecoder.spectralStart = spectralStart; - this.scanDecoder.spectralEnd = spectralEnd; - this.scanDecoder.successiveHigh = successiveApproximation >> 4; - this.scanDecoder.successiveLow = successiveApproximation & 15; + this.scanDecoder.SpectralStart = spectralStart; + this.scanDecoder.SpectralEnd = spectralEnd; + this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; + this.scanDecoder.SuccessiveLow = successiveApproximation & 15; this.scanDecoder.ParseEntropyCodedData(); } From 3fb7105f86b00a6c7872b940537afa9505de8144 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 14:17:23 +0300 Subject: [PATCH 060/119] Fixed docs --- src/ImageSharp/Image{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index fb1e6d92f..2aa9c5394 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class - /// wrapping an external pixel bufferx. /// /// The configuration providing initialization code which allows extending the library. /// Pixel buffer. From 73d35b779c9e113c66c17e359c9e020e4b3f4141 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 14:35:14 +0300 Subject: [PATCH 061/119] Fixed no color deduction for metadata only pass --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 08e6ae021..a45f51db0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -875,6 +875,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); this.ComponentCount = this.Frame.ComponentCount; + this.ColorSpace = this.DeduceJpegColorSpace(); + this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; + if (!metadataOnly) { remaining -= length; @@ -891,7 +894,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.ComponentIds = new byte[this.ComponentCount]; this.Frame.ComponentOrder = new byte[this.ComponentCount]; this.Frame.Components = new JpegComponent[this.ComponentCount]; - this.ColorSpace = this.DeduceJpegColorSpace(); int maxH = 0; int maxV = 0; @@ -922,8 +924,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.MaxHorizontalFactor = maxH; this.Frame.MaxVerticalFactor = maxV; - this.ColorSpace = this.DeduceJpegColorSpace(); - this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); this.Frame.InitComponents(); From ccd660115828b0d874915d1c5dac4447c7a403e3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 15:51:35 +0300 Subject: [PATCH 062/119] Marked ParseStream private as it now can't be called outside of Decode/Identify methods --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a45f51db0..6dd88a00c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The input stream /// Whether to decode metadata only. /// The token to monitor cancellation. - public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) + private void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) { this.Metadata = new ImageMetadata(); From b9f12a6a127a5eb92ab382f9d38e73f46c854b3d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 15:55:51 +0300 Subject: [PATCH 063/119] Removed unsupported benchmark --- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 68a102e3c..6796faa6d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,59 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Tests; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg -{ - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeJpegParseStreamOnly - { - [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] - public string TestImage { get; set; } - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - private byte[] jpegBytes; - - [GlobalSetup] - public void Setup() - => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - - [Benchmark(Baseline = true, Description = "System.Drawing FULL")] - public SDSize JpegSystemDrawing() - { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var image = System.Drawing.Image.FromStream(memoryStream); - return image.Size; - } - - [Benchmark(Description = "JpegDecoderCore.ParseStream")] - public void ParseStream() - { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); - - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); - decoder.ParseStream(bufferedStream); - decoder.Dispose(); - } - } - - /* - | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | - |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| - | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | - | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | - | | | | | | | | | | | | | - | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | - | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | - | | | | | | | | | | | | | - | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | - | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | - */ -} +//// Copyright (c) Six Labors. +//// Licensed under the Apache License, Version 2.0. + +//using System.IO; +//using BenchmarkDotNet.Attributes; +//using SixLabors.ImageSharp.Formats.Jpeg; +//using SixLabors.ImageSharp.IO; +//using SixLabors.ImageSharp.Tests; +//using SDSize = System.Drawing.Size; + +//namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +//{ +// [Config(typeof(Config.ShortMultiFramework))] +// public class DecodeJpegParseStreamOnly +// { +// [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] +// public string TestImage { get; set; } + +// private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + +// private byte[] jpegBytes; + +// [GlobalSetup] +// public void Setup() +// => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + +// [Benchmark(Baseline = true, Description = "System.Drawing FULL")] +// public SDSize JpegSystemDrawing() +// { +// using var memoryStream = new MemoryStream(this.jpegBytes); +// using var image = System.Drawing.Image.FromStream(memoryStream); +// return image.Size; +// } + +// [Benchmark(Description = "JpegDecoderCore.ParseStream")] +// public void ParseStream() +// { +// using var memoryStream = new MemoryStream(this.jpegBytes); +// using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); + +// var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); +// decoder.ParseStream(bufferedStream); +// decoder.Dispose(); +// } +// } + +// /* +// | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| +// | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | +// | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | +// | | | | | | | | | | | | | +// | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | +// | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | +// | | | | | | | | | | | | | +// | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | +// | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | +// */ +//} From ef80d98ee29a3000d501b0eec0e3bfe6402ce7d7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 16:08:35 +0300 Subject: [PATCH 064/119] Tests no longer use ParseStream method --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 3 +-- .../ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs | 4 ++-- .../ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs | 10 +++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d13a9696c..a2f7583a1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -79,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var ms = new MemoryStream(bytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - decoder.ParseStream(bufferedStream); + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); // I don't know why these numbers are different. All I know is that the decoder works // and spectral data is exactly correct also. diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 91b1b9cd7..fe31c5118 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - decoder.ParseStream(bufferedStream); + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); VerifyJpeg.SaveSpectralImage(provider, data); @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - decoder.ParseStream(bufferedStream); + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); this.VerifySpectralCorrectnessImpl(provider, imageSharpData); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index c6f4704f0..ccb7f6f1e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -9,6 +9,7 @@ using System.Text; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; @@ -196,7 +197,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - decoder.ParseStream(bufferedStream, metaDataOnly); + if (metaDataOnly) + { + decoder.Identify(bufferedStream, cancellationToken: default); + } + else + { + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + } return decoder; } From 8078688d6eba8b5ff80f98443943dface307011d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 16:08:45 +0300 Subject: [PATCH 065/119] Fixed null reference in spectral converter --- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 1d1770aa7..6d38bde06 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -105,12 +105,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public override void Dispose() { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + if (this.componentProcessors != null) { - cpp.Dispose(); + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } } - this.rgbaBuffer.Dispose(); + this.rgbaBuffer?.Dispose(); } private void ConvertNextStride(int spectralStep) From 7c63fb4a1cb5c5e2e463497e06c6d9a9ab7f3198 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 16:48:53 +0300 Subject: [PATCH 066/119] Fixed out of range exception at component postprocessor --- .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 10 +++------- .../Components/Decoder/JpegComponentPostProcessor.cs | 8 +++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 05f46aaba..614e96e54 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -135,14 +135,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return; } - int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; - int blocksPerColumnForMcu = fullScan ? this.Frame.McusPerColumn * this.VerticalSamplingFactor : this.VerticalSamplingFactor; - - int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); - int width = this.WidthInBlocks + 1; - int height = totalNumberOfBlocks / width; + int spectralAllocWidth = this.SizeInBlocks.Width; + int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index a853d06fd..2d38b417c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -67,6 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
public void CopyBlocksToColorBuffer(int step) { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; + var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1; @@ -78,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int yBlock = yBlockStart + y; - if (yBlock >= this.SizeInBlocks.Height) + if (yBlock >= spectralBuffer.Height) { break; } @@ -86,10 +88,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); - Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); + Span blockRow = spectralBuffer.GetRowSpan(yBlock); // see: https://github.com/SixLabors/ImageSharp/issues/824 - int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); + int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width); for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { From ae1b40dee887718ee5b054937bb8b4335bc6f40b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 17:04:50 +0300 Subject: [PATCH 067/119] Skipped old post processing pipeline tests --- .../Formats/Jpg/JpegImagePostProcessorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index 93d9aee92..1a969060c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - [Theory] + [Theory(Skip = "Decoding core had breaking internal change and no longer supports post processing after stream parsing.")] [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void DoProcessorStep(TestImageProvider provider) @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - [Theory] + [Theory(Skip = "Decoding core had breaking internal change and no longer supports post processing after stream parsing.")] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void PostProcess(TestImageProvider provider) where TPixel : unmanaged, IPixel From 3c4d0fefd3cd1a41ec5a725e93177ccb42c908a4 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 17:05:04 +0300 Subject: [PATCH 068/119] Fixed invalid frame mcu size calculation --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 6dd88a00c..37628e88d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -924,10 +924,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.MaxHorizontalFactor = maxH; this.Frame.MaxVerticalFactor = maxV; - this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); - this.Frame.InitComponents(); + this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); + // This can be injected in SOF marker callback this.scanDecoder.InjectFrameData(this.Frame, this); } From 7fbc33c1d18bf9e750b315a46288669cdb386b11 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 23:54:17 +0300 Subject: [PATCH 069/119] Fixed invalid internal deconding mode selection for grayscale jpegs --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 7a63ef7c6..29de8059c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineData() { - if (this.ComponentsLength == 1) + if (this.ComponentsLength != this.frame.ComponentCount) { this.frame.AllocateComponents(fullScan: true); this.ParseBaselineDataNonInterleaved(); From e4787956448cb54f4ace99f212f2c7f45794fa77 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 11 Jul 2021 23:59:30 +0300 Subject: [PATCH 070/119] Cosmetic fixes --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 29de8059c..89120813b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -121,16 +121,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineData() { - if (this.ComponentsLength != this.frame.ComponentCount) + if (this.ComponentsLength == this.frame.ComponentCount) { - this.frame.AllocateComponents(fullScan: true); - this.ParseBaselineDataNonInterleaved(); + // interleaved - we can convert spectral data stride by stride + this.frame.AllocateComponents(fullScan: false); + this.ParseBaselineDataInterleaved(); } else { - // interleaved baseline is the only place where we can optimize memory footprint via reusing single spectral stride - this.frame.AllocateComponents(fullScan: false); - this.ParseBaselineDataInterleaved(); + // non-interleaved - each scan contains + this.frame.AllocateComponents(fullScan: true); + this.ParseBaselineDataNonInterleaved(); } } @@ -179,7 +180,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // by the basic H and V specified for the component for (int y = 0; y < v; y++) { - int blockRow = (mcuRow * v) + y; Span blockSpan = component.SpectralBlocks.GetRowSpan(y); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); From b8e13e7eb52df0603cc25e1256b3c35d8e100405 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 02:42:49 +0300 Subject: [PATCH 071/119] Disabled spectral tests due to new architecture incompatibility --- tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index fe31c5118..8e787e725 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg VerifyJpeg.SaveSpectralImage(provider, data); } - [Theory] + [Theory(Skip = "Temporary skipped due to new decoder core architecture")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) where TPixel : unmanaged, IPixel From 519c6b227a6f148d79e4d7b57458b2331630c3fd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 18:24:07 +0300 Subject: [PATCH 072/119] Baseline jpegs now clear allocated buffers in the decoding loop --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 8 +++----- .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 4 +++- .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 8 ++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 89120813b..f2f926d4c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -96,6 +96,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.scanBuffer = new HuffmanScanBuffer(this.stream); + bool fullScan = this.frame.Progressive || this.frame.MultiScan; + this.frame.AllocateComponents(fullScan); + if (!this.frame.Progressive) { this.ParseBaselineData(); @@ -123,14 +126,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (this.ComponentsLength == this.frame.ComponentCount) { - // interleaved - we can convert spectral data stride by stride - this.frame.AllocateComponents(fullScan: false); this.ParseBaselineDataInterleaved(); } else { - // non-interleaved - each scan contains - this.frame.AllocateComponents(fullScan: true); this.ParseBaselineDataNonInterleaved(); } } @@ -303,7 +302,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.CheckProgressiveData(); - this.frame.AllocateComponents(fullScan: true); if (this.ComponentsLength == 1) { this.ParseProgressiveDataNonInterleaved(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 614e96e54..58c34ecf0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -138,7 +138,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int spectralAllocWidth = this.SizeInBlocks.Width; int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); + // We don't need to clear buffer for stride-by-stride approach + AllocationOptions allocOptions = fullScan ? AllocationOptions.Clean : AllocationOptions.None; + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, allocOptions); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 595f39dbd..9f89fd085 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -20,6 +20,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
public bool Progressive { get; set; } + /// + /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). + /// + /// + /// This is true for progressive and baseline non-interleaved images. + /// + public bool MultiScan { get; set; } + /// /// Gets or sets the precision. /// From daccbfbccce21314d6d5d54e82888afcea9e59e7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 18:26:21 +0300 Subject: [PATCH 073/119] Basement for spectral tests --- .../Decoder/SpectralConverter{TPixel}.cs | 12 +++---- .../Formats/Jpeg/JpegDecoderCore.cs | 25 +++++++++------ .../Formats/Jpg/SpectralJpegTests.cs | 31 +++++++++++++++++++ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 6d38bde06..6ad2bf00c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -32,10 +32,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int pixelRowCounter; - public SpectralConverter(Configuration configuration, CancellationToken ct) + public SpectralConverter(Configuration configuration, CancellationToken cancellationToken) { this.configuration = configuration; - this.cancellationToken = ct; + this.cancellationToken = cancellationToken; } private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height; @@ -90,10 +90,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public override void ConvertStrideBaseline() { - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor - this.ConvertNextStride(spectralStep: 0); - // Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride // Which leads to decoding artifacts // Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride @@ -101,6 +97,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { cpp.ClearSpectralBuffers(); } + + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor + this.ConvertNextStride(spectralStep: 0); } public override void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 37628e88d..c7a5bc42c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -220,9 +220,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { using var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); - this.scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); + var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); - this.ParseStream(stream, cancellationToken: cancellationToken); + this.ParseStream(stream, scanDecoder, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ParseStream(stream, true, cancellationToken); + this.ParseStream(stream, scanDecoder: null, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); @@ -244,13 +244,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Parses the input stream for file markers + /// Parses the input stream for file markers. /// - /// The input stream - /// Whether to decode metadata only. - /// The token to monitor cancellation. - private void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) + /// The input stream. + /// Scan decoder used exclusively to decode SOS marker. + /// The token to monitor cancellation. + internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken ct) { + bool metadataOnly = scanDecoder == null; + + this.scanDecoder = scanDecoder; + this.Metadata = new ImageMetadata(); // Check for the Start Of Image marker. @@ -279,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { - cancellationToken.ThrowIfCancellationRequested(); + ct.ThrowIfCancellationRequested(); if (!fileMarker.Invalid) { @@ -297,7 +301,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(stream, cancellationToken); + this.ProcessStartOfScanMarker(stream, ct); break; } else @@ -1030,6 +1034,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } int selectorsCount = stream.ReadByte(); + this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; for (int i = 0; i < selectorsCount; i++) { int componentIndex = -1; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 8e787e725..3c7855367 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; @@ -76,6 +77,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + using var spectralConverter = new SpectralConverter(Configuration.Default, cancellationToken: default); + + var scanDecoder = new HuffmanScanDecoder(bufferedStream, spectralConverter, cancellationToken: default); + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); @@ -126,5 +131,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.True(totalDifference < tolerance); } + + private class DebugSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + private readonly SpectralConverter converter; + + public DebugSpectralConverter(SpectralConverter converter) + { + this.converter = converter; + } + + public override void ConvertStrideBaseline() + { + this.converter.ConvertStrideBaseline(); + } + + public override void Dispose() + { + this.converter?.Dispose(); + } + + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.converter.InjectFrameData(frame, jpegData); + } + } } } From bbbfb50f509fb016a9e951f9ebc54ae2d9bb94b3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 18:44:10 +0300 Subject: [PATCH 074/119] Additional fixes for spectral tests --- .../Formats/Jpg/SpectralJpegTests.cs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 3c7855367..0a457985f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Linq; - +using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; @@ -71,29 +71,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + // Expected data from libjpeg + LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); + // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - using var spectralConverter = new SpectralConverter(Configuration.Default, cancellationToken: default); - var scanDecoder = new HuffmanScanDecoder(bufferedStream, spectralConverter, cancellationToken: default); + // internal scan decoder which we substitute to assert spectral correctness + using var debugConverter = new DebugSpectralConverter(Configuration.Default, cancellationToken: default); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); - using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + // This would parse entire image + // Due to underlying architecture, baseline interleaved jpegs would be tested inside the parsing loop + // Everything else must be checked manually after this method + decoder.ParseStream(bufferedStream, scanDecoder, ct: default); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - this.VerifySpectralCorrectnessImpl(provider, imageSharpData); + this.VerifySpectralCorrectnessImpl(libJpegData, imageSharpData); } - private void VerifySpectralCorrectnessImpl( - TestImageProvider provider, + private void VerifySpectralCorrectnessImpl( + LibJpegTools.SpectralData libJpegData, LibJpegTools.SpectralData imageSharpData) - where TPixel : unmanaged, IPixel { - LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); - bool equality = libJpegData.Equals(imageSharpData); this.Output.WriteLine("Spectral data equality: " + equality); @@ -137,25 +141,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { private readonly SpectralConverter converter; - public DebugSpectralConverter(SpectralConverter converter) - { - this.converter = converter; - } + public DebugSpectralConverter(Configuration configuration, CancellationToken cancellationToken) + => this.converter = new SpectralConverter(configuration, cancellationToken); public override void ConvertStrideBaseline() { this.converter.ConvertStrideBaseline(); + + // This would be called only for baseline non-interleaved images + // We must test spectral strides here } public override void Dispose() { this.converter?.Dispose(); - } - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.converter.InjectFrameData(frame, jpegData); + // As we are only testing spectral data we don't care about pixels + // But we need to dispose allocated pixel buffer + this.converter.PixelBuffer.Dispose(); } + + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) => this.converter.InjectFrameData(frame, jpegData); } } } From 19e2e3d40df77f77a46f4468deb59515ffc45572 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 20:01:09 +0300 Subject: [PATCH 075/119] Fixed new spectral tests for progressive and multi-scan images --- .../Formats/Jpg/SpectralJpegTests.cs | 62 ++++++++++++++++--- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 31 ++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 0a457985f..0235ebb38 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -61,7 +61,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg VerifyJpeg.SaveSpectralImage(provider, data); } - [Theory(Skip = "Temporary skipped due to new decoder core architecture")] + //[Theory(Skip = "Temporary skipped due to new decoder core architecture")] + [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -86,12 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); // This would parse entire image - // Due to underlying architecture, baseline interleaved jpegs would be tested inside the parsing loop - // Everything else must be checked manually after this method decoder.ParseStream(bufferedStream, scanDecoder, ct: default); - var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - this.VerifySpectralCorrectnessImpl(libJpegData, imageSharpData); + // Actual verification + this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); } private void VerifySpectralCorrectnessImpl( @@ -141,27 +140,74 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { private readonly SpectralConverter converter; + private JpegFrame frame; + + private LibJpegTools.SpectralData spectralData; + + private int baselineScanRowCounter; + public DebugSpectralConverter(Configuration configuration, CancellationToken cancellationToken) => this.converter = new SpectralConverter(configuration, cancellationToken); + public LibJpegTools.SpectralData SpectralData + { + get + { + // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing + // Progressive and multi-scan images must be loaded manually + if (this.frame.Progressive || this.frame.MultiScan) + { + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) + { + components[i].LoadSpectral(this.frame.Components[i]); + } + } + + return this.spectralData; + } + } + public override void ConvertStrideBaseline() { this.converter.ConvertStrideBaseline(); // This would be called only for baseline non-interleaved images // We must test spectral strides here + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) + { + components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); + } + + this.baselineScanRowCounter++; } public override void Dispose() { - this.converter?.Dispose(); - // As we are only testing spectral data we don't care about pixels // But we need to dispose allocated pixel buffer this.converter.PixelBuffer.Dispose(); + + // Converter Dispose must be called after pixel buffer disposal because pixel buffer getter can do a full scan conversion + this.converter?.Dispose(); } - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) => this.converter.InjectFrameData(frame, jpegData); + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.converter.InjectFrameData(frame, jpegData); + + this.frame = frame; + + var spectralComponents = new LibJpegTools.ComponentData[frame.ComponentCount]; + for (int i = 0; i < spectralComponents.Length; i++) + { + JpegComponent component = frame.Components[i]; + spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); + } + + this.spectralData = new LibJpegTools.SpectralData(spectralComponents); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 6f6032ee2..4ec9b8d69 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -56,6 +56,37 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.SpectralBlocks[x, y] = new Block8x8(data); } + public void LoadSpectralStride(Buffer2D data, int strideIndex) + { + for (int y = 0; y < data.Height; y++) + { + Span blockRow = data.GetRowSpan(y); + for (int x = 0; x < data.Width; x++) + { + short[] block = blockRow[x].ToArray(); + + // x coordinate stays the same - we load entire stride + // y coordinate is tricky as we load single stride to full buffer - offset is needed + int yOffset = strideIndex * data.Height; + this.MakeBlock(block, y + yOffset, x); + } + } + } + + public void LoadSpectral(JpegComponent c) + { + Buffer2D data = c.SpectralBlocks; + for (int y = 0; y < c.HeightInBlocks; y++) + { + Span blockRow = data.GetRowSpan(y); + for (int x = 0; x < c.WidthInBlocks; x++) + { + short[] block = blockRow[x].ToArray(); + this.MakeBlock(block, y, x); + } + } + } + public static ComponentData Load(JpegComponent c, int index) { var result = new ComponentData( From 39dd5bc5364805899078cbcc71cc0c7cb2cc24c3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 21:00:06 +0300 Subject: [PATCH 076/119] Fixed out of range exception for baseline tests --- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 4ec9b8d69..edb8d457b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -58,17 +58,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public void LoadSpectralStride(Buffer2D data, int strideIndex) { - for (int y = 0; y < data.Height; y++) + int startIndex = strideIndex * data.Height; + + int endIndex = Math.Min(this.HeightInBlocks, startIndex + data.Height); + + for (int y = startIndex; y < endIndex; y++) { - Span blockRow = data.GetRowSpan(y); - for (int x = 0; x < data.Width; x++) + Span blockRow = data.GetRowSpan(y - startIndex); + for (int x = 0; x < this.WidthInBlocks; x++) { short[] block = blockRow[x].ToArray(); // x coordinate stays the same - we load entire stride // y coordinate is tricky as we load single stride to full buffer - offset is needed - int yOffset = strideIndex * data.Height; - this.MakeBlock(block, y + yOffset, x); + this.MakeBlock(block, y, x); } } } @@ -76,10 +79,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public void LoadSpectral(JpegComponent c) { Buffer2D data = c.SpectralBlocks; - for (int y = 0; y < c.HeightInBlocks; y++) + for (int y = 0; y < this.HeightInBlocks; y++) { Span blockRow = data.GetRowSpan(y); - for (int x = 0; x < c.WidthInBlocks; x++) + for (int x = 0; x < this.WidthInBlocks; x++) { short[] block = blockRow[x].ToArray(); this.MakeBlock(block, y, x); @@ -94,16 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils c.HeightInBlocks, index); - for (int y = 0; y < result.HeightInBlocks; y++) - { - Span blockRow = c.SpectralBlocks.GetRowSpan(y); - for (int x = 0; x < result.WidthInBlocks; x++) - { - short[] data = blockRow[x].ToArray(); - result.MakeBlock(data, y, x); - } - } - + result.LoadSpectral(c); return result; } From 0c78c676278122a1936c48146317b654f96b01bd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 21:11:16 +0300 Subject: [PATCH 077/119] Clarified diff logs --- .../Formats/Jpg/SpectralJpegTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 0235ebb38..197d18940 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -57,11 +57,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + + // TODO: Fix this var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); VerifyJpeg.SaveSpectralImage(provider, data); } - //[Theory(Skip = "Temporary skipped due to new decoder core architecture")] [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) @@ -116,11 +117,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; - (double total, double average) diff = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); + (double total, double average) = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); - this.Output.WriteLine($"Component{i}: {diff}"); - averageDifference += diff.average; - totalDifference += diff.total; + this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); + averageDifference += average; + totalDifference += total; tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length; } @@ -173,13 +174,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.converter.ConvertStrideBaseline(); // This would be called only for baseline non-interleaved images - // We must test spectral strides here + // We must copy spectral strides here LibJpegTools.ComponentData[] components = this.spectralData.Components; for (int i = 0; i < components.Length; i++) { components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); } - this.baselineScanRowCounter++; } From 4c97fcc79379ad76d4405a72c2ad9b5c4f3f9cf1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 21:17:58 +0300 Subject: [PATCH 078/119] Rolled back spectral buffer cleaning logic --- .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 4 +--- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 58c34ecf0..614e96e54 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -138,9 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int spectralAllocWidth = this.SizeInBlocks.Width; int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - // We don't need to clear buffer for stride-by-stride approach - AllocationOptions allocOptions = fullScan ? AllocationOptions.Clean : AllocationOptions.None; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, allocOptions); + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 6ad2bf00c..61f521c2f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -90,6 +90,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public override void ConvertStrideBaseline() { + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor + this.ConvertNextStride(spectralStep: 0); + // Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride // Which leads to decoding artifacts // Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride @@ -97,10 +101,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { cpp.ClearSpectralBuffers(); } - - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor - this.ConvertNextStride(spectralStep: 0); } public override void Dispose() From 024be3b2a2544290f3d64b01a7c7fc6c4b296b9a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 21:24:47 +0300 Subject: [PATCH 079/119] Spectral converter base class no longer implements IDisposable interface --- .../Formats/Jpeg/Components/Decoder/SpectralConverter.cs | 8 ++------ .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 35a879082..1d0ac200f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -1,16 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - internal abstract class SpectralConverter : IDisposable + internal abstract class SpectralConverter { public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); public abstract void ConvertStrideBaseline(); - - public abstract void Dispose(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 61f521c2f..9f3d4195c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - internal sealed class SpectralConverter : SpectralConverter + internal sealed class SpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { private readonly Configuration configuration; @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - public override void Dispose() + public void Dispose() { if (this.componentProcessors != null) { From 194f6e022a49a8a5316bc8d86918c625c7792894 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 21:31:44 +0300 Subject: [PATCH 080/119] Debug converter no longer use actual converter --- .../Formats/Jpg/SpectralJpegTests.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 197d18940..b694808b7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); // internal scan decoder which we substitute to assert spectral correctness - using var debugConverter = new DebugSpectralConverter(Configuration.Default, cancellationToken: default); + var debugConverter = new DebugSpectralConverter(); var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); // This would parse entire image @@ -147,9 +147,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private int baselineScanRowCounter; - public DebugSpectralConverter(Configuration configuration, CancellationToken cancellationToken) - => this.converter = new SpectralConverter(configuration, cancellationToken); - public LibJpegTools.SpectralData SpectralData { get @@ -171,8 +168,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public override void ConvertStrideBaseline() { - this.converter.ConvertStrideBaseline(); - // This would be called only for baseline non-interleaved images // We must copy spectral strides here LibJpegTools.ComponentData[] components = this.spectralData.Components; @@ -180,23 +175,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); } - this.baselineScanRowCounter++; - } - public override void Dispose() - { - // As we are only testing spectral data we don't care about pixels - // But we need to dispose allocated pixel buffer - this.converter.PixelBuffer.Dispose(); - - // Converter Dispose must be called after pixel buffer disposal because pixel buffer getter can do a full scan conversion - this.converter?.Dispose(); + this.baselineScanRowCounter++; } public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { - this.converter.InjectFrameData(frame, jpegData); - this.frame = frame; var spectralComponents = new LibJpegTools.ComponentData[frame.ComponentCount]; From 7540fd9018aae10e351d738ad626204fd8ed0348 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 23:36:08 +0300 Subject: [PATCH 081/119] Fixed baseline images tsting code --- .../Formats/Jpg/SpectralJpegTests.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index b694808b7..09548e276 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -6,8 +6,10 @@ using System.IO; using System.Linq; using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; @@ -139,8 +141,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private class DebugSpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel { - private readonly SpectralConverter converter; - private JpegFrame frame; private LibJpegTools.SpectralData spectralData; @@ -177,6 +177,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } this.baselineScanRowCounter++; + + // As spectral buffers are reused for each stride decoding - we need to manually clear it like it's done in SpectralConverter + foreach (JpegComponent component in this.frame.Components) + { + Buffer2D spectralBlocks = component.SpectralBlocks; + for (int i = 0; i < spectralBlocks.Height; i++) + { + spectralBlocks.GetRowSpan(i).Clear(); + } + } } public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) From 0261ea9350ae725d5ae98e662823293e0fb0aa24 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 23:38:54 +0300 Subject: [PATCH 082/119] Fixed bad EOI image --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index f2f926d4c..a09c7ada3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -162,7 +162,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int i = 0; i < mcusPerLine; i++) { // Scan an interleaved mcu... process components in order - int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; for (int k = 0; k < this.ComponentsLength; k++) { @@ -186,6 +185,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (buffer.NoData) { + // It is very likely that some spectral data was decoded before we encountered EOI marker + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); return; } From 79eb6c401839b54efa6288475f607e9065b0aa42 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 00:00:50 +0300 Subject: [PATCH 083/119] Fixed metadata only pass for a test --- tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index de8103d63..a124ec191 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var expectedColorSpace = (JpegColorSpace)expectedColorSpaceValue; - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile, metaDataOnly: true)) { Assert.Equal(expectedColorSpace, decoder.ColorSpace); } From d7084eb686b06cd4e397a5c432c82e19954fc822 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 12:49:36 +0300 Subject: [PATCH 084/119] Style fixes --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index a2f7583a1..a052ee88a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -131,10 +131,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(0)] [InlineData(0.5)] [InlineData(0.9)] - public async Task Decode_IsCancellable(int percentageOfStreamReadToCancel) + public async Task DecodeAsync_IsCancellable(int percentageOfStreamReadToCancel) { var cts = new CancellationTokenSource(); - var file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); pausedStream.OnWaiting(s => { From 2e5b0ad74da6b476f812266b193be81c63ba17f5 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 14:03:34 +0300 Subject: [PATCH 085/119] Fixed baseline image invalid reference output png image --- .../Jpg/SpectralToPixelConversionTests.cs | 69 +++++++++++++++++++ .../DecodeBaselineJpeg_jpeg420small.png | 4 +- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs new file mode 100644 index 000000000..b0e5a3db6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Linq; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public class SpectralToPixelConversionTests + { + public static readonly string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; + + public SpectralToPixelConversionTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Theory] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void Decoder_PixelBufferComparison(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Stream + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + + // Decoding + using var converter = new SpectralConverter(Configuration.Default, cancellationToken: default); + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); + decoder.ParseStream(bufferedStream, scanDecoder, ct: default); + + // Test metadata + provider.Utility.TestGroupName = nameof(JpegDecoderTests); + provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + + // Comparison + using (Image image = new Image(Configuration.Default, converter.PixelBuffer, new ImageMetadata())) + using (Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) + { + ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); + + this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); + this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); + + // ReSharper disable once PossibleInvalidOperationException + Assert.True(report.TotalNormalizedDifference.Value < 0.005f); + } + } + } +} diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png index c57b00d0e..4032a32af 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a76832570111a868ea6cb6e8287aae1976c575c94c63880c74346a4b5db5d305 -size 27007 +oid sha256:2b5e1d91fb6dc1ddb696fbee63331ba9c6ef3548b619c005887e60c5b01f4981 +size 27303 From 005fff7fb3bc37ff31104c5cdcc84dd95c9d5bbb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 14:11:35 +0300 Subject: [PATCH 086/119] Removed post processor tests --- .../Jpg/JpegImagePostProcessorTests.cs | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs deleted file mode 100644 index 1a969060c..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - [Trait("Format", "Jpg")] - public class JpegImagePostProcessorTests - { - public static string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Ycck, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg444, - }; - - public JpegImagePostProcessorTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) - { - image.DebugSave(provider, $"-C{cp.Component.Index}-"); - } - } - - [Theory(Skip = "Decoding core had breaking internal change and no longer supports post processing after stream parsing.")] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] - public void DoProcessorStep(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string imageFile = provider.SourceFileOrDescription; - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) - using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) - using (var imageFrame = new ImageFrame(Configuration.Default, decoder.ImageWidth, decoder.ImageHeight)) - { - pp.DoPostProcessorStep(imageFrame); - - JpegComponentPostProcessor[] cp = pp.ComponentProcessors; - - SaveBuffer(cp[0], provider); - SaveBuffer(cp[1], provider); - SaveBuffer(cp[2], provider); - } - } - - [Theory(Skip = "Decoding core had breaking internal change and no longer supports post processing after stream parsing.")] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void PostProcess(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string imageFile = provider.SourceFileOrDescription; - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) - using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) - using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) - { - pp.PostProcess(image.Frames.RootFrame, default); - - image.DebugSave(provider); - - ImagingTestCaseUtility testUtil = provider.Utility; - testUtil.TestGroupName = nameof(JpegDecoderTests); - testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; - - using (Image referenceImage = - provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) - { - ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); - - this.Output.WriteLine($"*** {imageFile} ***"); - this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); - - // ReSharper disable once PossibleInvalidOperationException - Assert.True(report.TotalNormalizedDifference.Value < 0.005f); - } - } - } - } -} From c6a2c6b8f84dbdcee5d3f8dff4b8b0289d2f351d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 14:12:22 +0300 Subject: [PATCH 087/119] Removed post processor from jpeg decoder --- .../Decoder/JpegImagePostProcessor.cs | 164 ------------------ .../Formats/Jpeg/JpegDecoderCore.cs | 27 --- 2 files changed, 191 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs deleted file mode 100644 index 26a063524..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Numerics; -using System.Threading; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Encapsulates the execution od post-processing algorithms to be applied on a to produce a valid :
- /// (1) Dequantization
- /// (2) IDCT
- /// (3) Color conversion form one of the -s into a buffer of RGBA values
- /// (4) Packing pixels from the buffer.
- /// These operations are executed in steps. - /// image rows are converted in one step, - /// which means that size of the allocated memory is limited (does not depend on ). - ///
- internal class JpegImagePostProcessor : IDisposable - { - private readonly Configuration configuration; - - /// - /// The number of block rows to be processed in one Step. - /// - private readonly int blockRowsPerStep; - - /// - /// The number of image pixel rows to be processed in one step. - /// - private readonly int pixelRowsPerStep; - - /// - /// Temporal buffer to store a row of colors. - /// - private readonly IMemoryOwner rgbaBuffer; - - /// - /// The corresponding to the current determined by . - /// - private readonly JpegColorConverter colorConverter; - - /// - /// Initializes a new instance of the class. - /// - /// The to configure internal operations. - /// The representing the uncompressed spectral Jpeg data - public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg) - { - this.configuration = configuration; - this.RawJpeg = rawJpeg; - IJpegComponent c0 = rawJpeg.Components[0]; - - this.blockRowsPerStep = c0.SamplingFactors.Height; - this.pixelRowsPerStep = this.blockRowsPerStep * 8; - - this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.blockRowsPerStep; - - var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; - for (int i = 0; i < rawJpeg.Components.Length; i++) - { - this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this.RawJpeg, postProcessorBufferSize, rawJpeg.Components[i]); - } - - this.rgbaBuffer = memoryAllocator.Allocate(rawJpeg.ImageSizeInPixels.Width); - this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); - } - - /// - /// Gets the instances. - /// - public JpegComponentPostProcessor[] ComponentProcessors { get; } - - /// - /// Gets the to be processed. - /// - public IRawJpegData RawJpeg { get; } - - /// - /// Gets the total number of post processor steps deduced from the height of the image and . - /// - public int NumberOfPostProcessorSteps { get; } - - /// - /// Gets the value of the counter that grows by each step by . - /// - public int PixelRowCounter { get; private set; } - - /// - public void Dispose() - { - foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) - { - cpp.Dispose(); - } - - this.rgbaBuffer.Dispose(); - } - - /// - /// Process all pixels into 'destination'. The image dimensions should match . - /// - /// The pixel type - /// The destination image - /// The token to request cancellation. - public void PostProcess(ImageFrame destination, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - this.PixelRowCounter = 0; - - if (this.RawJpeg.ImageSizeInPixels != destination.Size()) - { - throw new ArgumentException("Input image is not of the size of the processed one!"); - } - - while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) - { - cancellationToken.ThrowIfCancellationRequested(); - this.DoPostProcessorStep(destination); - } - } - - /// - /// Execute one step processing pixel rows into 'destination'. - /// Convert and copy row of colors into 'destination' starting at row . - /// - /// The pixel type - /// The destination image - public void DoPostProcessorStep(ImageFrame destination) - where TPixel : unmanaged, IPixel - { - int maxY = Math.Min(destination.Height, this.PixelRowCounter + this.pixelRowsPerStep); - - var buffers = new Buffer2D[this.ComponentProcessors.Length]; - for (int i = 0; i < this.ComponentProcessors.Length; i++) - { - this.ComponentProcessors[i].CopyBlocksToColorBuffer(); - buffers[i] = this.ComponentProcessors[i].ColorBuffer; - } - - for (int yy = this.PixelRowCounter; yy < maxY; yy++) - { - int y = yy - this.PixelRowCounter; - - var values = new JpegColorConverter.ComponentValues(buffers, y); - this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); - - Span destRow = destination.GetPixelRowSpan(yy); - - // TODO: Investigate if slicing is actually necessary - PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); - } - - this.PixelRowCounter += this.pixelRowsPerStep; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index c7a5bc42c..c4f8a1281 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1112,32 +1112,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg stream.Read(this.markerBuffer, 0, 2); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } - - /// - /// Post processes the pixels into the destination image. - /// - /// The pixel format. - /// The . - private Image PostProcessIntoImage(CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - if (this.ImageWidth == 0 || this.ImageHeight == 0) - { - JpegThrowHelper.ThrowInvalidImageDimensions(this.ImageWidth, this.ImageHeight); - } - - var image = Image.CreateUninitialized( - this.Configuration, - this.ImageWidth, - this.ImageHeight, - this.Metadata); - - using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this)) - { - postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken); - } - - return image; - } } } From 865c7060a38e9164980426b8cc0284488c629b2c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 14:14:52 +0300 Subject: [PATCH 088/119] Added new tolerance to the Jpeg420Small test image --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 2faea2611..304dd93a6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -17,8 +17,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Turtle420, TestImages.Jpeg.Baseline.Testorig420, - - // BUG: The following image has a high difference compared to the expected output: 1.0096% TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, TestImages.Jpeg.Baseline.Jpeg444, @@ -101,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, // Progressive: From 8b6ad9ce8a9e333b6c47d6592efa49ce78d62934 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 14:24:33 +0300 Subject: [PATCH 089/119] Fixed docs --- .../Jpeg/Components/Decoder/JpegComponentPostProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 2d38b417c..79965a3f0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Encapsulates postprocessing data for one component for . + /// Encapsulates spectral data to rgba32 processing for one component. /// internal class JpegComponentPostProcessor : IDisposable { From 84900dcd2954d93c66169396dfa965df24824376 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 15:58:36 +0300 Subject: [PATCH 090/119] Restored memory stress test to the sandbox --- tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 9aa983ac5..e6e82b981 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -31,13 +31,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { + LoadResizeSaveParallelMemoryStress.Run(); // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - Console.ReadLine(); + // Console.ReadLine(); } private static void RunJpegEncoderProfilingTests() From 82e22c30b4f01c90f0f6c4b36b6b64902abfb981 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 16:11:05 +0300 Subject: [PATCH 091/119] Restored decoder parse stream only benchmark --- .../Formats/Jpeg/JpegDecoderCore.cs | 8 +- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 135 ++++++++++-------- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- .../Jpg/SpectralToPixelConversionTests.cs | 2 +- 4 files changed, 82 insertions(+), 65 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index c4f8a1281..922e9797c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -248,8 +248,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ///
/// The input stream. /// Scan decoder used exclusively to decode SOS marker. - /// The token to monitor cancellation. - internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken ct) + /// The token to monitor cancellation. + internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken) { bool metadataOnly = scanDecoder == null; @@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { - ct.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (!fileMarker.Invalid) { @@ -301,7 +301,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(stream, ct); + this.ProcessStartOfScanMarker(stream, cancellationToken); break; } else diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 6796faa6d..8659aee63 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,59 +1,76 @@ -//// Copyright (c) Six Labors. -//// Licensed under the Apache License, Version 2.0. - -//using System.IO; -//using BenchmarkDotNet.Attributes; -//using SixLabors.ImageSharp.Formats.Jpeg; -//using SixLabors.ImageSharp.IO; -//using SixLabors.ImageSharp.Tests; -//using SDSize = System.Drawing.Size; - -//namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg -//{ -// [Config(typeof(Config.ShortMultiFramework))] -// public class DecodeJpegParseStreamOnly -// { -// [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] -// public string TestImage { get; set; } - -// private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - -// private byte[] jpegBytes; - -// [GlobalSetup] -// public void Setup() -// => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - -// [Benchmark(Baseline = true, Description = "System.Drawing FULL")] -// public SDSize JpegSystemDrawing() -// { -// using var memoryStream = new MemoryStream(this.jpegBytes); -// using var image = System.Drawing.Image.FromStream(memoryStream); -// return image.Size; -// } - -// [Benchmark(Description = "JpegDecoderCore.ParseStream")] -// public void ParseStream() -// { -// using var memoryStream = new MemoryStream(this.jpegBytes); -// using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); - -// var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); -// decoder.ParseStream(bufferedStream); -// decoder.Dispose(); -// } -// } - -// /* -// | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| -// | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | -// | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | -// | | | | | | | | | | | | | -// | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | -// | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | -// | | | | | | | | | | | | | -// | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | -// | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | -// */ -//} +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Tests; +using SDSize = System.Drawing.Size; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + //[Config(typeof(Config.ShortMultiFramework))] + public class DecodeJpegParseStreamOnly + { + [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] + public string TestImage { get; set; } + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + private byte[] jpegBytes; + + [GlobalSetup] + public void Setup() + => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + + //[Benchmark(Baseline = true, Description = "System.Drawing FULL")] + //public SDSize JpegSystemDrawing() + //{ + // using var memoryStream = new MemoryStream(this.jpegBytes); + // using var image = System.Drawing.Image.FromStream(memoryStream); + // return image.Size; + //} + + [Benchmark(Description = "JpegDecoderCore.ParseStream")] + public void ParseStream() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); + + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, new NoopSpectralConverter(), cancellationToken: default); + decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); + decoder.Dispose(); + } + + // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels + // Nor we need to allocate final pixel buffer + // Note: this still introduces virtual method call overhead for baseline interleaved images + // There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction + private class NoopSpectralConverter : SpectralConverter + { + public override void ConvertStrideBaseline() + { + } + + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + } + } + } + + /* + | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | + |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| + | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | + | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | + | | | | | | | | | | | | | + | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | + | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | + | | | | | | | | | | | | | + | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | + | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | + */ +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 09548e276..0d4881ada 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); // This would parse entire image - decoder.ParseStream(bufferedStream, scanDecoder, ct: default); + decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); // Actual verification this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index b0e5a3db6..353ae39f0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var converter = new SpectralConverter(Configuration.Default, cancellationToken: default); var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); - decoder.ParseStream(bufferedStream, scanDecoder, ct: default); + decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); // Test metadata provider.Utility.TestGroupName = nameof(JpegDecoderTests); From 0c27adc96fd2f2848ed9a5c64f4e9b280086de00 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 16:26:13 +0300 Subject: [PATCH 092/119] Updated StreamParseOnly benchmark --- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 8659aee63..9db666c37 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -11,7 +11,7 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - //[Config(typeof(Config.ShortMultiFramework))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpegParseStreamOnly { [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] @@ -25,13 +25,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public void Setup() => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - //[Benchmark(Baseline = true, Description = "System.Drawing FULL")] - //public SDSize JpegSystemDrawing() - //{ - // using var memoryStream = new MemoryStream(this.jpegBytes); - // using var image = System.Drawing.Image.FromStream(memoryStream); - // return image.Size; - //} + [Benchmark(Baseline = true, Description = "System.Drawing FULL")] + public SDSize JpegSystemDrawing() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = System.Drawing.Image.FromStream(memoryStream); + return image.Size; + } [Benchmark(Description = "JpegDecoderCore.ParseStream")] public void ParseStream() @@ -60,17 +60,26 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } } - - /* - | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | - |---------------------------- |----------- |-------------- |--------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| - | 'System.Drawing FULL' | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.828 ms | 0.9885 ms | 0.0542 ms | 1.00 | 46.8750 | - | - | 211566 B | - | JpegDecoderCore.ParseStream | Job-HITJFX | .NET 4.7.2 | Jpg/b(...)e.jpg [21] | 5.833 ms | 0.2923 ms | 0.0160 ms | 1.00 | - | - | - | 12416 B | - | | | | | | | | | | | | | - | 'System.Drawing FULL' | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 6.018 ms | 2.1374 ms | 0.1172 ms | 1.00 | 46.8750 | - | - | 210768 B | - | JpegDecoderCore.ParseStream | Job-WPSKZD | .NET Core 2.1 | Jpg/b(...)e.jpg [21] | 4.382 ms | 0.9009 ms | 0.0494 ms | 0.73 | - | - | - | 12360 B | - | | | | | | | | | | | | | - | 'System.Drawing FULL' | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 5.714 ms | 0.4078 ms | 0.0224 ms | 1.00 | - | - | - | 176 B | - | JpegDecoderCore.ParseStream | Job-ZLSNRP | .NET Core 3.1 | Jpg/b(...)e.jpg [21] | 4.239 ms | 1.0943 ms | 0.0600 ms | 0.74 | - | - | - | 12406 B | - */ } + +/* +BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1083 (20H2/October2020Update) +Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores +.NET SDK=6.0.100-preview.3.21202.5 + [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT + Job-VAJCIU : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT + Job-INPXCR : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT + Job-JRCLOJ : .NET Framework 4.8 (4.8.4390.0), X64 RyuJIT + +IterationCount=3 LaunchCount=1 WarmupCount=3 +| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | +|---------------------------- |----------- |--------------------- |---------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| +| 'System.Drawing FULL' | Job-VAJCIU | .NET Core 2.1 | Jpg/baseline/Lake.jpg | 5.196 ms | 0.7520 ms | 0.0412 ms | 1.00 | 46.8750 | - | - | 210,768 B | +| JpegDecoderCore.ParseStream | Job-VAJCIU | .NET Core 2.1 | Jpg/baseline/Lake.jpg | 3.467 ms | 0.0784 ms | 0.0043 ms | 0.67 | - | - | - | 12,416 B | +| | | | | | | | | | | | | +| 'System.Drawing FULL' | Job-INPXCR | .NET Core 3.1 | Jpg/baseline/Lake.jpg | 5.201 ms | 0.4105 ms | 0.0225 ms | 1.00 | - | - | - | 183 B | +| JpegDecoderCore.ParseStream | Job-INPXCR | .NET Core 3.1 | Jpg/baseline/Lake.jpg | 3.349 ms | 0.0468 ms | 0.0026 ms | 0.64 | - | - | - | 12,408 B | +| | | | | | | | | | | | | +| 'System.Drawing FULL' | Job-JRCLOJ | .NET Framework 4.7.2 | Jpg/baseline/Lake.jpg | 5.164 ms | 0.6524 ms | 0.0358 ms | 1.00 | 46.8750 | - | - | 211,571 B | +| JpegDecoderCore.ParseStream | Job-JRCLOJ | .NET Framework 4.7.2 | Jpg/baseline/Lake.jpg | 4.548 ms | 0.3357 ms | 0.0184 ms | 0.88 | - | - | - | 12,480 B | +*/ From 2eaa2d54e366777472e17c05d5958f03ea9f0e44 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 17:23:24 +0300 Subject: [PATCH 093/119] Added docs --- .../Components/Decoder/SpectralConverter.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 1d0ac200f..e84d13ff1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -3,10 +3,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { + /// + /// Converter used to convert jpeg spectral data. + /// + /// + /// This is tightly coupled with and . + /// internal abstract class SpectralConverter { + /// + /// Injects jpeg image decoding metadata. + /// + /// + /// This is guaranteed to be called only once at SOF marker by . + /// + /// instance containing decoder-specific parameters. + /// instance containing decoder-specific parameters. public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); + /// + /// Called once per spectral stride for each component in . + /// This is called only for baseline interleaved jpegs. + /// + /// + /// Spectral 'stride' doesn't particularly mean 'single stride'. + /// Actual stride height depends on the subsampling factor of the given component. + /// public abstract void ConvertStrideBaseline(); } } From 13c3a45a9832188783e0857b700b2f4d5e3d6235 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 17:53:17 +0300 Subject: [PATCH 094/119] Added DivideCeil --- src/ImageSharp/Common/Helpers/Numerics.cs | 8 +++++ .../Jpeg/Components/Decoder/JpegFrame.cs | 4 +-- .../ImageSharp.Tests/Common/NumericsTests.cs | 34 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index db65b84cc..ba5c588ca 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -879,5 +879,13 @@ namespace SixLabors.ImageSharp (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here } #endif + + /// + /// Fast division with ceiling for numbers. + /// + /// Divident value. + /// Divisor value. + /// Ceiled division result. + public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 9f89fd085..3a136b410 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -103,8 +103,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
public void InitComponents() { - this.McusPerLine = (int)MathF.Ceiling(this.PixelWidth / 8F / this.MaxHorizontalFactor); - this.McusPerColumn = (int)MathF.Ceiling(this.PixelHeight / 8F / this.MaxVerticalFactor); + this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8); for (int i = 0; i < this.ComponentCount; i++) { diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs index 29eae6d48..62819af49 100644 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Common int expected = 0; int actual = Numerics.Log2(value); - Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + Assert.Equal(expected, actual); } [Fact] @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Common int expected = i; int actual = Numerics.Log2(value); - Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + Assert.Equal(expected, actual); } } @@ -66,7 +66,35 @@ namespace SixLabors.ImageSharp.Tests.Common int expected = Log2_ReferenceImplementation(value); int actual = Numerics.Log2(value); - Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + Assert.Equal(expected, actual); + } + } + + private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor); + + [Fact] + public void DivideCeil_DivideZero() + { + uint expected = 0; + uint actual = Numerics.DivideCeil(0, 100); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1, 100)] + public void DivideCeil_RandomValues(int seed, int count) + { + var rng = new Random(seed); + for (int i = 0; i < count; i++) + { + uint value = (uint)rng.Next(); + uint divisor = (uint)rng.Next(); + + uint expected = DivideCeil_ReferenceImplementation(value, divisor); + uint actual = Numerics.DivideCeil(value, divisor); + + Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}"); } } } From 269c0735200815b777377f7a5a25d5e1584bff89 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 18:27:10 +0300 Subject: [PATCH 095/119] Fixed spectral data as image saving test --- .../Formats/Jpg/SpectralJpegTests.cs | 17 ++++++++++------- .../Jpg/Utils/LibJpegTools.SpectralData.cs | 8 -------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 0d4881ada..805e19d97 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -46,23 +46,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - [Theory(Skip = "Debug only, enable manually!")] + //[Theory(Skip = "Debug only, enable manually!")] + [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : unmanaged, IPixel { - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - + // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + // internal scan decoder which we substitute to assert spectral correctness + var debugConverter = new DebugSpectralConverter(); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); - // TODO: Fix this - var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - VerifyJpeg.SaveSpectralImage(provider, data); + // This would parse entire image + decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); + VerifyJpeg.SaveSpectralImage(provider, debugConverter.SpectralData); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index 6ed7c15ae..2d0672f17 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -29,14 +29,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.Components = components; } - public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) - { - JpegComponent[] srcComponents = decoder.Frame.Components; - LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); - - return new SpectralData(destComponents); - } - public Image TryCreateRGBSpectralImage() { if (this.ComponentCount != 3) From 190964c9bafa9e04cbe36e2eafeafcfae6b5a6c2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 13 Jul 2021 18:31:38 +0300 Subject: [PATCH 096/119] Disabled image saving test --- tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 805e19d97..0b819bf13 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -46,8 +46,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - //[Theory(Skip = "Debug only, enable manually!")] - [Theory] + [Theory(Skip = "Debug only, enable manually!")] + //[Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : unmanaged, IPixel From 6f45485203e61b6733c7eaf7015b453742d4679d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 15 Jul 2021 18:33:54 +0300 Subject: [PATCH 097/119] Huffman tables are now handled by scan decoder, not decoder core --- .../Components/Decoder/HuffmanScanDecoder.cs | 54 +++++++++++++------ .../Formats/Jpeg/JpegDecoderCore.cs | 40 ++------------ 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index a09c7ada3..97ec45ec1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -31,6 +31,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. private int eobrun; + /// + /// The DC Huffman tables. + /// + private readonly HuffmanTable[] dcHuffmanTables; + + /// + /// The AC Huffman tables + /// + private readonly HuffmanTable[] acHuffmanTables; + // The unzig data. private ZigZag dctZigZag; @@ -55,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.stream = stream; this.spectralConverter = converter; this.cancellationToken = cancellationToken; - } - - // huffman tables - public HuffmanTable[] DcHuffmanTables { get; set; } - public HuffmanTable[] AcHuffmanTables { get; set; } + // TODO: this is actually a variable value depending on component count + const int maxTables = 4; + this.dcHuffmanTables = new HuffmanTable[maxTables]; + this.acHuffmanTables = new HuffmanTable[maxTables]; + } // Reset interval public int ResetInterval @@ -148,8 +158,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int order = this.frame.ComponentOrder[i]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; dcHuffmanTable.Configure(); acHuffmanTable.Configure(); } @@ -168,8 +178,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -221,8 +231,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; dcHuffmanTable.Configure(); acHuffmanTable.Configure(); @@ -327,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; dcHuffmanTable.Configure(); } @@ -342,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -390,7 +400,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (this.SpectralStart == 0) { - ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; dcHuffmanTable.Configure(); for (int j = 0; j < h; j++) @@ -418,7 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } else { - ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; acHuffmanTable.Configure(); for (int j = 0; j < h; j++) @@ -722,5 +732,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return false; } + + /// + /// Build the huffman table using code lengths and code values. + /// + /// Table type. + /// Table index. + /// Code lengths. + /// Code values. + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) + { + HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[index] = new HuffmanTable(codeLengths, values); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 922e9797c..ee723f062 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -42,16 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ///
private readonly byte[] markerBuffer = new byte[2]; - /// - /// The DC Huffman tables. - /// - private HuffmanTable[] dcHuffmanTables; - - /// - /// The AC Huffman tables - /// - private HuffmanTable[] acHuffmanTables; - /// /// The reset interval determined by RST markers. /// @@ -270,14 +260,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); this.QuantizationTables = new Block8x8F[4]; - // Only assign what we need - if (!metadataOnly) - { - const int maxTables = 4; - this.dcHuffmanTables = new HuffmanTable[maxTables]; - this.acHuffmanTables = new HuffmanTable[maxTables]; - } - // Break only when we discover a valid EOI marker. // https://github.com/SixLabors/ImageSharp/issues/695 while (fileMarker.Marker != JpegConstants.Markers.EOI @@ -392,8 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Set large fields to null. this.Frame = null; - this.dcHuffmanTables = null; - this.acHuffmanTables = null; + this.scanDecoder = null; } /// @@ -996,8 +977,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg i += 17 + codeLengthSum; - this.BuildHuffmanTable( - tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + this.scanDecoder.BuildHuffmanTable( + tableType, tableIndex, codeLengthsSpan, huffmanValuesSpan); @@ -1071,10 +1052,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // All the comments below are for separate refactoring PR // Main reason it's not fixed here is to make this commit less intrusive - // Huffman tables can be calculated directly in the scan decoder class - this.scanDecoder.DcHuffmanTables = this.dcHuffmanTables; - this.scanDecoder.AcHuffmanTables = this.acHuffmanTables; - // This can be injectd in DRI marker callback this.scanDecoder.ResetInterval = this.resetInterval; @@ -1090,17 +1067,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.scanDecoder.ParseEntropyCodedData(); } - /// - /// Builds the huffman tables - /// - /// The tables - /// The table index - /// The codelengths - /// The values - [MethodImpl(InliningOptions.ShortMethod)] - private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) - => tables[index] = new HuffmanTable(codeLengths, values); - /// /// Reads a from the stream advancing it by two bytes /// From d9745e4d3bd2a6fd14393e1278fa4bd07ed94881 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 15 Jul 2021 18:42:30 +0300 Subject: [PATCH 098/119] Restart interval is now handled by scan decoder --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 8 ++++++-- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 10 +--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 97ec45ec1..688414b33 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -22,7 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private JpegFrame frame; private JpegComponent[] components; - // The restart interval. + /// + /// The reset interval determined by RST markers. + /// private int restartInterval; // How many mcu's are left to do. @@ -72,7 +74,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.acHuffmanTables = new HuffmanTable[maxTables]; } - // Reset interval + /// + /// Sets reset interval determined by RST markers. + /// public int ResetInterval { set diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ee723f062..e61909798 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -42,11 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private readonly byte[] markerBuffer = new byte[2]; - /// - /// The reset interval determined by RST markers. - /// - private ushort resetInterval; - /// /// Whether the image has an EXIF marker. /// @@ -1001,7 +996,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); } - this.resetInterval = this.ReadUint16(stream); + this.scanDecoder.ResetInterval = this.ReadUint16(stream); } /// @@ -1052,9 +1047,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // All the comments below are for separate refactoring PR // Main reason it's not fixed here is to make this commit less intrusive - // This can be injectd in DRI marker callback - this.scanDecoder.ResetInterval = this.resetInterval; - // This can be passed as ParseEntropyCodedData() parameter as it is used only there this.scanDecoder.ComponentsLength = selectorsCount; From b299e1a2f7600f1611be32be0b703ba2b24dddb6 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 15 Jul 2021 19:04:03 +0300 Subject: [PATCH 099/119] Scan component count refactor --- .../Components/Decoder/HuffmanScanDecoder.cs | 24 ++++++++++--------- .../Formats/Jpeg/JpegDecoderCore.cs | 8 +------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 688414b33..ea76df7a8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -22,6 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private JpegFrame frame; private JpegComponent[] components; + // The number of interleaved components. + private int componentsCount; + /// /// The reset interval determined by RST markers. /// @@ -86,9 +89,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - // The number of interleaved components. - public int ComponentsLength { get; set; } - // The spectral selection start. public int SpectralStart { get; set; } @@ -104,10 +104,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Decodes the entropy coded data. /// - public void ParseEntropyCodedData() + public void ParseEntropyCodedData(int componentCount) { this.cancellationToken.ThrowIfCancellationRequested(); + this.componentsCount = componentCount; + this.scanBuffer = new HuffmanScanBuffer(this.stream); bool fullScan = this.frame.Progressive || this.frame.MultiScan; @@ -138,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineData() { - if (this.ComponentsLength == this.frame.ComponentCount) + if (this.componentsCount == this.frame.ComponentCount) { this.ParseBaselineDataInterleaved(); } @@ -157,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanScanBuffer buffer = ref this.scanBuffer; // Pre-derive the huffman table to avoid in-loop checks. - for (int i = 0; i < this.ComponentsLength; i++) + for (int i = 0; i < this.componentsCount; i++) { int order = this.frame.ComponentOrder[i]; JpegComponent component = this.components[order]; @@ -177,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { // Scan an interleaved mcu... process components in order int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.ComponentsLength; k++) + for (int k = 0; k < this.componentsCount; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; @@ -286,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } // AC scans may have only one component. - if (this.ComponentsLength != 1) + if (this.componentsCount != 1) { invalid = true; } @@ -318,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.CheckProgressiveData(); - if (this.ComponentsLength == 1) + if (this.componentsCount == 1) { this.ParseProgressiveDataNonInterleaved(); } @@ -337,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanScanBuffer buffer = ref this.scanBuffer; // Pre-derive the huffman table to avoid in-loop checks. - for (int k = 0; k < this.ComponentsLength; k++) + for (int k = 0; k < this.componentsCount; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; @@ -352,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Scan an interleaved mcu... process components in order int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.ComponentsLength; k++) + for (int k = 0; k < this.componentsCount; k++) { int order = this.frame.ComponentOrder[k]; JpegComponent component = this.components[order]; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index e61909798..97a6d3999 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1044,19 +1044,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - // All the comments below are for separate refactoring PR - // Main reason it's not fixed here is to make this commit less intrusive - - // This can be passed as ParseEntropyCodedData() parameter as it is used only there - this.scanDecoder.ComponentsLength = selectorsCount; - // This is okay to inject here, might be good to wrap it in a separate struct but not really necessary this.scanDecoder.SpectralStart = spectralStart; this.scanDecoder.SpectralEnd = spectralEnd; this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; this.scanDecoder.SuccessiveLow = successiveApproximation & 15; - this.scanDecoder.ParseEntropyCodedData(); + this.scanDecoder.ParseEntropyCodedData(selectorsCount); } /// From 9067c64b3074d067fbf1184377a6956c82093d98 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 02:00:59 +0300 Subject: [PATCH 100/119] Added SOF data precision comments, decoupled precision value from decoder core --- .../Jpeg/Components/Decoder/IRawJpegData.cs | 7 +------ .../Decoder/JpegBlockPostProcessor.cs | 6 ------ .../Decoder/JpegComponentPostProcessor.cs | 13 +++++++++++-- .../Decoder/SpectralConverter{TPixel}.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 18 ++++++++---------- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index b1ac1f78f..b715eef98 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// JpegColorSpace ColorSpace { get; } - /// - /// Gets the number of bits used for precision. - /// - int Precision { get; } - /// /// Gets the components. /// @@ -41,4 +36,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Block8x8F[] QuantizationTables { get; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index e0311dafe..7cfbaddcc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -38,11 +38,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
private Size subSamplingDivisors; - /// - /// Defines the maximum value derived from the bitdepth. - /// - private readonly int maximumValue; - /// /// Initializes a new instance of the struct. /// @@ -53,7 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int qtIndex = component.QuantizationTableIndex; this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.subSamplingDivisors = component.SubSamplingDivisors; - this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1; this.SourceBlock = default; this.WorkspaceBlock1 = default; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 79965a3f0..31214b4c1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -21,11 +21,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private readonly Size blockAreaSize; + /// + /// Jpeg frame instance containing required decoding metadata. + /// + private readonly JpegFrame frame; + /// /// Initializes a new instance of the class. /// - public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) { + this.frame = frame; + this.Component = component; this.RawJpeg = rawJpeg; this.blockAreaSize = this.Component.SubSamplingDivisors * 8; @@ -70,7 +77,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Buffer2D spectralBuffer = this.Component.SpectralBlocks; var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); - float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1; + + // TODO: this is a constant value for ALL components + float maximumValue = MathF.Pow(2, this.frame.Precision) - 1; int destAreaStride = this.ColorBuffer.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 9f3d4195c..50cfa0188 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { - this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, jpegData, postProcessorBufferSize, frame.Components[i]); + this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); } // single 'stride' rgba32 buffer for conversion between spectral and TPixel diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 97a6d3999..3c262be32 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The only supported precision /// - private readonly int[] supportedPrecisions = { 8, 12 }; + private readonly byte[] supportedPrecisions = { 8, 12 }; /// /// The buffer used to temporarily store bytes read from the stream. @@ -148,9 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegColorSpace ColorSpace { get; private set; } - /// - public int Precision { get; private set; } - /// /// Gets the components. /// @@ -825,23 +822,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); } - // Read initial marker definitions. + // Read initial marker definitions const int length = 6; stream.Read(this.temp, 0, length); - // We only support 8-bit and 12-bit precision. - if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) + // 1 byte: Bits/sample precision + byte precision = this.temp[0]; + + // Validity check: only 8-bit and 12-bit precisions are supported + if (Array.IndexOf(this.supportedPrecisions, precision) == -1) { JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); } - this.Precision = this.temp[0]; - this.Frame = new JpegFrame { Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, - Precision = this.temp[0], + Precision = precision, PixelHeight = (this.temp[1] << 8) | this.temp[2], PixelWidth = (this.temp[3] << 8) | this.temp[4], ComponentCount = this.temp[5] From 04eef159b3a4a52b99adf935550ee6942cce9e85 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 02:07:32 +0300 Subject: [PATCH 101/119] Added frame dimensions proper check & comments --- .../Formats/Jpeg/JpegDecoderCore.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 3c262be32..45d08dadf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -835,21 +835,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); } + // 2 byte: height + int frameHeight = (this.temp[1] << 8) | this.temp[2]; + + // 2 byte: width + int frameWidth = (this.temp[3] << 8) | this.temp[4]; + + // Validity check: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that) + if (frameHeight == 0 || frameWidth == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight); + } + + this.Frame = new JpegFrame { Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, Precision = precision, - PixelHeight = (this.temp[1] << 8) | this.temp[2], - PixelWidth = (this.temp[3] << 8) | this.temp[4], + PixelHeight = frameHeight, + PixelWidth = frameWidth, ComponentCount = this.temp[5] }; - if (this.Frame.PixelWidth == 0 || this.Frame.PixelHeight == 0) - { - JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight); - } - this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); this.ComponentCount = this.Frame.ComponentCount; From 24b1ca64d195c801b3acbeb73302c37d2cb2ce87 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 02:22:04 +0300 Subject: [PATCH 102/119] Added component count proper check, comments & decoupled it from actual decoding --- .../Jpeg/Components/Decoder/IRawJpegData.cs | 5 --- .../Jpeg/Components/Decoder/JpegFrame.cs | 5 +++ .../Formats/Jpeg/JpegDecoderCore.cs | 43 ++++++++----------- .../Formats/Jpg/ParseStreamTests.cs | 4 +- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index b715eef98..948f4dc8c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -16,11 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Size ImageSizeInPixels { get; } - /// - /// Gets the number of components. - /// - int ComponentCount { get; } - /// /// Gets the color space /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 3a136b410..a1242a43a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -84,6 +84,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public int McusPerColumn { get; set; } + /// + /// Gets the color depth, in number of bits per pixel. + /// + public int BitsPerPixel => this.ComponentCount * this.Precision; + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 45d08dadf..0fd4b2f0f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -127,11 +127,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public int ImageHeight => this.ImageSizeInPixels.Height; - /// - /// Gets the color depth, in number of bits per pixel. - /// - public int BitsPerPixel => this.ComponentCount * this.Frame.Precision; - /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// @@ -142,9 +137,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public ImageMetadata Metadata { get; private set; } - /// - public int ComponentCount { get; private set; } - /// public JpegColorSpace ColorSpace { get; private set; } @@ -222,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); + return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); } /// @@ -373,14 +365,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Returns the correct colorspace based on the image component count /// /// The - private JpegColorSpace DeduceJpegColorSpace() + private JpegColorSpace DeduceJpegColorSpace(byte componentCount) { - if (this.ComponentCount == 1) + if (componentCount == 1) { return JpegColorSpace.Grayscale; } - if (this.ComponentCount == 3) + if (componentCount == 3) { if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) { @@ -392,14 +384,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return JpegColorSpace.YCbCr; } - if (this.ComponentCount == 4) + if (componentCount == 4) { return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck ? JpegColorSpace.Ycck : JpegColorSpace.Cmyk; } - JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {componentCount}"); return default; } @@ -835,18 +827,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); } - // 2 byte: height + // 2 byte: Height int frameHeight = (this.temp[1] << 8) | this.temp[2]; - // 2 byte: width + // 2 byte: Width int frameWidth = (this.temp[3] << 8) | this.temp[4]; // Validity check: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that) if (frameHeight == 0 || frameWidth == 0) { - JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight); + JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); } + // 1 byte: Number of components + byte componentCount = this.temp[5]; + this.ColorSpace = this.DeduceJpegColorSpace(componentCount); this.Frame = new JpegFrame { @@ -855,13 +850,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Precision = precision, PixelHeight = frameHeight, PixelWidth = frameWidth, - ComponentCount = this.temp[5] + ComponentCount = componentCount }; this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); - this.ComponentCount = this.Frame.ComponentCount; - this.ColorSpace = this.DeduceJpegColorSpace(); this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; if (!metadataOnly) @@ -869,7 +862,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg remaining -= length; const int componentBytes = 3; - if (remaining > this.ComponentCount * componentBytes) + if (remaining > componentCount * componentBytes) { JpegThrowHelper.ThrowBadMarker("SOFn", remaining); } @@ -877,14 +870,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg stream.Read(this.temp, 0, remaining); // No need to pool this. They max out at 4 - this.Frame.ComponentIds = new byte[this.ComponentCount]; - this.Frame.ComponentOrder = new byte[this.ComponentCount]; - this.Frame.Components = new JpegComponent[this.ComponentCount]; + this.Frame.ComponentIds = new byte[componentCount]; + this.Frame.ComponentOrder = new byte[componentCount]; + this.Frame.Components = new JpegComponent[componentCount]; int maxH = 0; int maxV = 0; int index = 0; - for (int i = 0; i < this.ComponentCount; i++) + for (int i = 0; i < componentCount; i++) { byte hv = this.temp[index + 1]; int h = (hv >> 4) & 15; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index a124ec191..2162ee13c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) { - Assert.Equal(1, decoder.ComponentCount); + Assert.Equal(1, decoder.Frame.ComponentCount); Assert.Equal(1, decoder.Components.Length); Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { - Assert.Equal(componentCount, decoder.ComponentCount); + Assert.Equal(componentCount, decoder.Frame.ComponentCount); Assert.Equal(componentCount, decoder.Components.Length); JpegComponent c0 = decoder.Components[0]; From 6b214ca1291153099fafd8221b305cd6107ead6b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 12:32:01 +0300 Subject: [PATCH 103/119] Removed core obsolete properties, decoupled frame metadata from the decoder --- .../Jpeg/Components/Decoder/IRawJpegData.cs | 5 ----- .../Jpeg/Components/Decoder/JpegFrame.cs | 5 +++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 17 +++-------------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 948f4dc8c..391dac784 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal interface IRawJpegData : IDisposable { - /// - /// Gets the image size in pixels. - /// - Size ImageSizeInPixels { get; } - /// /// Gets the color space /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index a1242a43a..4baaab386 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -43,6 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public int PixelWidth { get; set; } + /// + /// Gets the pixel size of the image. + /// + public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight); + /// /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 0fd4b2f0f..04495f172 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -110,23 +110,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Size ImageSizeInPixels { get; private set; } /// - Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels; + Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize; /// /// Gets the number of MCU blocks in the image as . /// public Size ImageSizeInMCU { get; private set; } - /// - /// Gets the image width - /// - public int ImageWidth => this.ImageSizeInPixels.Width; - - /// - /// Gets the image height - /// - public int ImageHeight => this.ImageSizeInPixels.Height; - /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// @@ -214,7 +204,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); + Size pixelSize = this.Frame.PixelSize; + return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); } /// @@ -853,8 +844,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ComponentCount = componentCount }; - this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); - this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; if (!metadataOnly) From 7077473d71bff5029841bd51245d11c085e3900e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 12:57:12 +0300 Subject: [PATCH 104/119] Decoupled mcu size from the decoder, fixed SOF component bytes length check --- .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 5 +++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 14 +++----------- .../Formats/Jpg/ParseStreamTests.cs | 6 +++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 4baaab386..b8f88cfe0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -89,6 +89,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public int McusPerColumn { get; set; } + /// + /// Gets the mcu size of the image. + /// + public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn); + /// /// Gets the color depth, in number of bits per pixel. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 04495f172..3c48aabee 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -112,11 +112,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize; - /// - /// Gets the number of MCU blocks in the image as . - /// - public Size ImageSizeInMCU { get; private set; } - /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// @@ -834,6 +829,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg byte componentCount = this.temp[5]; this.ColorSpace = this.DeduceJpegColorSpace(componentCount); + this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; + this.Frame = new JpegFrame { Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, @@ -844,14 +841,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ComponentCount = componentCount }; - this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; - if (!metadataOnly) { remaining -= length; const int componentBytes = 3; - if (remaining > componentCount * componentBytes) + if (remaining != componentCount * componentBytes) { JpegThrowHelper.ThrowBadMarker("SOFn", remaining); } @@ -894,9 +889,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Frame.MaxVerticalFactor = maxV; this.Frame.InitComponents(); - this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); - - // This can be injected in SOF marker callback this.scanDecoder.InjectFrameData(this.Frame, this); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index 2162ee13c..e1307d3fc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); - Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU); + Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize); var uniform1 = new Size(1, 1); JpegComponent c0 = decoder.Components[0]; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { sb.AppendLine(imageFile); - sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}"); + sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.Frame.McuSize}"); JpegComponent c0 = decoder.Components[0]; JpegComponent c1 = decoder.Components[1]; @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var uniform1 = new Size(1, 1); - Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma); + Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma); Size divisor = fLuma.DivideBy(fChroma); From 4d599d14f63d15f06838f530d067a7558b1d734a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 13:32:38 +0300 Subject: [PATCH 105/119] Comments, docs, decoupling, removed redundant properties --- .../Jpeg/Components/Decoder/JpegComponent.cs | 11 +++++++--- .../Jpeg/Components/Decoder/JpegFrame.cs | 20 ++++++------------- .../Formats/Jpeg/JpegDecoderCore.cs | 9 +++------ .../Formats/Jpg/ParseStreamTests.cs | 4 ++-- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 614e96e54..33b5ef77a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -106,13 +106,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.SpectralBlocks = null; } - public void Init() + /// + /// Initializes component for future buffers initialization. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(int maxSubFactorH, int maxSubFactorV) { this.WidthInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); + MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); this.HeightInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); + MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index b8f88cfe0..01863b7a8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -69,16 +69,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public JpegComponent[] Components { get; set; } - /// - /// Gets or sets the maximum horizontal sampling factor. - /// - public int MaxHorizontalFactor { get; set; } - - /// - /// Gets or sets the maximum vertical sampling factor. - /// - public int MaxVerticalFactor { get; set; } - /// /// Gets or sets the number of MCU's per line. /// @@ -116,15 +106,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Allocates the frame component blocks. /// - public void InitComponents() + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(int maxSubFactorH, int maxSubFactorV) { - this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8); + this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); for (int i = 0; i < this.ComponentCount; i++) { JpegComponent component = this.Components[i]; - component.Init(); + component.Init(maxSubFactorH, maxSubFactorV); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 3c48aabee..406458fe3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -106,9 +106,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegFrame Frame { get; private set; } - /// - public Size ImageSizeInPixels { get; private set; } - /// Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize; @@ -845,12 +842,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { remaining -= length; + // Validity check: remaining part must be equal to components * 3 const int componentBytes = 3; if (remaining != componentCount * componentBytes) { JpegThrowHelper.ThrowBadMarker("SOFn", remaining); } + // components*3 bytes: component data stream.Read(this.temp, 0, remaining); // No need to pool this. They max out at 4 @@ -885,9 +884,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg index += componentBytes; } - this.Frame.MaxHorizontalFactor = maxH; - this.Frame.MaxVerticalFactor = maxV; - this.Frame.InitComponents(); + this.Frame.Init(maxH, maxV); this.scanDecoder.InjectFrameData(this.Frame, this); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index e1307d3fc..0a4d85344 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(1, decoder.Frame.ComponentCount); Assert.Equal(1, decoder.Components.Length); - Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); + Size expectedSizeInBlocks = decoder.Frame.PixelSize.DivideRoundUp(8); Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize); @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { sb.AppendLine(imageFile); - sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.Frame.McuSize}"); + sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}"); JpegComponent c0 = decoder.Components[0]; JpegComponent c1 = decoder.Components[1]; From c751b27334b579a16482b2d5634c15390f33b776 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 14:34:45 +0300 Subject: [PATCH 106/119] Removed first component dependency in compoentn initialization code --- .../Formats/Jpeg/Components/Decoder/JpegComponent.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 33b5ef77a..ba3dfb629 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -123,8 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - JpegComponent c0 = this.Frame.Components[0]; - this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); + this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) { From 0e4e9501e59f371b2d590173fd1709eaaa641730 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 15:11:10 +0300 Subject: [PATCH 107/119] Introduced JpegFrame ctor, closed some setters --- .../Jpeg/Components/Decoder/JpegFrame.cs | 43 +++++++++++++------ .../Formats/Jpeg/JpegDecoderCore.cs | 10 +---- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 01863b7a8..0e842de9d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -10,15 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal sealed class JpegFrame : IDisposable { + public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) + { + this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1; + this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2; + + this.Precision = precision; + this.MaxColorChannelValue = MathF.Pow(2, precision) - 1; + + this.PixelWidth = width; + this.PixelHeight = height; + + this.ComponentCount = componentCount; + } + /// - /// Gets or sets a value indicating whether the frame uses the extended specification. + /// Gets a value indicating whether the frame uses the extended specification. /// - public bool Extended { get; set; } + public bool Extended { get; private set; } /// - /// Gets or sets a value indicating whether the frame uses the progressive specification. + /// Gets a value indicating whether the frame uses the progressive specification. /// - public bool Progressive { get; set; } + public bool Progressive { get; private set; } /// /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). @@ -29,19 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public bool MultiScan { get; set; } /// - /// Gets or sets the precision. + /// Gets the precision. + /// + public byte Precision { get; private set; } + + /// + /// Gets the maximum color value derived from . /// - public byte Precision { get; set; } + public float MaxColorChannelValue { get; private set; } /// - /// Gets or sets the number of scanlines within the frame. + /// Gets the number of pixel per row. /// - public int PixelHeight { get; set; } + public int PixelHeight { get; private set; } /// - /// Gets or sets the number of samples per scanline. + /// Gets the number of pixels per line. /// - public int PixelWidth { get; set; } + public int PixelWidth { get; private set; } /// /// Gets the pixel size of the image. @@ -49,9 +68,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight); /// - /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. + /// Gets the number of components within a frame. In progressive frames this value can range from only 1 to 4. /// - public byte ComponentCount { get; set; } + public byte ComponentCount { get; private set; } /// /// Gets or sets the component id collection. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 406458fe3..86871fc00 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -828,15 +828,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; - this.Frame = new JpegFrame - { - Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, - Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, - Precision = precision, - PixelHeight = frameHeight, - PixelWidth = frameWidth, - ComponentCount = componentCount - }; + this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); if (!metadataOnly) { From 36a1ea6456c7506fa0041e8badae00aff7f595d9 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 15:14:24 +0300 Subject: [PATCH 108/119] Color channel max value is now cached per jpeg frame --- .../Jpeg/Components/Decoder/JpegComponentPostProcessor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 31214b4c1..9a659d621 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -78,8 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); - // TODO: this is a constant value for ALL components - float maximumValue = MathF.Pow(2, this.frame.Precision) - 1; + float maximumValue = this.frame.MaxColorChannelValue; int destAreaStride = this.ColorBuffer.Width; From 00c1a2138015d323dec2ab21066d72079f87a617 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 15:38:55 +0300 Subject: [PATCH 109/119] Fixed fuzzed issue related to selectorsCount, added appropriate checks --- .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 0e842de9d..fc109be26 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight); /// - /// Gets the number of components within a frame. In progressive frames this value can range from only 1 to 4. + /// Gets the number of components within a frame. /// public byte ComponentCount { get; private set; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 86871fc00..00ea05ba5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -804,7 +804,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 1 byte: Bits/sample precision byte precision = this.temp[0]; - // Validity check: only 8-bit and 12-bit precisions are supported + // Validate: only 8-bit and 12-bit precisions are supported if (Array.IndexOf(this.supportedPrecisions, precision) == -1) { JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); @@ -816,7 +816,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 2 byte: Width int frameWidth = (this.temp[3] << 8) | this.temp[4]; - // Validity check: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that) + // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that) if (frameHeight == 0 || frameWidth == 0) { JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); @@ -834,7 +834,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { remaining -= length; - // Validity check: remaining part must be equal to components * 3 + // Validate: remaining part must be equal to components * 3 const int componentBytes = 3; if (remaining != componentCount * componentBytes) { @@ -978,7 +978,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); } + // 1 byte: Number of components in scan int selectorsCount = stream.ReadByte(); + + // Validate: 0 < count <= totalComponents + if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [0 < count <= {this.Frame.ComponentCount}]"); + } + this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; for (int i = 0; i < selectorsCount; i++) { From 245b7868403564f3d2cf3b498a8eeec73f28f5bc Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 15:45:14 +0300 Subject: [PATCH 110/119] Small refactoring, added progressive data comments --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 00ea05ba5..4e4d6ec52 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -984,7 +984,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Validate: 0 < count <= totalComponents if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) { - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [0 < count <= {this.Frame.ComponentCount}]"); + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [1 <= count <= {this.Frame.ComponentCount}]"); } this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; @@ -1008,22 +1008,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}."); } - ref JpegComponent component = ref this.Frame.Components[componentIndex]; + this.Frame.ComponentOrder[i] = (byte)componentIndex; + int tableSpec = stream.ReadByte(); + ref JpegComponent component = ref this.Frame.Components[componentIndex]; component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; - this.Frame.ComponentOrder[i] = (byte)componentIndex; } + // 3 bytes: Progressive scan decoding data stream.Read(this.temp, 0, 3); int spectralStart = this.temp[0]; - int spectralEnd = this.temp[1]; - int successiveApproximation = this.temp[2]; - - // This is okay to inject here, might be good to wrap it in a separate struct but not really necessary this.scanDecoder.SpectralStart = spectralStart; + + int spectralEnd = this.temp[1]; this.scanDecoder.SpectralEnd = spectralEnd; + + int successiveApproximation = this.temp[2]; this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; this.scanDecoder.SuccessiveLow = successiveApproximation & 15; From 5596ce1830057a579d7a8903a47b5be2dbe51f42 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 15:55:58 +0300 Subject: [PATCH 111/119] Refactored componentIndex validation --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4e4d6ec52..35f88e495 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -984,28 +984,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Validate: 0 < count <= totalComponents if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) { - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [1 <= count <= {this.Frame.ComponentCount}]"); + // TODO: extract as separate method? + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [1 <= count <= {this.Frame.ComponentCount}]."); } this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; for (int i = 0; i < selectorsCount; i++) { - int componentIndex = -1; - int selector = stream.ReadByte(); + // 1 byte: Component id + int componentSelectorId = stream.ReadByte(); + int componentIndex = -1; for (int j = 0; j < this.Frame.ComponentIds.Length; j++) { byte id = this.Frame.ComponentIds[j]; - if (selector == id) + if (componentSelectorId == id) { componentIndex = j; break; } } - if (componentIndex < 0) + // Validate: must be found among registered components + if (componentIndex == -1) { - JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}."); + // TODO: extract as separate method? + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid component id in scan: {componentSelectorId}. Must be [0 <= id <= {this.Frame.ComponentCount - 1}]"); } this.Frame.ComponentOrder[i] = (byte)componentIndex; From 95bec1cffbe0fad53135f7fbee734b29d6a47b53 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 16:41:22 +0300 Subject: [PATCH 112/119] Added comments, validated huffman table indices --- .../Formats/Jpeg/JpegDecoderCore.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 35f88e495..8dc0bb501 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -985,7 +985,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) { // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}. Must be [1 <= count <= {this.Frame.ComponentCount}]."); + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); } this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; @@ -1009,15 +1009,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (componentIndex == -1) { // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid component id in scan: {componentSelectorId}. Must be [0 <= id <= {this.Frame.ComponentCount - 1}]"); + JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}."); } this.Frame.ComponentOrder[i] = (byte)componentIndex; + JpegComponent component = this.Frame.Components[componentIndex]; + + // 1 byte: Huffman table selectors. + // 4 bits - dc + // 4 bits - ac int tableSpec = stream.ReadByte(); - ref JpegComponent component = ref this.Frame.Components[componentIndex]; - component.DCHuffmanTableId = tableSpec >> 4; - component.ACHuffmanTableId = tableSpec & 15; + int dcTableIndex = tableSpec >> 4; + int acTableIndex = tableSpec & 15; + + // Validate: both must be < 4 + if (dcTableIndex >= 4 || acTableIndex >= 4) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); + } + + component.DCHuffmanTableId = dcTableIndex; + component.ACHuffmanTableId = acTableIndex; } // 3 bytes: Progressive scan decoding data From 0ace1a042a5193eed12137962e6c55f94c953d30 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 16:52:36 +0300 Subject: [PATCH 113/119] Added issue-1693 images & tests cases - all passing after fix --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs | 4 +++- tests/ImageSharp.Tests/TestImages.cs | 2 ++ .../Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg | 3 +++ .../Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg create mode 100644 tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 304dd93a6..d12240cba 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -87,7 +87,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, - TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839 + TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B }; private static readonly Dictionary CustomToleranceValues = diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6d2f65f57..fac8cb4a3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -261,6 +261,8 @@ namespace SixLabors.ImageSharp.Tests public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; + public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; + public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; } } diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg new file mode 100644 index 000000000..eb8fb9010 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbb6acd612cdb09825493d04ec7c6aba8ef2a94cc9a86c6b16218720adfb8f5c +size 58065 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg new file mode 100644 index 000000000..7dd428591 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8720a9ccf118c3f55407aa250ee490d583286c7e40c8c62a6f8ca449ca3ddff3 +size 58067 From a92161f73a0146f9e8fd5f5289000433694b0de6 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 16:56:15 +0300 Subject: [PATCH 114/119] Fixed some warnings --- .../Formats/Jpeg/JpegDecoderCore.cs | 22 +++++++++---------- .../Formats/Jpg/JpegDecoderTests.cs | 7 ++---- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 8dc0bb501..80155dcb2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - var profile = new byte[remaining]; + byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) @@ -547,14 +547,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return; } - var identifier = new byte[Icclength]; + byte[] identifier = new byte[Icclength]; stream.Read(identifier, 0, Icclength); remaining -= Icclength; // We have read it by this point if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) { this.isIcc = true; - var profile = new byte[remaining]; + byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); if (this.iccData is null) @@ -592,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) { - var resourceBlockData = new byte[remaining]; + byte[] resourceBlockData = new byte[remaining]; stream.Read(resourceBlockData, 0, remaining); Span blockDataSpan = resourceBlockData.AsSpan(); @@ -607,8 +607,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Span imageResourceBlockId = blockDataSpan.Slice(0, 2); if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) { - var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int dataStartIdx = 2 + resourceBlockNameLength + 4; if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) { @@ -619,8 +619,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int dataStartIdx = 2 + resourceBlockNameLength + 4; if (blockDataSpan.Length < dataStartIdx + resourceDataSize) { @@ -643,7 +643,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private static int ReadImageResourceNameLength(Span blockDataSpan) { byte nameLength = blockDataSpan[2]; - var nameDataSize = nameLength == 0 ? 2 : nameLength; + int nameDataSize = nameLength == 0 ? 2 : nameLength; if (nameDataSize % 2 != 0) { nameDataSize++; @@ -660,9 +660,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The block length. [MethodImpl(InliningOptions.ShortMethod)] private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength) - { - return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); - } + => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); /// /// Processes the application header containing the Adobe identifier diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index a052ee88a..674aa6d8f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -62,10 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); } - public JpegDecoderTests(ITestOutputHelper output) - { - this.Output = output; - } + public JpegDecoderTests(ITestOutputHelper output) => this.Output = output; private ITestOutputHelper Output { get; } @@ -163,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var cts = new CancellationTokenSource(); - var file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); pausedStream.OnWaiting(s => { From 7c8261a51bacaf00887529bc4fb830963b166db2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 17:17:16 +0300 Subject: [PATCH 115/119] Reduced number of stream reads in SOS marker, added check for remaining bytes --- .../Formats/Jpeg/JpegDecoderCore.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 80155dcb2..bafd8e215 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -250,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(stream, cancellationToken); + this.ProcessStartOfScanMarker(stream, remaining, cancellationToken); break; } else @@ -969,7 +969,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Processes the SOS (Start of scan marker). /// - private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken) + private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining, CancellationToken cancellationToken) { if (this.Frame is null) { @@ -986,11 +986,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); } + // Validate: marker must contain exactly (4 + selectorsCount*2) bytes + int selectorsBytes = selectorsCount * 2; + if (remaining != 4 + selectorsBytes) + { + JpegThrowHelper.ThrowBadMarker("SOS", remaining); + } + + // selectorsCount*2 bytes: component index + huffman tables indices + stream.Read(this.temp, 0, selectorsBytes); + this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; - for (int i = 0; i < selectorsCount; i++) + for (int i = 0; i < selectorsBytes; i += 2) { // 1 byte: Component id - int componentSelectorId = stream.ReadByte(); + int componentSelectorId = this.temp[i]; int componentIndex = -1; for (int j = 0; j < this.Frame.ComponentIds.Length; j++) @@ -1010,14 +1020,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}."); } - this.Frame.ComponentOrder[i] = (byte)componentIndex; + this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; JpegComponent component = this.Frame.Components[componentIndex]; // 1 byte: Huffman table selectors. // 4 bits - dc // 4 bits - ac - int tableSpec = stream.ReadByte(); + int tableSpec = this.temp[i + 1]; int dcTableIndex = tableSpec >> 4; int acTableIndex = tableSpec & 15; From db04e41b88ba9c5ab6770c85ca4baf6363c38f9a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 17:21:13 +0300 Subject: [PATCH 116/119] Added comments to major properties --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index ea76df7a8..70a446512 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -18,11 +18,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { private readonly BufferedReadStream stream; - // Frame related + /// + /// instance containing decoding-related information. + /// private JpegFrame frame; + + /// + /// Shortcut for .Components. + /// private JpegComponent[] components; - // The number of interleaved components. + /// + /// Number of component in the current scan. + /// private int componentsCount; /// From 97500145b71a3cd2c302701c7f9c4ba20c24aac1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 16 Jul 2021 17:34:59 +0300 Subject: [PATCH 117/119] Added todo --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index bafd8e215..77b1b44af 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1034,6 +1034,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Validate: both must be < 4 if (dcTableIndex >= 4 || acTableIndex >= 4) { + // TODO: extract as separate method? JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); } From e3e2785b6614ac6b6e7798fbe32a989249a17a82 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 18 Jul 2021 20:43:24 -0400 Subject: [PATCH 118/119] Fix a few uses of DeflateStream.Read The code is assuming it'll always return the requested amount unless it hits EOF, but that's not guaranteed. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 24 ++++++++++++------- .../Decompressors/DeflateTiffCompression.cs | 13 +++++++++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index de70a9dff..987dc150c 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -506,11 +506,15 @@ namespace SixLabors.ImageSharp.Formats.Png while (this.currentRow < this.header.Height) { Span scanlineSpan = this.scanline.GetSpan(); - int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); - this.currentRowBytesRead += bytesRead; - if (this.currentRowBytesRead < this.bytesPerScanline) + while (this.currentRowBytesRead < this.bytesPerScanline) { - return; + int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + if (bytesRead <= 0) + { + return; + } + + this.currentRowBytesRead += bytesRead; } this.currentRowBytesRead = 0; @@ -577,11 +581,15 @@ namespace SixLabors.ImageSharp.Formats.Png while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); - this.currentRowBytesRead += bytesRead; - if (this.currentRowBytesRead < bytesPerInterlaceScanline) + while (this.currentRowBytesRead < bytesPerInterlaceScanline) { - return; + int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + if (bytesRead <= 0) + { + return; + } + + this.currentRowBytesRead += bytesRead; } this.currentRowBytesRead = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 67af4ff6c..2188913bc 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -46,7 +46,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { deframeStream.AllocateNewBytes(byteCount, true); DeflateStream dataStream = deframeStream.CompressedStream; - dataStream.Read(buffer, 0, buffer.Length); + + int totalRead = 0; + while (totalRead < buffer.Length) + { + int bytesRead = dataStream.Read(buffer, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) + { + break; + } + + totalRead += bytesRead; + } } if (this.Predictor == TiffPredictor.Horizontal) From a7d44a435c2424c4d67883fec124ece547c1f892 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 30 Jul 2021 16:27:53 +0200 Subject: [PATCH 119/119] Use same byte order as IFD directory to decode pixels for 16 bit per channel data, fixes #1716 --- .../Rgb161616TiffColor{TPixel}.cs | 58 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 4 +- .../Formats/Tiff/TiffDecoderCore.cs | 8 ++- .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/Issues/Issue1716.tiff | 3 + 6 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/Issues/Issue1716.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs new file mode 100644 index 000000000..635be95f4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with 16 bits for each channel. + /// + internal class Rgb161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb161616TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var rgba = default(Rgba64); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + + for (int x = left; x < left + width; x++) + { + ulong r = this.ConvertToShort(data.Slice(offset, 2)); + offset += 2; + ulong g = this.ConvertToShort(data.Slice(offset, 2)); + offset += 2; + ulong b = this.ConvertToShort(data.Slice(offset, 2)); + offset += 2; + + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + + pixelRow[x] = color; + } + } + } + + private ushort ConvertToShort(ReadOnlySpan buffer) => this.isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 36d2ab746..8e711d3eb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal static class TiffColorDecoderFactory where TPixel : unmanaged, IPixel { - public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) + public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) { switch (colorType) { @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); + return new Rgb161616TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 3d5bfc737..484e182c5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -36,6 +36,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// private BufferedReadStream inputStream; + /// + /// Indicates the byte order of the stream. + /// + private ByteOrder byteOrder; + /// /// Initializes a new instance of the class. /// @@ -109,6 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff var reader = new DirectoryReader(stream); IEnumerable directories = reader.Read(); + this.byteOrder = reader.ByteOrder; var frames = new List>(); foreach (ExifProfile ifd in directories) @@ -310,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.Predictor, this.FaxCompressionOptions); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap); + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index a007cd3a9..ab53ca156 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -164,6 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fac8cb4a3..c54c82d7d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -582,6 +582,7 @@ namespace SixLabors.ImageSharp.Tests public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; diff --git a/tests/Images/Input/Tiff/Issues/Issue1716.tiff b/tests/Images/Input/Tiff/Issues/Issue1716.tiff new file mode 100644 index 000000000..b7b1fe556 --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue1716.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c734dd489c65fb77bd7a35cd663aa16ce986df2c2ab8c7ca43d8b65db9d47c03 +size 6666162