diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
index 06c114c71f..571da5bb24 100644
--- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -3,18 +3,98 @@
using System;
using System.Buffers;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp
{
///
- /// Encodes the alpha channel data.
- /// Data is either compressed as lossless webp image or uncompressed.
+ /// Methods for encoding the alpha data of a VP8 image.
///
internal static class AlphaEncoder
{
- public static byte[] EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator)
+ ///
+ /// Encodes the alpha channel data.
+ /// Data is either compressed as lossless webp image or uncompressed.
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// 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)
+ where TPixel : unmanaged, IPixel
+ {
+ byte[] alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
+ int width = image.Width;
+ int height = image.Height;
+ if (compress)
+ {
+ WebpEncodingMethod effort = WebpEncodingMethod.Default;
+ int quality = 8 * (int)effort;
+ using var lossLessEncoder = new Vp8LEncoder(
+ memoryAllocator,
+ configuration,
+ width,
+ height,
+ quality,
+ effort,
+ WebpTransparentColorMode.Preserve,
+ false,
+ 0);
+
+ // 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);
+
+ return lossLessEncoder.EncodeAlphaImageData(alphaAsImage);
+ }
+
+ return alphaData;
+ }
+
+ ///
+ /// Store the transparency in the green channel.
+ ///
+ /// The pixel format.
+ /// 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)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+ var alphaAsImage = new Image(width, height);
+
+ for (int y = 0; y < height; y++)
+ {
+ Memory rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y);
+ Span pixelRow = rowBuffer.Span;
+ Span alphaRow = alphaData.AsSpan(y * width, width);
+ for (int x = 0; x < width; x++)
+ {
+ // Leave A/R/B channels zero'd.
+ pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
+ }
+ }
+
+ return alphaAsImage;
+ }
+
+ ///
+ /// Extract the alpha data of the image.
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// 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)
where TPixel : unmanaged, IPixel
{
Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer;
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index b33f7987c1..84c9d3f133 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -146,7 +146,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
///
/// The stream to write to.
/// The alpha channel data bytes.
- protected void WriteAlphaChunk(Stream stream, byte[] dataBytes)
+ /// Indicates, if the alpha channel data is compressed.
+ protected void WriteAlphaChunk(Stream stream, byte[] dataBytes, bool alphaDataIsCompressed)
{
DebugGuard.NotNull(dataBytes, nameof(dataBytes));
@@ -157,9 +158,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
- // Write flags, all zero for now.
- stream.WriteByte(0);
+ byte flags = 0;
+ if (alphaDataIsCompressed)
+ {
+ flags |= 1;
+ }
+ stream.WriteByte(flags);
stream.Write(dataBytes);
// Add padding byte if needed.
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index 3f16fc89bc..577a87e6a1 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -410,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// The height of the image.
/// Flag indicating, if a alpha channel is present.
/// The alpha channel data.
- public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData)
+ /// 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)
{
bool isVp8X = false;
byte[] exifBytes = null;
@@ -461,7 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
- this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData);
+ this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData, alphaDataIsCompressed);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@@ -660,7 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
ExifProfile exifProfile,
XmpProfile xmpProfile,
bool hasAlpha,
- byte[] alphaData)
+ byte[] alphaData,
+ bool alphaDataIsCompressed)
{
this.WriteRiffHeader(stream, riffSize);
@@ -670,7 +672,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
if (hasAlpha)
{
- this.WriteAlphaChunk(stream, alphaData);
+ this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
}
}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index e9dce913a3..797d0794f9 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public Vp8LHashChain HashChain { get; }
///
- /// Encodes the image to the specified stream from the .
+ /// Encodes the image as lossless webp to the specified stream.
///
/// The pixel format.
/// The to encode from.
@@ -236,10 +236,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public void Encode(Image image, Stream stream)
where TPixel : unmanaged, IPixel
{
- image.Metadata.SyncProfiles();
int width = image.Width;
int height = image.Height;
+ ImageMetadata metadata = image.Metadata;
+ metadata.SyncProfiles();
+
// Convert image pixels to bgra array.
bool hasAlpha = this.ConvertPixelsToBgra(image, width, height);
@@ -253,11 +255,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.EncodeStream(image);
// 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);
}
+ ///
+ /// Encodes the alpha image data using the webp lossless compression.
+ ///
+ /// The type of the pixel.
+ /// The to encode from.
+ /// The encoded alpha stream.
+ public byte[] EncodeAlphaImageData(Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+
+ // 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.
+ this.EncodeStream(image);
+ this.bitWriter.Finish();
+ using var ms = new MemoryStream();
+ this.bitWriter.WriteToStream(ms);
+ return ms.ToArray();
+ }
+
///
/// Writes the image size to the bitwriter buffer.
///
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 48af53960c..d8bd8f759c 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -71,10 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
private int uvAlpha;
- ///
- /// Scratch buffer to reduce allocations.
- ///
- private readonly int[] scratch = new int[16];
+ private readonly bool alphaCompression;
private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
@@ -105,6 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// Number of entropy-analysis passes (in [1..10]).
/// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
/// The spatial noise shaping. 0=off, 100=maximum.
+ /// If true, the alpha channel will be compressed with the lossless compression.
public Vp8Encoder(
MemoryAllocator memoryAllocator,
Configuration configuration,
@@ -114,7 +112,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
WebpEncodingMethod method,
int entropyPasses,
int filterStrength,
- int spatialNoiseShaping)
+ int spatialNoiseShaping,
+ bool alphaCompression)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
@@ -125,6 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10);
this.filterStrength = Numerics.Clamp(filterStrength, 0, 100);
this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100);
+ this.alphaCompression = alphaCompression;
this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll
: method >= WebpEncodingMethod.Level5 ? Vp8RdLevel.RdOptTrellis
: method >= WebpEncodingMethod.Level3 ? Vp8RdLevel.RdOptBasic
@@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
- alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator);
+ alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression);
}
// Stats-collection loop.
@@ -363,7 +363,7 @@ 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.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha, alphaData, this.alphaCompression);
}
///
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index 195fa62bdc..deed08b729 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -22,7 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Webp
private readonly MemoryAllocator memoryAllocator;
///
- /// TODO: not used at the moment.
/// Indicating whether the alpha plane should be compressed with Webp lossless format.
///
private readonly bool alphaCompression;
@@ -100,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
///
- /// Encodes the image to the specified stream from the .
+ /// Encodes the image as webp to the specified stream.
///
/// The pixel format.
/// The to encode from.
@@ -149,7 +148,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.method,
this.entropyPasses,
this.filterStrength,
- this.spatialNoiseShaping);
+ this.spatialNoiseShaping,
+ this.alphaCompression);
enc.Encode(image, stream);
}
}