diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
index 9d0b68e79..0163d74c0 100644
--- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.WebP
@@ -14,6 +15,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
internal class AlphaDecoder : IDisposable
{
+ private readonly MemoryAllocator memoryAllocator;
+
///
/// Initializes a new instance of the class.
///
@@ -28,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.Width = width;
this.Height = height;
this.Data = data;
+ this.memoryAllocator = memoryAllocator;
this.LastRow = 0;
+ int totalPixels = width * height;
var compression = (AlphaCompressionMethod)(alphaChunkHeader & 0x03);
if (compression != AlphaCompressionMethod.NoCompression && compression != AlphaCompressionMethod.WebPLosslessCompression)
@@ -45,8 +50,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found");
}
+ this.Alpha = memoryAllocator.Allocate(totalPixels);
this.AlphaFilterType = (WebPAlphaFilterType)filter;
- this.Alpha = memoryAllocator.Allocate(width * height);
this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator);
if (this.Compressed)
@@ -54,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
var bitReader = new Vp8LBitReader(data);
this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration);
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
+ this.Use8BDecode = Is8BOptimizable(this.Vp8LDec.Metadata);
}
}
@@ -110,11 +116,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
private WebPLosslessDecoder LosslessDecoder { get; }
///
- /// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding.
+ /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding.
/// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate
/// 4 bytes per pixel internally during decode.
///
- public bool Use8BDecode { get; set; }
+ public bool Use8BDecode { get; }
///
/// Decodes and filters the maybe compressed alpha data.
@@ -162,7 +168,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
else
{
- this.LosslessDecoder.DecodeAlphaData(this);
+ if (this.Use8BDecode)
+ {
+ this.LosslessDecoder.DecodeAlphaData(this);
+ }
+ else
+ {
+ this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
+ this.ExtractAlphaRows(this.Vp8LDec);
+ }
}
}
@@ -204,6 +218,25 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.PrevRow = lastRow - 1;
}
+ ///
+ /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet.
+ ///
+ /// The VP8L decoder.
+ private void ExtractAlphaRows(Vp8LDecoder dec)
+ {
+ int numRowsToProcess = dec.Height;
+ int width = dec.Width;
+ Span pixels = dec.Pixels.Memory.Span;
+ Span input = pixels;
+ Span output = this.Alpha.Memory.Span;
+
+ // Extract alpha (which is stored in the green plane).
+ int pixelCount = width * numRowsToProcess;
+ WebPLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator);
+ this.AlphaApplyFilter(0, numRowsToProcess, output, width);
+ ExtractGreen(input, output, pixelCount);
+ }
+
private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width)
{
byte pred = (byte)(prev == null ? 0 : prev[0]);
@@ -252,7 +285,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
- private static bool Is8bOptimizable(Vp8LMetadata hdr)
+ ///
+ /// Row-processing for the special case when alpha data contains only one
+ /// transform (color indexing), and trivial non-green literals.
+ ///
+ /// The VP8L meta data.
+ /// True, if alpha channel has one byte per pixel, otherwise 4.
+ private static bool Is8BOptimizable(Vp8LMetadata hdr)
{
if (hdr.ColorCacheSize > 0)
{
@@ -264,17 +303,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
for (int i = 0; i < hdr.NumHTreeGroups; ++i)
{
List htrees = hdr.HTreeGroups[i].HTrees;
- if (htrees[HuffIndex.Red][0].Value > 0)
+ if (htrees[HuffIndex.Red][0].BitsUsed > 0)
{
return false;
}
- if (htrees[HuffIndex.Blue][0].Value > 0)
+ if (htrees[HuffIndex.Blue][0].BitsUsed > 0)
{
return false;
}
- if (htrees[HuffIndex.Alpha][0].Value > 0)
+ if (htrees[HuffIndex.Alpha][0].BitsUsed > 0)
{
return false;
}
@@ -290,6 +329,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit
}
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static void ExtractGreen(Span argb, Span alpha, int size)
+ {
+ for (int i = 0; i < size; ++i)
+ {
+ alpha[i] = (byte)(argb[i] >> 8);
+ }
+ }
+
///
public void Dispose()
{
diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs
index 4908c133b..78519da82 100644
--- a/src/ImageSharp/Formats/WebP/WebPConstants.cs
+++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs
@@ -85,6 +85,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
public const uint ArgbBlack = 0xff000000;
+ public const int NumArgbCacheRows = 16;
+
public const int NumLiteralCodes = 256;
public const int NumLengthCodes = 24;
diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
index 6783c3f58..2c0b69911 100644
--- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
@@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
private static readonly int BitsSpecialMarker = 0x100;
- private static readonly int NumArgbCacheRows = 16;
-
private static readonly uint PackedNonLiteralCode = 0;
private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length;
@@ -191,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
int width = decoder.Width;
// Apply reverse transformations, if any are present.
- this.ApplyInverseTransforms(decoder, pixelData);
+ ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator);
Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData);
for (int y = 0; y < decoder.Height; y++)
@@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
- private void DecodeImageData(Vp8LDecoder decoder, Span pixelData)
+ public void DecodeImageData(Vp8LDecoder decoder, Span pixelData)
{
int lastPixel = 0;
int width = decoder.Width;
@@ -673,7 +671,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// The decoder holding the transformation infos.
/// The pixel data to apply the transformation.
- private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData)
+ /// The memory allocator is needed to allocate memory during the predictor transform.
+ public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator)
{
List transforms = decoder.Transforms;
for (int i = transforms.Count - 1; i >= 0; i--)
@@ -682,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
switch (transformType)
{
case Vp8LTransformType.PredictorTransform:
- using (IMemoryOwner output = this.memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean))
+ using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean))
{
LosslessUtils.PredictorInverseTransform(transforms[i], pixelData, output.GetSpan());
}
@@ -744,7 +743,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
col = 0;
++row;
- if (row <= lastRow && (row % NumArgbCacheRows is 0))
+ if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0))
{
this.ExtractPalettedAlphaRows(dec, row);
}
@@ -774,7 +773,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
col -= width;
++row;
- if (row <= lastRow && (row % NumArgbCacheRows is 0))
+ if (row <= lastRow && (row % WebPConstants.NumArgbCacheRows is 0))
{
this.ExtractPalettedAlphaRows(dec, row);
}
@@ -802,8 +801,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
// For vertical and gradient filtering, we need to decode the part above the
// cropTop row, in order to have the correct spatial predictors.
int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal)
- ? dec.CropTop
- : dec.LastRow;
+ ? dec.CropTop
+ : dec.LastRow;
int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow;
if (lastRow > firstRow)
{
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
index 60ff49049..4c96b1e41 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
@@ -179,6 +179,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
[WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.Alpha1, PixelTypes.Rgba32)]
+ [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)]
+ [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 7c2f4ebbf..55a9e5e94 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -524,7 +524,7 @@ namespace SixLabors.ImageSharp.Tests
// Lossy images with an alpha channel.
public const string Alpha1 = "WebP/lossy_alpha1.webp";
public const string Alpha2 = "WebP/lossy_alpha2.webp";
- public const string Alpha3 = "WebP/lossy_alpha3.webp";
+ public const string Alpha3 = "WebP/alpha_color_cache.webp";
public const string AlphaNoCompression = "WebP/alpha_no_compression.webp";
public const string AlphaNoCompressionNoFilter = "WebP/alpha_filter_0_method_0.webp";
public const string AlphaCompressedNoFilter = "WebP/alpha_filter_0_method_1.webp";