diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs index 711fe99db9..80208918dc 100644 --- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs @@ -91,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader public uint Remaining { get; set; } + [MethodImpl(InliningOptions.ShortMethod)] public int GetBit(int prob) { uint range = this.range; @@ -184,6 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.BitReader this.LoadNewBytes(); } + [MethodImpl(InliningOptions.ColdPath)] private void LoadNewBytes() { if (this.pos < this.bufferMax) diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs index f3a095328c..f115fd6b70 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs @@ -381,9 +381,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); int huffmanPixels = huffmanXSize * huffmanYSize; + IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); Span huffmanImageSpan = huffmanImage.GetSpan(); decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; + + // TODO: Isn't huffmanPixels the length of the span? for (int i = 0; i < huffmanPixels; ++i) { // The huffman data is stored in red and green bytes. @@ -440,6 +443,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } + // TODO: Avoid allocation. hTreeGroup.HTrees.Add(huffmanTable.ToArray()); HuffmanCode huffTableZero = huffmanTable[0]; @@ -472,10 +476,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless hTreeGroup.IsTrivialCode = false; if (isTrivialLiteral) { - uint red = hTreeGroup.HTrees[HuffIndex.Red].First().Value; - uint blue = hTreeGroup.HTrees[HuffIndex.Blue].First().Value; - uint green = hTreeGroup.HTrees[HuffIndex.Green].First().Value; - uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha].First().Value; + uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; + uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; + uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; + uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; if (totalSize == 0 && green < WebPConstants.NumLiteralCodes) { @@ -542,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); } - this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths); + this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); } int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); @@ -550,7 +554,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless return size; } - private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { int maxSymbol; int symbol = 0; @@ -580,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless this.bitReader.FillBitWindow(); ulong prefetchBits = this.bitReader.PrefetchBits(); - ulong idx = prefetchBits & 127; + int idx = (int)(prefetchBits & 127); HuffmanCode huffmanCode = table[idx]; this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); uint codeLen = huffmanCode.Value; @@ -625,6 +629,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless var transform = new Vp8LTransform(transformType, xSize, ySize); // Each transform is allowed to be used only once. + // TODO: No Linq, avoid 'transform' closure allocation. if (decoder.Transforms.Any(t => t.TransformType == transform.TransformType)) { WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once"); diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs index cc60be5c0e..d9ff33ec02 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs @@ -492,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy public static void TransformOne(Span src, Span dst) { - var tmp = new int[4 * 4]; + Span tmp = stackalloc int[4 * 4]; int tmpOffset = 0; for (int srcOffset = 0; srcOffset < 4; srcOffset++) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs index 742a6212f7..bc3e762318 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs @@ -55,14 +55,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1); var pictureHeader = new Vp8PictureHeader() - { - Width = (uint)width, - Height = (uint)height, - XScale = info.XScale, - YScale = info.YScale, - ColorSpace = colorSpace, - ClampType = clampType - }; + { + Width = (uint)width, + Height = (uint)height, + XScale = info.XScale, + YScale = info.YScale, + ColorSpace = colorSpace, + ClampType = clampType + }; // Paragraph 9.3: Parse the segment header. var proba = new Vp8Proba(); @@ -135,10 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy Span alphaSpan = alpha.Memory.Span; for (int y = 0; y < height; y++) { + // TODO: Can we use span.Length here? int yMulWidth = y * width; Span pixelRow = pixels.GetRowSpan(y); for (int x = 0; x < width; x++) { + // TODO: Could use cast to Bgr24/Bgra32 then set alpha. int offset = yMulWidth + x; int idxBgr = offset * 3; byte b = pixelData[idxBgr]; @@ -165,9 +167,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossy this.ParseIntraMode(dec, mbX); } - for (; dec.MbX < dec.MbWidth; ++dec.MbX) + while (dec.MbX < dec.MbWidth) { this.DecodeMacroBlock(dec, bitreader); + ++dec.MbX; } // Prepare for next scanline. diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs index f73bb1a837..024d81b0bf 100644 --- a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs +++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP n >>= 8; } - return logValue + WebPLookupTables.LogTable8Bit[n]; + return logValue + Unsafe.Add(ref MemoryMarshal.GetReference(WebPLookupTables.LogTable8Bit), (int)n); } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index c3e0e89ddf..d71949fa5a 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.Experimental.WebP { +#pragma warning disable SA1201 // Elements should appear in the correct order internal static class WebPLookupTables { public static readonly Dictionary Abs0; @@ -418,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP }; // 31 ^ clz(i) - public static readonly byte[] LogTable8Bit = + public static ReadOnlySpan LogTable8Bit => new byte[] { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 43dc8f9d50..84130cf40d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs @@ -5,7 +5,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; - +using SixLabors.ImageSharp.Formats.Experimental.WebP; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Config(typeof(Config.ShortClr))] public class DecodeWebp : BenchmarkBase { + private Configuration configuration; + private byte[] webpLossyBytes; private byte[] webpLosslessBytes; @@ -31,6 +33,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void ReadImages() { + this.configuration = Configuration.CreateDefaultInstance(); + new WebPConfigurationModule().Configure(this.configuration); + this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); } @@ -47,7 +52,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossy() { using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = Image.Load(memoryStream); + using var image = Image.Load(this.configuration, memoryStream); return image.Height; } @@ -63,7 +68,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossless() { using var memoryStream = new MemoryStream(this.webpLosslessBytes); - using var image = Image.Load(memoryStream); + using var image = Image.Load(this.configuration, memoryStream); return image.Height; } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index cb17184ad4..0c39b8b4b7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -332,5 +332,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } }); } + + [Theory] + [WithFile(Lossless.Earth, PixelTypes.Rgba32)] + public void ProfileTestLossless(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + } } }