diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs index 571da5bb2..38497281f 100644 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs @@ -24,13 +24,15 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The global configuration. /// The memory manager. /// Indicates, if the data should be compressed with the lossless webp compression. - /// The alpha data. - public static byte[] EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress) + /// The size in bytes of the alpha data. + /// The encoded alpha data. + public static IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size) where TPixel : unmanaged, IPixel { - byte[] alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); int width = image.Width; int height = image.Height; + IMemoryOwner alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); + if (compress) { WebpEncodingMethod effort = WebpEncodingMethod.Default; @@ -49,11 +51,14 @@ namespace SixLabors.ImageSharp.Formats.Webp // The transparency information will be stored in the green channel of the ARGB quadruplet. // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, // that can improve compression. - using Image alphaAsImage = DispatchAlphaToGreen(image, alphaData); + using Image alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan()); + + size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData); - return lossLessEncoder.EncodeAlphaImageData(alphaAsImage); + return alphaData; } + size = width * height; return alphaData; } @@ -64,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The to encode from. /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. /// The transparency image. - private static Image DispatchAlphaToGreen(Image image, byte[] alphaData) + private static Image DispatchAlphaToGreen(Image image, Span alphaData) where TPixel : unmanaged, IPixel { int width = image.Width; @@ -75,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { Memory rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y); Span pixelRow = rowBuffer.Span; - Span alphaRow = alphaData.AsSpan(y * width, width); + Span alphaRow = alphaData.Slice(y * width, width); for (int x = 0; x < width; x++) { // Leave A/R/B channels zero'd. @@ -94,13 +99,14 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The global configuration. /// The memory manager. /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - private static byte[] ExtractAlphaChannel(Image image, Configuration configuration, MemoryAllocator memoryAllocator) + private static IMemoryOwner ExtractAlphaChannel(Image image, Configuration configuration, MemoryAllocator memoryAllocator) where TPixel : unmanaged, IPixel { Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; int height = image.Height; int width = image.Width; - byte[] alphaData = new byte[width * height]; + IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height); + Span alphaData = alphaDataBuffer.GetSpan(); using IMemoryOwner rowBuffer = memoryAllocator.Allocate(width); Span rgbaRow = rowBuffer.GetSpan(); @@ -116,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } } - return alphaData; + return alphaDataBuffer; } } } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 84c9d3f13..fc1accfde 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -47,6 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// The stream to write to. public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); + /// + /// Writes the encoded bytes of the image to the given buffer. Call Finish() before this. + /// + /// The destination buffer. + public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest); + /// /// Resizes the buffer to write to. /// @@ -108,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// /// The alpha chunk bytes. /// The alpha data chunk size in bytes. - protected uint AlphaChunkSize(byte[] alphaBytes) + protected uint AlphaChunkSize(Span alphaBytes) { uint alphaSize = (uint)alphaBytes.Length + 1; uint alphaChunkSize = WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1); @@ -147,10 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// The stream to write to. /// The alpha channel data bytes. /// Indicates, if the alpha channel data is compressed. - protected void WriteAlphaChunk(Stream stream, byte[] dataBytes, bool alphaDataIsCompressed) + protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed) { - DebugGuard.NotNull(dataBytes, nameof(dataBytes)); - uint size = (uint)dataBytes.Length + 1; Span buf = this.scratchBuffer.AsSpan(0, 4); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 577a87e6a..fa6e09d87 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -411,7 +411,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// Flag indicating, if a alpha channel is present. /// The alpha channel data. /// Indicates, if the alpha data is compressed. - public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData, bool alphaDataIsCompressed) + public void WriteEncodedImageToStream( + Stream stream, + ExifProfile exifProfile, + XmpProfile xmpProfile, + uint width, + uint height, + bool hasAlpha, + Span alphaData, + bool alphaDataIsCompressed) { bool isVp8X = false; byte[] exifBytes = null; @@ -661,7 +669,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter ExifProfile exifProfile, XmpProfile xmpProfile, bool hasAlpha, - byte[] alphaData, + Span alphaData, bool alphaDataIsCompressed) { this.WriteRiffHeader(stream, riffSize); diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 797d0794f..ece9aefd0 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -263,8 +263,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The type of the pixel. /// The to encode from. - /// The encoded alpha stream. - public byte[] EncodeAlphaImageData(Image image) + /// The destination buffer to write the encoded alpha data to. + /// The size of the data in bytes. + public int EncodeAlphaImageData(Image image, IMemoryOwner alphaData) where TPixel : unmanaged, IPixel { int width = image.Width; @@ -273,12 +274,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Convert image pixels to bgra array. this.ConvertPixelsToBgra(image, width, height); - // The image-stream does NOT contain any headers describing the image dimension, the dimension is already known. + // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known. this.EncodeStream(image); this.bitWriter.Finish(); - using var ms = new MemoryStream(); - this.bitWriter.WriteToStream(ms); - return ms.ToArray(); + int size = this.bitWriter.NumBytes(); + this.bitWriter.WriteToBuffer(alphaData.GetSpan()); + return size; } /// diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index d8bd8f759..4b7f3f5c8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -241,6 +241,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public int DqUvDc { get; private set; } + /// + /// Gets or sets the alpha data. + /// + private IMemoryOwner AlphaData { get; set; } + /// /// Gets the luma component. /// @@ -322,12 +327,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; this.bitWriter = new Vp8BitWriter(expectedSize, this); - // Extract and encode alpha data, if present. - byte[] alphaData = null; + // Extract and encode alpha channel data, if present. + int alphaDataSize = 0; if (hasAlpha) { // TODO: This can potentially run in an separate task. - alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression); + this.AlphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize); } // Stats-collection loop. @@ -363,7 +368,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Write bytes from the bitwriter buffer to the stream. ImageMetadata metadata = image.Metadata; metadata.SyncProfiles(); - this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha, alphaData, this.alphaCompression); + this.bitWriter.WriteEncodedImageToStream( + stream, + metadata.ExifProfile, + metadata.XmpProfile, + (uint)width, + (uint)height, + hasAlpha, + hasAlpha ? this.AlphaData.GetSpan().Slice(0, alphaDataSize) : Span.Empty, + this.alphaCompression); } /// @@ -372,6 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Y.Dispose(); this.U.Dispose(); this.V.Dispose(); + this.AlphaData?.Dispose(); } ///