From 3b3769248ab3dc834b7561afba0e0c1b4aca930b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Mar 2020 19:55:23 +0100 Subject: [PATCH] Start implementing alpha decoding: uncompressed alpha without filtering works so far --- src/ImageSharp/Formats/WebP/AlphaDecoder.cs | 58 +++++++++++++++---- .../Formats/WebP/Filters/WebPFilterType.cs | 16 +++++ src/ImageSharp/Formats/WebP/LoopFilter.cs | 2 + .../Formats/WebP/WebPDecoderCore.cs | 13 ++++- src/ImageSharp/Formats/WebP/WebPFeatures.cs | 7 ++- .../Formats/WebP/WebPLossyDecoder.cs | 35 ++++++++--- 6 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs index d2c8d583f1..f6ee5ec16a 100644 --- a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs @@ -4,24 +4,27 @@ using System; using SixLabors.ImageSharp.Formats.WebP.Filters; +using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats.WebP { - internal ref struct AlphaDecoder + internal class AlphaDecoder { public int Width { get; set; } public int Height { get; set; } - public int Method { get; set; } - public WebPFilterBase Filter { get; set; } - public int PreProcessing { get; set; } + private WebPFilterType FilterType { get; } + + private int PreProcessing { get; } + + private bool Compressed { get; } - public Vp8LDecoder Vp8LDec { get; set; } + private byte[] Data { get; } - public Vp8Io Io { get; set; } + private Vp8LDecoder Vp8LDec { get; set; } /// /// Although Alpha Channel requires only 1 byte per pixel, @@ -30,22 +33,57 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool Use8BDecode { get; set; } - // last output row (or null) - private Span PrevLine { get; set; } + public AlphaDecoder(int width, int height, byte[] data) + { + this.Width = width; + this.Height = height; + this.Data = data; + + // Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format) + int method = data[0] & 0x03; + if (method < 0 || method > 1) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found"); + } + + this.Compressed = !(method is 0); + + // The filtering method used. Only values between 0 and 3 are valid. + int filter = (data[0] >> 2) & 0x03; + if (filter < 0 || filter > 3) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); + } + + this.FilterType = (WebPFilterType)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. + // 0: no pre-processing, 1: level reduction + this.PreProcessing = (data[0] >> 4) & 0x03; + } private int PrevLineOffset { get; set; } + public void Decode(Vp8Decoder dec, Span dst) + { + if (this.Compressed is false) + { + this.Data.AsSpan(1, this.Width * this.Height).CopyTo(dst); + } + } + // Taken from vp8l_dec.c AlphaApplyFilter public void AlphaApplyFilter( int firstRow, int lastRow, + Span prevLine, Span output, int outputOffset, int stride) { if (!(this.Filter is WebPFilterNone)) { - Span prevLine = this.PrevLine; int prevLineOffset = this.PrevLineOffset; for (int y = firstRow; y < lastRow; y++) @@ -62,8 +100,6 @@ namespace SixLabors.ImageSharp.Formats.WebP prevLineOffset = outputOffset; outputOffset += stride; } - - this.PrevLine = prevLine; } } } diff --git a/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs new file mode 100644 index 0000000000..4b79ea5f54 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs @@ -0,0 +1,16 @@ +// 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/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs index 2f67661080..e48d89cea2 100644 --- a/src/ImageSharp/Formats/WebP/LoopFilter.cs +++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs @@ -6,7 +6,9 @@ namespace SixLabors.ImageSharp.Formats.WebP internal enum LoopFilter { None = 0, + Simple = 1, + Complex = 2, } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 6e9631729d..f55bc0ed03 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -168,6 +168,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// - A 'VP8X' chunk with information about features used in the file. /// - An optional 'ICCP' chunk with color profile. /// - An optional 'ANIM' chunk with animation control data. + /// - An optional 'ALPH' chunk with alpha channel data. /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. /// /// Information about this webp image. @@ -240,19 +241,25 @@ namespace SixLabors.ImageSharp.Formats.WebP }; } + byte[] alphaData = null; if (isAlphaPresent) { chunkType = this.ReadChunkType(); - uint alphaChunkSize = this.ReadChunkSize(); + if (chunkType != WebPChunkType.Alpha) + { + WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing"); + } - // ALPH chunks will be skipped for now. - this.currentStream.Skip((int)alphaChunkSize); + uint alphaChunkSize = this.ReadChunkSize(); + alphaData = new byte[alphaChunkSize]; + this.currentStream.Read(alphaData, 0, (int)alphaChunkSize); } var features = new WebPFeatures() { Animation = isAnimationPresent, Alpha = isAlphaPresent, + AlphaData = alphaData, ExifProfile = isExifPresent, IccProfile = isIccPresent, XmpMetaData = isXmpPresent diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs index 6ae4f1e9d4..f0d7284020 100644 --- a/src/ImageSharp/Formats/WebP/WebPFeatures.cs +++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Image features of a VP8X image. /// - public class WebPFeatures + internal class WebPFeatures { /// /// Gets or sets a value indicating whether this image has a ICC Profile. @@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public bool Alpha { get; set; } + /// + /// Gets or sets the alpha data, if an ALPH chunk is present. + /// + public byte[] AlphaData { get; set; } + /// /// Gets or sets a value indicating whether this image has a EXIF Profile. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index 42402e025f..34ddade1e5 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -66,25 +66,46 @@ namespace SixLabors.ImageSharp.Formats.WebP // Decode image data. this.ParseFrame(decoder, io); - this.DecodePixelValues(width, height, decoder.Bgr, pixels); + byte[] decodedAlpha = null; + if (info.Features?.Alpha is true) + { + var alphaDecoder = new AlphaDecoder(width, height, info.Features.AlphaData); + + // TODO: use memory allocator. + decodedAlpha = new byte[width * height]; + alphaDecoder.Decode(decoder, decodedAlpha.AsSpan()); + } + + this.DecodePixelValues(width, height, decoder.Bgr, decodedAlpha, pixels); } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D pixels) + private void DecodePixelValues(int width, int height, Span pixelData, byte[] alpha, Buffer2D pixels) where TPixel : struct, IPixel { TPixel color = default; + bool hasAlpha = alpha != null; for (int y = 0; y < height; y++) { Span pixelRow = pixels.GetRowSpan(y); for (int x = 0; x < width; x++) { - int idx = ((y * width) + x) * 3; - byte b = pixelData[idx]; - byte g = pixelData[idx + 1]; - byte r = pixelData[idx + 2]; + int offset = (y * width) + x; + int idxBgr = offset * 3; + byte b = pixelData[idxBgr]; + byte g = pixelData[idxBgr + 1]; + byte r = pixelData[idxBgr + 2]; // TODO: use bulk conversion here. - color.FromBgr24(new Bgr24(r, g, b)); + if (hasAlpha) + { + byte a = alpha[offset]; + color.FromBgra32(new Bgra32(r, g, b, a)); + } + else + { + color.FromBgr24(new Bgr24(r, g, b)); + } + pixelRow[x] = color; } }