From 960145b8dfe419fe96fefba19f699e3bf06bc07b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 03:20:36 +0300 Subject: [PATCH 01/22] Added comments Added comments to the huffman spec --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 4 ++++ .../Formats/Jpeg/Components/Encoder/HuffmanSpec.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 860a9c3236..10fdfeea75 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -306,6 +306,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { byte b = (byte)(bits >> 24); this.emitBuffer[this.emitLen++] = b; + + // Adding stuff byte + // This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte) + // Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker if (b == byte.MaxValue) { this.emitBuffer[this.emitLen++] = byte.MinValue; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index f9c16c5be7..1f9899562b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + + // Luminance AC. new HuffmanSpec( new byte[] { @@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }), + + // Chrominance DC. new HuffmanSpec( new byte[] { @@ -132,4 +136,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Values = values; } } -} \ No newline at end of file +} From 10b4a59c7c0c147f43b1b22ebf980d8e191f2d13 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 18:16:40 +0300 Subject: [PATCH 02/22] Added debug methods Fixed System namespace usage --- .../Components/Encoder/HuffmanScanEncoder.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 10fdfeea75..9650a3db46 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using SixLabors.ImageSharp.Memory; @@ -427,5 +428,53 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return Numerics.Log2(value << 1); #endif } + + // !!! DO NOT DELETE THIS !!! until custom huffman tables are supported - this is very handy for debugging + /// + /// Prints given huffman codes to the System.Console for debugging puroses. + /// + /// Codes array. + /// Custom title to print. + /// Flag indicating if all codes should be printed. + /// Indicates which number base will be used to print codes + private static void PrintHuffmanSummary(int[] codes, string title, bool printCode, int codePrintBase = 2) + { + System.Console.WriteLine(title); + System.Console.WriteLine($"Codes count: {codes.Length}"); + + // This is possible if custom tree is provided, especially for per-image optimized tree + if (codes.Length == 0) + { + return; + } + + // Min + int min = codes.Min(); + int min_len = min >> 24; + string min_code = System.Convert.ToString(min & ((1 << 24) - 1), codePrintBase); + + // Max + int max = codes.Max(); + int max_len = max >> 24; + string max_code = System.Convert.ToString(max & ((1 << 24) - 1), codePrintBase); + + System.Console.WriteLine($"Min code: {min_code}, len: {min_len} \nMax code: {max_code}, len: {max_len}"); + + // Printing codes + if (printCode) + { + PrintHuffmanCodes(codes, codePrintBase); + } + } + + private static void PrintHuffmanCodes(int[] codes, int codePrintBase) + { + for (int i = 0; i < codes.Length; i++) + { + int huffCode = codes[i]; + string code = System.Convert.ToString(huffCode & ((1 << 24) - 1), codePrintBase); + System.Console.WriteLine($"\t{code}"); + } + } } } From 05f222e7047d55d97fdddf0509491af7bbd0d84e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 21:50:14 +0300 Subject: [PATCH 03/22] Huffman tables are now injected rather than taken from some static variable --- .../Components/Encoder/HuffmanScanEncoder.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 9650a3db46..36f97a23df 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class HuffmanScanEncoder { + private HuffmanLut[] huffmanTables; + /// /// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count). /// @@ -65,6 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -123,6 +127,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -188,6 +194,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -247,10 +255,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int dc = (int)refTemp2[0]; // Emit the DC delta. - this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC); + this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); // Emit the AC components. - int h = (2 * (int)index) + 1; + int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; + int runLength = 0; for (int zig = 1; zig < Block8x8F.Size; zig++) @@ -265,18 +274,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { while (runLength > 15) { - this.EmitHuff(h, 0xf0); + this.EmitHuff(huffmanTable, 0xf0); runLength -= 16; } - this.EmitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(huffmanTable, runLength, ac); runLength = 0; } } if (runLength > 0) { - this.EmitHuff(h, 0x00); + this.EmitHuff(huffmanTable, 0x00); } return dc; @@ -339,23 +348,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Emits the given value with the given Huffman encoder. /// - /// The index of the Huffman encoder + /// Compiled Huffman spec values. /// The value to encode. [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuff(int index, int value) + private void EmitHuff(int[] table, int value) { - int x = HuffmanLut.TheHuffmanLut[index].Values[value]; + int x = table[value]; this.Emit(x & ((1 << 24) - 1), x >> 24); } /// /// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// - /// The index of the Huffman encoder + /// Compiled Huffman spec values. /// The number of copies to encode. /// The value to encode. [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuffRLE(int index, int runLength, int value) + private void EmitHuffRLE(int[] table, int runLength, int value) { int a = value; int b = value; @@ -367,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int bt = GetHuffmanEncodingLength((uint)a); - this.EmitHuff(index, (runLength << 4) | bt); + this.EmitHuff(table, (runLength << 4) | bt); if (bt > 0) { this.Emit(b & ((1 << bt) - 1), bt); From 43b8d4157dc22ea0a0c15c9d276514435043a788 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 19 Jun 2021 04:52:13 +1000 Subject: [PATCH 04/22] Allow clearing and reusing a pixel map. --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 23 +++--- .../Quantization/EuclideanPixelMap{TPixel}.cs | 30 +++++++- .../Quantization/OctreeQuantizer{TPixel}.cs | 21 ++--- .../Quantization/PaletteQuantizer.cs | 4 +- .../Quantization/PaletteQuantizer{TPixel}.cs | 23 ++---- .../Quantization/WuQuantizer{TPixel}.cs | 76 +++++++++---------- 6 files changed, 89 insertions(+), 88 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 585f87b3e8..4c881ec3f2 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -7,7 +7,6 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel sampling strategy for global quantization. /// - private IPixelSamplingStrategy pixelSamplingStrategy; + private readonly IPixelSamplingStrategy pixelSamplingStrategy; /// /// Initializes a new instance of the class. @@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif // The palette quantizer can reuse the same pixel map across multiple frames // since the palette is unchanging. This allows a reduction of memory usage across // multi frame gifs using a global palette. - EuclideanPixelMap pixelMap = default; - bool pixelMapHasValue = false; + PaletteQuantizer paletteFrameQuantizer = default; + bool quantizerInitialized = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; @@ -166,22 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - if (!pixelMapHasValue) + if (!quantizerInitialized) { - pixelMapHasValue = true; - pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); + quantizerInitialized = true; + paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); } - using var paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, pixelMap, true); using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } } - if (pixelMapHasValue) - { - pixelMap.Dispose(); - } + paletteFrameQuantizer.Dispose(); } private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) @@ -310,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - ratio = (byte)(((1 / vr) * 64) - 15); + ratio = (byte)((1 / vr * 64) - 15); } } } @@ -354,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - for (var i = 0; i < metadata.Comments.Count; i++) + for (int i = 0; i < metadata.Comments.Count; i++) { string comment = metadata.Comments[i]; this.buffer[0] = GifConstants.ExtensionIntroducer; diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 0311c40be4..947bd70dce 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -18,20 +18,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// This class is not threadsafe and should not be accessed in parallel. /// Doing so will result in non-idempotent results. /// - internal readonly struct EuclideanPixelMap : IDisposable + internal sealed class EuclideanPixelMap : IDisposable where TPixel : unmanaged, IPixel { - private readonly Rgba32[] rgbaPalette; + private Rgba32[] rgbaPalette; private readonly ColorDistanceCache cache; + private readonly Configuration configuration; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the class. /// /// The configuration. /// The color palette to map from. - [MethodImpl(InliningOptions.ShortMethod)] public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { + this.configuration = configuration; this.Palette = palette; this.rgbaPalette = new Rgba32[palette.Length]; this.cache = new ColorDistanceCache(configuration.MemoryAllocator); @@ -46,6 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { [MethodImpl(InliningOptions.ShortMethod)] get; + + [MethodImpl(InliningOptions.ShortMethod)] + private set; } /// @@ -72,6 +76,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return index; } + /// + /// Clears the map, resetting it to use the given palette. + /// + /// The color palette to map from. + public void Clear(ReadOnlyMemory palette) + { + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); + this.cache.Clear(); + } + [MethodImpl(InliningOptions.ShortMethod)] private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { @@ -177,6 +193,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return match > -1; } + /// + /// Clears the cahe resetting each entry to empty. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Clear() => this.table.GetSpan().Fill(-1); + [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) => (r << ((IndexBits << 1) + IndexAlphaBits)) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 10b26337f4..311a8aa2e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private EuclideanPixelMap pixelMap; - private bool pixelMapHasValue; private readonly bool isDithering; private bool isDisposed; @@ -48,7 +47,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree = new Octree(this.bitDepth); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; - this.pixelMapHasValue = false; this.palette = default; this.isDithering = !(this.Options.Dither is null); this.isDisposed = false; @@ -112,15 +110,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.Palletize(paletteSpan, max, ref paletteIndex); ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); - // When called by QuantizerUtilities.BuildPalette this prevents - // mutiple instances of the map being created but not disposed. - if (this.pixelMapHasValue) + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) { - this.pixelMap.Dispose(); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + } + else + { + this.pixelMap.Clear(result); } - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - this.pixelMapHasValue = true; this.palette = result; } @@ -153,9 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (!this.isDisposed) { this.isDisposed = true; - this.paletteOwner.Dispose(); + this.paletteOwner?.Dispose(); this.paletteOwner = null; - this.pixelMap.Dispose(); + this.pixelMap?.Dispose(); + this.pixelMap = null; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index a83c760c20..4f73f4ac81 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -58,9 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var palette = new TPixel[length]; Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); - - var pixelMap = new EuclideanPixelMap(configuration, palette); - return new PaletteQuantizer(configuration, options, pixelMap, false); + return new PaletteQuantizer(configuration, options, palette); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index 9329bdfebe..284f4fa701 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -16,32 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization internal struct PaletteQuantizer : IQuantizer where TPixel : unmanaged, IPixel { - private readonly EuclideanPixelMap pixelMap; - private readonly bool leaveMap; + private EuclideanPixelMap pixelMap; /// /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. - /// The pixel map for looking up color matches from a predefined palette. - /// - /// to leave the pixel map undisposed after disposing the object; otherwise, . - /// + /// The palette to use. [MethodImpl(InliningOptions.ShortMethod)] - public PaletteQuantizer( - Configuration configuration, - QuantizerOptions options, - EuclideanPixelMap pixelMap, - bool leaveMap) + public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(options, nameof(options)); this.Configuration = configuration; this.Options = options; - this.pixelMap = pixelMap; - this.leaveMap = leaveMap; + this.pixelMap = new EuclideanPixelMap(configuration, palette); } /// @@ -72,10 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public void Dispose() { - if (!this.leaveMap) - { - this.pixelMap.Dispose(); - } + this.pixelMap?.Dispose(); + this.pixelMap = null; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index b6f4be4949..bf4a5ca41e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private int maxColors; private readonly Box[] colorCube; private EuclideanPixelMap pixelMap; - private bool pixelMapHasValue; private readonly bool isDithering; private bool isDisposed; @@ -97,7 +96,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.colorCube = new Box[this.maxColors]; this.isDisposed = false; this.pixelMap = default; - this.pixelMapHasValue = false; this.palette = default; this.isDithering = this.isDithering = !(this.Options.Dither is null); } @@ -147,15 +145,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); if (this.isDithering) { - // When called by QuantizerUtilities.BuildPalette this prevents - // mutiple instances of the map being created but not disposed. - if (this.pixelMapHasValue) + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) { - this.pixelMap.Dispose(); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + } + else + { + this.pixelMap.Clear(result); } - - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - this.pixelMapHasValue = true; } this.palette = result; @@ -201,7 +200,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.momentsOwner = null; this.tagsOwner = null; this.paletteOwner = null; - this.pixelMap.Dispose(); + this.pixelMap?.Dispose(); + this.pixelMap = null; } } @@ -215,16 +215,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The index. [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) - { - return (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - } + => (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; /// /// Computes sum over a box of any given statistic. @@ -233,24 +231,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The moment. /// The result. private static Moment Volume(ref Box cube, ReadOnlySpan moments) - { - return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - } + => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; /// /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). @@ -835,7 +831,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public int Volume; /// - public readonly override bool Equals(object obj) + public override readonly bool Equals(object obj) => obj is Box box && this.Equals(box); @@ -852,7 +848,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization && this.Volume == other.Volume; /// - public readonly override int GetHashCode() + public override readonly int GetHashCode() { HashCode hash = default; hash.Add(this.RMin); From 3eb6d6d1d620eeed6adced59584c137e4fb48323 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 17 Jun 2021 12:19:09 +0300 Subject: [PATCH 05/22] Simplified huffman encoding via compiled tree Squash squash --- .../Formats/Jpeg/Components/Encoder/HuffmanLut.cs | 8 ++++---- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 3 ++- .../Formats/Jpeg/Components/Encoder/HuffmanSpec.cs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index bc6c8c6cc7..ec77bf87db 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -5,8 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// /// A compiled look-up table representation of a huffmanSpec. - /// Each value maps to a uint32 of which the 8 most significant bits hold the - /// codeword size in bits and the 24 least significant bits hold the codeword. + /// Each value maps to a int32 of which the 24 most significant bits hold the + /// codeword in bits and the 8 least significant bits hold the codeword size. /// The maximum codeword size is 16 bits. /// internal readonly struct HuffmanLut @@ -51,10 +51,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int i = 0; i < spec.Count.Length; i++) { - int bits = (i + 1) << 24; + int len = i + 1; for (int j = 0; j < spec.Count[i]; j++) { - this.Values[spec.Values[k]] = bits | code; + this.Values[spec.Values[k]] = len | (code << 8); code++; k++; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 36f97a23df..3f490d2958 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -354,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private void EmitHuff(int[] table, int value) { int x = table[value]; - this.Emit(x & ((1 << 24) - 1), x >> 24); + this.Emit(x >> 8, x & 0xff); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 1f9899562b..51364e3c73 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0, 0, 0 }, new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), // Luminance AC. new HuffmanSpec( From c3fdf990411af978c7954048239b07c85775c6ee Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 03:04:31 +0300 Subject: [PATCH 06/22] Removed redundant if check for rle emitter --- .../Components/Encoder/HuffmanScanEncoder.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 3f490d2958..79ed59f6a0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int dc = (int)refTemp2[0]; // Emit the DC delta. - this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); + this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; @@ -358,6 +358,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Emit(x >> 8, x & 0xff); } + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitDirectCurrentTerm(int[] table, int value) + { + int a = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + int bt = GetHuffmanEncodingLength((uint)a); + + this.EmitHuff(table, bt); + if (bt > 0) + { + this.Emit(b & ((1 << bt) - 1), bt); + } + } + /// /// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// @@ -378,10 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int bt = GetHuffmanEncodingLength((uint)a); this.EmitHuff(table, (runLength << 4) | bt); - if (bt > 0) - { - this.Emit(b & ((1 << bt) - 1), bt); - } + this.Emit(b & ((1 << bt) - 1), bt); } /// From 4e210d2e1951eac1f3fed3371d1887553c55a77a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 03:55:48 +0300 Subject: [PATCH 07/22] Jpeg encoder no longer codes trailing zeros, it writes EOB instead --- .../Components/Encoder/HuffmanScanEncoder.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 79ed59f6a0..58f483ecca 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -253,6 +253,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); + int lastValuableIndex = GetLastNonZeroElement(ref refTemp2); + int dc = (int)refTemp2[0]; // Emit the DC delta. @@ -263,7 +265,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int runLength = 0; - for (int zig = 1; zig < Block8x8F.Size; zig++) + for (int zig = 1; zig <= lastValuableIndex; zig++) { int ac = (int)refTemp2[zig]; @@ -284,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - if (runLength > 0) + if (lastValuableIndex != 63) { this.EmitHuff(huffmanTable, 0x00); } @@ -456,7 +458,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #endif } - // !!! DO NOT DELETE THIS !!! until custom huffman tables are supported - this is very handy for debugging + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLastNonZeroElement(ref Block8x8F block) + { + int index = 63; + + ref float elemRef = ref Unsafe.As(ref block); + while ((int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + return index; + } + /// /// Prints given huffman codes to the System.Console for debugging puroses. /// From 8403ebc1e03cf2ca5a26b4fd94d9c0673a4df859 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 04:32:07 +0300 Subject: [PATCH 08/22] Removed obsolete debug code --- .../Components/Encoder/HuffmanScanEncoder.cs | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 58f483ecca..8457ddbf30 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -471,52 +471,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return index; } - - /// - /// Prints given huffman codes to the System.Console for debugging puroses. - /// - /// Codes array. - /// Custom title to print. - /// Flag indicating if all codes should be printed. - /// Indicates which number base will be used to print codes - private static void PrintHuffmanSummary(int[] codes, string title, bool printCode, int codePrintBase = 2) - { - System.Console.WriteLine(title); - System.Console.WriteLine($"Codes count: {codes.Length}"); - - // This is possible if custom tree is provided, especially for per-image optimized tree - if (codes.Length == 0) - { - return; - } - - // Min - int min = codes.Min(); - int min_len = min >> 24; - string min_code = System.Convert.ToString(min & ((1 << 24) - 1), codePrintBase); - - // Max - int max = codes.Max(); - int max_len = max >> 24; - string max_code = System.Convert.ToString(max & ((1 << 24) - 1), codePrintBase); - - System.Console.WriteLine($"Min code: {min_code}, len: {min_len} \nMax code: {max_code}, len: {max_len}"); - - // Printing codes - if (printCode) - { - PrintHuffmanCodes(codes, codePrintBase); - } - } - - private static void PrintHuffmanCodes(int[] codes, int codePrintBase) - { - for (int i = 0; i < codes.Length; i++) - { - int huffCode = codes[i]; - string code = System.Convert.ToString(huffCode & ((1 << 24) - 1), codePrintBase); - System.Console.WriteLine($"\t{code}"); - } - } } } From 007c52a3f2efb4f52c55c0415aa48fbfd51b3bfd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 05:02:06 +0300 Subject: [PATCH 09/22] Fixed possible out of range exception, added docs --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 8457ddbf30..43d7b07b91 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -458,13 +458,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #endif } + /// + /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero + /// + /// Input mcu. + /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetLastNonZeroElement(ref Block8x8F block) + internal static int GetLastNonZeroElement(ref Block8x8F block) { int index = 63; - ref float elemRef = ref Unsafe.As(ref block); - while ((int)Unsafe.Add(ref elemRef, index) == 0) + while (index > -1 && (int)Unsafe.Add(ref elemRef, index) == 0) { index--; } From 26a0f0bc6fa25087f89b7ef0d40841cab360c679 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 05:10:24 +0300 Subject: [PATCH 10/22] Fixeds docs, fixed API, fixed if check --- .../Components/Encoder/HuffmanScanEncoder.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 43d7b07b91..c610c24838 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -286,7 +286,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - if (lastValuableIndex != 63) + // if mcu block contains trailing zeros - we must write end of block (EOB) values indicating that current block is over + // this can be done for any number of trailing zeros + if (lastValuableIndex < Block8x8F.Size - 1) { this.EmitHuff(huffmanTable, 0x00); } @@ -461,14 +463,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero /// - /// Input mcu. + /// Return range is [1..63]. + /// Mcu block. /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int GetLastNonZeroElement(ref Block8x8F block) + private static int GetLastNonZeroElement(ref Block8x8F mcu) { - int index = 63; - ref float elemRef = ref Unsafe.As(ref block); - while (index > -1 && (int)Unsafe.Add(ref elemRef, index) == 0) + int index = Block8x8F.Size - 1; + ref float elemRef = ref Unsafe.As(ref mcu); + + // Index range is [63..0), first element is a DC value which will be emitted even if entire mcu contains only zeros + while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) { index--; } From 683a125566988a186d8deb4ecae5008d199b6644 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 06:39:20 +0300 Subject: [PATCH 11/22] Added comments --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index c610c24838..631128c8c8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. - int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; + int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int runLength = 0; @@ -277,20 +277,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { while (runLength > 15) { - this.EmitHuff(huffmanTable, 0xf0); + this.EmitHuff(acHuffTable, 0xf0); runLength -= 16; } - this.EmitHuffRLE(huffmanTable, runLength, ac); + this.EmitHuffRLE(acHuffTable, runLength, ac); runLength = 0; } } - // if mcu block contains trailing zeros - we must write end of block (EOB) values indicating that current block is over - // this can be done for any number of trailing zeros + // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over + // this can be done for any number of trailing zeros, even when all 63 ac values are zero if (lastValuableIndex < Block8x8F.Size - 1) { - this.EmitHuff(huffmanTable, 0x00); + this.EmitHuff(acHuffTable, 0x00); } return dc; From 467a069620b6ccbd12c09d307ee8eaa594569353 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:38:06 +0300 Subject: [PATCH 12/22] Docs, cleanup, ready for tests --- .../Components/Encoder/HuffmanScanEncoder.cs | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 631128c8c8..e42a2f0f37 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -253,18 +255,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); - int lastValuableIndex = GetLastNonZeroElement(ref refTemp2); - - int dc = (int)refTemp2[0]; - // Emit the DC delta. + int dc = (int)refTemp2[0]; this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int runLength = 0; - + int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2); for (int zig = 1; zig <= lastValuableIndex; zig++) { int ac = (int)refTemp2[zig]; @@ -288,7 +287,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over // this can be done for any number of trailing zeros, even when all 63 ac values are zero - if (lastValuableIndex < Block8x8F.Size - 1) + // (Block8x8F.Size - 1) == 63 - last index of the mcu elements + if (lastValuableIndex != Block8x8F.Size - 1) { this.EmitHuff(acHuffTable, 0x00); } @@ -433,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetHuffmanEncodingLength(uint value) + internal static int GetHuffmanEncodingLength(uint value) { DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); #if SUPPORTS_BITOPERATIONS @@ -461,24 +461,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero + /// Returns index of the last non-zero element in given mcu block /// - /// Return range is [1..63]. + /// + /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. + /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. + /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// /// Mcu block. /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetLastNonZeroElement(ref Block8x8F mcu) + internal static int GetLastValuableElementIndex(ref Block8x8F mcu) { - int index = Block8x8F.Size - 1; - ref float elemRef = ref Unsafe.As(ref mcu); - - // Index range is [63..0), first element is a DC value which will be emitted even if entire mcu contains only zeros - while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - index--; + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + Vector256 zero8 = Vector256.Zero; + + ref Vector256 mcuStride = ref mcu.V0; + + for (int i = 8; i >= 0; i--) + { + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); + + // we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros + if (areEqual != equalityMask) + { + // last index in the stride, we go from the end to the start of the stride + int startIndex = i * 8; + int index = startIndex + 7; + ref float elemRef = ref Unsafe.As(ref mcu); + while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + // this implementation will return -1 if all ac components are zero and dc are zero + return index; + } + } + + return -1; } + else +#endif + { + int index = Block8x8F.Size - 1; + ref float elemRef = ref Unsafe.As(ref mcu); + + while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } - return index; + // this implementation will return 0 if all ac components and dc are zero + return index; + } } } } From 79a1e117a360760e39e138558153c0ca4bab95f0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:41:05 +0300 Subject: [PATCH 13/22] Removed redundant check --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index e42a2f0f37..f1c7cdc742 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -416,11 +416,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder if (padBitsCount != 0) { this.Emit((1 << padBitsCount) - 1, padBitsCount); - } - - // flush remaining bytes - if (this.emitLen != 0) - { this.target.Write(this.emitBuffer, 0, this.emitLen); } } From 158969501ed986a3c0365e2c3eb1a7d9ff730185 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:41:59 +0300 Subject: [PATCH 14/22] Small fixes --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index f1c7cdc742..a0ea14146b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -424,10 +424,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. /// /// - /// This method returns 0 for input value 0. This is done specificaly for huffman encoding + /// This is an internal operation supposed to be used only in class for jpeg encoding. /// /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] internal static int GetHuffmanEncodingLength(uint value) { DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); @@ -456,16 +456,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// Returns index of the last non-zero element in given mcu block - /// - /// + /// Returns index of the last non-zero element in given mcu block. /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// + /// + /// This is an internal operation supposed to be used only in class for jpeg encoding. /// /// Mcu block. /// Index of the last non-zero element. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] internal static int GetLastValuableElementIndex(ref Block8x8F mcu) { #if SUPPORTS_RUNTIME_INTRINSICS From 6d14b8205cad6a809e014077ca31fc09939521e0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 10:23:16 +0300 Subject: [PATCH 15/22] Added tests for GetHuffmanEncodingLength --- .../Formats/Jpg/HuffmanScanEncoderTests.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs new file mode 100644 index 0000000000..095fdef812 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; +#endif +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public class HuffmanScanEncoderTests + { + private ITestOutputHelper Output { get; } + + public HuffmanScanEncoderTests(ITestOutputHelper output) + { + Output = output; + } + + private static int GetHuffmanEncodingLength_Reference(uint number) + { + int bits = 0; + if (number > 32767) + { + number >>= 16; + bits += 16; + } + if (number > 127) + { + number >>= 8; + bits += 8; + } + if (number > 7) + { + number >>= 4; + bits += 4; + } + if (number > 1) + { + number >>= 2; + bits += 2; + } + if (number > 0) + { + bits++; + } + return bits; + } + + [Fact] + public void GetHuffmanEncodingLength_Zero() + { + int expected = 0; + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetHuffmanEncodingLength_Random(int seed) + { + int maxNumber = 1 << 16; + + var rng = new Random(seed); + for (int i = 0; i < 1000; i++) + { + uint number = (uint)rng.Next(0, maxNumber); + + int expected = GetHuffmanEncodingLength_Reference(number); + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); + + Assert.Equal(expected, actual); + } + } + } +} From 0a6bf553e32f79f21f48574938fff9052b221950 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:44:59 +0300 Subject: [PATCH 16/22] Added tests for GetLastValuableElementIndex --- .../Formats/Jpg/HuffmanScanEncoderTests.cs | 163 +++++++++++++++++- 1 file changed, 158 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs index 095fdef812..b953e80b80 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -2,12 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics.X86; -#endif using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -22,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public HuffmanScanEncoderTests(ITestOutputHelper output) { - Output = output; + this.Output = output; } private static int GetHuffmanEncodingLength_Reference(uint number) @@ -33,25 +29,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg number >>= 16; bits += 16; } + if (number > 127) { number >>= 8; bits += 8; } + if (number > 7) { number >>= 4; bits += 4; } + if (number > 1) { number >>= 2; bits += 2; } + if (number > 0) { bits++; } + return bits; } @@ -84,5 +85,157 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } } + + [Fact] + public void GetLastValuableElementIndex_AllZero() + { + static void RunTest() + { + Block8x8F data = default; + + int expectedLessThan = 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.True(actual < expectedLessThan); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Fact] + public void GetLastValuableElementIndex_AllNonZero() + { + static void RunTest() + { + Block8x8F data = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + data[i] = 10; + } + + int expected = Block8x8F.Size - 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledSingle(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int setIndex = rng.Next(1, Block8x8F.Size); + data[setIndex] = rng.Next(); + + int expected = setIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledPartially(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int lastIndex = rng.Next(1, Block8x8F.Size); + int fillValue = rng.Next(); + for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledFragmented(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int fillValue = rng.Next(); + + // first filled chunk + int lastIndex1 = rng.Next(1, Block8x8F.Size / 2); + for (int dataIndex = 0; dataIndex <= lastIndex1; dataIndex++) + { + data[dataIndex] = fillValue; + } + + // second filled chunk, there might be a spot with zero(s) between first and second chunk + int lastIndex2 = rng.Next(lastIndex1 + 1, Block8x8F.Size); + for (int dataIndex = 0; dataIndex <= lastIndex2; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex2; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } } } From 2aff02351466a04839619370dc992e5722454bfd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:45:17 +0300 Subject: [PATCH 17/22] Fixed GetLastValuableElementIndex invalid indexing --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index a0ea14146b..23ff2ab35f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -478,7 +478,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Vector256 mcuStride = ref mcu.V0; - for (int i = 8; i >= 0; i--) + for (int i = 7; i >= 0; i--) { int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); From b06fd195c354989dedd9ae5084525acea7973831 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:52:21 +0300 Subject: [PATCH 18/22] Added docs --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 23ff2ab35f..331da275cc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -15,6 +15,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class HuffmanScanEncoder { + /// + /// Compiled huffman tree to encode given values. + /// + /// Yields codewords by index consisting of [run length | bitsize]. private HuffmanLut[] huffmanTables; /// From ba8f344e1317f6a3960e5f489653f1f3e66a2939 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 13:11:47 +0300 Subject: [PATCH 19/22] Added updated benchmarks --- .../Codecs/Jpeg/EncodeJpeg.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index d472791e43..87170e8d24 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -114,21 +114,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET Core 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 [AttachedDebugger] + [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -| Method | Quality | Mean | Error | StdDev | Ratio | RatioSD | -|---------------------------- |-------- |---------:|---------:|---------:|------:|--------:| -| 'System.Drawing Jpeg 4:2:0' | 75 | 30.60 ms | 0.496 ms | 0.464 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 75 | 29.86 ms | 0.350 ms | 0.311 ms | 0.98 | 0.02 | -| 'ImageSharp Jpeg 4:4:4' | 75 | 45.36 ms | 0.899 ms | 1.036 ms | 1.48 | 0.05 | -| | | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 90 | 34.05 ms | 0.669 ms | 0.687 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 90 | 37.26 ms | 0.706 ms | 0.660 ms | 1.10 | 0.03 | -| 'ImageSharp Jpeg 4:4:4' | 90 | 52.54 ms | 0.579 ms | 0.514 ms | 1.55 | 0.04 | -| | | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 100 | 39.36 ms | 0.267 ms | 0.237 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 100 | 42.44 ms | 0.410 ms | 0.383 ms | 1.08 | 0.01 | -| 'ImageSharp Jpeg 4:4:4' | 100 | 70.88 ms | 0.508 ms | 0.450 ms | 1.80 | 0.02 | +| Method | Quality | Mean | Error | StdDev | Ratio | +|---------------------------- |-------- |---------:|---------:|---------:|------:| +| 'System.Drawing Jpeg 4:2:0' | 75 | 29.41 ms | 0.108 ms | 0.096 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 75 | 26.30 ms | 0.131 ms | 0.109 ms | 0.89 | +| 'ImageSharp Jpeg 4:4:4' | 75 | 36.70 ms | 0.303 ms | 0.269 ms | 1.25 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 90 | 32.67 ms | 0.226 ms | 0.211 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 90 | 33.56 ms | 0.237 ms | 0.222 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 90 | 44.82 ms | 0.250 ms | 0.234 ms | 1.37 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 100 | 39.06 ms | 0.233 ms | 0.218 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 100 | 40.23 ms | 0.225 ms | 0.277 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 100 | 63.35 ms | 0.486 ms | 0.431 ms | 1.62 | */ From 0791b576bd8a633b4f079158de5198fd127167fb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 20 Jun 2021 08:55:45 +1000 Subject: [PATCH 20/22] Rename methods to use Dangerous prefix. --- src/ImageSharp/Memory/Buffer2DExtensions.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 12 ++++++------ .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Processors/Transforms/Resize/ResizeWorker.cs | 6 +++--- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- .../Helpers/ParallelRowIteratorTests.cs | 2 +- .../Image/ImageTests.WrapMemory.cs | 8 ++++---- tests/ImageSharp.Tests/Memory/Buffer2DTests.cs | 14 +++++++------- .../TestUtilities/TestImageExtensions.cs | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 9fce9a4f4e..6458ad7e4c 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Memory /// Copy columns of inplace, /// from positions starting at to positions at . /// - internal static unsafe void CopyColumns( + internal static unsafe void DangerousCopyColumns( this Buffer2D buffer, int sourceIndex, int destIndex, @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); + Span span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span); fixed (byte* ptr = span) { diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 38ca89e59b..21c19f5d52 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -168,10 +168,10 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span GetSingleSpan() + internal Span DangerousGetSingleSpan() { // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow(); + return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow(); } /// @@ -183,10 +183,10 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory GetSingleMemory() + internal Memory DangerousGetSingleMemory() { // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow(); + return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow(); } /// @@ -203,10 +203,10 @@ namespace SixLabors.ImageSharp.Memory private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); [MethodImpl(InliningOptions.ColdPath)] - private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single(); + private Memory DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single(); [MethodImpl(InliningOptions.ColdPath)] - private Span GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; + private Span DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; [MethodImpl(InliningOptions.ColdPath)] private ref T GetElementSlow(int x, int y) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 2ab1d8b5a7..a58c20f687 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.GetSingleMemory().Pin(); + this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index e7207c7e63..7ade3aeeea 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempColSpan = this.tempColumnBuffer.GetSpan(); // When creating transposedFirstPassBuffer, we made sure it's contiguous: - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) { @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Copy previous bottom band to the new top: // (rows <--> columns, because the buffer is transposed) - this.transposedFirstPassBuffer.CopyColumns( + this.transposedFirstPassBuffer.DangerousCopyColumns( this.workerHeight - this.windowBandHeight, 0, this.windowBandHeight); @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 4d6de7e279..91b1b9cd78 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index c93eb41c27..7d4f2da425 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers in operation); // Assert: - TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); + TestImageExtensions.CompareBuffers(expected.DangerousGetSingleSpan(), actual.DangerousGetSingleSpan()); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index bb75578a4b..27188b0b45 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); imageSpan.Fill(bg); for (var i = 10; i < 20; i++) @@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); imageSpan.Fill(bg); for (var i = 10; i < 20; i++) @@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height)) { Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span; + Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; // We can't compare the two Memory instances directly as they wrap different memory managers. // To check that the underlying data matches, we can just manually check their lenth, and the @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(p, bmp.Width, bmp.Height)) { Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span; + Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 549ecb7f4f..015b3617bc 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.GetSingleSpan().Length); + Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); } } @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSingleSpan(); + Span span = buffer.DangerousGetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -249,9 +249,9 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - rnd.RandomFill(b.GetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.CopyColumns(startIndex, destIndex, columnCount); + b.DangerousCopyColumns(startIndex, destIndex, columnCount); for (int y = 0; y < b.Height; y++) { @@ -271,10 +271,10 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - rnd.RandomFill(b.GetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.CopyColumns(0, 50, 22); - b.CopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); for (int y = 0; y < b.Height; y++) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index de365c4295..26378796bd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -672,7 +672,7 @@ namespace SixLabors.ImageSharp.Tests var image = new Image(buffer.Width, buffer.Height); Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); - Span bufferSpan = buffer.GetSingleSpan(); + Span bufferSpan = buffer.DangerousGetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { From 82fd63bb95cc3e63c045a0ca20a97f4de33e07f9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 20 Jun 2021 10:47:54 +1000 Subject: [PATCH 21/22] Update AllocatePaddedPixelRowBuffer --- .../Common/Extensions/StreamExtensions.cs | 6 - src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 170 +++++++++--------- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 69 +++---- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 157 ++++++++-------- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 92 +++++----- .../Memory/MemoryAllocatorExtensions.cs | 8 +- 6 files changed, 246 insertions(+), 256 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index f2367d488a..47a0e0bbf0 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -72,12 +72,6 @@ namespace SixLabors.ImageSharp } } - public static void Read(this Stream stream, IManagedByteBuffer buffer) - => stream.Read(buffer.Array, 0, buffer.Length()); - - public static void Write(this Stream stream, IManagedByteBuffer buffer) - => stream.Write(buffer.Array, 0, buffer.Length()); - #if !SUPPORTS_SPAN_STREAM // This is a port of the CoreFX implementation and is MIT Licensed: // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index f6fefda485..03124781a8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -928,20 +928,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 3); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding); + Span rowSpan = row.GetSpan(); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding)) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - PixelOperations.Instance.FromBgr24Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } + this.stream.Read(rowSpan); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgr24Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); } } @@ -957,20 +956,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + Span rowSpan = row.GetSpan(); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } + this.stream.Read(rowSpan); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); } } @@ -987,87 +985,85 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); - - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) - using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + using IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width); + Span rowSpan = row.GetSpan(); + Span bgraRowSpan = bgraRow.GetSpan(); + long currentPosition = this.stream.Position; + bool hasAlpha = false; + + // Loop though the rows checking each pixel. We start by assuming it's + // an BGR0 image. If we hit a non-zero alpha value, then we know it's + // actually a BGRA image, and change tactics accordingly. + for (int y = 0; y < height; y++) { - Span bgraRowSpan = bgraRow.GetSpan(); - long currentPosition = this.stream.Position; - bool hasAlpha = false; - - // Loop though the rows checking each pixel. We start by assuming it's - // an BGR0 image. If we hit a non-zero alpha value, then we know it's - // actually a BGRA image, and change tactics accordingly. - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - bgraRowSpan, - width); + this.stream.Read(rowSpan); - // Check each pixel in the row to see if it has an alpha value. - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - if (bgra.A > 0) - { - hasAlpha = true; - break; - } - } + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + bgraRowSpan, + width); - if (hasAlpha) + // Check each pixel in the row to see if it has an alpha value. + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + if (bgra.A > 0) { + hasAlpha = true; break; } } - // Reset our stream for a second pass. - this.stream.Position = currentPosition; - - // Process the pixels in bulk taking the raw alpha component value. if (hasAlpha) { - for (int y = 0; y < height; y++) - { - this.stream.Read(row); - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - pixelSpan, - width); - } - - return; + break; } + } - // Slow path. We need to set each alpha component value to fully opaque. + // Reset our stream for a second pass. + this.stream.Position = currentPosition; + + // Process the pixels in bulk taking the raw alpha component value. + if (hasAlpha) + { for (int y = 0; y < height; y++) { - this.stream.Read(row); - PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, - row.GetSpan(), - bgraRowSpan, - width); + this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); Span pixelSpan = pixels.GetRowSpan(newY); - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - bgra.A = byte.MaxValue; - ref TPixel pixel = ref pixelSpan[x]; - pixel.FromBgra32(bgra); - } + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + pixelSpan, + width); + } + + return; + } + + // Slow path. We need to set each alpha component value to fully opaque. + for (int y = 0; y < height; y++) + { + this.stream.Read(rowSpan); + PixelOperations.Instance.FromBgra32Bytes( + this.Configuration, + rowSpan, + bgraRowSpan, + width); + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + bgra.A = byte.MaxValue; + ref TPixel pixel = ref pixelSpan[x]; + pixel.FromBgra32(bgra); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b407ad221f..7a18d847c3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -257,7 +257,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } - private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); /// /// Writes the 32bit color palette to the stream. @@ -268,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write32Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -294,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int width = pixels.Width; int rowBytesWithoutPadding = width * 3; - using (IManagedByteBuffer row = this.AllocateRow(width, 3)) + using IMemoryOwner row = this.AllocateRow(width, 3); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - width); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + width); + stream.Write(rowSpan); } } @@ -320,20 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int width = pixels.Width; int rowBytesWithoutPadding = width * 2; - using (IManagedByteBuffer row = this.AllocateRow(width, 2)) + using IMemoryOwner row = this.AllocateRow(width, 2); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - pixelSpan.Length); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + stream.Write(rowSpan); } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index eef6e7362b..f717c22304 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -373,22 +373,21 @@ namespace SixLabors.ImageSharp.Formats.Tga return; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); + if (invertY) { - bool invertY = InvertY(origin); - if (invertY) + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadL8Row(width, pixels, row, y); - } + this.ReadL8Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadL8Row(width, pixels, row, y); - } + this.ReadL8Row(width, pixels, rowSpan, y); } } } @@ -406,58 +405,57 @@ namespace SixLabors.ImageSharp.Formats.Tga { TPixel color = default; bool invertX = InvertX(origin); - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) - { - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0); + Span rowSpan = row.GetSpan(); - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - this.currentStream.Read(this.scratchBuffer, 0, 2); - if (!this.hasAlpha) - { - this.scratchBuffer[1] |= 1 << 7; - } - - if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) - { - color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); - } - else - { - color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); - } + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); - pixelSpan[x] = color; - } - } - else + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - this.currentStream.Read(row); - Span rowSpan = row.GetSpan(); - + this.currentStream.Read(this.scratchBuffer, 0, 2); if (!this.hasAlpha) { - // We need to set the alpha component value to fully opaque. - for (int x = 1; x < rowSpan.Length; x += 2) - { - rowSpan[x] |= 1 << 7; - } + this.scratchBuffer[1] |= 1 << 7; } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { - PixelOperations.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width); + color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); } else { - PixelOperations.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width); + color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); + } + + pixelSpan[x] = color; + } + } + else + { + this.currentStream.Read(rowSpan); + + if (!this.hasAlpha) + { + // We need to set the alpha component value to fully opaque. + for (int x = 1; x < rowSpan.Length; x += 2) + { + rowSpan[x] |= 1 << 7; } } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + PixelOperations.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width); + } + else + { + PixelOperations.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width); + } } } } @@ -490,23 +488,22 @@ namespace SixLabors.ImageSharp.Formats.Tga return; } - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) - { - bool invertY = InvertY(origin); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); - if (invertY) + if (invertY) + { + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgr24Row(width, pixels, row, y); - } + this.ReadBgr24Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadBgr24Row(width, pixels, row, y); - } + this.ReadBgr24Row(width, pixels, rowSpan, y); } } } @@ -526,21 +523,21 @@ namespace SixLabors.ImageSharp.Formats.Tga bool invertX = InvertX(origin); if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); + Span rowSpan = row.GetSpan(); + + if (InvertY(origin)) { - if (InvertY(origin)) + for (int y = height - 1; y >= 0; y--) { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgra32Row(width, pixels, row, y); - } + this.ReadBgra32Row(width, pixels, rowSpan, y); } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - this.ReadBgra32Row(width, pixels, row, y); - } + this.ReadBgra32Row(width, pixels, rowSpan, y); } } @@ -652,12 +649,12 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromL8Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -679,12 +676,12 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -698,12 +695,12 @@ namespace SixLabors.ImageSharp.Formats.Tga } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y) + private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { this.currentStream.Read(row); Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row.GetSpan(), pixelSpan, width); + PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 1d31ea9f4e..4bf4ca60a1 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; @@ -258,7 +259,8 @@ namespace SixLabors.ImageSharp.Formats.Tga return equalPixelCount; } - private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); /// /// Writes the 8bit pixels uncompressed to the stream. @@ -269,18 +271,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write8Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToL8Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToL8Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -293,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write16Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -317,18 +319,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write24Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } @@ -341,18 +343,18 @@ namespace SixLabors.ImageSharp.Formats.Tga private void Write32Bit(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.GetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - row.GetSpan(), - pixelSpan.Length); - stream.Write(row.Array, 0, row.Length()); - } + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 922088b26d..2f70ac05e8 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -67,21 +67,21 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). + /// Allocates padded buffers. Generally used by encoder/decoders. /// /// The . /// Pixel count in the row /// The pixel size in bytes, eg. 3 for RGB. /// The padding. - /// A . - internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + /// A . + internal static IMemoryOwner AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, int pixelSizeInBytes, int paddingInBytes) { int length = (width * pixelSizeInBytes) + paddingInBytes; - return memoryAllocator.AllocateManagedByteBuffer(length); + return memoryAllocator.Allocate(length); } /// From d96f26a6a9519f1bc9ff32b13e8ac97e943ae510 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 20 Jun 2021 10:33:07 +0100 Subject: [PATCH 22/22] Update src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- .../Processors/Quantization/EuclideanPixelMap{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 947bd70dce..b82ce71bbd 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Clears the cahe resetting each entry to empty. + /// Clears the cache resetting each entry to empty. /// [MethodImpl(InliningOptions.ShortMethod)] public void Clear() => this.table.GetSpan().Fill(-1);