diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 4ca97b3718..7480318606 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -4,6 +4,8 @@ using System; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// /// The pixel data to apply the transformation. - public static void AddGreenToBlueAndRed(uint[] pixelData) + public static void AddGreenToBlueAndRed(Span pixelData) { for (int i = 0; i < pixelData.Length; i++) { @@ -28,12 +30,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void ColorIndexInverseTransform(Vp8LTransform transform, uint[] pixelData) + public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) { int bitsPerPixel = 8 >> transform.Bits; int width = transform.XSize; int height = transform.YSize; - uint[] colorMap = transform.Data; + Span colorMap = transform.Data.GetSpan(); int decodedPixels = 0; if (bitsPerPixel < 8) { @@ -57,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } - decodedPixelData[decodedPixels++] = colorMap[packedPixels & bitMask]; + decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; packedPixels >>= bitsPerPixel; } } @@ -72,13 +74,13 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int x = 0; x < width; ++x) { uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); - pixelData[decodedPixels] = colorMap[colorMapIndex]; + pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; decodedPixels++; } } } - public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData) + public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) { int width = transform.XSize; int yEnd = transform.YSize; @@ -89,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int tilesPerRow = SubSampleSize(width, transform.Bits); int y = 0; int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; + Span transformData = transform.Data.GetSpan(); int pixelPos = 0; while (y < yEnd) @@ -99,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int srcEnd = pixelPos + width; while (pixelPos < srcSafeEnd) { - uint colorCode = transform.Data[predRowIdx++]; + uint colorCode = transformData[predRowIdx++]; ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, tileWidth); pixelPos += tileWidth; @@ -107,7 +110,7 @@ namespace SixLabors.ImageSharp.Formats.WebP if (pixelPos < srcEnd) { - uint colorCode = transform.Data[predRowIdx]; + uint colorCode = transformData[predRowIdx]; ColorCodeToMultipliers(colorCode, ref m); TransformColorInverse(m, pixelData, pixelPos, remainingWidth); pixelPos += remainingWidth; @@ -121,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void TransformColorInverse(Vp8LMultipliers m, uint[] pixelData, int start, int numPixels) + public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels) { int end = start + numPixels; for (int i = start; i < end; i++) @@ -141,14 +144,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static uint[] ExpandColorMap(int numColors, Vp8LTransform transform, uint[] transformData) + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) { - int finalNumColors = 1 << (8 >> transform.Bits); - - // TODO: use memoryAllocator here - var newColorMap = new uint[finalNumColors]; newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); Span newData = MemoryMarshal.Cast(newColorMap); int i; @@ -158,19 +156,18 @@ namespace SixLabors.ImageSharp.Formats.WebP newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); } - for (; i < 4 * finalNumColors; ++i) + for (; i < 4 * newColorMap.Length; ++i) { newData[i] = 0; // black tail. } - - return newColorMap; } - public static void PredictorInverseTransform(Vp8LTransform transform, uint[] pixelData, Span output) + public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) { int processedPixels = 0; int yStart = 0; int width = transform.XSize; + Span transformData = transform.Data.GetSpan(); // First Row follows the L (mode=1) mode. PredictorAdd0(pixelData, processedPixels, 1, output); @@ -194,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (x < width) { - uint predictorMode = (transform.Data[predictorModeIdx++] >> 8) & 0xf; + uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; int xEnd = (x & ~mask) + tileWidth; if (xEnd > width) { diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs index 25ecea6b8e..737def7f41 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Buffers; + namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8LMetadata @@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public int HuffmanXSize { get; set; } - public uint[] HuffmanImage { get; set; } + public IMemoryOwner HuffmanImage { get; set; } public int NumHTreeGroups { get; set; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs index 78874554a1..ae62123d31 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Buffers; using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP @@ -41,6 +42,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the transform data. /// - public uint[] Data { get; set; } + public IMemoryOwner Data { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 667212f907..b12ed0e726 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reads the header of lossless webp image. + /// Reads the header of a lossless webp image. /// /// Webp image features. /// Information about this image. diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index d878a0d2bd..ecdaf606c3 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -99,11 +99,19 @@ namespace SixLabors.ImageSharp.Formats.WebP where TPixel : struct, IPixel { var decoder = new Vp8LDecoder(width, height); - uint[] pixelData = this.DecodeImageStream(decoder, width, height, true); - this.DecodePixelValues(decoder, pixelData, pixels); + IMemoryOwner pixelData = this.DecodeImageStream(decoder, width, height, true); + this.DecodePixelValues(decoder, pixelData.GetSpan(), pixels); + + // Free up allocated memory. + pixelData.Dispose(); + foreach (Vp8LTransform transform in decoder.Transforms) + { + transform.Data?.Dispose(); + } + decoder.Metadata?.HuffmanImage?.Dispose(); } - private uint[] DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) + private IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { int numberOfTransformsPresent = 0; if (isLevel0) @@ -162,7 +170,8 @@ namespace SixLabors.ImageSharp.Formats.WebP this.UpdateDecoder(decoder, xSize, ySize); - uint[] pixelData = this.DecodeImageData(decoder, colorCacheSize, colorCache); + IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); + this.DecodeImageData(decoder, pixelData.GetSpan(), colorCacheSize, colorCache); if (!isLevel0) { decoder.Metadata = new Vp8LMetadata(); @@ -171,7 +180,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return pixelData; } - private void DecodePixelValues(Vp8LDecoder decoder, uint[] pixelData, Buffer2D pixels) + private void DecodePixelValues(Vp8LDecoder decoder, Span pixelData, Buffer2D pixels) where TPixel : struct, IPixel { // Apply reverse transformations, if any are present. @@ -195,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private uint[] DecodeImageData(Vp8LDecoder decoder, int colorCacheSize, ColorCache colorCache) + private void DecodeImageData(Vp8LDecoder decoder, Span pixelData, int colorCacheSize, ColorCache colorCache) { int lastPixel = 0; int width = decoder.Width; @@ -206,8 +215,6 @@ namespace SixLabors.ImageSharp.Formats.WebP int colorCacheLimit = lenCodeLimit + colorCacheSize; int mask = decoder.Metadata.HuffmanMask; HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row); - // TODO: use memory allocator - var pixelData = new uint[width * height]; int totalPixels = width * height; int decodedPixels = 0; @@ -331,11 +338,9 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("Webp parsing error"); } } - - return pixelData; } - private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, uint[] pixelData, ref int lastCached) + private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) { ++col; decodedPixels++; @@ -369,14 +374,15 @@ namespace SixLabors.ImageSharp.Formats.WebP uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision); - int huffmanPixs = huffmanXSize * huffmanYSize; - uint[] huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + int huffmanPixels = huffmanXSize * huffmanYSize; + IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + Span huffmanImageSpan = huffmanImage.GetSpan(); decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision; - for (int i = 0; i < huffmanPixs; ++i) + for (int i = 0; i < huffmanPixels; ++i) { // The huffman data is stored in red and green bytes. - uint group = (huffmanImage[i] >> 8) & 0xffff; - huffmanImage[i] = group; + uint group = (huffmanImageSpan[i] >> 8) & 0xffff; + huffmanImageSpan[i] = group; if (group >= numHTreeGroupsMax) { numHTreeGroupsMax = (int)group + 1; @@ -625,19 +631,26 @@ namespace SixLabors.ImageSharp.Formats.WebP : 3; transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); transform.Bits = bits; - uint[] colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false); - transform.Data = LosslessUtils.ExpandColorMap((int)numColors, transform, colorMap); + using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) + { + int finalNumColors = 1 << (8 >> transform.Bits); + IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean); + LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); + transform.Data = newColorMap; + } + break; case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: { transform.Bits = (int)this.bitReader.ReadBits(3) + 2; - transform.Data = this.DecodeImageStream( + IMemoryOwner transformData = this.DecodeImageStream( decoder, LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), false); + transform.Data = transformData; break; } } @@ -650,7 +663,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation. - private void ApplyInverseTransforms(Vp8LDecoder decoder, uint[] pixelData) + private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData) { List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) @@ -710,7 +723,7 @@ namespace SixLabors.ImageSharp.Formats.WebP return tableSpan[0].Value; } - private uint ReadPackedSymbols(HTreeGroup[] group, uint[] pixelData, int decodedPixels) + private uint ReadPackedSymbols(HTreeGroup[] group, Span pixelData, int decodedPixels) { uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); HuffmanCode code = group[0].PackedTable[val]; @@ -726,18 +739,18 @@ namespace SixLabors.ImageSharp.Formats.WebP return code.Value; } - private void CopyBlock(uint[] pixelData, int decodedPixels, int dist, int length) + private void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) { if (dist >= length) { - Span src = pixelData.AsSpan(decodedPixels - dist, length); - Span dest = pixelData.AsSpan(decodedPixels); + Span src = pixelData.Slice(decodedPixels - dist, length); + Span dest = pixelData.Slice(decodedPixels); src.CopyTo(dest); } else { - Span src = pixelData.AsSpan(decodedPixels - dist); - Span dest = pixelData.AsSpan(decodedPixels); + Span src = pixelData.Slice(decodedPixels - dist); + Span dest = pixelData.Slice(decodedPixels); for (int i = 0; i < length; ++i) { dest[i] = src[i]; @@ -811,14 +824,15 @@ namespace SixLabors.ImageSharp.Formats.WebP return hCode.BitsUsed; } - private uint GetMetaIndex(uint[] image, int xSize, int bits, int x, int y) + private uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) { if (bits is 0) { return 0; } - return image[(xSize * (y >> bits)) + (x >> bits)]; + Span huffmanImageSpan = huffmanImage.GetSpan(); + return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; } private HTreeGroup[] GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y)