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 System;
using SixLabors.ImageSharp.Formats.WebP.Filters; using SixLabors.ImageSharp.Formats.WebP.Filters;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
{ {
internal ref struct AlphaDecoder internal class AlphaDecoder
{ {
public int Width { get; set; } public int Width { get; set; }
public int Height { get; set; } public int Height { get; set; }
public int Method { get; set; }
public WebPFilterBase Filter { 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> /// <summary>
/// Although Alpha Channel requires only 1 byte per pixel, /// Although Alpha Channel requires only 1 byte per pixel,
@ -30,22 +33,57 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
public bool Use8BDecode { get; set; } public bool Use8BDecode { get; set; }
// last output row (or null) public AlphaDecoder(int width, int height, byte[] data)
private Span<byte> PrevLine { get; set; } {
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; } 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 // Taken from vp8l_dec.c AlphaApplyFilter
public void AlphaApplyFilter( public void AlphaApplyFilter(
int firstRow, int firstRow,
int lastRow, int lastRow,
Span<byte> prevLine,
Span<byte> output, Span<byte> output,
int outputOffset, int outputOffset,
int stride) int stride)
{ {
if (!(this.Filter is WebPFilterNone)) if (!(this.Filter is WebPFilterNone))
{ {
Span<byte> prevLine = this.PrevLine;
int prevLineOffset = this.PrevLineOffset; int prevLineOffset = this.PrevLineOffset;
for (int y = firstRow; y < lastRow; y++) for (int y = firstRow; y < lastRow; y++)
@ -62,8 +100,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
prevLineOffset = outputOffset; prevLineOffset = outputOffset;
outputOffset += stride; 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 internal enum LoopFilter
{ {
None = 0, None = 0,
Simple = 1, Simple = 1,
Complex = 2, 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. /// - A 'VP8X' chunk with information about features used in the file.
/// - An optional 'ICCP' chunk with color profile. /// - An optional 'ICCP' chunk with color profile.
/// - An optional 'ANIM' chunk with animation control data. /// - 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. /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow.
/// </summary> /// </summary>
/// <returns>Information about this webp image.</returns> /// <returns>Information about this webp image.</returns>
@ -240,19 +241,25 @@ namespace SixLabors.ImageSharp.Formats.WebP
}; };
} }
byte[] alphaData = null;
if (isAlphaPresent) if (isAlphaPresent)
{ {
chunkType = this.ReadChunkType(); 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. uint alphaChunkSize = this.ReadChunkSize();
this.currentStream.Skip((int)alphaChunkSize); alphaData = new byte[alphaChunkSize];
this.currentStream.Read(alphaData, 0, (int)alphaChunkSize);
} }
var features = new WebPFeatures() var features = new WebPFeatures()
{ {
Animation = isAnimationPresent, Animation = isAnimationPresent,
Alpha = isAlphaPresent, Alpha = isAlphaPresent,
AlphaData = alphaData,
ExifProfile = isExifPresent, ExifProfile = isExifPresent,
IccProfile = isIccPresent, IccProfile = isIccPresent,
XmpMetaData = isXmpPresent XmpMetaData = isXmpPresent

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// Image features of a VP8X image. /// Image features of a VP8X image.
/// </summary> /// </summary>
public class WebPFeatures internal class WebPFeatures
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this image has a ICC Profile. /// Gets or sets a value indicating whether this image has a ICC Profile.
@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
public bool Alpha { get; set; } 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> /// <summary>
/// Gets or sets a value indicating whether this image has a EXIF Profile. /// Gets or sets a value indicating whether this image has a EXIF Profile.
/// </summary> /// </summary>

35
src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

@ -66,25 +66,46 @@ namespace SixLabors.ImageSharp.Formats.WebP
// Decode image data. // Decode image data.
this.ParseFrame(decoder, io); 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> where TPixel : struct, IPixel<TPixel>
{ {
TPixel color = default; TPixel color = default;
bool hasAlpha = alpha != null;
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> pixelRow = pixels.GetRowSpan(y); Span<TPixel> pixelRow = pixels.GetRowSpan(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
int idx = ((y * width) + x) * 3; int offset = (y * width) + x;
byte b = pixelData[idx]; int idxBgr = offset * 3;
byte g = pixelData[idx + 1]; byte b = pixelData[idxBgr];
byte r = pixelData[idx + 2]; byte g = pixelData[idxBgr + 1];
byte r = pixelData[idxBgr + 2];
// TODO: use bulk conversion here. // 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; pixelRow[x] = color;
} }
} }

Loading…
Cancel
Save