Browse Source

Start implementing alpha decoding: uncompressed alpha without filtering works so far

pull/1552/head
Brian Popow 6 years ago
parent
commit
3b3769248a
  1. 58
      src/ImageSharp/Formats/WebP/AlphaDecoder.cs
  2. 16
      src/ImageSharp/Formats/WebP/Filters/WebPFilterType.cs
  3. 2
      src/ImageSharp/Formats/WebP/LoopFilter.cs
  4. 13
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  5. 7
      src/ImageSharp/Formats/WebP/WebPFeatures.cs
  6. 35
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

58
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; }
/// <summary>
/// Although Alpha Channel requires only 1 byte per pixel,
@ -30,22 +33,57 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public bool Use8BDecode { get; set; }
// last output row (or null)
private Span<byte> 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<byte> 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<byte> prevLine,
Span<byte> output,
int outputOffset,
int stride)
{
if (!(this.Filter is WebPFilterNone))
{
Span<byte> 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;
}
}
}

16
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,
}
}

2
src/ImageSharp/Formats/WebP/LoopFilter.cs

@ -6,7 +6,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
internal enum LoopFilter
{
None = 0,
Simple = 1,
Complex = 2,
}
}

13
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.
/// </summary>
/// <returns>Information about this webp image.</returns>
@ -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

7
src/ImageSharp/Formats/WebP/WebPFeatures.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Image features of a VP8X image.
/// </summary>
public class WebPFeatures
internal class WebPFeatures
{
/// <summary>
/// Gets or sets a value indicating whether this image has a ICC Profile.
@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public bool Alpha { get; set; }
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
public byte[] AlphaData { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this image has a EXIF Profile.
/// </summary>

35
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<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> pixels)
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, byte[] alpha, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
bool hasAlpha = alpha != null;
for (int y = 0; y < height; y++)
{
Span<TPixel> 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;
}
}

Loading…
Cancel
Save