diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index cf92e38762..8a276cebc3 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -2,45 +2,26 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; +using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { - internal class AlphaDecoder + /// + /// Implements decoding for lossy alpha chunks which may be compressed. + /// + internal class AlphaDecoder : IDisposable { - public int Width { get; } - - public int Height { get; } - - public WebPFilterBase Filter { get; } - - public WebPFilterType FilterType { get; } - - public int CropTop { get; } - - public int LastRow { get; set; } - - public Vp8LDecoder Vp8LDec { get; } - - public byte[] Alpha { get; } - - private int PreProcessing { get; } - - private bool Compressed { get; } - - private byte[] Data { get; } - - private WebPLosslessDecoder LosslessDecoder { get; } - /// - /// Although Alpha Channel requires only 1 byte per pixel, - /// sometimes Vp8LDecoder may need to allocate - /// 4 bytes per pixel internally during decode. + /// Initializes a new instance of the class. /// - public bool Use8BDecode { get; set; } - + /// The width of the image. + /// The height of the image. + /// The (maybe compressed) alpha data. + /// The first byte of the alpha image stream contains information on ow to decode the stream. + /// Used for allocating memory during decoding. public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator) { this.Width = width; @@ -64,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); } - this.FilterType = (WebPFilterType)filter; + this.AlphaFilterType = (WebPAlphaFilterType)filter; // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression. // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display. @@ -73,8 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); - // TODO: use memory allocator - this.Alpha = new byte[width * height]; + this.Alpha = memoryAllocator.Allocate(width * height); if (this.Compressed) { @@ -84,8 +64,74 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private int PrevLineOffset { get; set; } + /// + /// Gets the the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets the used filter type. + /// + public WebPAlphaFilterType AlphaFilterType { get; } + + /// + /// Gets or sets the last decoded row. + /// + public int LastRow { get; set; } + + /// + /// Gets or sets the row before the last decoded row. + /// + public int PrevRow { get; set; } + + /// + /// Gets information for decoding Vp8L compressed alpha data. + /// + public Vp8LDecoder Vp8LDec { get; } + + /// + /// Gets the decoded alpha data. + /// + public IMemoryOwner Alpha { get; } + + public int CropTop { get; } + + /// + /// Gets a value indicating whether pre-processing was used during compression. + /// 0: no pre-processing, 1: level reduction. + /// + private int PreProcessing { get; } + + /// + /// Gets a value indicating whether the alpha channel uses compression. + /// + private bool Compressed { get; } + + /// + /// Gets the (maybe compressed) alpha data. + /// + private byte[] Data { get; } + + /// + /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. + /// + private WebPLosslessDecoder LosslessDecoder { get; } + + /// + /// Gets or sets 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; } + /// + /// Decodes and filters the maybe compressed alpha data. + /// public void Decode() { if (this.Compressed is false) @@ -95,26 +141,27 @@ namespace SixLabors.ImageSharp.Formats.WebP WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } - if (this.FilterType == WebPFilterType.None) + Span alphaSpan = this.Alpha.Memory.Span; + if (this.AlphaFilterType == WebPAlphaFilterType.None) { - this.Data.AsSpan(0, this.Width * this.Height).CopyTo(this.Alpha); + this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan); return; } Span deltas = this.Data.AsSpan(); - Span dst = this.Alpha.AsSpan(); + Span dst = alphaSpan; Span prev = null; for (int y = 0; y < this.Height; ++y) { - switch (this.FilterType) + switch (this.AlphaFilterType) { - case WebPFilterType.Horizontal: + case WebPAlphaFilterType.Horizontal: HorizontalUnfilter(prev, deltas, dst, this.Width); break; - case WebPFilterType.Vertical: + case WebPAlphaFilterType.Vertical: VerticalUnfilter(prev, deltas, dst, this.Width); break; - case WebPFilterType.Gradient: + case WebPAlphaFilterType.Gradient: GradientUnfilter(prev, deltas, dst, this.Width); break; } @@ -130,6 +177,44 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Applies filtering to a set of rows. + /// + /// The first row index to start filtering. + /// The last row index for filtering. + /// The destination to store the filtered data. + /// The stride to use. + public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) + { + if (this.AlphaFilterType is WebPAlphaFilterType.None) + { + return; + } + + Span alphaSpan = this.Alpha.Memory.Span; + Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow); + for (int y = firstRow; y < lastRow; ++y) + { + switch (this.AlphaFilterType) + { + case WebPAlphaFilterType.Horizontal: + HorizontalUnfilter(prev, dst, dst, this.Width); + break; + case WebPAlphaFilterType.Vertical: + VerticalUnfilter(prev, dst, dst, this.Width); + break; + case WebPAlphaFilterType.Gradient: + GradientUnfilter(prev, dst, dst, this.Width); + break; + } + + prev = dst; + dst = dst.Slice(stride); + } + + this.PrevRow = lastRow - 1; + } + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { byte pred = (byte)(prev == null ? 0 : prev[0]); @@ -177,42 +262,48 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private static bool Is8bOptimizable(Vp8LMetadata hdr) + { + if (hdr.ColorCacheSize > 0) + { + return false; + } + + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (int i = 0; i < hdr.NumHTreeGroups; ++i) + { + List htrees = hdr.HTreeGroups[i].HTrees; + if (htrees[HuffIndex.Red][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Blue][0].Value > 0) + { + return false; + } + + if (htrees[HuffIndex.Alpha][0].Value > 0) + { + return false; + } + } + + return true; + } + private static int GradientPredictor(byte a, byte b, byte c) { int g = a + b - c; return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit } - // Taken from vp8l_dec.c AlphaApplyFilter - public void AlphaApplyFilter( - int firstRow, - int lastRow, - Span prevLine, - Span output, - int outputOffset, - int stride) + /// + public void Dispose() { - if (this.Filter is WebPFilterNone) - { - return; - } - - int prevLineOffset = this.PrevLineOffset; - - for (int y = firstRow; y < lastRow; y++) - { - this.Filter - .Unfilter( - prevLine, - prevLineOffset, - output, - outputOffset, - output, - outputOffset, - stride); - prevLineOffset = outputOffset; - outputOffset += stride; - } + this.Vp8LDec?.Dispose(); + this.Alpha?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs deleted file mode 100644 index 74b69f43ab..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterBase.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - // TODO from dsp.h - // public enum WebPFilterType - // { - // None = 0, - // Horizontal, - // Vertical, - // Gradient, - // Last = Gradient + 1, // end marker - // Best, // meta types - // Fast - // } - - internal abstract class WebPFilterBase - { - /// - /// - /// - /// nullable as prevLine is nullable in the original but Span'T can't be null. - /// - /// - /// - /// - /// - public abstract void Unfilter( - Span prevLine, - int? prevLineOffset, - Span preds, - int predsOffset, - Span currentLine, - int currentLineOffset, - int width); - - public abstract void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset); - - protected static void SanityCheck( - Span input, Span output, int width, int numRows, int height, int stride, int row) - { - Debug.Assert(input != null); - Debug.Assert(output != null); - Debug.Assert(width > 0); - Debug.Assert(height > 0); - Debug.Assert(stride > width); - Debug.Assert(row >= 0); - Debug.Assert(height > 0); - Debug.Assert(row + numRows <= height); - } - - protected static void PredictLine( - Span src, - int srcOffset, - Span pred, - int predOffset, - Span dst, - int dstOffset, - int length, - bool inverse) - { - if (inverse) - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] + pred[i]); - } - } - else - { - for (int i = 0; i < length; i++) - { - dst[i] = (byte)(src[i] - pred[i]); - } - } - } - - protected void UnfilterHorizontalOrVerticalCore( - byte pred, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); - pred = output[i]; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs deleted file mode 100644 index 27bca57706..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterGradient.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - class WebPFilterGradient : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffsetNullable, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - byte top = prevLine[prevLineOffset]; - byte topLeft = top; - byte left = top; - for (int i = 0; i < width; i++) - { - top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out - left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); - topLeft = top; - output[outputOffset + i] = left; - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - // calling (input, width, height, stride, 0, height, 0, output - int row = 0; - int numRows = height; - bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + numRows; - SanityCheck(input, output, width, numRows, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - output[outputOffset] = input[inputOffset]; - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - } - - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset - stride, - output, - outputOffset, - 1, - inverse); - - for (int w = 1; w < width; w++) - { - int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); - int signedPred = inverse ? pred : -pred; - output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); - } - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - - private static int GradientPredictor(byte a, byte b, byte c) - { - int g = a + b + c; - return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs deleted file mode 100644 index fdec37e5de..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterHorizontal.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - internal class WebPFilterHorizontal : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffsetNullable, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - byte pred = prevLineOffsetNullable is int prevLineOffset - ? prevLine[prevLineOffset] - : (byte)0; - - this.UnfilterHorizontalOrVerticalCore( - pred, - input, - inputOffset, - output, - outputOffset, - width); - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - int numRows = height; - int row = 0; - - const bool inverse = false; - - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, numRows, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row is 0) - { - // leftmost pixel is the same as Input for topmost scanline - output[0] = input[0]; - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - - row = 1; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - - // Filter line by line. - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset - stride, - output, - 0, - 1, - inverse); - PredictLine( - input, - inputOffset, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs deleted file mode 100644 index 04dfafe241..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterNone.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - // TODO: check if this is a filter or just a placeholder from the C implementation details - class WebPFilterNone : WebPFilterBase - { - public override void Unfilter( - Span prevLine, - int? prevLineOffset, - Span input, - int inputOffset, - Span output, - int outputOffset, - int width) - { - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs deleted file mode 100644 index 4b79ea5f54..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - internal enum WebPFilterType - { - None = 0, - - Horizontal = 1, - - Vertical = 2, - - Gradient = 3, - } -} diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs deleted file mode 100644 index 04eb2a5874..0000000000 --- a/src/ImageSharp/Formats/WebP/Filters/WebPFilterVertical.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Formats.WebP.Filters -{ - class WebPFilterVertical : WebPFilterBase - { - public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) - { - if (prevLineOffsetNullable is int prevLineOffset) - { - for (int i = 0; i < width; i++) - { - output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); - } - } - else - { - this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); - } - } - - public override void Filter( - Span input, - int inputOffset, - int width, - int height, - int stride, - Span output, - int outputOffset) - { - int row = 0; - bool inverse = false; - - // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 - int startOffset = row * stride; - int lastRow = row + height; - SanityCheck(input, output, width, height, height, stride, row); - inputOffset += startOffset; - outputOffset += startOffset; - Span preds; - int predsOffset; - - if (inverse) - { - preds = output; - predsOffset = outputOffset; - } - else - { - preds = input; - predsOffset = inputOffset; - } - - if (row == 0) - { - // Very first top-left pixel is copied. - output[0] = input[0]; - - // Rest of top scan-line is left-predicted: - PredictLine( - input, - inputOffset + 1, - preds, - predsOffset, - output, - outputOffset + 1, - width - 1, - inverse); - row = 1; - inputOffset += stride; - outputOffset += stride; - } - else - { - predsOffset -= stride; - } - - // Filter line-by-line. - while (row < lastRow) - { - PredictLine( - input, - inputOffset, - preds, - predsOffset, - output, - outputOffset, - width, - inverse); - row++; - predsOffset += stride; - inputOffset += stride; - outputOffset += stride; - } - } - } -} diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 21835c3d45..e004d86f25 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private int bits; /// - /// Max packed-read position on buffer. + /// Max packed-read position of the buffer. /// private uint bufferMax; @@ -54,6 +54,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The input stream to read from. /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. + /// The partition length. /// Start index in the data array. Defaults to 0. public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) { @@ -63,6 +64,12 @@ namespace SixLabors.ImageSharp.Formats.WebP this.InitBitreader(partitionLength, startPos); } + /// + /// Initializes a new instance of the class. + /// + /// The raw encoded image data. + /// The partition length. + /// Start index in the data array. Defaults to 0. public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0) { this.Data = imageData; diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs index 82a8cbba8a..36b561847b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Io.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs @@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Formats.WebP public int MbH { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the luma component. /// public Span Y { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the U chroma component. /// public Span U { get; set; } /// - /// Rows to copy (in YUV format). + /// Gets or sets the V chroma component. /// public Span V { get; set; } @@ -76,10 +76,5 @@ namespace SixLabors.ImageSharp.Formats.WebP public int ScaledWidth { get; set; } public int ScaledHeight { get; set; } - - /// - /// User data - /// - private object Opaque { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index f4114f5be9..38c5ffb64d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -8,7 +8,7 @@ using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.WebP { /// - /// A bit reader for VP8L streams. + /// A bit reader for reading lossless webp streams. /// internal class Vp8LBitReader : BitReaderBase { @@ -20,12 +20,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Number of bits prefetched. /// - private const int Vp8LLbits = 64; + private const int Lbits = 64; /// /// Minimum number of bytes ready after VP8LFillBitWindow. /// - private const int Vp8LWbits = 32; + private const int Wbits = 32; private readonly uint[] bitMask = { @@ -58,11 +58,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// private int bitPos; - /// - /// True if a bit was read past the end of buffer. - /// - public bool Eos; - /// /// Initializes a new instance of the class. /// @@ -117,6 +112,11 @@ namespace SixLabors.ImageSharp.Formats.WebP this.pos = length; } + /// + /// Gets or sets a value indicating whether a bit was read past the end of buffer. + /// + public bool Eos { get; set; } + /// /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. /// @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The pre-fetched bits. public ulong PrefetchBits() { - return this.value >> (this.bitPos & (Vp8LLbits - 1)); + return this.value >> (this.bitPos & (Lbits - 1)); } /// @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public void FillBitWindow() { - if (this.bitPos >= Vp8LWbits) + if (this.bitPos >= Wbits) { this.DoFillBitWindow(); } @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// True, if end of buffer was reached. public bool IsEndOfStream() { - return this.Eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); + return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); } private void DoFillBitWindow() @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.WebP while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; - this.value |= (ulong)this.Data[this.pos] << (Vp8LLbits - 8); + this.value |= (ulong)this.Data[this.pos] << (Lbits - 8); ++this.pos; this.bitPos -= 8; } diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs index 968d98b9d4..69b3c3ed74 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public IMemoryOwner Pixels { get; } - /// + /// public void Dispose() { this.Pixels.Dispose(); diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs new file mode 100644 index 0000000000..dfdc8281cf --- /dev/null +++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// Enum for the different alpha filter types. + /// + internal enum WebPAlphaFilterType + { + /// + /// No filtering. + /// + None = 0, + + /// + /// Horizontal filter. + /// + Horizontal = 1, + + /// + /// Vertical filter. + /// + Vertical = 2, + + /// + /// Gradient filter. + /// + Gradient = 3, + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index b64cc0a173..6ca54690fc 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -23,6 +22,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// internal sealed class WebPLosslessDecoder { + /// + /// A bit reader for reading lossless webp streams. + /// private readonly Vp8LBitReader bitReader; private static readonly int BitsSpecialMarker = 0x100; @@ -751,12 +753,11 @@ namespace SixLabors.ImageSharp.Formats.WebP int dist = this.PlaneCodeToDistance(width, distCode); if (pos >= dist && end - pos >= length) { - //CopyBlock8b(data + pos, dist, length); + data.Slice(pos - dist, length).CopyTo(data.Slice(pos)); } else { - // TODO: error? - break; + WebPThrowHelper.ThrowImageFormatException("error while decoding alpha data"); } pos += length; @@ -792,14 +793,14 @@ 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.FilterType is WebPFilterType.None || dec.FilterType is WebPFilterType.Horizontal) + int topRow = (dec.AlphaFilterType is WebPAlphaFilterType.None || dec.AlphaFilterType is WebPAlphaFilterType.Horizontal) ? dec.CropTop : dec.LastRow; int firstRow = (dec.LastRow < topRow) ? topRow : dec.LastRow; if (lastRow > firstRow) { // Special method for paletted alpha data. We only process the cropped area. - Span output = dec.Alpha.AsSpan(); + Span output = dec.Alpha.Memory.Span; Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); Span dst = output.Slice(dec.Width * firstRow); @@ -808,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: check if any and the correct transform is present Vp8LTransform transform = dec.Vp8LDec.Transforms[0]; this.ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); - //dec.AlphaApplyFilter(firstRow, lastRow, dst, width); + dec.AlphaApplyFilter(firstRow, lastRow, dst, dec.Width); } dec.LastRow = lastRow; @@ -867,37 +868,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private static bool Is8bOptimizable(Vp8LMetadata hdr) - { - if (hdr.ColorCacheSize > 0) - { - return false; - } - - // When the Huffman tree contains only one symbol, we can skip the - // call to ReadSymbol() for red/blue/alpha channels. - for (int i = 0; i < hdr.NumHTreeGroups; ++i) - { - List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Blue][0].Value > 0) - { - return false; - } - - if (htrees[HuffIndex.Alpha][0].Value > 0) - { - return false; - } - } - - return true; - } - private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) { int numBits = decoder.Metadata.HuffmanSubSampleBits; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 9c906a8281..2a2effd921 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Linq; using System.Runtime.InteropServices; @@ -13,10 +14,21 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal sealed class WebPLossyDecoder { + /// + /// A bit reader for reading lossy webp streams. + /// private readonly Vp8BitReader bitReader; + /// + /// Used for allocating memory during processing operations. + /// private readonly MemoryAllocator memoryAllocator; + /// + /// Initializes a new instance of the class. + /// + /// Bitreader to read from the stream. + /// Used for allocating memory during processing operations. public WebPLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; @@ -77,11 +89,18 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, byte[] alpha = null) + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels, IMemoryOwner alpha = null) where TPixel : struct, IPixel { TPixel color = default; - bool hasAlpha = alpha != null; + bool hasAlpha = false; + Span alphaSpan = null; + if (alpha != null) + { + hasAlpha = true; + alphaSpan = alpha.Memory.Span; + } + for (int y = 0; y < height; y++) { Span pixelRow = pixels.GetRowSpan(y); @@ -96,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO: use bulk conversion here. if (hasAlpha) { - byte a = alpha[offset]; + byte a = alphaSpan[offset]; color.FromBgra32(new Bgra32(r, g, b, a)); } else @@ -781,26 +800,6 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private Vp8Profile DecodeProfile(int version) - { - switch (version) - { - case 0: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bicubic, LoopFilter = LoopFilter.Complex }; - case 1: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.Simple }; - case 2: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.Bilinear, LoopFilter = LoopFilter.None }; - case 3: - return new Vp8Profile { ReconstructionFilter = ReconstructionFilter.None, LoopFilter = LoopFilter.None }; - default: - // Reserved for future use in Spec. - // https://tools.ietf.org/html/rfc6386#page-30 - WebPThrowHelper.ThrowNotSupportedException($"unsupported VP8 version {version} found"); - return new Vp8Profile(); - } - } - private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) { Vp8MacroBlock left = dec.LeftMacroBlock; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs index 5041f35ae7..80362ec72a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs @@ -175,6 +175,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel {