diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
new file mode 100644
index 0000000000..1019073d87
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -0,0 +1,133 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+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
+{
+ ///
+ /// Methods for encoding the alpha data of a VP8 image.
+ ///
+ internal class AlphaEncoder : IDisposable
+ {
+ private IMemoryOwner alphaData;
+
+ ///
+ /// 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 size in bytes of the alpha data.
+ /// The encoded alpha data.
+ public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+ this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
+
+ 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, this.alphaData.GetSpan());
+
+ size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData);
+
+ return this.alphaData;
+ }
+
+ size = width * height;
+ return this.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, Span 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.Slice(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 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;
+ IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height);
+ Span alphaData = alphaDataBuffer.GetSpan();
+
+ using IMemoryOwner rowBuffer = memoryAllocator.Allocate(width);
+ Span rgbaRow = rowBuffer.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ Span rowSpan = imageBuffer.DangerousGetRowSpan(y);
+ PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow);
+ int offset = y * width;
+ for (int x = 0; x < width; x++)
+ {
+ alphaData[offset + x] = rgbaRow[x].A;
+ }
+ }
+
+ return alphaDataBuffer;
+ }
+
+ ///
+ public void Dispose() => this.alphaData?.Dispose();
+ }
+}
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index ac039be797..fc1accfdee 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -47,6 +47,12 @@ internal abstract class BitWriterBase
/// 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.
///
@@ -94,7 +100,7 @@ protected void WriteRiffHeader(Stream stream, uint riffSize)
/// Calculates the chunk size of EXIF or XMP metadata.
///
/// The metadata profile bytes.
- /// The exif chunk size in bytes.
+ /// The metadata chunk size in bytes.
protected uint MetadataChunkSize(byte[] metadataBytes)
{
uint metaSize = (uint)metadataBytes.Length;
@@ -103,6 +109,19 @@ protected uint MetadataChunkSize(byte[] metadataBytes)
return metaChunkSize;
}
+ ///
+ /// Calculates the chunk size of a alpha chunk.
+ ///
+ /// The alpha chunk bytes.
+ /// The alpha data chunk size in bytes.
+ protected uint AlphaChunkSize(Span alphaBytes)
+ {
+ uint alphaSize = (uint)alphaBytes.Length + 1;
+ uint alphaChunkSize = WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
+
+ return alphaChunkSize;
+ }
+
///
/// Writes a metadata profile (EXIF or XMP) to the stream.
///
@@ -128,6 +147,37 @@ protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChu
}
}
+ ///
+ /// Writes the alpha chunk to the stream.
+ ///
+ /// The stream to write to.
+ /// The alpha channel data bytes.
+ /// Indicates, if the alpha channel data is compressed.
+ protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed)
+ {
+ uint size = (uint)dataBytes.Length + 1;
+ Span buf = this.scratchBuffer.AsSpan(0, 4);
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
+ stream.Write(buf);
+
+ byte flags = 0;
+ if (alphaDataIsCompressed)
+ {
+ flags |= 1;
+ }
+
+ stream.WriteByte(flags);
+ stream.Write(dataBytes);
+
+ // Add padding byte if needed.
+ if ((size & 1) == 1)
+ {
+ stream.WriteByte(0);
+ }
+ }
+
///
/// Writes a VP8X header to the stream.
///
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index 4e91bedb0b..fa6e09d875 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -409,7 +409,17 @@ private void Flush()
/// The width of the image.
/// The height of the image.
/// Flag indicating, if a alpha channel is present.
- public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha)
+ /// 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,
+ Span alphaData,
+ bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
@@ -418,7 +428,6 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, Xm
if (exifProfile != null)
{
isVp8X = true;
- riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes);
}
@@ -426,11 +435,21 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, Xm
if (xmpProfile != null)
{
isVp8X = true;
- riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes);
}
+ if (hasAlpha)
+ {
+ isVp8X = true;
+ riffSize += this.AlphaChunkSize(alphaData);
+ }
+
+ if (isVp8X)
+ {
+ riffSize += ExtendedFileChunkSize;
+ }
+
this.Finish();
uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh;
@@ -451,7 +470,7 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, Xm
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
- this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha);
+ this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData, alphaDataIsCompressed);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@@ -639,7 +658,19 @@ private void CodeIntraModes(Vp8BitWriter bitWriter)
while (it.Next());
}
- private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile, XmpProfile xmpProfile, bool hasAlpha)
+ private void WriteWebpHeaders(
+ Stream stream,
+ uint size0,
+ uint vp8Size,
+ uint riffSize,
+ bool isVp8X,
+ uint width,
+ uint height,
+ ExifProfile exifProfile,
+ XmpProfile xmpProfile,
+ bool hasAlpha,
+ Span alphaData,
+ bool alphaDataIsCompressed)
{
this.WriteRiffHeader(stream, riffSize);
@@ -647,6 +678,10 @@ private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riff
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
+ if (hasAlpha)
+ {
+ this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
+ }
}
this.WriteVp8Header(stream, vp8Size);
diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
index d119d3031f..57ec32753d 100644
--- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
@@ -31,6 +31,7 @@ internal interface IWebpEncoderOptions
///
/// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format.
+ /// Defaults to true.
///
bool UseAlphaCompression { get; }
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index e9dce913a3..30d65562ae 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -228,7 +228,7 @@ public Vp8LEncoder(
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 @@ public Vp8LEncoder(
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,42 @@ public void Encode(Image image, Stream stream)
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 destination buffer to write the encoded alpha data to.
+ /// The size of the compressed data in bytes.
+ /// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed.
+ ///
+ public int EncodeAlphaImageData(Image image, IMemoryOwner alphaData)
+ where TPixel : unmanaged, IPixel
+ {
+ int width = image.Width;
+ int height = image.Height;
+ int pixelCount = width * height;
+
+ // Convert image pixels to bgra array.
+ this.ConvertPixelsToBgra(image, width, height);
+
+ // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
+ this.EncodeStream(image);
+ this.bitWriter.Finish();
+ int size = this.bitWriter.NumBytes();
+ if (size >= pixelCount)
+ {
+ // Compressing would not yield in smaller data -> leave the data uncompressed.
+ return pixelCount;
+ }
+
+ this.bitWriter.WriteToBuffer(alphaData.GetSpan());
+ return size;
+ }
+
///
/// 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 0222320502..695359e5ea 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -71,12 +71,7 @@ internal class Vp8Encoder : IDisposable
///
private int uvAlpha;
- ///
- /// Scratch buffer to reduce allocations.
- ///
- private readonly int[] scratch = new int[16];
-
- private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 };
+ private readonly bool alphaCompression;
private const int NumMbSegments = 4;
@@ -105,6 +100,7 @@ internal class Vp8Encoder : IDisposable
/// 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 +110,8 @@ public Vp8Encoder(
WebpEncodingMethod method,
int entropyPasses,
int filterStrength,
- int spatialNoiseShaping)
+ int spatialNoiseShaping,
+ bool alphaCompression)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
@@ -125,6 +122,7 @@ public Vp8Encoder(
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
@@ -174,6 +172,9 @@ public Vp8Encoder(
this.ResetBoundaryPredictions();
}
+ // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs.
+ private static ReadOnlySpan AverageBytesPerMb => new byte[] { 50, 24, 16, 9, 7, 5, 3, 2 };
+
public int BaseQuant { get; set; }
///
@@ -297,10 +298,11 @@ public void Encode(Image image, Stream stream)
{
int width = image.Width;
int height = image.Height;
+ int pixelCount = width * height;
Span y = this.Y.GetSpan();
Span u = this.U.GetSpan();
Span v = this.V.GetSpan();
- YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
+ bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
int yStride = width;
int uvStride = (yStride + 1) >> 1;
@@ -318,12 +320,26 @@ public void Encode(Image image, Stream stream)
this.SetLoopParams(this.quality);
// Initialize the bitwriter.
- int averageBytesPerMacroBlock = this.averageBytesPerMb[this.BaseQuant >> 4];
+ int averageBytesPerMacroBlock = AverageBytesPerMb[this.BaseQuant >> 4];
int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock;
this.bitWriter = new Vp8BitWriter(expectedSize, this);
- // TODO: EncodeAlpha();
- bool hasAlpha = false;
+ // Extract and encode alpha channel data, if present.
+ int alphaDataSize = 0;
+ bool alphaCompressionSucceeded = false;
+ using var alphaEncoder = new AlphaEncoder();
+ Span alphaData = Span.Empty;
+ if (hasAlpha)
+ {
+ // TODO: This can potentially run in an separate task.
+ IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize);
+ alphaData = encodedAlphaData.GetSpan();
+ if (alphaDataSize < pixelCount)
+ {
+ // Only use compressed data, if the compressed data is actually smaller then the uncompressed data.
+ alphaCompressionSucceeded = true;
+ }
+ }
// Stats-collection loop.
this.StatLoop(width, height, yStride, uvStride);
@@ -358,7 +374,15 @@ public void Encode(Image image, Stream stream)
// 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);
+ this.bitWriter.WriteEncodedImageToStream(
+ stream,
+ metadata.ExifProfile,
+ metadata.XmpProfile,
+ (uint)width,
+ (uint)height,
+ hasAlpha,
+ alphaData,
+ this.alphaCompression && alphaCompressionSucceeded);
}
///
diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
index 7a731f4284..878bebd105 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
@@ -318,7 +318,8 @@ private static void PackAndStore(Vector128 a, Vector128 b, Vector128
/// Span to store the luma component of the image.
/// Span to store the u component of the image.
/// Span to store the v component of the image.
- public static void ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
+ /// true, if the image contains alpha data.
+ public static bool ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v)
where TPixel : unmanaged, IPixel
{
Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer;
@@ -335,6 +336,7 @@ public static void ConvertRgbToYuv(Image image, Configuration co
Span bgraRow1 = bgraRow1Buffer.GetSpan();
int uvRowIndex = 0;
int rowIndex;
+ bool hasAlpha = false;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{
Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
@@ -343,6 +345,10 @@ public static void ConvertRgbToYuv(Image image, Configuration co
PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1);
+ if (rowsHaveAlpha)
+ {
+ hasAlpha = true;
+ }
// Downsample U/V planes, two rows at a time.
if (!rowsHaveAlpha)
@@ -375,10 +381,13 @@ public static void ConvertRgbToYuv(Image image, Configuration co
else
{
AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width);
+ hasAlpha = true;
}
ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth);
}
+
+ return hasAlpha;
}
///
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
index bdcbb194b1..d0b60d18cd 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
@@ -24,7 +24,7 @@ public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions
public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default;
///
- public bool UseAlphaCompression { get; set; }
+ public bool UseAlphaCompression { get; set; } = true;
///
public int EntropyPasses { get; set; } = 1;
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index 195fa62bdc..0fbff81fe4 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -22,8 +22,8 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
private readonly MemoryAllocator memoryAllocator;
///
- /// TODO: not used at the moment.
/// Indicating whether the alpha plane should be compressed with Webp lossless format.
+ /// Defaults to true.
///
private readonly bool alphaCompression;
@@ -100,7 +100,7 @@ public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAlloca
}
///
- /// 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 +149,8 @@ public void Encode(Image image, Stream stream, CancellationToken
this.method,
this.entropyPasses,
this.filterStrength,
- this.spatialNoiseShaping);
+ this.spatialNoiseShaping,
+ this.alphaCompression);
enc.Encode(image, stream);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
index 7043549b22..7c74429edc 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
@@ -167,18 +167,6 @@ public void Encode_Lossless_WithPreserveTransparentColor_Works(TestImage
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
}
- [Theory]
- [WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
- [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
- public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
- using Image image = provider.GetImage();
-
- var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
- image.VerifyEncoder(provider, "webp", string.Empty, encoder);
- }
-
[Fact]
public void Encode_Lossless_OneByOnePixel_Works()
{
@@ -279,6 +267,34 @@ public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageP
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality));
}
+ [Theory]
+ [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, false)]
+ [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, true)]
+ public void Encode_Lossy_WithAlpha_Works(TestImageProvider provider, bool compressed)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new WebpEncoder()
+ {
+ FileFormat = WebpFileFormatType.Lossy,
+ UseAlphaCompression = compressed
+ };
+
+ using Image image = provider.GetImage();
+ image.VerifyEncoder(provider, "webp", $"with_alpha_compressed_{compressed}", encoder, ImageComparer.Tolerant(0.04f));
+ }
+
+ [Theory]
+ [WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
+ [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
+ public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+
+ var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
+ image.VerifyEncoder(provider, "webp", string.Empty, encoder);
+ }
+
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index bce22799da..a73d262433 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -15,6 +15,7 @@ public static class TestImages
{
public static class Png
{
+ public const string Transparency = "Png/transparency.png";
public const string P1 = "Png/pl.png";
public const string Pd = "Png/pd.png";
public const string Blur = "Png/blur.png";
diff --git a/tests/Images/Input/Png/transparency.png b/tests/Images/Input/Png/transparency.png
new file mode 100644
index 0000000000..26de0f2d1a
--- /dev/null
+++ b/tests/Images/Input/Png/transparency.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:843bea4db378f52935e2f19f60d289df8ebe20ddde3977c63225f1d58a10bd62
+size 48119