Browse Source

alpha_color_cache.webp now decodes correctly

pull/1147/head
Brian Popow 6 years ago
parent
commit
45bf1064e3
  1. 64
      src/ImageSharp/Formats/WebP/AlphaDecoder.cs
  2. 2
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  3. 19
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
  4. 2
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
  5. 2
      tests/ImageSharp.Tests/TestImages.cs

64
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
/// </summary>
internal class AlphaDecoder : IDisposable
{
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Initializes a new instance of the <see cref="AlphaDecoder"/> class.
/// </summary>
@ -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<byte>(totalPixels);
this.AlphaFilterType = (WebPAlphaFilterType)filter;
this.Alpha = memoryAllocator.Allocate<byte>(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; }
/// <summary>
/// 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.
/// </summary>
public bool Use8BDecode { get; set; }
public bool Use8BDecode { get; }
/// <summary>
/// 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;
}
/// <summary>
/// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet.
/// </summary>
/// <param name="dec">The VP8L decoder.</param>
private void ExtractAlphaRows(Vp8LDecoder dec)
{
int numRowsToProcess = dec.Height;
int width = dec.Width;
Span<uint> pixels = dec.Pixels.Memory.Span;
Span<uint> input = pixels;
Span<byte> 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<byte> prev, Span<byte> input, Span<byte> 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)
/// <summary>
/// Row-processing for the special case when alpha data contains only one
/// transform (color indexing), and trivial non-green literals.
/// </summary>
/// <param name="hdr">The VP8L meta data.</param>
/// <returns>True, if alpha channel has one byte per pixel, otherwise 4.</returns>
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<HuffmanCode[]> 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<uint> argb, Span<byte> alpha, int size)
{
for (int i = 0; i < size; ++i)
{
alpha[i] = (byte)(argb[i] >> 8);
}
}
/// <inheritdoc/>
public void Dispose()
{

2
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;

19
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<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData);
for (int y = 0; y < decoder.Height; y++)
@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
private void DecodeImageData(Vp8LDecoder decoder, Span<uint> pixelData)
public void DecodeImageData(Vp8LDecoder decoder, Span<uint> pixelData)
{
int lastPixel = 0;
int width = decoder.Width;
@ -673,7 +671,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
/// <param name="decoder">The decoder holding the transformation infos.</param>
/// <param name="pixelData">The pixel data to apply the transformation.</param>
private void ApplyInverseTransforms(Vp8LDecoder decoder, Span<uint> pixelData)
/// <param name="memoryAllocator">The memory allocator is needed to allocate memory during the predictor transform.</param>
public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span<uint> pixelData, MemoryAllocator memoryAllocator)
{
List<Vp8LTransform> 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<uint> output = this.memoryAllocator.Allocate<uint>(pixelData.Length, AllocationOptions.Clean))
using (IMemoryOwner<uint> output = memoryAllocator.Allocate<uint>(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)
{

2
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{

2
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";

Loading…
Cancel
Save