diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
new file mode 100644
index 0000000000..06c114c71f
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+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.
+ ///
+ internal static class AlphaEncoder
+ {
+ public static byte[] EncodeAlpha(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];
+
+ 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 alphaData;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index ac039be797..b33f7987c1 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// 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 +103,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
return metaChunkSize;
}
+ ///
+ /// Calculates the chunk size of a alpha chunk.
+ ///
+ /// The alpha chunk bytes.
+ /// The alpha data chunk size in bytes.
+ protected uint AlphaChunkSize(byte[] 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 +141,34 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
}
}
+ ///
+ /// Writes the alpha chunk to the stream.
+ ///
+ /// The stream to write to.
+ /// The alpha channel data bytes.
+ protected void WriteAlphaChunk(Stream stream, byte[] dataBytes)
+ {
+ DebugGuard.NotNull(dataBytes, nameof(dataBytes));
+
+ 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);
+
+ // Write flags, all zero for now.
+ stream.WriteByte(0);
+
+ 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..3f16fc89bc 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -409,7 +409,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
/// 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.
+ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha, byte[] alphaData)
{
bool isVp8X = false;
byte[] exifBytes = null;
@@ -418,7 +419,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (exifProfile != null)
{
isVp8X = true;
- riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes);
}
@@ -426,11 +426,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
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 +461,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);
+ this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@@ -639,7 +649,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.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,
+ byte[] alphaData)
{
this.WriteRiffHeader(stream, riffSize);
@@ -647,6 +668,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
if (isVp8X)
{
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
+ if (hasAlpha)
+ {
+ this.WriteAlphaChunk(stream, alphaData);
+ }
}
this.WriteVp8Header(stream, vp8Size);
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 0222320502..48af53960c 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
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;
@@ -322,8 +322,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock;
this.bitWriter = new Vp8BitWriter(expectedSize, this);
- // TODO: EncodeAlpha();
- bool hasAlpha = false;
+ // Extract and encode alpha data, if present.
+ byte[] alphaData = null;
+ if (hasAlpha)
+ {
+ // TODO: This can potentially run in an separate task.
+ alphaData = AlphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator);
+ }
// Stats-collection loop.
this.StatLoop(width, height, yStride, uvStride);
@@ -358,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);
+ this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha, alphaData);
}
///
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 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
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 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
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 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
else
{
AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width);
+ hasAlpha = true;
}
ConvertRgbaToUv(tmpRgbSpan, u.Slice(uvRowIndex * uvWidth), v.Slice(uvRowIndex * uvWidth), uvWidth);
}
+
+ return hasAlpha;
}
///