mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # src/ImageSharp/Configuration.cs # tests/ImageSharp.Tests/TestImages.cs # tests/Images/Externalpull/1552/head
202 changed files with 9220 additions and 2 deletions
@ -0,0 +1,309 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Implements decoding for lossy alpha chunks which may be compressed.
|
|||
/// </summary>
|
|||
internal class AlphaDecoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AlphaDecoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="height">The height of the image.</param>
|
|||
/// <param name="data">The (maybe compressed) alpha data.</param>
|
|||
/// <param name="alphaChunkHeader">The first byte of the alpha image stream contains information on ow to decode the stream.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory during decoding.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Data = data; |
|||
this.LastRow = 0; |
|||
|
|||
// Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format)
|
|||
int method = alphaChunkHeader & 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 = (alphaChunkHeader >> 2) & 0x03; |
|||
if (filter < 0 || filter > 3) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); |
|||
} |
|||
|
|||
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.
|
|||
// 0: no pre-processing, 1: level reduction
|
|||
this.PreProcessing = (alphaChunkHeader >> 4) & 0x03; |
|||
|
|||
this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); |
|||
|
|||
this.Alpha = memoryAllocator.Allocate<byte>(width * height); |
|||
|
|||
if (this.Compressed) |
|||
{ |
|||
var bitReader = new Vp8LBitReader(data); |
|||
this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration); |
|||
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the the width of the image.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height of the image.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the used filter type.
|
|||
/// </summary>
|
|||
public WebPAlphaFilterType AlphaFilterType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the last decoded row.
|
|||
/// </summary>
|
|||
public int LastRow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the row before the last decoded row.
|
|||
/// </summary>
|
|||
public int PrevRow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets information for decoding Vp8L compressed alpha data.
|
|||
/// </summary>
|
|||
public Vp8LDecoder Vp8LDec { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the decoded alpha data.
|
|||
/// </summary>
|
|||
public IMemoryOwner<byte> Alpha { get; } |
|||
|
|||
public int CropTop { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether pre-processing was used during compression.
|
|||
/// 0: no pre-processing, 1: level reduction.
|
|||
/// </summary>
|
|||
private int PreProcessing { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the alpha channel uses compression.
|
|||
/// </summary>
|
|||
private bool Compressed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the (maybe compressed) alpha data.
|
|||
/// </summary>
|
|||
private byte[] Data { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
|
|||
/// </summary>
|
|||
private WebPLosslessDecoder LosslessDecoder { get; } |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
public bool Use8BDecode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Decodes and filters the maybe compressed alpha data.
|
|||
/// </summary>
|
|||
public void Decode() |
|||
{ |
|||
if (this.Compressed is false) |
|||
{ |
|||
if (this.Data.Length < (this.Width * this.Height)) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); |
|||
} |
|||
|
|||
Span<byte> alphaSpan = this.Alpha.Memory.Span; |
|||
if (this.AlphaFilterType == WebPAlphaFilterType.None) |
|||
{ |
|||
this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan); |
|||
return; |
|||
} |
|||
|
|||
Span<byte> deltas = this.Data.AsSpan(); |
|||
Span<byte> dst = alphaSpan; |
|||
Span<byte> prev = null; |
|||
for (int y = 0; y < this.Height; ++y) |
|||
{ |
|||
switch (this.AlphaFilterType) |
|||
{ |
|||
case WebPAlphaFilterType.Horizontal: |
|||
HorizontalUnfilter(prev, deltas, dst, this.Width); |
|||
break; |
|||
case WebPAlphaFilterType.Vertical: |
|||
VerticalUnfilter(prev, deltas, dst, this.Width); |
|||
break; |
|||
case WebPAlphaFilterType.Gradient: |
|||
GradientUnfilter(prev, deltas, dst, this.Width); |
|||
break; |
|||
} |
|||
|
|||
prev = dst; |
|||
deltas = deltas.Slice(this.Width); |
|||
dst = dst.Slice(this.Width); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.LosslessDecoder.DecodeAlphaData(this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies filtering to a set of rows.
|
|||
/// </summary>
|
|||
/// <param name="firstRow">The first row index to start filtering.</param>
|
|||
/// <param name="lastRow">The last row index for filtering.</param>
|
|||
/// <param name="dst">The destination to store the filtered data.</param>
|
|||
/// <param name="stride">The stride to use.</param>
|
|||
public void AlphaApplyFilter(int firstRow, int lastRow, Span<byte> dst, int stride) |
|||
{ |
|||
if (this.AlphaFilterType is WebPAlphaFilterType.None) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Span<byte> alphaSpan = this.Alpha.Memory.Span; |
|||
Span<byte> 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<byte> prev, Span<byte> input, Span<byte> dst, int width) |
|||
{ |
|||
byte pred = (byte)(prev == null ? 0 : prev[0]); |
|||
|
|||
for (int i = 0; i < width; ++i) |
|||
{ |
|||
dst[i] = (byte)(pred + input[i]); |
|||
pred = dst[i]; |
|||
} |
|||
} |
|||
|
|||
private static void VerticalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width) |
|||
{ |
|||
if (prev == null) |
|||
{ |
|||
HorizontalUnfilter(null, input, dst, width); |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < width; ++i) |
|||
{ |
|||
dst[i] = (byte)(prev[i] + input[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void GradientUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width) |
|||
{ |
|||
if (prev == null) |
|||
{ |
|||
HorizontalUnfilter(null, input, dst, width); |
|||
} |
|||
else |
|||
{ |
|||
byte top = prev[0]; |
|||
byte topLeft = top; |
|||
byte left = top; |
|||
for (int i = 0; i < width; ++i) |
|||
{ |
|||
top = prev[i]; |
|||
left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); |
|||
topLeft = top; |
|||
dst[i] = left; |
|||
} |
|||
} |
|||
} |
|||
|
|||
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<HuffmanCode[]> 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
|
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.Vp8LDec?.Dispose(); |
|||
this.Alpha?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for VP8 and VP8L bitreader.
|
|||
/// </summary>
|
|||
internal abstract class BitReaderBase |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the raw encoded image data.
|
|||
/// </summary>
|
|||
public byte[] Data { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Copies the raw encoded image data from the stream into a byte array.
|
|||
/// </summary>
|
|||
/// <param name="input">The input stream.</param>
|
|||
/// <param name="bytesToRead">Number of bytes to read as indicated from the chunk size.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
|
|||
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) |
|||
{ |
|||
using (var ms = new MemoryStream()) |
|||
using (IManagedByteBuffer buffer = memoryAllocator.AllocateManagedByteBuffer(4096)) |
|||
{ |
|||
Span<byte> bufferSpan = buffer.GetSpan(); |
|||
int read; |
|||
while (bytesToRead > 0 && (read = input.Read(buffer.Array, 0, Math.Min(bufferSpan.Length, bytesToRead))) > 0) |
|||
{ |
|||
ms.Write(buffer.Array, 0, read); |
|||
bytesToRead -= read; |
|||
} |
|||
|
|||
if (bytesToRead > 0) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("image file has insufficient data"); |
|||
} |
|||
|
|||
this.Data = ms.ToArray(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes.
|
|||
/// </summary>
|
|||
internal class ColorCache |
|||
{ |
|||
private const uint HashMul = 0x1e35a7bdu; |
|||
|
|||
/// <summary>
|
|||
/// Gets the color entries.
|
|||
/// </summary>
|
|||
public uint[] Colors { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the hash shift: 32 - hashBits.
|
|||
/// </summary>
|
|||
public int HashShift { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the hash bits.
|
|||
/// </summary>
|
|||
public int HashBits { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new color cache.
|
|||
/// </summary>
|
|||
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
|
|||
public void Init(int hashBits) |
|||
{ |
|||
int hashSize = 1 << hashBits; |
|||
this.Colors = new uint[hashSize]; |
|||
this.HashBits = hashBits; |
|||
this.HashShift = 32 - hashBits; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inserts a new color into the cache.
|
|||
/// </summary>
|
|||
/// <param name="argb">The color to insert.</param>
|
|||
public void Insert(uint argb) |
|||
{ |
|||
int key = this.HashPix(argb, this.HashShift); |
|||
this.Colors[key] = argb; |
|||
} |
|||
|
|||
public uint Lookup(int key) |
|||
{ |
|||
return this.Colors[key]; |
|||
} |
|||
|
|||
private int HashPix(uint argb, int shift) |
|||
{ |
|||
return (int)((argb * HashMul) >> shift); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Huffman table group.
|
|||
/// Includes special handling for the following cases:
|
|||
/// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN)
|
|||
/// - IsTrivialCode: only 1 code (no bit is read from the bitstream)
|
|||
/// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[]
|
|||
/// The common literal base, if applicable, is stored in 'LiteralArb'.
|
|||
/// </summary>
|
|||
internal class HTreeGroup |
|||
{ |
|||
public HTreeGroup(uint packedTableSize) |
|||
{ |
|||
this.HTrees = new List<HuffmanCode[]>(WebPConstants.HuffmanCodesPerMetaCode); |
|||
this.PackedTable = new HuffmanCode[packedTableSize]; |
|||
for (int i = 0; i < packedTableSize; i++) |
|||
{ |
|||
this.PackedTable[i] = new HuffmanCode(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the Huffman trees. This has a maximum of HuffmanCodesPerMetaCode (5) entry's.
|
|||
/// </summary>
|
|||
public List<HuffmanCode[]> HTrees { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code).
|
|||
/// </summary>
|
|||
public bool IsTrivialLiteral { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a the literal argb value of the pixel.
|
|||
/// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero.
|
|||
/// </summary>
|
|||
public uint LiteralArb { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether there is only one code.
|
|||
/// </summary>
|
|||
public bool IsTrivialCode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to use packed table below for short literal code.
|
|||
/// </summary>
|
|||
public bool UsePackedTable { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets table mapping input bits to packed values, or escape case to literal code.
|
|||
/// </summary>
|
|||
public HuffmanCode[] PackedTable { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Five Huffman codes are used at each meta code.
|
|||
/// </summary>
|
|||
public static class HuffIndex |
|||
{ |
|||
/// <summary>
|
|||
/// Green + length prefix codes + color cache codes.
|
|||
/// </summary>
|
|||
public const int Green = 0; |
|||
|
|||
/// <summary>
|
|||
/// Red.
|
|||
/// </summary>
|
|||
public const int Red = 1; |
|||
|
|||
/// <summary>
|
|||
/// Blue.
|
|||
/// </summary>
|
|||
public const int Blue = 2; |
|||
|
|||
/// <summary>
|
|||
/// Alpha.
|
|||
/// </summary>
|
|||
public const int Alpha = 3; |
|||
|
|||
/// <summary>
|
|||
/// Distance prefix codes.
|
|||
/// </summary>
|
|||
public const int Dist = 4; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] |
|||
internal class HuffmanCode |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the number of bits used for this symbol.
|
|||
/// </summary>
|
|||
public int BitsUsed { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the symbol value or table offset.
|
|||
/// </summary>
|
|||
public uint Value { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,216 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Utility functions related to creating the huffman tables.
|
|||
/// </summary>
|
|||
internal static class HuffmanUtils |
|||
{ |
|||
public const int HuffmanTableBits = 8; |
|||
|
|||
public const int HuffmanPackedBits = 6; |
|||
|
|||
public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; |
|||
|
|||
public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; |
|||
|
|||
public static int BuildHuffmanTable(Span<HuffmanCode> table, int rootBits, int[] codeLengths, int codeLengthsSize) |
|||
{ |
|||
Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); |
|||
Guard.NotNull(codeLengths, nameof(codeLengths)); |
|||
Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); |
|||
|
|||
// sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
|
|||
var sorted = new int[codeLengthsSize]; |
|||
int totalSize = 1 << rootBits; // total size root table + 2nd level table.
|
|||
int len; // current code length.
|
|||
int symbol; // symbol index in original or sorted table.
|
|||
var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
|
|||
var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
|
|||
|
|||
// Build histogram of code lengths.
|
|||
for (symbol = 0; symbol < codeLengthsSize; ++symbol) |
|||
{ |
|||
if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
count[codeLengths[symbol]]++; |
|||
} |
|||
|
|||
// Error, all code lengths are zeros.
|
|||
if (count[0] == codeLengthsSize) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
// Generate offsets into sorted symbol table by code length.
|
|||
offset[1] = 0; |
|||
for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len) |
|||
{ |
|||
if (count[len] > (1 << len)) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
offset[len + 1] = offset[len] + count[len]; |
|||
} |
|||
|
|||
// Sort symbols by length, by symbol order within each length.
|
|||
for (symbol = 0; symbol < codeLengthsSize; ++symbol) |
|||
{ |
|||
int symbolCodeLength = codeLengths[symbol]; |
|||
if (codeLengths[symbol] > 0) |
|||
{ |
|||
sorted[offset[symbolCodeLength]++] = symbol; |
|||
} |
|||
} |
|||
|
|||
// Special case code with only one value.
|
|||
if (offset[WebPConstants.MaxAllowedCodeLength] is 1) |
|||
{ |
|||
var huffmanCode = new HuffmanCode() |
|||
{ |
|||
BitsUsed = 0, |
|||
Value = (uint)sorted[0] |
|||
}; |
|||
ReplicateValue(table, 1, totalSize, huffmanCode); |
|||
return totalSize; |
|||
} |
|||
|
|||
int step; // step size to replicate values in current table
|
|||
int low = -1; // low bits for current root entry
|
|||
int mask = totalSize - 1; // mask for low bits
|
|||
int key = 0; // reversed prefix code
|
|||
int numNodes = 1; // number of Huffman tree nodes
|
|||
int numOpen = 1; // number of open branches in current tree level
|
|||
int tableBits = rootBits; // key length of current table
|
|||
int tableSize = 1 << tableBits; // size of current table
|
|||
symbol = 0; |
|||
|
|||
// Fill in root table.
|
|||
for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) |
|||
{ |
|||
numOpen <<= 1; |
|||
numNodes += numOpen; |
|||
numOpen -= count[len]; |
|||
if (numOpen < 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
for (; count[len] > 0; --count[len]) |
|||
{ |
|||
var huffmanCode = new HuffmanCode() |
|||
{ |
|||
BitsUsed = len, |
|||
Value = (uint)sorted[symbol++] |
|||
}; |
|||
ReplicateValue(table.Slice(key), step, tableSize, huffmanCode); |
|||
key = GetNextKey(key, len); |
|||
} |
|||
} |
|||
|
|||
// Fill in 2nd level tables and add pointers to root table.
|
|||
Span<HuffmanCode> tableSpan = table; |
|||
int tablePos = 0; |
|||
for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1) |
|||
{ |
|||
numOpen <<= 1; |
|||
numNodes += numOpen; |
|||
numOpen -= count[len]; |
|||
if (numOpen < 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
for (; count[len] > 0; --count[len]) |
|||
{ |
|||
if ((key & mask) != low) |
|||
{ |
|||
tableSpan = tableSpan.Slice(tableSize); |
|||
tablePos += tableSize; |
|||
tableBits = NextTableBitSize(count, len, rootBits); |
|||
tableSize = 1 << tableBits; |
|||
totalSize += tableSize; |
|||
low = key & mask; |
|||
uint v = (uint)(tablePos - low); |
|||
table[low] = new HuffmanCode |
|||
{ |
|||
BitsUsed = tableBits + rootBits, |
|||
Value = (uint)(tablePos - low) |
|||
}; |
|||
} |
|||
|
|||
var huffmanCode = new HuffmanCode |
|||
{ |
|||
BitsUsed = len - rootBits, |
|||
Value = (uint)sorted[symbol++] |
|||
}; |
|||
ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode); |
|||
key = GetNextKey(key, len); |
|||
} |
|||
} |
|||
|
|||
return totalSize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
|
|||
/// len is the code length of the next processed symbol.
|
|||
/// </summary>
|
|||
private static int NextTableBitSize(int[] count, int len, int rootBits) |
|||
{ |
|||
int left = 1 << (len - rootBits); |
|||
while (len < WebPConstants.MaxAllowedCodeLength) |
|||
{ |
|||
left -= count[len]; |
|||
if (left <= 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
++len; |
|||
left <<= 1; |
|||
} |
|||
|
|||
return len - rootBits; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores code in table[0], table[step], table[2*step], ..., table[end].
|
|||
/// Assumes that end is an integer multiple of step.
|
|||
/// </summary>
|
|||
private static void ReplicateValue(Span<HuffmanCode> table, int step, int end, HuffmanCode code) |
|||
{ |
|||
Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); |
|||
|
|||
do |
|||
{ |
|||
end -= step; |
|||
table[end] = code; |
|||
} |
|||
while (end > 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
|
|||
/// bit-wise reversal of the len least significant bits of key.
|
|||
/// </summary>
|
|||
private static int GetNextKey(int key, int len) |
|||
{ |
|||
int step = 1 << (len - 1); |
|||
while ((key & step) != 0) |
|||
{ |
|||
step >>= 1; |
|||
} |
|||
|
|||
return step != 0 ? (key & (step - 1)) + step : key; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a webp stream.
|
|||
/// </summary>
|
|||
internal interface IWebPDecoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
bool IgnoreMetadata { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Enum for the different loop filters used. VP8 supports two types of loop filters.
|
|||
/// </summary>
|
|||
internal enum LoopFilter |
|||
{ |
|||
/// <summary>
|
|||
/// No filter is used.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Simple loop filter.
|
|||
/// </summary>
|
|||
Simple = 1, |
|||
|
|||
/// <summary>
|
|||
/// Complex loop filter.
|
|||
/// </summary>
|
|||
Complex = 2, |
|||
} |
|||
} |
|||
@ -0,0 +1,656 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Utility functions for the lossless decoder.
|
|||
/// </summary>
|
|||
internal static class LosslessUtils |
|||
{ |
|||
/// <summary>
|
|||
/// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
|
|||
/// </summary>
|
|||
/// <param name="pixelData">The pixel data to apply the transformation.</param>
|
|||
public static void AddGreenToBlueAndRed(Span<uint> pixelData) |
|||
{ |
|||
for (int i = 0; i < pixelData.Length; i++) |
|||
{ |
|||
uint argb = pixelData[i]; |
|||
uint green = (argb >> 8) & 0xff; |
|||
uint redBlue = argb & 0x00ff00ffu; |
|||
redBlue += (green << 16) | green; |
|||
redBlue &= 0x00ff00ffu; |
|||
pixelData[i] = (argb & 0xff00ff00u) | redBlue; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices.
|
|||
/// This will reverse the color index transform.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform data contains color table size and the entries in the color table.</param>
|
|||
/// <param name="pixelData">The pixel data to apply the reverse transform on.</param>
|
|||
public static void ColorIndexInverseTransform(Vp8LTransform transform, Span<uint> pixelData) |
|||
{ |
|||
int bitsPerPixel = 8 >> transform.Bits; |
|||
int width = transform.XSize; |
|||
int height = transform.YSize; |
|||
Span<uint> colorMap = transform.Data.GetSpan(); |
|||
int decodedPixels = 0; |
|||
if (bitsPerPixel < 8) |
|||
{ |
|||
int pixelsPerByte = 1 << transform.Bits; |
|||
int countMask = pixelsPerByte - 1; |
|||
int bitMask = (1 << bitsPerPixel) - 1; |
|||
|
|||
var decodedPixelData = new uint[width * height]; |
|||
int pixelDataPos = 0; |
|||
for (int y = 0; y < height; ++y) |
|||
{ |
|||
uint packedPixels = 0; |
|||
for (int x = 0; x < width; ++x) |
|||
{ |
|||
// We need to load fresh 'packed_pixels' once every
|
|||
// 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte
|
|||
// is a power of 2, so can just use a mask for that, instead of
|
|||
// decrementing a counter.
|
|||
if ((x & countMask) is 0) |
|||
{ |
|||
packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); |
|||
} |
|||
|
|||
decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; |
|||
packedPixels >>= bitsPerPixel; |
|||
} |
|||
} |
|||
|
|||
decodedPixelData.AsSpan().CopyTo(pixelData); |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int y = 0; y < height; ++y) |
|||
{ |
|||
for (int x = 0; x < width; ++x) |
|||
{ |
|||
uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); |
|||
pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; |
|||
decodedPixels++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The goal of the color transform is to de-correlate the R, G and B values of each pixel.
|
|||
/// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform data.</param>
|
|||
/// <param name="pixelData">The pixel data to apply the inverse transform on.</param>
|
|||
public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span<uint> pixelData) |
|||
{ |
|||
int width = transform.XSize; |
|||
int yEnd = transform.YSize; |
|||
int tileWidth = 1 << transform.Bits; |
|||
int mask = tileWidth - 1; |
|||
int safeWidth = width & ~mask; |
|||
int remainingWidth = width - safeWidth; |
|||
int tilesPerRow = SubSampleSize(width, transform.Bits); |
|||
int y = 0; |
|||
int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; |
|||
Span<uint> transformData = transform.Data.GetSpan(); |
|||
|
|||
int pixelPos = 0; |
|||
while (y < yEnd) |
|||
{ |
|||
int predRowIdx = predRowIdxStart; |
|||
Vp8LMultipliers m = default(Vp8LMultipliers); |
|||
int srcSafeEnd = pixelPos + safeWidth; |
|||
int srcEnd = pixelPos + width; |
|||
while (pixelPos < srcSafeEnd) |
|||
{ |
|||
uint colorCode = transformData[predRowIdx++]; |
|||
ColorCodeToMultipliers(colorCode, ref m); |
|||
TransformColorInverse(m, pixelData, pixelPos, tileWidth); |
|||
pixelPos += tileWidth; |
|||
} |
|||
|
|||
if (pixelPos < srcEnd) |
|||
{ |
|||
uint colorCode = transformData[predRowIdx]; |
|||
ColorCodeToMultipliers(colorCode, ref m); |
|||
TransformColorInverse(m, pixelData, pixelPos, remainingWidth); |
|||
pixelPos += remainingWidth; |
|||
} |
|||
|
|||
++y; |
|||
if ((y & mask) is 0) |
|||
{ |
|||
predRowIdxStart += tilesPerRow; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reverses the color space transform.
|
|||
/// </summary>
|
|||
/// <param name="m">The color transform element.</param>
|
|||
/// <param name="pixelData">The pixel data to apply the inverse transform on.</param>
|
|||
/// <param name="start">The start index of reverse transform.</param>
|
|||
/// <param name="numPixels">The number of pixels to apply the transform.</param>
|
|||
public static void TransformColorInverse(Vp8LMultipliers m, Span<uint> pixelData, int start, int numPixels) |
|||
{ |
|||
int end = start + numPixels; |
|||
for (int i = start; i < end; i++) |
|||
{ |
|||
uint argb = pixelData[i]; |
|||
sbyte green = (sbyte)(argb >> 8); |
|||
uint red = argb >> 16; |
|||
int newRed = (int)(red & 0xff); |
|||
int newBlue = (int)argb & 0xff; |
|||
newRed += ColorTransformDelta((sbyte)m.GreenToRed, (sbyte)green); |
|||
newRed &= 0xff; |
|||
newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, (sbyte)green); |
|||
newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); |
|||
newBlue &= 0xff; |
|||
|
|||
pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This will reverse the predictor transform.
|
|||
/// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated.
|
|||
/// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded.
|
|||
/// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform data.</param>
|
|||
/// <param name="pixelData">The pixel data to apply the inverse transform.</param>
|
|||
/// <param name="output">The resulting pixel data with the reversed transformation data.</param>
|
|||
public static void PredictorInverseTransform(Vp8LTransform transform, Span<uint> pixelData, Span<uint> output) |
|||
{ |
|||
int processedPixels = 0; |
|||
int yStart = 0; |
|||
int width = transform.XSize; |
|||
Span<uint> transformData = transform.Data.GetSpan(); |
|||
|
|||
// First Row follows the L (mode=1) mode.
|
|||
PredictorAdd0(pixelData, processedPixels, 1, output); |
|||
PredictorAdd1(pixelData, 1, width - 1, output); |
|||
processedPixels += width; |
|||
yStart++; |
|||
|
|||
int y = yStart; |
|||
int yEnd = transform.YSize; |
|||
int tileWidth = 1 << transform.Bits; |
|||
int mask = tileWidth - 1; |
|||
int tilesPerRow = SubSampleSize(width, transform.Bits); |
|||
int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; |
|||
while (y < yEnd) |
|||
{ |
|||
int predictorModeIdx = predictorModeIdxBase; |
|||
int x = 1; |
|||
|
|||
// First pixel follows the T (mode=2) mode.
|
|||
PredictorAdd2(pixelData, processedPixels, 1, width, output); |
|||
|
|||
while (x < width) |
|||
{ |
|||
uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; |
|||
int xEnd = (x & ~mask) + tileWidth; |
|||
if (xEnd > width) |
|||
{ |
|||
xEnd = width; |
|||
} |
|||
|
|||
// There are 14 different prediction modes.
|
|||
// In each prediction mode, the current pixel value is predicted from one or more neighboring pixels whose values are already known.
|
|||
int startIdx = processedPixels + x; |
|||
int numberOfPixels = xEnd - x; |
|||
switch (predictorMode) |
|||
{ |
|||
case 0: |
|||
PredictorAdd0(pixelData, startIdx, numberOfPixels, output); |
|||
break; |
|||
case 1: |
|||
PredictorAdd1(pixelData, startIdx, numberOfPixels, output); |
|||
break; |
|||
case 2: |
|||
PredictorAdd2(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 3: |
|||
PredictorAdd3(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 4: |
|||
PredictorAdd4(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 5: |
|||
PredictorAdd5(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 6: |
|||
PredictorAdd6(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 7: |
|||
PredictorAdd7(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 8: |
|||
PredictorAdd8(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 9: |
|||
PredictorAdd9(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 10: |
|||
PredictorAdd10(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 11: |
|||
PredictorAdd11(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 12: |
|||
PredictorAdd12(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
case 13: |
|||
PredictorAdd13(pixelData, startIdx, numberOfPixels, width, output); |
|||
break; |
|||
} |
|||
|
|||
x = xEnd; |
|||
} |
|||
|
|||
processedPixels += width; |
|||
++y; |
|||
if ((y & mask) is 0) |
|||
{ |
|||
// Use the same mask, since tiles are squares.
|
|||
predictorModeIdxBase += tilesPerRow; |
|||
} |
|||
} |
|||
|
|||
output.CopyTo(pixelData); |
|||
} |
|||
|
|||
private static void PredictorAdd0(Span<uint> input, int startIdx, int numberOfPixels, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor0(); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd1(Span<uint> input, int startIdx, int numberOfPixels, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
uint left = output[startIdx - 1]; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
output[x] = left = AddPixels(input[x], left); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd2(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor2(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd3(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor3(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd4(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor4(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd5(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor5(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd6(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor6(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd7(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor7(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd8(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor8(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd9(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor9(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd10(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor10(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd11(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor11(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd12(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor12(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static void PredictorAdd13(Span<uint> input, int startIdx, int numberOfPixels, int width, Span<uint> output) |
|||
{ |
|||
int endIdx = startIdx + numberOfPixels; |
|||
int offset = 0; |
|||
for (int x = startIdx; x < endIdx; ++x) |
|||
{ |
|||
uint pred = Predictor13(output[x - 1], output, startIdx - width + offset++); |
|||
output[x] = AddPixels(input[x], pred); |
|||
} |
|||
} |
|||
|
|||
private static uint Predictor0() |
|||
{ |
|||
return WebPConstants.ArgbBlack; |
|||
} |
|||
|
|||
private static uint Predictor1(uint left, Span<uint> top) |
|||
{ |
|||
return left; |
|||
} |
|||
|
|||
private static uint Predictor2(uint left, Span<uint> top, int idx) |
|||
{ |
|||
return top[idx]; |
|||
} |
|||
|
|||
private static uint Predictor3(uint left, Span<uint> top, int idx) |
|||
{ |
|||
return top[idx + 1]; |
|||
} |
|||
|
|||
private static uint Predictor4(uint left, Span<uint> top, int idx) |
|||
{ |
|||
return top[idx - 1]; |
|||
} |
|||
|
|||
private static uint Predictor5(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average3(left, top[idx], top[idx + 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor6(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average2(left, top[idx - 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor7(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average2(left, top[idx]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor8(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average2(top[idx - 1], top[idx]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor9(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average2(top[idx], top[idx + 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor10(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor11(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = Select(top[idx], left, top[idx - 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor12(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint Predictor13(uint left, Span<uint> top, int idx) |
|||
{ |
|||
uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]); |
|||
return pred; |
|||
} |
|||
|
|||
private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) |
|||
{ |
|||
int a = AddSubtractComponentFull((int)(c0 >> 24), (int)(c1 >> 24), (int)(c2 >> 24)); |
|||
int r = AddSubtractComponentFull( |
|||
(int)((c0 >> 16) & 0xff), |
|||
(int)((c1 >> 16) & 0xff), |
|||
(int)((c2 >> 16) & 0xff)); |
|||
int g = AddSubtractComponentFull( |
|||
(int)((c0 >> 8) & 0xff), |
|||
(int)((c1 >> 8) & 0xff), |
|||
(int)((c2 >> 8) & 0xff)); |
|||
int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); |
|||
return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; |
|||
} |
|||
|
|||
private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) |
|||
{ |
|||
uint ave = Average2(c0, c1); |
|||
int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); |
|||
int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); |
|||
int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); |
|||
int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); |
|||
return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; |
|||
} |
|||
|
|||
private static int AddSubtractComponentHalf(int a, int b) |
|||
{ |
|||
return (int)Clip255((uint)(a + ((a - b) / 2))); |
|||
} |
|||
|
|||
private static int AddSubtractComponentFull(int a, int b, int c) |
|||
{ |
|||
return (int)Clip255((uint)(a + b - c)); |
|||
} |
|||
|
|||
private static uint Clip255(uint a) |
|||
{ |
|||
if (a < 256) |
|||
{ |
|||
return a; |
|||
} |
|||
|
|||
return ~a >> 24; |
|||
} |
|||
|
|||
private static uint Select(uint a, uint b, uint c) |
|||
{ |
|||
int paMinusPb = |
|||
Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) + |
|||
Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + |
|||
Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + |
|||
Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); |
|||
return (paMinusPb <= 0) ? a : b; |
|||
} |
|||
|
|||
private static int Sub3(int a, int b, int c) |
|||
{ |
|||
int pb = b - c; |
|||
int pa = a - c; |
|||
return Math.Abs(pb) - Math.Abs(pa); |
|||
} |
|||
|
|||
private static uint Average2(uint a0, uint a1) |
|||
{ |
|||
return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); |
|||
} |
|||
|
|||
private static uint Average3(uint a0, uint a1, uint a2) |
|||
{ |
|||
return Average2(Average2(a0, a2), a1); |
|||
} |
|||
|
|||
private static uint Average4(uint a0, uint a1, uint a2, uint a3) |
|||
{ |
|||
return Average2(Average2(a0, a1), Average2(a2, a3)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes sampled size of 'size' when sampling using 'sampling bits'.
|
|||
/// </summary>
|
|||
public static int SubSampleSize(int size, int samplingBits) |
|||
{ |
|||
return (size + (1 << samplingBits) - 1) >> samplingBits; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sum of each component, mod 256.
|
|||
/// </summary>
|
|||
private static uint AddPixels(uint a, uint b) |
|||
{ |
|||
uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); |
|||
uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); |
|||
return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Difference of each component, mod 256.
|
|||
/// </summary>
|
|||
private static uint SubPixels(uint a, uint b) |
|||
{ |
|||
uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); |
|||
uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); |
|||
return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); |
|||
} |
|||
|
|||
private static uint GetArgbIndex(uint idx) |
|||
{ |
|||
return (idx >> 8) & 0xff; |
|||
} |
|||
|
|||
public static void ExpandColorMap(int numColors, Span<uint> transformData, Span<uint> newColorMap) |
|||
{ |
|||
newColorMap[0] = transformData[0]; |
|||
Span<byte> data = MemoryMarshal.Cast<uint, byte>(transformData); |
|||
Span<byte> newData = MemoryMarshal.Cast<uint, byte>(newColorMap); |
|||
int i; |
|||
for (i = 4; i < 4 * numColors; ++i) |
|||
{ |
|||
// Equivalent to AddPixelEq(), on a byte-basis.
|
|||
newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); |
|||
} |
|||
|
|||
for (; i < 4 * newColorMap.Length; ++i) |
|||
{ |
|||
newData[i] = 0; // black tail.
|
|||
} |
|||
} |
|||
|
|||
private static int ColorTransformDelta(sbyte colorPred, sbyte color) |
|||
{ |
|||
return ((int)colorPred * color) >> 5; |
|||
} |
|||
|
|||
private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) |
|||
{ |
|||
m.GreenToRed = (byte)(colorCode & 0xff); |
|||
m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); |
|||
m.RedToBlue = (byte)((colorCode >> 16) & 0xff); |
|||
} |
|||
|
|||
internal struct Vp8LMultipliers |
|||
{ |
|||
public byte GreenToRed; |
|||
|
|||
public byte GreenToBlue; |
|||
|
|||
public byte RedToBlue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,912 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal static class LossyUtils |
|||
{ |
|||
private static void Put16(int v, Span<byte> dst) |
|||
{ |
|||
for (int j = 0; j < 16; ++j) |
|||
{ |
|||
Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16); |
|||
} |
|||
} |
|||
|
|||
public static void DC16(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
int dc = 16; |
|||
for (int j = 0; j < 16; ++j) |
|||
{ |
|||
// DC += dst[-1 + j * BPS] + dst[j - BPS];
|
|||
dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps]; |
|||
} |
|||
|
|||
Put16(dc >> 5, dst); |
|||
} |
|||
|
|||
public static void TM16(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
TrueMotion(dst, yuv, offset, 16); |
|||
} |
|||
|
|||
public static void VE16(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// vertical
|
|||
Span<byte> src = yuv.Slice(offset - WebPConstants.Bps, 16); |
|||
for (int j = 0; j < 16; ++j) |
|||
{ |
|||
// memcpy(dst + j * BPS, dst - BPS, 16);
|
|||
src.CopyTo(dst.Slice(j * WebPConstants.Bps)); |
|||
} |
|||
} |
|||
|
|||
public static void HE16(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// horizontal
|
|||
for (int j = 16; j > 0; --j) |
|||
{ |
|||
// memset(dst, dst[-1], 16);
|
|||
byte v = yuv[offset - 1]; |
|||
Memset(dst, v, 0, 16); |
|||
offset += WebPConstants.Bps; |
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
} |
|||
} |
|||
|
|||
public static void DC16NoTop(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// DC with top samples not available.
|
|||
int dc = 8; |
|||
for (int j = 0; j < 16; ++j) |
|||
{ |
|||
// DC += dst[-1 + j * BPS];
|
|||
dc += yuv[-1 + (j * WebPConstants.Bps) + offset]; |
|||
} |
|||
|
|||
Put16(dc >> 4, dst); |
|||
} |
|||
|
|||
public static void DC16NoLeft(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// DC with left samples not available.
|
|||
int dc = 8; |
|||
for (int i = 0; i < 16; ++i) |
|||
{ |
|||
// DC += dst[i - BPS];
|
|||
dc += yuv[i - WebPConstants.Bps + offset]; |
|||
} |
|||
|
|||
Put16(dc >> 4, dst); |
|||
} |
|||
|
|||
public static void DC16NoTopLeft(Span<byte> dst) |
|||
{ |
|||
// DC with no top and left samples.
|
|||
Put16(0x80, dst); |
|||
} |
|||
|
|||
public static void DC8uv(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
int dc0 = 8; |
|||
for (int i = 0; i < 8; ++i) |
|||
{ |
|||
// dc0 += dst[i - BPS] + dst[-1 + i * BPS];
|
|||
dc0 += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; |
|||
} |
|||
|
|||
Put8x8uv((byte)(dc0 >> 4), dst); |
|||
} |
|||
|
|||
public static void TM8uv(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// TrueMotion
|
|||
TrueMotion(dst, yuv, offset, 8); |
|||
} |
|||
|
|||
public static void VE8uv(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// vertical
|
|||
Span<byte> src = yuv.Slice(offset - WebPConstants.Bps, 8); |
|||
|
|||
for (int j = 0; j < 8; ++j) |
|||
{ |
|||
// memcpy(dst + j * BPS, dst - BPS, 8);
|
|||
src.CopyTo(dst.Slice(j * WebPConstants.Bps)); |
|||
} |
|||
} |
|||
|
|||
public static void HE8uv(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// horizontal
|
|||
for (int j = 0; j < 8; ++j) |
|||
{ |
|||
// memset(dst, dst[-1], 8);
|
|||
// dst += BPS;
|
|||
byte v = yuv[offset - 1]; |
|||
Memset(dst, v, 0, 8); |
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
offset += WebPConstants.Bps; |
|||
} |
|||
} |
|||
|
|||
public static void DC8uvNoTop(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// DC with no top samples.
|
|||
int dc0 = 4; |
|||
for (int i = 0; i < 8; ++i) |
|||
{ |
|||
// dc0 += dst[-1 + i * BPS];
|
|||
dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)]; |
|||
} |
|||
|
|||
Put8x8uv((byte)(dc0 >> 3), dst); |
|||
} |
|||
|
|||
public static void DC8uvNoLeft(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// DC with no left samples.
|
|||
int dc0 = 4; |
|||
for (int i = 0; i < 8; ++i) |
|||
{ |
|||
// dc0 += dst[i - BPS];
|
|||
dc0 += yuv[offset + i - WebPConstants.Bps]; |
|||
} |
|||
|
|||
Put8x8uv((byte)(dc0 >> 3), dst); |
|||
} |
|||
|
|||
public static void DC8uvNoTopLeft(Span<byte> dst) |
|||
{ |
|||
// DC with nothing.
|
|||
Put8x8uv(0x80, dst); |
|||
} |
|||
|
|||
public static void DC4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
int dc = 4; |
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
dc += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)]; |
|||
} |
|||
|
|||
dc >>= 3; |
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
Memset(dst, (byte)dc, i * WebPConstants.Bps, 4); |
|||
} |
|||
} |
|||
|
|||
public static void TM4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
TrueMotion(dst, yuv, offset, 4); |
|||
} |
|||
|
|||
public static void VE4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// vertical
|
|||
int topOffset = offset - WebPConstants.Bps; |
|||
byte[] vals = |
|||
{ |
|||
Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]), |
|||
Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]), |
|||
Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]), |
|||
Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]) |
|||
}; |
|||
|
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
vals.CopyTo(dst.Slice(i * WebPConstants.Bps)); |
|||
} |
|||
} |
|||
|
|||
public static void HE4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// horizontal
|
|||
byte a = yuv[offset - 1 - WebPConstants.Bps]; |
|||
byte b = yuv[offset - 1]; |
|||
byte c = yuv[offset - 1 + WebPConstants.Bps]; |
|||
byte d = yuv[offset - 1 + (2 * WebPConstants.Bps)]; |
|||
byte e = yuv[offset - 1 + (3 * WebPConstants.Bps)]; |
|||
uint val = 0x01010101U * Avg3(a, b, c); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst, val); |
|||
val = 0x01010101U * Avg3(b, c, d); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebPConstants.Bps), val); |
|||
val = 0x01010101U * Avg3(c, d, e); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); |
|||
val = 0x01010101U * Avg3(d, e, e); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); |
|||
} |
|||
|
|||
public static void RD4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Down-right
|
|||
byte i = yuv[offset - 1]; |
|||
byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; |
|||
byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; |
|||
byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; |
|||
byte x = yuv[offset - 1 - WebPConstants.Bps]; |
|||
byte a = yuv[offset - WebPConstants.Bps]; |
|||
byte b = yuv[offset + 1 - WebPConstants.Bps]; |
|||
byte c = yuv[offset + 2 - WebPConstants.Bps]; |
|||
byte d = yuv[offset + 3 - WebPConstants.Bps]; |
|||
|
|||
Dst(dst, 0, 3, Avg3(j, k, l)); |
|||
byte ijk = Avg3(i, j, k); |
|||
Dst(dst, 1, 3, ijk); |
|||
Dst(dst, 0, 2, ijk); |
|||
byte xij = Avg3(x, i, j); |
|||
Dst(dst, 2, 3, xij); |
|||
Dst(dst, 1, 2, xij); |
|||
Dst(dst, 0, 1, xij); |
|||
byte axi = Avg3(a, x, i); |
|||
Dst(dst, 3, 3, axi); |
|||
Dst(dst, 2, 2, axi); |
|||
Dst(dst, 1, 1, axi); |
|||
Dst(dst, 0, 0, axi); |
|||
byte bax = Avg3(b, a, x); |
|||
Dst(dst, 3, 2, bax); |
|||
Dst(dst, 2, 1, bax); |
|||
Dst(dst, 1, 0, bax); |
|||
byte cba = Avg3(c, b, a); |
|||
Dst(dst, 3, 1, cba); |
|||
Dst(dst, 2, 0, cba); |
|||
Dst(dst, 3, 0, Avg3(d, c, b)); |
|||
} |
|||
|
|||
public static void VR4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Vertical-Right
|
|||
byte i = yuv[offset - 1]; |
|||
byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; |
|||
byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; |
|||
byte x = yuv[offset - 1 - WebPConstants.Bps]; |
|||
byte a = yuv[offset - WebPConstants.Bps]; |
|||
byte b = yuv[offset + 1 - WebPConstants.Bps]; |
|||
byte c = yuv[offset + 2 - WebPConstants.Bps]; |
|||
byte d = yuv[offset + 3 - WebPConstants.Bps]; |
|||
|
|||
byte xa = Avg2(x, a); |
|||
Dst(dst, 0, 0, xa); |
|||
Dst(dst, 1, 2, xa); |
|||
byte ab = Avg2(a, b); |
|||
Dst(dst, 1, 0, ab); |
|||
Dst(dst, 2, 2, ab); |
|||
byte bc = Avg2(b, c); |
|||
Dst(dst, 2, 0, bc); |
|||
Dst(dst, 3, 2, bc); |
|||
Dst(dst, 3, 0, Avg2(c, d)); |
|||
Dst(dst, 0, 3, Avg3(k, j, i)); |
|||
Dst(dst, 0, 2, Avg3(j, i, x)); |
|||
byte ixa = Avg3(i, x, a); |
|||
Dst(dst, 0, 1, ixa); |
|||
Dst(dst, 1, 3, ixa); |
|||
byte xab = Avg3(x, a, b); |
|||
Dst(dst, 1, 1, xab); |
|||
Dst(dst, 2, 3, xab); |
|||
byte abc = Avg3(a, b, c); |
|||
Dst(dst, 2, 1, abc); |
|||
Dst(dst, 3, 3, abc); |
|||
Dst(dst, 3, 1, Avg3(b, c, d)); |
|||
} |
|||
|
|||
public static void LD4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Down-Left
|
|||
byte a = yuv[offset - WebPConstants.Bps]; |
|||
byte b = yuv[offset + 1 - WebPConstants.Bps]; |
|||
byte c = yuv[offset + 2 - WebPConstants.Bps]; |
|||
byte d = yuv[offset + 3 - WebPConstants.Bps]; |
|||
byte e = yuv[offset + 4 - WebPConstants.Bps]; |
|||
byte f = yuv[offset + 5 - WebPConstants.Bps]; |
|||
byte g = yuv[offset + 6 - WebPConstants.Bps]; |
|||
byte h = yuv[offset + 7 - WebPConstants.Bps]; |
|||
|
|||
Dst(dst, 0, 0, Avg3(a, b, c)); |
|||
byte bcd = Avg3(b, c, d); |
|||
Dst(dst, 1, 0, bcd); |
|||
Dst(dst, 0, 1, bcd); |
|||
byte cde = Avg3(c, d, e); |
|||
Dst(dst, 2, 0, cde); |
|||
Dst(dst, 1, 1, cde); |
|||
Dst(dst, 0, 2, cde); |
|||
byte def = Avg3(d, e, f); |
|||
Dst(dst, 3, 0, def); |
|||
Dst(dst, 2, 1, def); |
|||
Dst(dst, 1, 2, def); |
|||
Dst(dst, 0, 3, def); |
|||
byte efg = Avg3(e, f, g); |
|||
Dst(dst, 3, 1, efg); |
|||
Dst(dst, 2, 2, efg); |
|||
Dst(dst, 1, 3, efg); |
|||
byte fgh = Avg3(f, g, h); |
|||
Dst(dst, 3, 2, fgh); |
|||
Dst(dst, 2, 3, fgh); |
|||
Dst(dst, 3, 3, Avg3(g, h, h)); |
|||
} |
|||
|
|||
public static void VL4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Vertical-Left
|
|||
byte a = yuv[offset - WebPConstants.Bps]; |
|||
byte b = yuv[offset + 1 - WebPConstants.Bps]; |
|||
byte c = yuv[offset + 2 - WebPConstants.Bps]; |
|||
byte d = yuv[offset + 3 - WebPConstants.Bps]; |
|||
byte e = yuv[offset + 4 - WebPConstants.Bps]; |
|||
byte f = yuv[offset + 5 - WebPConstants.Bps]; |
|||
byte g = yuv[offset + 6 - WebPConstants.Bps]; |
|||
byte h = yuv[offset + 7 - WebPConstants.Bps]; |
|||
|
|||
Dst(dst, 0, 0, Avg2(a, b)); |
|||
byte bc = Avg2(b, c); |
|||
Dst(dst, 1, 0, bc); |
|||
Dst(dst, 0, 2, bc); |
|||
byte cd = Avg2(c, d); |
|||
Dst(dst, 2, 0, cd); |
|||
Dst(dst, 1, 2, cd); |
|||
byte de = Avg2(d, e); |
|||
Dst(dst, 3, 0, de); |
|||
Dst(dst, 2, 2, de); |
|||
Dst(dst, 0, 1, Avg3(a, b, c)); |
|||
byte bcd = Avg3(b, c, d); |
|||
Dst(dst, 1, 1, bcd); |
|||
Dst(dst, 0, 3, bcd); |
|||
byte cde = Avg3(c, d, e); |
|||
Dst(dst, 2, 1, cde); |
|||
Dst(dst, 1, 3, cde); |
|||
byte def = Avg3(d, e, f); |
|||
Dst(dst, 3, 1, def); |
|||
Dst(dst, 2, 3, def); |
|||
Dst(dst, 3, 2, Avg3(e, f, g)); |
|||
Dst(dst, 3, 3, Avg3(f, g, h)); |
|||
} |
|||
|
|||
public static void HD4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Horizontal-Down
|
|||
byte i = yuv[offset - 1]; |
|||
byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; |
|||
byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; |
|||
byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; |
|||
byte x = yuv[offset - 1 - WebPConstants.Bps]; |
|||
byte a = yuv[offset - WebPConstants.Bps]; |
|||
byte b = yuv[offset + 1 - WebPConstants.Bps]; |
|||
byte c = yuv[offset + 2 - WebPConstants.Bps]; |
|||
|
|||
byte ix = Avg2(i, x); |
|||
Dst(dst, 0, 0, ix); |
|||
Dst(dst, 2, 1, ix); |
|||
byte ji = Avg2(j, i); |
|||
Dst(dst, 0, 1, ji); |
|||
Dst(dst, 2, 2, ji); |
|||
byte kj = Avg2(k, j); |
|||
Dst(dst, 0, 2, kj); |
|||
Dst(dst, 2, 3, kj); |
|||
Dst(dst, 0, 3, Avg2(l, k)); |
|||
Dst(dst, 3, 0, Avg3(a, b, c)); |
|||
Dst(dst, 2, 0, Avg3(x, a, b)); |
|||
byte ixa = Avg3(i, x, a); |
|||
Dst(dst, 1, 0, ixa); |
|||
Dst(dst, 3, 1, ixa); |
|||
byte jix = Avg3(j, i, x); |
|||
Dst(dst, 1, 1, jix); |
|||
Dst(dst, 3, 2, jix); |
|||
byte kji = Avg3(k, j, i); |
|||
Dst(dst, 1, 2, kji); |
|||
Dst(dst, 3, 3, kji); |
|||
Dst(dst, 1, 3, Avg3(l, k, j)); |
|||
} |
|||
|
|||
public static void HU4(Span<byte> dst, Span<byte> yuv, int offset) |
|||
{ |
|||
// Horizontal-Up
|
|||
byte i = yuv[offset - 1]; |
|||
byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)]; |
|||
byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)]; |
|||
byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)]; |
|||
|
|||
Dst(dst, 0, 0, Avg2(i, j)); |
|||
byte jk = Avg2(j, k); |
|||
Dst(dst, 2, 0, jk); |
|||
Dst(dst, 0, 1, jk); |
|||
byte kl = Avg2(k, l); |
|||
Dst(dst, 2, 1, kl); |
|||
Dst(dst, 0, 2, kl); |
|||
Dst(dst, 1, 0, Avg3(i, j, k)); |
|||
byte jkl = Avg3(j, k, l); |
|||
Dst(dst, 3, 0, jkl); |
|||
Dst(dst, 1, 1, jkl); |
|||
byte kll = Avg3(k, l, l); |
|||
Dst(dst, 3, 1, kll); |
|||
Dst(dst, 1, 2, kll); |
|||
Dst(dst, 3, 2, l); |
|||
Dst(dst, 2, 2, l); |
|||
Dst(dst, 0, 3, l); |
|||
Dst(dst, 1, 3, l); |
|||
Dst(dst, 2, 3, l); |
|||
Dst(dst, 3, 3, l); |
|||
} |
|||
|
|||
public static void Transform(Span<short> src, Span<byte> dst, bool doTwo) |
|||
{ |
|||
TransformOne(src, dst); |
|||
if (doTwo) |
|||
{ |
|||
TransformOne(src.Slice(16), dst.Slice(4)); |
|||
} |
|||
} |
|||
|
|||
public static void TransformOne(Span<short> src, Span<byte> dst) |
|||
{ |
|||
var tmp = new int[4 * 4]; |
|||
int tmpOffset = 0; |
|||
int srcOffset = 0; |
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
// vertical pass
|
|||
int a = src[srcOffset] + src[srcOffset + 8]; |
|||
int b = src[srcOffset] - src[srcOffset + 8]; |
|||
int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]); |
|||
int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]); |
|||
tmp[tmpOffset] = a + d; |
|||
tmp[tmpOffset + 1] = b + c; |
|||
tmp[tmpOffset + 2] = b - c; |
|||
tmp[tmpOffset + 3] = a - d; |
|||
tmpOffset += 4; |
|||
srcOffset++; |
|||
} |
|||
|
|||
// Each pass is expanding the dynamic range by ~3.85 (upper bound).
|
|||
// The exact value is (2. + (20091 + 35468) / 65536).
|
|||
// After the second pass, maximum interval is [-3794, 3794], assuming
|
|||
// an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range.
|
|||
// In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968].
|
|||
tmpOffset = 0; |
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
// horizontal pass
|
|||
int dc = tmp[tmpOffset] + 4; |
|||
int a = dc + tmp[tmpOffset + 8]; |
|||
int b = dc - tmp[tmpOffset + 8]; |
|||
int c = Mul2(tmp[tmpOffset + 4]) - Mul1(tmp[tmpOffset + 12]); |
|||
int d = Mul1(tmp[tmpOffset + 4]) + Mul2(tmp[tmpOffset + 12]); |
|||
Store(dst, 0, 0, a + d); |
|||
Store(dst, 1, 0, b + c); |
|||
Store(dst, 2, 0, b - c); |
|||
Store(dst, 3, 0, a - d); |
|||
tmpOffset++; |
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
} |
|||
} |
|||
|
|||
public static void TransformDc(Span<short> src, Span<byte> dst) |
|||
{ |
|||
int dc = src[0] + 4; |
|||
for (int j = 0; j < 4; ++j) |
|||
{ |
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
Store(dst, i, j, dc); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Simplified transform when only src[0], src[1] and src[4] are non-zero
|
|||
public static void TransformAc3(Span<short> src, Span<byte> dst) |
|||
{ |
|||
int a = src[0] + 4; |
|||
int c4 = Mul2(src[4]); |
|||
int d4 = Mul1(src[4]); |
|||
int c1 = Mul2(src[1]); |
|||
int d1 = Mul1(src[1]); |
|||
Store2(dst, 0, a + d4, d1, c1); |
|||
Store2(dst, 1, a + c4, d1, c1); |
|||
Store2(dst, 2, a - c4, d1, c1); |
|||
Store2(dst, 3, a - d4, d1, c1); |
|||
} |
|||
|
|||
public static void TransformUv(Span<short> src, Span<byte> dst) |
|||
{ |
|||
Transform(src.Slice(0 * 16), dst, true); |
|||
Transform(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps), true); |
|||
} |
|||
|
|||
public static void TransformDcuv(Span<short> src, Span<byte> dst) |
|||
{ |
|||
if (src[0 * 16] != 0) |
|||
{ |
|||
TransformDc(src.Slice(0 * 16), dst); |
|||
} |
|||
|
|||
if (src[1 * 16] != 0) |
|||
{ |
|||
TransformDc(src.Slice(1 * 16), dst.Slice(4)); |
|||
} |
|||
|
|||
if (src[2 * 16] != 0) |
|||
{ |
|||
TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps)); |
|||
} |
|||
|
|||
if (src[3 * 16] != 0) |
|||
{ |
|||
TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebPConstants.Bps) + 4)); |
|||
} |
|||
} |
|||
|
|||
private static void TrueMotion(Span<byte> dst, Span<byte> yuv, int offset, int size) |
|||
{ |
|||
// For information about how true motion works, see rfc6386, page 52. ff and section 20.14.
|
|||
int topOffset = offset - WebPConstants.Bps; |
|||
Span<byte> top = yuv.Slice(topOffset); |
|||
byte p = yuv[topOffset - 1]; |
|||
int leftOffset = offset - 1; |
|||
byte left = yuv[leftOffset]; |
|||
for (int y = 0; y < size; ++y) |
|||
{ |
|||
for (int x = 0; x < size; ++x) |
|||
{ |
|||
dst[x] = (byte)Clamp255(left + top[x] - p); |
|||
} |
|||
|
|||
leftOffset += WebPConstants.Bps; |
|||
left = yuv[leftOffset]; |
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
} |
|||
} |
|||
|
|||
// Simple In-loop filtering (Paragraph 15.2)
|
|||
public static void SimpleVFilter16(Span<byte> p, int offset, int stride, int thresh) |
|||
{ |
|||
int thresh2 = (2 * thresh) + 1; |
|||
for (int i = 0; i < 16; ++i) |
|||
{ |
|||
if (NeedsFilter(p, offset + i, stride, thresh2)) |
|||
{ |
|||
DoFilter2(p, offset + i, stride); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void SimpleHFilter16(Span<byte> p, int offset, int stride, int thresh) |
|||
{ |
|||
int thresh2 = (2 * thresh) + 1; |
|||
for (int i = 0; i < 16; ++i) |
|||
{ |
|||
if (NeedsFilter(p, offset + (i * stride), 1, thresh2)) |
|||
{ |
|||
DoFilter2(p, offset + (i * stride), 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void SimpleVFilter16i(Span<byte> p, int offset, int stride, int thresh) |
|||
{ |
|||
for (int k = 3; k > 0; --k) |
|||
{ |
|||
offset += 4 * stride; |
|||
SimpleVFilter16(p, offset, stride, thresh); |
|||
} |
|||
} |
|||
|
|||
public static void SimpleHFilter16i(Span<byte> p, int offset, int stride, int thresh) |
|||
{ |
|||
for (int k = 3; k > 0; --k) |
|||
{ |
|||
offset += 4; |
|||
SimpleHFilter16(p, offset, stride, thresh); |
|||
} |
|||
} |
|||
|
|||
public static void VFilter16(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static void HFilter16(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static void VFilter16i(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
for (int k = 3; k > 0; --k) |
|||
{ |
|||
offset += 4 * stride; |
|||
FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); |
|||
} |
|||
} |
|||
|
|||
public static void HFilter16i(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
for (int k = 3; k > 0; --k) |
|||
{ |
|||
offset += 4; |
|||
FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); |
|||
} |
|||
} |
|||
|
|||
// 8-pixels wide variant, for chroma filtering.
|
|||
public static void VFilter8(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); |
|||
FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static void HFilter8(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); |
|||
FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static void VFilter8i(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); |
|||
FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static void HFilter8i(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh) |
|||
{ |
|||
FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); |
|||
FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh); |
|||
} |
|||
|
|||
public static uint LoadUv(byte u, byte v) |
|||
{ |
|||
// We process u and v together stashed into 32bit(16bit each).
|
|||
return (uint)(u | (v << 16)); |
|||
} |
|||
|
|||
public static void YuvToBgr(int y, int u, int v, Span<byte> bgr) |
|||
{ |
|||
bgr[0] = (byte)YuvToB(y, u); |
|||
bgr[1] = (byte)YuvToG(y, u, v); |
|||
bgr[2] = (byte)YuvToR(y, v); |
|||
} |
|||
|
|||
public static int YuvToR(int y, int v) |
|||
{ |
|||
return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); |
|||
} |
|||
|
|||
public static int YuvToG(int y, int u, int v) |
|||
{ |
|||
return Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); |
|||
} |
|||
|
|||
public static int YuvToB(int y, int u) |
|||
{ |
|||
return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); |
|||
} |
|||
|
|||
// Complex In-loop filtering (Paragraph 15.3)
|
|||
private static void FilterLoop24( |
|||
Span<byte> p, |
|||
int offset, |
|||
int hStride, |
|||
int vStride, |
|||
int size, |
|||
int thresh, |
|||
int ithresh, |
|||
int hevThresh) |
|||
{ |
|||
int thresh2 = (2 * thresh) + 1; |
|||
while (size-- > 0) |
|||
{ |
|||
if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) |
|||
{ |
|||
if (Hev(p, offset, hStride, hevThresh)) |
|||
{ |
|||
DoFilter2(p, offset, hStride); |
|||
} |
|||
else |
|||
{ |
|||
DoFilter4(p, offset, hStride); |
|||
} |
|||
} |
|||
|
|||
offset += vStride; |
|||
} |
|||
} |
|||
|
|||
private static void FilterLoop26( |
|||
Span<byte> p, |
|||
int offset, |
|||
int hStride, |
|||
int vStride, |
|||
int size, |
|||
int thresh, |
|||
int ithresh, |
|||
int hevThresh) |
|||
{ |
|||
int thresh2 = (2 * thresh) + 1; |
|||
while (size-- > 0) |
|||
{ |
|||
if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) |
|||
{ |
|||
if (Hev(p, offset, hStride, hevThresh)) |
|||
{ |
|||
DoFilter2(p, offset, hStride); |
|||
} |
|||
else |
|||
{ |
|||
DoFilter6(p, offset, hStride); |
|||
} |
|||
} |
|||
|
|||
offset += vStride; |
|||
} |
|||
} |
|||
|
|||
private static void DoFilter2(Span<byte> p, int offset, int step) |
|||
{ |
|||
// 4 pixels in, 2 pixels out.
|
|||
int p1 = p[offset - (2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
int a = (3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]; |
|||
int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; |
|||
int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; |
|||
p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; |
|||
p[offset] = WebPLookupTables.Clip1[q0 - a1]; |
|||
} |
|||
|
|||
private static void DoFilter4(Span<byte> p, int offset, int step) |
|||
{ |
|||
// 4 pixels in, 4 pixels out.
|
|||
int p1 = p[offset - (2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
int a = 3 * (q0 - p0); |
|||
int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3]; |
|||
int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3]; |
|||
int a3 = (a1 + 1) >> 1; |
|||
p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a3]; |
|||
p[offset - step] = WebPLookupTables.Clip1[p0 + a2]; |
|||
p[offset] = WebPLookupTables.Clip1[q0 - a1]; |
|||
p[offset + step] = WebPLookupTables.Clip1[q1 - a3]; |
|||
} |
|||
|
|||
private static void DoFilter6(Span<byte> p, int offset, int step) |
|||
{ |
|||
// 6 pixels in, 6 pixels out.
|
|||
int p2 = p[offset - (3 * step)]; |
|||
int p1 = p[offset - (2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
int q2 = p[offset + (2 * step)]; |
|||
int a = WebPLookupTables.Sclip1[(3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]]; |
|||
|
|||
// a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9]
|
|||
int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7
|
|||
int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7
|
|||
int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7
|
|||
p[offset - (3 * step)] = WebPLookupTables.Clip1[p2 + a3]; |
|||
p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a2]; |
|||
p[offset - step] = WebPLookupTables.Clip1[p0 + a1]; |
|||
p[offset] = WebPLookupTables.Clip1[q0 - a1]; |
|||
p[offset + step] = WebPLookupTables.Clip1[q1 - a2]; |
|||
p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3]; |
|||
} |
|||
|
|||
private static bool NeedsFilter(Span<byte> p, int offset, int step, int t) |
|||
{ |
|||
int p1 = p[offset + (-2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
return ((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) <= t; |
|||
} |
|||
|
|||
private static bool NeedsFilter2(Span<byte> p, int offset, int step, int t, int it) |
|||
{ |
|||
int p3 = p[offset - (4 * step)]; |
|||
int p2 = p[offset - (3 * step)]; |
|||
int p1 = p[offset - (2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
int q2 = p[offset + (2 * step)]; |
|||
int q3 = p[offset + (3 * step)]; |
|||
if (((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) > t) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return WebPLookupTables.Abs0[p3 - p2] <= it && WebPLookupTables.Abs0[p2 - p1] <= it && |
|||
WebPLookupTables.Abs0[p1 - p0] <= it && WebPLookupTables.Abs0[q3 - q2] <= it && |
|||
WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it; |
|||
} |
|||
|
|||
private static bool Hev(Span<byte> p, int offset, int step, int thresh) |
|||
{ |
|||
int p1 = p[offset - (2 * step)]; |
|||
int p0 = p[offset - step]; |
|||
int q0 = p[offset]; |
|||
int q1 = p[offset + step]; |
|||
return (WebPLookupTables.Abs0[p1 - p0] > thresh) || (WebPLookupTables.Abs0[q1 - q0] > thresh); |
|||
} |
|||
|
|||
private static int MultHi(int v, int coeff) |
|||
{ |
|||
return (v * coeff) >> 8; |
|||
} |
|||
|
|||
private static void Store(Span<byte> dst, int x, int y, int v) |
|||
{ |
|||
dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3)); |
|||
} |
|||
|
|||
private static void Store2(Span<byte> dst, int y, int dc, int d, int c) |
|||
{ |
|||
Store(dst, 0, y, dc + d); |
|||
Store(dst, 1, y, dc + c); |
|||
Store(dst, 2, y, dc - c); |
|||
Store(dst, 3, y, dc - d); |
|||
} |
|||
|
|||
private static int Mul1(int a) |
|||
{ |
|||
return ((a * 20091) >> 16) + a; |
|||
} |
|||
|
|||
private static int Mul2(int a) |
|||
{ |
|||
return (a * 35468) >> 16; |
|||
} |
|||
|
|||
private static byte Clip8B(int v) |
|||
{ |
|||
return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255); |
|||
} |
|||
|
|||
private static byte Clip8(int v) |
|||
{ |
|||
int yuvMask = (256 << 6) - 1; |
|||
return (byte)(((v & ~yuvMask) is 0) ? (v >> 6) : (v < 0) ? 0 : 255); |
|||
} |
|||
|
|||
private static void Put8x8uv(byte value, Span<byte> dst) |
|||
{ |
|||
for (int j = 0; j < 8; ++j) |
|||
{ |
|||
// memset(dst + j * BPS, value, 8);
|
|||
Memset(dst, value, j * WebPConstants.Bps, 8); |
|||
} |
|||
} |
|||
|
|||
private static void Memset(Span<byte> dst, byte value, int startIdx, int count) |
|||
{ |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
dst[startIdx + i] = value; |
|||
} |
|||
} |
|||
|
|||
private static byte Avg2(byte a, byte b) |
|||
{ |
|||
return (byte)((a + b + 1) >> 1); |
|||
} |
|||
|
|||
private static byte Avg3(byte a, byte b, byte c) |
|||
{ |
|||
return (byte)((a + (2 * b) + c + 2) >> 2); |
|||
} |
|||
|
|||
private static void Dst(Span<byte> dst, int x, int y, byte v) |
|||
{ |
|||
dst[x + (y * WebPConstants.Bps)] = v; |
|||
} |
|||
|
|||
private static int Clamp255(int x) |
|||
{ |
|||
return x < 0 ? 0 : (x > 255 ? 255 : x); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
# WebP Format |
|||
|
|||
Reference implementation, specification and stuff like that: |
|||
|
|||
- [google webp introduction](https://developers.google.com/speed/webp) |
|||
- [WebP Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) |
|||
- [WebP VP8 chunk Spec](http://tools.ietf.org/html/rfc6386) |
|||
- [WebP filefront](https://wiki.fileformat.com/image/webp/) |
|||
- [WebP test data](https://github.com/webmproject/libwebp-test-data/) |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal enum ReconstructionFilter |
|||
{ |
|||
None, |
|||
Bicubic, |
|||
Bilinear |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// All the probabilities associated to one band.
|
|||
/// </summary>
|
|||
internal class Vp8BandProbas |
|||
{ |
|||
public Vp8BandProbas() |
|||
{ |
|||
this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx]; |
|||
for (int i = 0; i < WebPConstants.NumCtx; i++) |
|||
{ |
|||
this.Probabilities[i] = new Vp8ProbaArray(); |
|||
} |
|||
} |
|||
|
|||
public Vp8ProbaArray[] Probabilities { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,239 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// A bit reader for VP8 streams.
|
|||
/// </summary>
|
|||
internal class Vp8BitReader : BitReaderBase |
|||
{ |
|||
private const int BitsCount = 56; |
|||
|
|||
/// <summary>
|
|||
/// Current value.
|
|||
/// </summary>
|
|||
private ulong value; |
|||
|
|||
/// <summary>
|
|||
/// Current range minus 1. In [127, 254] interval.
|
|||
/// </summary>
|
|||
private uint range; |
|||
|
|||
/// <summary>
|
|||
/// Number of valid bits left.
|
|||
/// </summary>
|
|||
private int bits; |
|||
|
|||
/// <summary>
|
|||
/// Max packed-read position of the buffer.
|
|||
/// </summary>
|
|||
private uint bufferMax; |
|||
|
|||
private uint bufferEnd; |
|||
|
|||
/// <summary>
|
|||
/// True if input is exhausted.
|
|||
/// </summary>
|
|||
private bool eof; |
|||
|
|||
/// <summary>
|
|||
/// Byte position in buffer.
|
|||
/// </summary>
|
|||
private long pos; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">The input stream to read from.</param>
|
|||
/// <param name="imageDataSize">The raw image data size in bytes.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
|
|||
/// <param name="partitionLength">The partition length.</param>
|
|||
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
|
|||
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) |
|||
{ |
|||
this.ImageDataSize = imageDataSize; |
|||
this.PartitionLength = partitionLength; |
|||
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); |
|||
this.InitBitreader(partitionLength, startPos); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="imageData">The raw encoded image data.</param>
|
|||
/// <param name="partitionLength">The partition length.</param>
|
|||
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
|
|||
public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0) |
|||
{ |
|||
this.Data = imageData; |
|||
this.ImageDataSize = (uint)imageData.Length; |
|||
this.PartitionLength = partitionLength; |
|||
this.InitBitreader(partitionLength, startPos); |
|||
} |
|||
|
|||
public int Pos |
|||
{ |
|||
get { return (int)this.pos; } |
|||
} |
|||
|
|||
public uint ImageDataSize { get; } |
|||
|
|||
public uint PartitionLength { get; } |
|||
|
|||
public uint Remaining { get; set; } |
|||
|
|||
public int GetBit(int prob) |
|||
{ |
|||
uint range = this.range; |
|||
if (this.bits < 0) |
|||
{ |
|||
this.LoadNewBytes(); |
|||
} |
|||
|
|||
int pos = this.bits; |
|||
uint split = (uint)((range * prob) >> 8); |
|||
ulong value = this.value >> pos; |
|||
bool bit = value > split; |
|||
if (bit) |
|||
{ |
|||
range -= split; |
|||
this.value -= (ulong)(split + 1) << pos; |
|||
} |
|||
else |
|||
{ |
|||
range = split + 1; |
|||
} |
|||
|
|||
int shift = 7 ^ this.BitsLog2Floor(range); |
|||
range <<= shift; |
|||
this.bits -= shift; |
|||
|
|||
this.range = range - 1; |
|||
|
|||
return bit ? 1 : 0; |
|||
} |
|||
|
|||
// Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here)
|
|||
public int GetSigned(int v) |
|||
{ |
|||
if (this.bits < 0) |
|||
{ |
|||
this.LoadNewBytes(); |
|||
} |
|||
|
|||
int pos = this.bits; |
|||
uint split = this.range >> 1; |
|||
ulong value = this.value >> pos; |
|||
ulong mask = (split - value) >> 31; // -1 or 0
|
|||
this.bits -= 1; |
|||
this.range += (uint)mask; |
|||
this.range |= 1; |
|||
this.value -= ((split + 1) & mask) << pos; |
|||
|
|||
return (v ^ (int)mask) - (int)mask; |
|||
} |
|||
|
|||
public bool ReadBool() |
|||
{ |
|||
return this.ReadValue(1) is 1; |
|||
} |
|||
|
|||
public uint ReadValue(int nBits) |
|||
{ |
|||
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); |
|||
|
|||
uint v = 0; |
|||
while (nBits-- > 0) |
|||
{ |
|||
v |= (uint)this.GetBit(0x80) << nBits; |
|||
} |
|||
|
|||
return v; |
|||
} |
|||
|
|||
public int ReadSignedValue(int nBits) |
|||
{ |
|||
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); |
|||
|
|||
int value = (int)this.ReadValue(nBits); |
|||
return this.ReadValue(1) != 0 ? -value : value; |
|||
} |
|||
|
|||
private void InitBitreader(uint size, int pos = 0) |
|||
{ |
|||
this.range = 255 - 1; |
|||
this.value = 0; |
|||
this.bits = -8; // to load the very first 8 bits.
|
|||
this.eof = false; |
|||
this.pos = pos; |
|||
this.bufferEnd = (uint)(pos + size); |
|||
this.bufferMax = (uint)(size > 8 ? pos + size - 8 + 1 : pos); |
|||
|
|||
this.LoadNewBytes(); |
|||
} |
|||
|
|||
private void LoadNewBytes() |
|||
{ |
|||
if (this.pos < this.bufferMax) |
|||
{ |
|||
ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8)); |
|||
this.pos += BitsCount >> 3; |
|||
ulong bits = this.ByteSwap64(inBits); |
|||
bits >>= 64 - BitsCount; |
|||
this.value = bits | (this.value << BitsCount); |
|||
this.bits += BitsCount; |
|||
} |
|||
else |
|||
{ |
|||
this.LoadFinalBytes(); |
|||
} |
|||
} |
|||
|
|||
private void LoadFinalBytes() |
|||
{ |
|||
// Only read 8bits at a time.
|
|||
if (this.pos < this.bufferEnd) |
|||
{ |
|||
this.bits += 8; |
|||
this.value = this.Data[this.pos++] | (this.value << 8); |
|||
} |
|||
else if (!this.eof) |
|||
{ |
|||
this.value <<= 8; |
|||
this.bits += 8; |
|||
this.eof = true; |
|||
} |
|||
else |
|||
{ |
|||
this.bits = 0; // This is to avoid undefined behaviour with shifts.
|
|||
} |
|||
} |
|||
|
|||
private ulong ByteSwap64(ulong x) |
|||
{ |
|||
x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); |
|||
x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16); |
|||
x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); |
|||
return x; |
|||
} |
|||
|
|||
// Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n).
|
|||
private int BitsLog2Floor(uint n) |
|||
{ |
|||
int logValue = 0; |
|||
while (n >= 256) |
|||
{ |
|||
logValue += 8; |
|||
n >>= 8; |
|||
} |
|||
|
|||
return logValue + WebPLookupTables.LogTable8bit[n]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,361 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Holds information for decoding a lossy webp image.
|
|||
/// </summary>
|
|||
internal class Vp8Decoder : IDisposable |
|||
{ |
|||
private Vp8MacroBlock leftMacroBlock; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8Decoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="frameHeader">The frame header.</param>
|
|||
/// <param name="pictureHeader">The picture header.</param>
|
|||
/// <param name="segmentHeader">The segment header.</param>
|
|||
/// <param name="probabilities">The probabilities.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory for the pixel data output and the temporary buffers.</param>
|
|||
public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.FilterHeader = new Vp8FilterHeader(); |
|||
this.FrameHeader = frameHeader; |
|||
this.PictureHeader = pictureHeader; |
|||
this.SegmentHeader = segmentHeader; |
|||
this.Probabilities = probabilities; |
|||
this.IntraL = new byte[4]; |
|||
this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); |
|||
this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); |
|||
this.CacheYStride = 16 * this.MbWidth; |
|||
this.CacheUvStride = 8 * this.MbWidth; |
|||
this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; |
|||
this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; |
|||
this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; |
|||
this.FilterInfo = new Vp8FilterInfo[this.MbWidth]; |
|||
for (int i = 0; i < this.MbWidth; i++) |
|||
{ |
|||
this.MacroBlockInfo[i] = new Vp8MacroBlock(); |
|||
this.MacroBlockData[i] = new Vp8MacroBlockData(); |
|||
this.YuvTopSamples[i] = new Vp8TopSamples(); |
|||
this.FilterInfo[i] = new Vp8FilterInfo(); |
|||
} |
|||
|
|||
this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); |
|||
|
|||
this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; |
|||
this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; |
|||
for (int i = 0; i < WebPConstants.NumMbSegments; i++) |
|||
{ |
|||
this.DeQuantMatrices[i] = new Vp8QuantMatrix(); |
|||
for (int j = 0; j < 2; j++) |
|||
{ |
|||
this.FilterStrength[i, j] = new Vp8FilterInfo(); |
|||
} |
|||
} |
|||
|
|||
uint width = pictureHeader.Width; |
|||
uint height = pictureHeader.Height; |
|||
|
|||
int extraRows = WebPConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter
|
|||
int extraY = extraRows * this.CacheYStride; |
|||
int extraUv = (extraRows / 2) * this.CacheUvStride; |
|||
this.YuvBuffer = memoryAllocator.Allocate<byte>((WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY); |
|||
this.CacheY = memoryAllocator.Allocate<byte>((16 * this.CacheYStride) + extraY); |
|||
this.CacheU = memoryAllocator.Allocate<byte>((16 * this.CacheUvStride) + extraUv); |
|||
this.CacheV = memoryAllocator.Allocate<byte>((16 * this.CacheUvStride) + extraUv); |
|||
this.TmpYBuffer = memoryAllocator.Allocate<byte>((int)width); |
|||
this.TmpUBuffer = memoryAllocator.Allocate<byte>((int)width); |
|||
this.TmpVBuffer = memoryAllocator.Allocate<byte>((int)width); |
|||
this.Pixels = memoryAllocator.Allocate<byte>((int)(width * height * 4)); |
|||
|
|||
this.YuvBuffer.Memory.Span.Fill(205); |
|||
this.CacheY.Memory.Span.Fill(205); |
|||
this.CacheU.Memory.Span.Fill(205); |
|||
this.CacheV.Memory.Span.Fill(205); |
|||
|
|||
this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the frame header.
|
|||
/// </summary>
|
|||
public Vp8FrameHeader FrameHeader { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture header.
|
|||
/// </summary>
|
|||
public Vp8PictureHeader PictureHeader { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter header.
|
|||
/// </summary>
|
|||
public Vp8FilterHeader FilterHeader { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the segment header.
|
|||
/// </summary>
|
|||
public Vp8SegmentHeader SegmentHeader { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of partitions minus one.
|
|||
/// </summary>
|
|||
public int NumPartsMinusOne { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the per-partition boolean decoders.
|
|||
/// </summary>
|
|||
public Vp8BitReader[] Vp8BitReaders { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the dequantization matrices (one set of DC/AC dequant factor per segment).
|
|||
/// </summary>
|
|||
public Vp8QuantMatrix[] DeQuantMatrices { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to use the skip probabilities.
|
|||
/// </summary>
|
|||
public bool UseSkipProbability { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the skip probability.
|
|||
/// </summary>
|
|||
public byte SkipProbability { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Probabilities.
|
|||
/// </summary>
|
|||
public Vp8Proba Probabilities { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the top intra modes values: 4 * MbWidth.
|
|||
/// </summary>
|
|||
public byte[] IntraT { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the left intra modes values.
|
|||
/// </summary>
|
|||
public byte[] IntraL { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width in macroblock units.
|
|||
/// </summary>
|
|||
public int MbWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height in macroblock units.
|
|||
/// </summary>
|
|||
public int MbHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the top-left x index of the macroblock that must be in-loop filtered.
|
|||
/// </summary>
|
|||
public int TopLeftMbX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the top-left y index of the macroblock that must be in-loop filtered.
|
|||
/// </summary>
|
|||
public int TopLeftMbY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the last bottom-right x index of the macroblock that must be decoded.
|
|||
/// </summary>
|
|||
public int BottomRightMbX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the last bottom-right y index of the macroblock that must be decoded.
|
|||
/// </summary>
|
|||
public int BottomRightMbY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current x position in macroblock units.
|
|||
/// </summary>
|
|||
public int MbX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current y position in macroblock units.
|
|||
/// </summary>
|
|||
public int MbY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the parsed reconstruction data.
|
|||
/// </summary>
|
|||
public Vp8MacroBlockData[] MacroBlockData { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the contextual macroblock info.
|
|||
/// </summary>
|
|||
public Vp8MacroBlock[] MacroBlockInfo { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce)
|
|||
/// visually objectionable artifacts.
|
|||
/// </summary>
|
|||
public LoopFilter Filter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter strengths.
|
|||
/// </summary>
|
|||
public Vp8FilterInfo[,] FilterStrength { get; } |
|||
|
|||
public IMemoryOwner<byte> YuvBuffer { get; } |
|||
|
|||
public Vp8TopSamples[] YuvTopSamples { get; } |
|||
|
|||
public IMemoryOwner<byte> CacheY { get; } |
|||
|
|||
public IMemoryOwner<byte> CacheU { get; } |
|||
|
|||
public IMemoryOwner<byte> CacheV { get; } |
|||
|
|||
public int CacheYOffset { get; set; } |
|||
|
|||
public int CacheUvOffset { get; set; } |
|||
|
|||
public int CacheYStride { get; } |
|||
|
|||
public int CacheUvStride { get; } |
|||
|
|||
public IMemoryOwner<byte> TmpYBuffer { get; } |
|||
|
|||
public IMemoryOwner<byte> TmpUBuffer { get; } |
|||
|
|||
public IMemoryOwner<byte> TmpVBuffer { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel buffer where the decoded pixel data will be stored.
|
|||
/// </summary>
|
|||
public IMemoryOwner<byte> Pixels { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets filter strength info.
|
|||
/// </summary>
|
|||
public Vp8FilterInfo[] FilterInfo { get; set; } |
|||
|
|||
public Vp8MacroBlock CurrentMacroBlock |
|||
{ |
|||
get |
|||
{ |
|||
return this.MacroBlockInfo[this.MbX]; |
|||
} |
|||
} |
|||
|
|||
public Vp8MacroBlock LeftMacroBlock |
|||
{ |
|||
get |
|||
{ |
|||
if (this.leftMacroBlock is null) |
|||
{ |
|||
this.leftMacroBlock = new Vp8MacroBlock(); |
|||
} |
|||
|
|||
return this.leftMacroBlock; |
|||
} |
|||
} |
|||
|
|||
public Vp8MacroBlockData CurrentBlockData |
|||
{ |
|||
get |
|||
{ |
|||
return this.MacroBlockData[this.MbX]; |
|||
} |
|||
} |
|||
|
|||
public void PrecomputeFilterStrengths() |
|||
{ |
|||
if (this.Filter is LoopFilter.None) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Vp8FilterHeader hdr = this.FilterHeader; |
|||
for (int s = 0; s < WebPConstants.NumMbSegments; ++s) |
|||
{ |
|||
int baseLevel; |
|||
|
|||
// First, compute the initial level.
|
|||
if (this.SegmentHeader.UseSegment) |
|||
{ |
|||
baseLevel = this.SegmentHeader.FilterStrength[s]; |
|||
if (!this.SegmentHeader.Delta) |
|||
{ |
|||
baseLevel += hdr.Level; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
baseLevel = hdr.Level; |
|||
} |
|||
|
|||
for (int i4x4 = 0; i4x4 <= 1; ++i4x4) |
|||
{ |
|||
Vp8FilterInfo info = this.FilterStrength[s, i4x4]; |
|||
int level = baseLevel; |
|||
if (hdr.UseLfDelta) |
|||
{ |
|||
level += hdr.RefLfDelta[0]; |
|||
if (i4x4 > 0) |
|||
{ |
|||
level += hdr.ModeLfDelta[0]; |
|||
} |
|||
} |
|||
|
|||
level = (level < 0) ? 0 : (level > 63) ? 63 : level; |
|||
if (level > 0) |
|||
{ |
|||
int iLevel = level; |
|||
if (hdr.Sharpness > 0) |
|||
{ |
|||
if (hdr.Sharpness > 4) |
|||
{ |
|||
iLevel >>= 2; |
|||
} |
|||
else |
|||
{ |
|||
iLevel >>= 1; |
|||
} |
|||
|
|||
if (iLevel > 9 - hdr.Sharpness) |
|||
{ |
|||
iLevel = 9 - hdr.Sharpness; |
|||
} |
|||
} |
|||
|
|||
if (iLevel < 1) |
|||
{ |
|||
iLevel = 1; |
|||
} |
|||
|
|||
info.InnerLevel = (byte)iLevel; |
|||
info.Limit = (byte)((2 * level) + iLevel); |
|||
info.HighEdgeVarianceThreshold = (byte)((level >= 40) ? 2 : (level >= 15) ? 1 : 0); |
|||
} |
|||
else |
|||
{ |
|||
info.Limit = 0; // no filtering.
|
|||
} |
|||
|
|||
info.UseInnerFiltering = (byte)i4x4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.YuvBuffer.Dispose(); |
|||
this.CacheY.Dispose(); |
|||
this.CacheU.Dispose(); |
|||
this.CacheV.Dispose(); |
|||
this.TmpYBuffer.Dispose(); |
|||
this.TmpUBuffer.Dispose(); |
|||
this.TmpVBuffer.Dispose(); |
|||
this.Pixels.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class Vp8FilterHeader |
|||
{ |
|||
private const int NumRefLfDeltas = 4; |
|||
|
|||
private const int NumModeLfDeltas = 4; |
|||
|
|||
public Vp8FilterHeader() |
|||
{ |
|||
this.RefLfDelta = new int[NumRefLfDeltas]; |
|||
this.ModeLfDelta = new int[NumModeLfDeltas]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the loop filter.
|
|||
/// </summary>
|
|||
public LoopFilter LoopFilter { get; set; } |
|||
|
|||
// [0..63]
|
|||
public int Level { get; set; } |
|||
|
|||
// [0..7]
|
|||
public int Sharpness { get; set; } |
|||
|
|||
public bool UseLfDelta { get; set; } |
|||
|
|||
public int[] RefLfDelta { get; private set; } |
|||
|
|||
public int[] ModeLfDelta { get; private set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Filter information.
|
|||
/// </summary>
|
|||
internal class Vp8FilterInfo : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8FilterInfo"/> class.
|
|||
/// </summary>
|
|||
public Vp8FilterInfo() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8FilterInfo"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The filter info to create an instance from.</param>
|
|||
public Vp8FilterInfo(Vp8FilterInfo other) |
|||
{ |
|||
this.Limit = other.Limit; |
|||
this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold; |
|||
this.InnerLevel = other.InnerLevel; |
|||
this.UseInnerFiltering = other.UseInnerFiltering; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the filter limit in [3..189], or 0 if no filtering.
|
|||
/// </summary>
|
|||
public byte Limit { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the inner limit in [1..63].
|
|||
/// </summary>
|
|||
public byte InnerLevel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to do inner filtering.
|
|||
/// TODO: can this be a bool?
|
|||
/// </summary>
|
|||
public byte UseInnerFiltering { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the high edge variance threshold in [0..2].
|
|||
/// </summary>
|
|||
public byte HighEdgeVarianceThreshold { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Vp8 frame header information.
|
|||
/// </summary>
|
|||
internal class Vp8FrameHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this is a key frame.
|
|||
/// </summary>
|
|||
public bool KeyFrame { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets Vp8 profile [0..3].
|
|||
/// </summary>
|
|||
public sbyte Profile { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the partition length.
|
|||
/// </summary>
|
|||
public uint PartitionLength { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Enum for the different VP8 chunk header types.
|
|||
/// </summary>
|
|||
public enum Vp8HeaderType |
|||
{ |
|||
/// <summary>
|
|||
/// Invalid VP8 header.
|
|||
/// </summary>
|
|||
Invalid = 0, |
|||
|
|||
/// <summary>
|
|||
/// A VP8 header.
|
|||
/// </summary>
|
|||
Vp8 = 1, |
|||
|
|||
/// <summary>
|
|||
/// VP8 header, signaling the use of VP8L lossless format.
|
|||
/// </summary>
|
|||
Vp8L = 2, |
|||
|
|||
/// <summary>
|
|||
/// Header for a extended-VP8 chunk.
|
|||
/// </summary>
|
|||
Vp8X = 3, |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal ref struct Vp8Io |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the picture width in pixels (invariable).
|
|||
/// Original, uncropped dimensions.
|
|||
/// The actual area passed to put() is stored in <see cref="MbW"/> /> field.
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the picture height in pixels (invariable).
|
|||
/// Original, uncropped dimensions.
|
|||
/// The actual area passed to put() is stored in <see cref="MbH"/> /> field.
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-position of the current macroblock.
|
|||
/// </summary>
|
|||
public int MbY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets number of columns in the sample.
|
|||
/// </summary>
|
|||
public int MbW { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets number of rows in the sample.
|
|||
/// </summary>
|
|||
public int MbH { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the luma component.
|
|||
/// </summary>
|
|||
public Span<byte> Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the U chroma component.
|
|||
/// </summary>
|
|||
public Span<byte> U { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the V chroma component.
|
|||
/// </summary>
|
|||
public Span<byte> V { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the row stride for luma.
|
|||
/// </summary>
|
|||
public int YStride { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the row stride for chroma.
|
|||
/// </summary>
|
|||
public int UvStride { get; set; } |
|||
|
|||
public bool UseCropping { get; set; } |
|||
|
|||
public int CropLeft { get; set; } |
|||
|
|||
public int CropRight { get; set; } |
|||
|
|||
public int CropTop { get; set; } |
|||
|
|||
public int CropBottom { get; set; } |
|||
|
|||
public bool UseScaling { get; set; } |
|||
|
|||
public int ScaledWidth { get; set; } |
|||
|
|||
public int ScaledHeight { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,219 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// A bit reader for reading lossless webp streams.
|
|||
/// </summary>
|
|||
internal class Vp8LBitReader : BitReaderBase |
|||
{ |
|||
/// <summary>
|
|||
/// Maximum number of bits (inclusive) the bit-reader can handle.
|
|||
/// </summary>
|
|||
private const int Vp8LMaxNumBitRead = 24; |
|||
|
|||
/// <summary>
|
|||
/// Number of bits prefetched.
|
|||
/// </summary>
|
|||
private const int Lbits = 64; |
|||
|
|||
/// <summary>
|
|||
/// Minimum number of bytes ready after VP8LFillBitWindow.
|
|||
/// </summary>
|
|||
private const int Wbits = 32; |
|||
|
|||
private readonly uint[] bitMask = |
|||
{ |
|||
0, |
|||
0x000001, 0x000003, 0x000007, 0x00000f, |
|||
0x00001f, 0x00003f, 0x00007f, 0x0000ff, |
|||
0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, |
|||
0x001fff, 0x003fff, 0x007fff, 0x00ffff, |
|||
0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, |
|||
0x1fffff, 0x3fffff, 0x7fffff, 0xffffff |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Pre-fetched bits.
|
|||
/// </summary>
|
|||
private ulong value; |
|||
|
|||
/// <summary>
|
|||
/// Buffer length.
|
|||
/// </summary>
|
|||
private readonly long len; |
|||
|
|||
/// <summary>
|
|||
/// Byte position in buffer.
|
|||
/// </summary>
|
|||
private long pos; |
|||
|
|||
/// <summary>
|
|||
/// Current bit-reading position in value.
|
|||
/// </summary>
|
|||
private int bitPos; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="data">Lossless compressed image data.</param>
|
|||
public Vp8LBitReader(byte[] data) |
|||
{ |
|||
this.Data = data; |
|||
this.len = data.Length; |
|||
this.value = 0; |
|||
this.bitPos = 0; |
|||
this.Eos = false; |
|||
|
|||
ulong currentValue = 0; |
|||
for (int i = 0; i < 8; ++i) |
|||
{ |
|||
currentValue |= (ulong)this.Data[i] << (8 * i); |
|||
} |
|||
|
|||
this.value = currentValue; |
|||
this.pos = 8; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">The input stream to read from.</param>
|
|||
/// <param name="imageDataSize">The raw image data size in bytes.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
|
|||
public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) |
|||
{ |
|||
long length = imageDataSize; |
|||
|
|||
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); |
|||
|
|||
this.len = length; |
|||
this.value = 0; |
|||
this.bitPos = 0; |
|||
this.Eos = false; |
|||
|
|||
if (length > sizeof(long)) |
|||
{ |
|||
length = sizeof(long); |
|||
} |
|||
|
|||
ulong currentValue = 0; |
|||
for (int i = 0; i < length; ++i) |
|||
{ |
|||
currentValue |= (ulong)this.Data[i] << (8 * i); |
|||
} |
|||
|
|||
this.value = currentValue; |
|||
this.pos = length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether a bit was read past the end of buffer.
|
|||
/// </summary>
|
|||
public bool Eos { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order.
|
|||
/// </summary>
|
|||
/// <param name="nBits">The number of bits to read (should not exceed 16).</param>
|
|||
/// <returns>A ushort value.</returns>
|
|||
public uint ReadValue(int nBits) |
|||
{ |
|||
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); |
|||
|
|||
if (!this.Eos && nBits <= Vp8LMaxNumBitRead) |
|||
{ |
|||
ulong val = this.PrefetchBits() & this.bitMask[nBits]; |
|||
int newBits = this.bitPos + nBits; |
|||
this.bitPos = newBits; |
|||
this.ShiftBytes(); |
|||
return (uint)val; |
|||
} |
|||
|
|||
this.SetEndOfStream(); |
|||
return 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a single bit from the stream.
|
|||
/// </summary>
|
|||
/// <returns>True if the bit read was 1, false otherwise.</returns>
|
|||
public bool ReadBit() |
|||
{ |
|||
uint bit = this.ReadValue(1); |
|||
return bit != 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow.
|
|||
/// </summary>
|
|||
/// <param name="numberOfBits">The number of bits to advance the position.</param>
|
|||
public void AdvanceBitPosition(int numberOfBits) |
|||
{ |
|||
this.bitPos += numberOfBits; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the pre-fetched bits, so they can be looked up.
|
|||
/// </summary>
|
|||
/// <returns>The pre-fetched bits.</returns>
|
|||
public ulong PrefetchBits() |
|||
{ |
|||
return this.value >> (this.bitPos & (Lbits - 1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Advances the read buffer by 4 bytes to make room for reading next 32 bits.
|
|||
/// </summary>
|
|||
public void FillBitWindow() |
|||
{ |
|||
if (this.bitPos >= Wbits) |
|||
{ |
|||
this.DoFillBitWindow(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns true if there was an attempt at reading bit past the end of the buffer.
|
|||
/// </summary>
|
|||
/// <returns>True, if end of buffer was reached.</returns>
|
|||
public bool IsEndOfStream() |
|||
{ |
|||
return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits)); |
|||
} |
|||
|
|||
private void DoFillBitWindow() |
|||
{ |
|||
this.ShiftBytes(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If not at EOS, reload up to Vp8LLbits byte-by-byte.
|
|||
/// </summary>
|
|||
private void ShiftBytes() |
|||
{ |
|||
while (this.bitPos >= 8 && this.pos < this.len) |
|||
{ |
|||
this.value >>= 8; |
|||
this.value |= (ulong)this.Data[this.pos] << (Lbits - 8); |
|||
++this.pos; |
|||
this.bitPos -= 8; |
|||
} |
|||
|
|||
if (this.IsEndOfStream()) |
|||
{ |
|||
this.SetEndOfStream(); |
|||
} |
|||
} |
|||
|
|||
private void SetEndOfStream() |
|||
{ |
|||
this.Eos = true; |
|||
this.bitPos = 0; // To avoid undefined behaviour with shifts.
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Holds information for decoding a lossless webp image.
|
|||
/// </summary>
|
|||
internal class Vp8LDecoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vp8LDecoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="height">The height of the image.</param>
|
|||
/// <param name="memoryAllocator">Used for allocating memory for the pixel data output.</param>
|
|||
public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Metadata = new Vp8LMetadata(); |
|||
this.Pixels = memoryAllocator.Allocate<uint>(width * height, AllocationOptions.Clean); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of the image to decode.
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of the image to decode.
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image.
|
|||
/// </summary>
|
|||
public Vp8LMetadata Metadata { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the transformations which needs to be reversed.
|
|||
/// </summary>
|
|||
public List<Vp8LTransform> Transforms { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel data.
|
|||
/// </summary>
|
|||
public IMemoryOwner<uint> Pixels { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.Pixels.Dispose(); |
|||
this.Metadata?.HuffmanImage?.Dispose(); |
|||
|
|||
if (this.Transforms != null) |
|||
{ |
|||
foreach (Vp8LTransform transform in this.Transforms) |
|||
{ |
|||
transform.Data?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class Vp8LMetadata |
|||
{ |
|||
public int ColorCacheSize { get; set; } |
|||
|
|||
public ColorCache ColorCache { get; set; } |
|||
|
|||
public int HuffmanMask { get; set; } |
|||
|
|||
public int HuffmanSubSampleBits { get; set; } |
|||
|
|||
public int HuffmanXSize { get; set; } |
|||
|
|||
public IMemoryOwner<uint> HuffmanImage { get; set; } |
|||
|
|||
public int NumHTreeGroups { get; set; } |
|||
|
|||
public HTreeGroup[] HTreeGroups { get; set; } |
|||
|
|||
public HuffmanCode[] HuffmanTables { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Data associated with a VP8L transformation to reduce the entropy.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("Transformtype: {TransformType}")] |
|||
internal class Vp8LTransform |
|||
{ |
|||
public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) |
|||
{ |
|||
this.TransformType = transformType; |
|||
this.XSize = xSize; |
|||
this.YSize = ySize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform type.
|
|||
/// </summary>
|
|||
public Vp8LTransformType TransformType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the subsampling bits defining the transform window.
|
|||
/// </summary>
|
|||
public int Bits { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the transform window X index.
|
|||
/// </summary>
|
|||
public int XSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform window Y index.
|
|||
/// </summary>
|
|||
public int YSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the transform data.
|
|||
/// </summary>
|
|||
public IMemoryOwner<uint> Data { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Enum for the different transform types. Transformations are reversible manipulations of the image data
|
|||
/// that can reduce the remaining symbolic entropy by modeling spatial and color correlations.
|
|||
/// Transformations can make the final compression more dense.
|
|||
/// </summary>
|
|||
public enum Vp8LTransformType : uint |
|||
{ |
|||
/// <summary>
|
|||
/// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated.
|
|||
/// </summary>
|
|||
PredictorTransform = 0, |
|||
|
|||
/// <summary>
|
|||
/// The goal of the color transform is to decorrelate the R, G and B values of each pixel.
|
|||
/// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red.
|
|||
/// </summary>
|
|||
CrossColorTransform = 1, |
|||
|
|||
/// <summary>
|
|||
/// The subtract green transform subtracts green values from red and blue values of each pixel.
|
|||
/// When this transform is present, the decoder needs to add the green value to both red and blue.
|
|||
/// There is no data associated with this transform.
|
|||
/// </summary>
|
|||
SubtractGreen = 2, |
|||
|
|||
/// <summary>
|
|||
/// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices.
|
|||
/// The color indexing transform achieves this.
|
|||
/// </summary>
|
|||
ColorIndexingTransform = 3, |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Contextual macroblock information.
|
|||
/// </summary>
|
|||
internal class Vp8MacroBlock |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma).
|
|||
/// </summary>
|
|||
public uint NoneZeroAcDcCoeffs { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets non-zero DC coeff (1bit).
|
|||
/// </summary>
|
|||
public uint NoneZeroDcCoeffs { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Data needed to reconstruct a macroblock.
|
|||
/// </summary>
|
|||
internal class Vp8MacroBlockData |
|||
{ |
|||
public Vp8MacroBlockData() |
|||
{ |
|||
this.Modes = new byte[16]; |
|||
this.Coeffs = new short[384]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4.
|
|||
/// </summary>
|
|||
public short[] Coeffs { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether its intra4x4.
|
|||
/// </summary>
|
|||
public bool IsI4x4 { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes.
|
|||
/// </summary>
|
|||
public byte[] Modes { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the chroma prediction mode.
|
|||
/// </summary>
|
|||
public byte UvMode { get; set; } |
|||
|
|||
public uint NonZeroY { get; set; } |
|||
|
|||
public uint NonZeroUv { get; set; } |
|||
|
|||
public byte Skip { get; set; } |
|||
|
|||
public byte Segment { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class Vp8PictureHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the width of the image.
|
|||
/// </summary>
|
|||
public uint Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Height of the image.
|
|||
/// </summary>
|
|||
public uint Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the horizontal scale.
|
|||
/// </summary>
|
|||
public sbyte XScale { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the vertical scale.
|
|||
/// </summary>
|
|||
public sbyte YScale { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the colorspace.
|
|||
/// 0 - YUV color space similar to the YCrCb color space defined in.
|
|||
/// 1 - Reserved for future use.
|
|||
/// </summary>
|
|||
public sbyte ColorSpace { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the clamp type.
|
|||
/// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive).
|
|||
/// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary.
|
|||
/// </summary>
|
|||
public sbyte ClampType { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Data for all frame-persistent probabilities.
|
|||
/// </summary>
|
|||
internal class Vp8Proba |
|||
{ |
|||
private const int MbFeatureTreeProbs = 3; |
|||
|
|||
public Vp8Proba() |
|||
{ |
|||
this.Segments = new uint[MbFeatureTreeProbs]; |
|||
this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands]; |
|||
this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1]; |
|||
|
|||
for (int i = 0; i < WebPConstants.NumTypes; i++) |
|||
{ |
|||
for (int j = 0; j < WebPConstants.NumBands; j++) |
|||
{ |
|||
this.Bands[i, j] = new Vp8BandProbas(); |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < WebPConstants.NumTypes; i++) |
|||
{ |
|||
for (int j = 0; j < 17; j++) |
|||
{ |
|||
this.BandsPtr[i, j] = new Vp8BandProbas(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public uint[] Segments { get; } |
|||
|
|||
public Vp8BandProbas[,] Bands { get; } |
|||
|
|||
public Vp8BandProbas[,] BandsPtr { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Probabilities associated to one of the contexts.
|
|||
/// </summary>
|
|||
internal class Vp8ProbaArray |
|||
{ |
|||
public Vp8ProbaArray() |
|||
{ |
|||
this.Probabilities = new byte[WebPConstants.NumProbas]; |
|||
} |
|||
|
|||
public byte[] Probabilities { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// The version number setting enables or disables certain features in the bitstream.
|
|||
/// </summary>
|
|||
internal class Vp8Profile |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the reconstruction filter.
|
|||
/// </summary>
|
|||
public ReconstructionFilter ReconstructionFilter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the loop filter.
|
|||
/// </summary>
|
|||
public LoopFilter LoopFilter { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class Vp8QuantMatrix |
|||
{ |
|||
public int[] Y1Mat { get; } = new int[2]; |
|||
|
|||
public int[] Y2Mat { get; } = new int[2]; |
|||
|
|||
public int[] UvMat { get; } = new int[2]; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the U/V quantizer value.
|
|||
/// </summary>
|
|||
public int UvQuant { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the dithering amplitude (0 = off, max=255).
|
|||
/// </summary>
|
|||
public int Dither { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Segment features.
|
|||
/// </summary>
|
|||
internal class Vp8SegmentHeader |
|||
{ |
|||
private const int NumMbSegments = 4; |
|||
|
|||
public Vp8SegmentHeader() |
|||
{ |
|||
this.Quantizer = new byte[NumMbSegments]; |
|||
this.FilterStrength = new byte[NumMbSegments]; |
|||
} |
|||
|
|||
public bool UseSegment { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to update the segment map or not.
|
|||
/// </summary>
|
|||
public bool UpdateMap { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to use delta values for quantizer and filter.
|
|||
/// If this value is false, absolute values are used.
|
|||
/// </summary>
|
|||
public bool Delta { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets quantization changes.
|
|||
/// </summary>
|
|||
public byte[] Quantizer { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter strength for segments.
|
|||
/// </summary>
|
|||
public byte[] FilterStrength { get; private set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class Vp8TopSamples |
|||
{ |
|||
public byte[] Y { get; } = new byte[16]; |
|||
|
|||
public byte[] U { get; } = new byte[8]; |
|||
|
|||
public byte[] V { get; } = new byte[8]; |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Enum for the different alpha filter types.
|
|||
/// </summary>
|
|||
internal enum WebPAlphaFilterType |
|||
{ |
|||
/// <summary>
|
|||
/// No filtering.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Horizontal filter.
|
|||
/// </summary>
|
|||
Horizontal = 1, |
|||
|
|||
/// <summary>
|
|||
/// Vertical filter.
|
|||
/// </summary>
|
|||
Vertical = 2, |
|||
|
|||
/// <summary>
|
|||
/// Gradient filter.
|
|||
/// </summary>
|
|||
Gradient = 3, |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Enumerates the available bits per pixel the webp image uses.
|
|||
/// </summary>
|
|||
public enum WebPBitsPerPixel : short |
|||
{ |
|||
/// <summary>
|
|||
/// 24 bits per pixel. Each pixel consists of 3 bytes.
|
|||
/// </summary>
|
|||
Pixel24 = 24, |
|||
|
|||
/// <summary>
|
|||
/// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present).
|
|||
/// </summary>
|
|||
Pixel32 = 32 |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Contains a list of different webp chunk types.
|
|||
/// </summary>
|
|||
public enum WebPChunkType : uint |
|||
{ |
|||
/// <summary>
|
|||
/// Header signaling the use of VP8 format.
|
|||
/// </summary>
|
|||
Vp8 = 0x56503820U, |
|||
|
|||
/// <summary>
|
|||
/// Header for a extended-VP8 chunk.
|
|||
/// </summary>
|
|||
Vp8L = 0x5650384CU, |
|||
|
|||
/// <summary>
|
|||
/// Header for a extended-VP8 chunk.
|
|||
/// </summary>
|
|||
Vp8X = 0x56503858U, |
|||
|
|||
/// <summary>
|
|||
/// Chunk contains information about the alpha channel.
|
|||
/// </summary>
|
|||
Alpha = 0x414C5048U, |
|||
|
|||
/// <summary>
|
|||
/// Chunk which contains a color profile.
|
|||
/// </summary>
|
|||
Iccp = 0x49434350U, |
|||
|
|||
/// <summary>
|
|||
/// Chunk which contains EXIF metadata about the image.
|
|||
/// </summary>
|
|||
Exif = 0x45584946U, |
|||
|
|||
/// <summary>
|
|||
/// Chunk contains XMP metadata about the image.
|
|||
/// </summary>
|
|||
Xmp = 0x584D5020U, |
|||
|
|||
/// <summary>
|
|||
/// For an animated image, this chunk contains the global parameters of the animation.
|
|||
/// </summary>
|
|||
AnimationParameter = 0x414E494D, |
|||
|
|||
/// <summary>
|
|||
/// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present.
|
|||
/// </summary>
|
|||
Animation = 0x414E4D46, |
|||
|
|||
/// <summary>
|
|||
/// TODO: not sure what this is for yet.
|
|||
/// </summary>
|
|||
FRGM = 0x4652474D, |
|||
} |
|||
} |
|||
@ -0,0 +1,174 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Constants used for decoding VP8 and VP8L bitstreams.
|
|||
/// </summary>
|
|||
internal static class WebPConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The list of file extensions that equate to WebP.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "webp" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a jpeg.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/webp", }; |
|||
|
|||
/// <summary>
|
|||
/// Signature which identifies a VP8 header.
|
|||
/// </summary>
|
|||
public static readonly byte[] Vp8MagicBytes = |
|||
{ |
|||
0x9D, |
|||
0x01, |
|||
0x2A |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// The header bytes identifying RIFF file.
|
|||
/// </summary>
|
|||
public static readonly byte[] RiffFourCc = |
|||
{ |
|||
0x52, // R
|
|||
0x49, // I
|
|||
0x46, // F
|
|||
0x46 // F
|
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// The header bytes identifying a WebP.
|
|||
/// </summary>
|
|||
public static readonly byte[] WebPHeader = |
|||
{ |
|||
0x57, // W
|
|||
0x45, // E
|
|||
0x42, // B
|
|||
0x50 // P
|
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Signature byte which identifies a VP8L header.
|
|||
/// </summary>
|
|||
public const byte Vp8LMagicByte = 0x2F; |
|||
|
|||
/// <summary>
|
|||
/// 3 bits reserved for version.
|
|||
/// </summary>
|
|||
public const int Vp8LVersionBits = 3; |
|||
|
|||
/// <summary>
|
|||
/// Bits for width and height infos of a VPL8 image.
|
|||
/// </summary>
|
|||
public const int Vp8LImageSizeBits = 14; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of color cache bits.
|
|||
/// </summary>
|
|||
public const int MaxColorCacheBits = 11; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of allowed transforms in a VP8L bitstream.
|
|||
/// </summary>
|
|||
public const int MaxNumberOfTransforms = 4; |
|||
|
|||
public const int MaxAllowedCodeLength = 15; |
|||
|
|||
public const int DefaultCodeLength = 8; |
|||
|
|||
public const int HuffmanCodesPerMetaCode = 5; |
|||
|
|||
public const uint ArgbBlack = 0xff000000; |
|||
|
|||
public const int NumLiteralCodes = 256; |
|||
|
|||
public const int NumLengthCodes = 24; |
|||
|
|||
public const int NumDistanceCodes = 40; |
|||
|
|||
public const int LengthTableBits = 7; |
|||
|
|||
public const uint CodeLengthLiterals = 16; |
|||
|
|||
public const int CodeLengthRepeatCode = 16; |
|||
|
|||
public static readonly int[] CodeLengthExtraBits = { 2, 3, 7 }; |
|||
|
|||
public static readonly int[] CodeLengthRepeatOffsets = { 3, 3, 11 }; |
|||
|
|||
public static readonly int[] AlphabetSize = |
|||
{ |
|||
NumLiteralCodes + NumLengthCodes, |
|||
NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, |
|||
NumDistanceCodes |
|||
}; |
|||
|
|||
// VP8 constants from here on:
|
|||
public const int NumMbSegments = 4; |
|||
|
|||
public const int MaxNumPartitions = 8; |
|||
|
|||
public const int NumTypes = 4; |
|||
|
|||
public const int NumBands = 8; |
|||
|
|||
public const int NumProbas = 11; |
|||
|
|||
public const int NumCtx = 3; |
|||
|
|||
// this is the common stride for enc/dec
|
|||
public const int Bps = 32; |
|||
|
|||
// intra prediction modes (TODO: maybe use an enum for this)
|
|||
public const int DcPred = 0; // predict DC using row above and column to the left
|
|||
public const int TmPred = 1; // propagate second differences a la "True Motion"
|
|||
public const int VPred = 2; // predict rows using row above
|
|||
public const int HPred = 3; // predict columns using column to the left
|
|||
|
|||
/// <summary>
|
|||
/// How many extra lines are needed on the MB boundary for caching, given a filtering level.
|
|||
/// Simple filter(1): up to 2 luma samples are read and 1 is written.
|
|||
/// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling).
|
|||
/// </summary>
|
|||
public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; |
|||
|
|||
// Paragraph 9.9
|
|||
public static readonly int[] Bands = |
|||
{ |
|||
0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 |
|||
}; |
|||
|
|||
public static readonly short[] Scan = |
|||
{ |
|||
0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps), |
|||
0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps), |
|||
0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps), |
|||
0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) |
|||
}; |
|||
|
|||
// Residual decoding (Paragraph 13.2 / 13.3)
|
|||
public static readonly byte[] Cat3 = { 173, 148, 140 }; |
|||
public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; |
|||
public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 }; |
|||
public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; |
|||
public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; |
|||
|
|||
public static readonly sbyte[] YModesIntra4 = |
|||
{ |
|||
-0, 1, |
|||
-1, 2, |
|||
-2, 3, |
|||
4, 6, |
|||
-3, 5, |
|||
-4, -5, |
|||
-6, 7, |
|||
-7, 8, |
|||
-8, -9 |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a webp stream.
|
|||
/// </summary>
|
|||
public sealed class WebPDecoder : IImageDecoder, IWebPDecoderOptions, IImageInfoDetector |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new WebPDecoderCore(configuration, this).Decode<TPixel>(stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new WebPDecoderCore(configuration, this).Identify(stream); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); |
|||
} |
|||
} |
|||
@ -0,0 +1,509 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Performs the bitmap decoding operation.
|
|||
/// </summary>
|
|||
internal sealed class WebPDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// Reusable buffer.
|
|||
/// </summary>
|
|||
private readonly byte[] buffer = new byte[4]; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Used for allocating memory during processing operations.
|
|||
/// </summary>
|
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
/// <summary>
|
|||
/// The stream to decode from.
|
|||
/// </summary>
|
|||
private Stream currentStream; |
|||
|
|||
/// <summary>
|
|||
/// The webp specific metadata.
|
|||
/// </summary>
|
|||
private WebPMetadata webpMetadata; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WebPDecoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
public WebPDecoderCore(Configuration configuration, IWebPDecoderOptions options) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.memoryAllocator = configuration.MemoryAllocator; |
|||
this.IgnoreMetadata = options.IgnoreMetadata; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
|
|||
/// </summary>
|
|||
public ImageMetadata Metadata { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image from the specified <see cref="Stream"/> and sets the data to the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The stream, where the image should be.</param>
|
|||
/// <returns>The decoded image.</returns>
|
|||
public Image<TPixel> Decode<TPixel>(Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.Metadata = new ImageMetadata(); |
|||
this.currentStream = stream; |
|||
|
|||
uint fileSize = this.ReadImageHeader(); |
|||
WebPImageInfo imageInfo = this.ReadVp8Info(); |
|||
if (imageInfo.Features != null && imageInfo.Features.Animation) |
|||
{ |
|||
WebPThrowHelper.ThrowNotSupportedException("Animations are not supported"); |
|||
} |
|||
|
|||
var image = new Image<TPixel>(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); |
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
if (imageInfo.IsLossLess) |
|||
{ |
|||
var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); |
|||
losslessDecoder.Decode(pixels, image.Width, image.Height); |
|||
} |
|||
else |
|||
{ |
|||
var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); |
|||
lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo); |
|||
} |
|||
|
|||
// There can be optional chunks after the image data, like EXIF and XMP.
|
|||
if (imageInfo.Features != null) |
|||
{ |
|||
this.ParseOptionalChunks(imageInfo.Features); |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the raw image information from the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
public IImageInfo Identify(Stream stream) |
|||
{ |
|||
this.currentStream = stream; |
|||
|
|||
this.ReadImageHeader(); |
|||
WebPImageInfo imageInfo = this.ReadVp8Info(); |
|||
|
|||
return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads and skips over the image header.
|
|||
/// </summary>
|
|||
/// <returns>The chunk size in bytes.</returns>
|
|||
private uint ReadImageHeader() |
|||
{ |
|||
// Skip FourCC header, we already know its a RIFF file at this point.
|
|||
this.currentStream.Skip(4); |
|||
|
|||
// Read file size.
|
|||
// The size of the file in bytes starting at offset 8.
|
|||
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
|
|||
uint chunkSize = this.ReadChunkSize(); |
|||
|
|||
// Skip 'WEBP' from the header.
|
|||
this.currentStream.Skip(4); |
|||
|
|||
return chunkSize; |
|||
} |
|||
|
|||
private WebPImageInfo ReadVp8Info() |
|||
{ |
|||
this.Metadata = new ImageMetadata(); |
|||
this.webpMetadata = this.Metadata.GetFormatMetadata(WebPFormat.Instance); |
|||
|
|||
WebPChunkType chunkType = this.ReadChunkType(); |
|||
|
|||
switch (chunkType) |
|||
{ |
|||
case WebPChunkType.Vp8: |
|||
return this.ReadVp8Header(); |
|||
case WebPChunkType.Vp8L: |
|||
return this.ReadVp8LHeader(); |
|||
case WebPChunkType.Vp8X: |
|||
return this.ReadVp8XHeader(); |
|||
} |
|||
|
|||
WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); |
|||
|
|||
return new WebPImageInfo(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads an the extended webp file header. An extended file header consists of:
|
|||
/// - 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>
|
|||
private WebPImageInfo ReadVp8XHeader() |
|||
{ |
|||
uint chunkSize = this.ReadChunkSize(); |
|||
|
|||
// The first byte contains information about the image features used.
|
|||
// The first two bit of it are reserved and should be 0. TODO: should an exception be thrown if its not the case, or just ignore it?
|
|||
byte imageFeatures = (byte)this.currentStream.ReadByte(); |
|||
|
|||
// If bit 3 is set, a ICC Profile Chunk should be present.
|
|||
bool isIccPresent = (imageFeatures & (1 << 5)) != 0; |
|||
|
|||
// If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk).
|
|||
bool isAlphaPresent = (imageFeatures & (1 << 4)) != 0; |
|||
|
|||
// If bit 5 is set, a EXIF metadata should be present.
|
|||
bool isExifPresent = (imageFeatures & (1 << 3)) != 0; |
|||
|
|||
// If bit 6 is set, XMP metadata should be present.
|
|||
bool isXmpPresent = (imageFeatures & (1 << 2)) != 0; |
|||
|
|||
// If bit 7 is set, animation should be present.
|
|||
bool isAnimationPresent = (imageFeatures & (1 << 1)) != 0; |
|||
|
|||
// 3 reserved bytes should follow which are supposed to be zero.
|
|||
this.currentStream.Read(this.buffer, 0, 3); |
|||
|
|||
// 3 bytes for the width.
|
|||
this.currentStream.Read(this.buffer, 0, 3); |
|||
this.buffer[3] = 0; |
|||
uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; |
|||
|
|||
// 3 bytes for the height.
|
|||
this.currentStream.Read(this.buffer, 0, 3); |
|||
this.buffer[3] = 0; |
|||
uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; |
|||
|
|||
// Optional chunks ICCP, ALPH and ANIM can follow here.
|
|||
WebPChunkType chunkType; |
|||
if (isIccPresent) |
|||
{ |
|||
chunkType = this.ReadChunkType(); |
|||
if (chunkType is WebPChunkType.Iccp) |
|||
{ |
|||
uint iccpChunkSize = this.ReadChunkSize(); |
|||
var iccpData = new byte[iccpChunkSize]; |
|||
this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); |
|||
var profile = new IccProfile(iccpData); |
|||
if (profile.CheckIsValid()) |
|||
{ |
|||
this.Metadata.IccProfile = profile; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (isAnimationPresent) |
|||
{ |
|||
this.webpMetadata.Animated = true; |
|||
|
|||
return new WebPImageInfo() |
|||
{ |
|||
Width = width, |
|||
Height = height, |
|||
Features = new WebPFeatures() |
|||
{ |
|||
Animation = true |
|||
} |
|||
}; |
|||
} |
|||
|
|||
byte[] alphaData = null; |
|||
byte alphaChunkHeader = 0; |
|||
if (isAlphaPresent) |
|||
{ |
|||
chunkType = this.ReadChunkType(); |
|||
if (chunkType != WebPChunkType.Alpha) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing"); |
|||
} |
|||
|
|||
uint alphaChunkSize = this.ReadChunkSize(); |
|||
alphaChunkHeader = (byte)this.currentStream.ReadByte(); |
|||
alphaData = new byte[alphaChunkSize - 1]; |
|||
this.currentStream.Read(alphaData, 0, alphaData.Length); |
|||
} |
|||
|
|||
var features = new WebPFeatures() |
|||
{ |
|||
Animation = isAnimationPresent, |
|||
Alpha = isAlphaPresent, |
|||
AlphaData = alphaData, |
|||
AlphaChunkHeader = alphaChunkHeader, |
|||
ExifProfile = isExifPresent, |
|||
IccProfile = isIccPresent, |
|||
XmpMetaData = isXmpPresent |
|||
}; |
|||
|
|||
// A VP8 or VP8L chunk should follow here.
|
|||
chunkType = this.ReadChunkType(); |
|||
|
|||
// TOOD: check if VP8 or VP8L info about the dimensions match VP8X info
|
|||
switch (chunkType) |
|||
{ |
|||
case WebPChunkType.Vp8: |
|||
return this.ReadVp8Header(features); |
|||
case WebPChunkType.Vp8L: |
|||
return this.ReadVp8LHeader(features); |
|||
} |
|||
|
|||
WebPThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); |
|||
|
|||
return new WebPImageInfo(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the header of a lossy webp image.
|
|||
/// </summary>
|
|||
/// <param name="features">Webp features.</param>
|
|||
/// <returns>Information about this webp image.</returns>
|
|||
private WebPImageInfo ReadVp8Header(WebPFeatures features = null) |
|||
{ |
|||
this.webpMetadata.Format = WebPFormatType.Lossy; |
|||
|
|||
// VP8 data size (not including this 4 bytes).
|
|||
this.currentStream.Read(this.buffer, 0, 4); |
|||
uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); |
|||
|
|||
// remaining counts the available image data payload.
|
|||
uint remaining = dataSize; |
|||
|
|||
// Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30
|
|||
// Frame tag that contains four fields:
|
|||
// - A 1-bit frame type (0 for key frames, 1 for interframes).
|
|||
// - A 3-bit version number.
|
|||
// - A 1-bit show_frame flag.
|
|||
// - A 19-bit field containing the size of the first data partition in bytes.
|
|||
this.currentStream.Read(this.buffer, 0, 3); |
|||
uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); |
|||
remaining -= 3; |
|||
bool isKeyFrame = (frameTag & 0x1) is 0; |
|||
if (!isKeyFrame) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); |
|||
} |
|||
|
|||
uint version = (frameTag >> 1) & 0x7; |
|||
if (version > 3) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); |
|||
} |
|||
|
|||
bool showFrame = ((frameTag >> 4) & 0x1) is 1; |
|||
if (!showFrame) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); |
|||
} |
|||
|
|||
uint partitionLength = frameTag >> 5; |
|||
if (partitionLength > dataSize) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); |
|||
} |
|||
|
|||
// Check for VP8 magic bytes.
|
|||
this.currentStream.Read(this.buffer, 0, 3); |
|||
if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8MagicBytes)) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); |
|||
} |
|||
|
|||
this.currentStream.Read(this.buffer, 0, 4); |
|||
uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer); |
|||
uint width = tmp & 0x3fff; |
|||
sbyte xScale = (sbyte)(tmp >> 6); |
|||
tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)); |
|||
uint height = tmp & 0x3fff; |
|||
sbyte yScale = (sbyte)(tmp >> 6); |
|||
remaining -= 7; |
|||
if (width is 0 || height is 0) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); |
|||
} |
|||
|
|||
if (partitionLength > remaining) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("bad partition length"); |
|||
} |
|||
|
|||
var vp8FrameHeader = new Vp8FrameHeader() |
|||
{ |
|||
KeyFrame = true, |
|||
Profile = (sbyte)version, |
|||
PartitionLength = partitionLength |
|||
}; |
|||
|
|||
var bitReader = new Vp8BitReader( |
|||
this.currentStream, |
|||
remaining, |
|||
this.memoryAllocator, |
|||
partitionLength); |
|||
bitReader.Remaining = remaining; |
|||
|
|||
return new WebPImageInfo() |
|||
{ |
|||
Width = width, |
|||
Height = height, |
|||
XScale = xScale, |
|||
YScale = yScale, |
|||
BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24, |
|||
IsLossLess = false, |
|||
Features = features, |
|||
Vp8Profile = (sbyte)version, |
|||
Vp8FrameHeader = vp8FrameHeader, |
|||
Vp8BitReader = bitReader |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the header of a lossless webp image.
|
|||
/// </summary>
|
|||
/// <param name="features">Webp image features.</param>
|
|||
/// <returns>Information about this image.</returns>
|
|||
private WebPImageInfo ReadVp8LHeader(WebPFeatures features = null) |
|||
{ |
|||
this.webpMetadata.Format = WebPFormatType.Lossless; |
|||
|
|||
// VP8 data size.
|
|||
uint imageDataSize = this.ReadChunkSize(); |
|||
|
|||
var bitReader = new Vp8LBitReader(this.currentStream, imageDataSize, this.memoryAllocator); |
|||
|
|||
// One byte signature, should be 0x2f.
|
|||
uint signature = bitReader.ReadValue(8); |
|||
if (signature != WebPConstants.Vp8LMagicByte) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); |
|||
} |
|||
|
|||
// The first 28 bits of the bitstream specify the width and height of the image.
|
|||
uint width = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; |
|||
uint height = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1; |
|||
if (width is 0 || height is 0) |
|||
{ |
|||
WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); |
|||
} |
|||
|
|||
// The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise.
|
|||
// TODO: this flag value is not used yet
|
|||
bool alphaIsUsed = bitReader.ReadBit(); |
|||
|
|||
// The next 3 bits are the version. The version number is a 3 bit code that must be set to 0.
|
|||
// Any other value should be treated as an error.
|
|||
uint version = bitReader.ReadValue(WebPConstants.Vp8LVersionBits); |
|||
if (version != 0) |
|||
{ |
|||
WebPThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); |
|||
} |
|||
|
|||
return new WebPImageInfo() |
|||
{ |
|||
Width = width, |
|||
Height = height, |
|||
BitsPerPixel = WebPBitsPerPixel.Pixel32, |
|||
IsLossLess = true, |
|||
Features = features, |
|||
Vp8LBitReader = bitReader |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP ').
|
|||
/// If there are more such chunks, readers MAY ignore all except the first one.
|
|||
/// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
|
|||
/// </summary>
|
|||
/// <param name="features">The webp features.</param>
|
|||
private void ParseOptionalChunks(WebPFeatures features) |
|||
{ |
|||
if (this.IgnoreMetadata || (features.ExifProfile is false && features.XmpMetaData is false)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
while (this.currentStream.Position < this.currentStream.Length) |
|||
{ |
|||
// Read chunk header.
|
|||
WebPChunkType chunkType = this.ReadChunkType(); |
|||
uint chunkLength = this.ReadChunkSize(); |
|||
|
|||
if (chunkType is WebPChunkType.Exif) |
|||
{ |
|||
var exifData = new byte[chunkLength]; |
|||
this.currentStream.Read(exifData, 0, (int)chunkLength); |
|||
this.Metadata.ExifProfile = new ExifProfile(exifData); |
|||
} |
|||
else |
|||
{ |
|||
// Skip XMP chunk data for now.
|
|||
this.currentStream.Skip((int)chunkLength); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Identifies the chunk type from the chunk.
|
|||
/// </summary>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// Thrown if the input stream is not valid.
|
|||
/// </exception>
|
|||
private WebPChunkType ReadChunkType() |
|||
{ |
|||
if (this.currentStream.Read(this.buffer, 0, 4) is 4) |
|||
{ |
|||
var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); |
|||
this.webpMetadata.ChunkTypes.Enqueue(chunkType); |
|||
return chunkType; |
|||
} |
|||
|
|||
throw new ImageFormatException("Invalid WebP data."); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
|
|||
/// so the chunk size will be increased by 1 in those cases.
|
|||
/// </summary>
|
|||
/// <returns>The chunk size in bytes.</returns>
|
|||
private uint ReadChunkSize() |
|||
{ |
|||
if (this.currentStream.Read(this.buffer, 0, 4) is 4) |
|||
{ |
|||
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); |
|||
return (chunkSize % 2 is 0) ? chunkSize : chunkSize + 1; |
|||
} |
|||
|
|||
throw new ImageFormatException("Invalid WebP data."); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Image features of a VP8X image.
|
|||
/// </summary>
|
|||
internal class WebPFeatures |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image has a ICC Profile.
|
|||
/// </summary>
|
|||
public bool IccProfile { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image has a alpha channel.
|
|||
/// </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 the alpha chunk header.
|
|||
/// </summary>
|
|||
public byte AlphaChunkHeader { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image has a EXIF Profile.
|
|||
/// </summary>
|
|||
public bool ExifProfile { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image has XMP Metadata.
|
|||
/// </summary>
|
|||
public bool XmpMetaData { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image is a animation.
|
|||
/// </summary>
|
|||
public bool Animation { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the WebP format
|
|||
/// </summary>
|
|||
public sealed class WebPFormat : IImageFormat<WebPMetadata> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static WebPFormat Instance { get; } = new WebPFormat(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "WebP"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/webp"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => WebPConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => WebPConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public WebPMetadata CreateDefaultFormatMetadata() => new WebPMetadata(); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Info about the webp format used.
|
|||
/// </summary>
|
|||
public enum WebPFormatType |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown webp format.
|
|||
/// </summary>
|
|||
Unknown, |
|||
|
|||
/// <summary>
|
|||
/// The lossless webp format.
|
|||
/// </summary>
|
|||
Lossless, |
|||
|
|||
/// <summary>
|
|||
/// The lossy webp format.
|
|||
/// </summary>
|
|||
Lossy, |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Detects WebP file headers.
|
|||
/// </summary>
|
|||
public sealed class WebPImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc />
|
|||
public int HeaderSize => 12; |
|||
|
|||
/// <inheritdoc />
|
|||
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
return this.IsSupportedFileFormat(header) ? WebPFormat.Instance : null; |
|||
} |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
return header.Length >= this.HeaderSize && |
|||
this.IsRiffContainer(header) && |
|||
this.IsWebPFile(header); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks, if the header starts with a valid RIFF FourCC.
|
|||
/// </summary>
|
|||
/// <param name="header">The header bytes.</param>
|
|||
/// <returns>True, if its a valid RIFF FourCC.</returns>
|
|||
private bool IsRiffContainer(ReadOnlySpan<byte> header) |
|||
{ |
|||
return header.Slice(0, 4).SequenceEqual(WebPConstants.RiffFourCc); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if 'WEBP' is present in the header.
|
|||
/// </summary>
|
|||
/// <param name="header">The header bytes.</param>
|
|||
/// <returns>True, if its a webp file.</returns>
|
|||
private bool IsWebPFile(ReadOnlySpan<byte> header) |
|||
{ |
|||
return header.Slice(8, 4).SequenceEqual(WebPConstants.WebPHeader); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal class WebPImageInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the bitmap width in pixels (signed integer).
|
|||
/// </summary>
|
|||
public uint Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bitmap height in pixels (signed integer).
|
|||
/// </summary>
|
|||
public uint Height { get; set; } |
|||
|
|||
public sbyte XScale { get; set; } |
|||
|
|||
public sbyte YScale { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the bits per pixel.
|
|||
/// </summary>
|
|||
public WebPBitsPerPixel BitsPerPixel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether this image uses lossless compression.
|
|||
/// </summary>
|
|||
public bool IsLossLess { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets additional features present in a VP8X image.
|
|||
/// </summary>
|
|||
public WebPFeatures Features { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1.
|
|||
/// </summary>
|
|||
public int Vp8Profile { get; set; } = -1; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the VP8 frame header.
|
|||
/// </summary>
|
|||
public Vp8FrameHeader Vp8FrameHeader { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the VP8L bitreader. Will be null, if its not lossless image.
|
|||
/// </summary>
|
|||
public Vp8LBitReader Vp8LBitReader { get; set; } = null; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the VP8 bitreader. Will be null, if its not a lossy image.
|
|||
/// </summary>
|
|||
public Vp8BitReader Vp8BitReader { get; set; } = null; |
|||
} |
|||
} |
|||
@ -0,0 +1,580 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal static class WebPLookupTables |
|||
{ |
|||
public static readonly Dictionary<int, byte> Abs0; |
|||
|
|||
public static readonly Dictionary<int, byte> Clip1; |
|||
|
|||
public static readonly Dictionary<int, sbyte> Sclip1; |
|||
|
|||
public static readonly Dictionary<int, sbyte> Sclip2; |
|||
|
|||
public static readonly byte[,][] ModesProba = new byte[10, 10][]; |
|||
|
|||
public static readonly int[] CodeToPlane = |
|||
{ |
|||
0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, |
|||
0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, |
|||
0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, |
|||
0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, |
|||
0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, |
|||
0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, |
|||
0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, |
|||
0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, |
|||
0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, |
|||
0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, |
|||
0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, |
|||
0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 |
|||
}; |
|||
|
|||
// 31 ^ clz(i)
|
|||
public static readonly byte[] LogTable8bit = |
|||
{ |
|||
0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, |
|||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, |
|||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, |
|||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, |
|||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 |
|||
}; |
|||
|
|||
// Paragraph 14.1
|
|||
public static readonly int[] DcTable = |
|||
{ |
|||
4, 5, 6, 7, 8, 9, 10, 10, |
|||
11, 12, 13, 14, 15, 16, 17, 17, |
|||
18, 19, 20, 20, 21, 21, 22, 22, |
|||
23, 23, 24, 25, 25, 26, 27, 28, |
|||
29, 30, 31, 32, 33, 34, 35, 36, |
|||
37, 37, 38, 39, 40, 41, 42, 43, |
|||
44, 45, 46, 46, 47, 48, 49, 50, |
|||
51, 52, 53, 54, 55, 56, 57, 58, |
|||
59, 60, 61, 62, 63, 64, 65, 66, |
|||
67, 68, 69, 70, 71, 72, 73, 74, |
|||
75, 76, 76, 77, 78, 79, 80, 81, |
|||
82, 83, 84, 85, 86, 87, 88, 89, |
|||
91, 93, 95, 96, 98, 100, 101, 102, |
|||
104, 106, 108, 110, 112, 114, 116, 118, |
|||
122, 124, 126, 128, 130, 132, 134, 136, |
|||
138, 140, 143, 145, 148, 151, 154, 157 |
|||
}; |
|||
|
|||
// Paragraph 14.1
|
|||
public static readonly int[] AcTable = |
|||
{ |
|||
4, 5, 6, 7, 8, 9, 10, 11, |
|||
12, 13, 14, 15, 16, 17, 18, 19, |
|||
20, 21, 22, 23, 24, 25, 26, 27, |
|||
28, 29, 30, 31, 32, 33, 34, 35, |
|||
36, 37, 38, 39, 40, 41, 42, 43, |
|||
44, 45, 46, 47, 48, 49, 50, 51, |
|||
52, 53, 54, 55, 56, 57, 58, 60, |
|||
62, 64, 66, 68, 70, 72, 74, 76, |
|||
78, 80, 82, 84, 86, 88, 90, 92, |
|||
94, 96, 98, 100, 102, 104, 106, 108, |
|||
110, 112, 114, 116, 119, 122, 125, 128, |
|||
131, 134, 137, 140, 143, 146, 149, 152, |
|||
155, 158, 161, 164, 167, 170, 173, 177, |
|||
181, 185, 189, 193, 197, 201, 205, 209, |
|||
213, 217, 221, 225, 229, 234, 239, 245, |
|||
249, 254, 259, 264, 269, 274, 279, 284 |
|||
}; |
|||
|
|||
// Paragraph 13
|
|||
public static readonly byte[,,,] CoeffsUpdateProba = |
|||
{ |
|||
{ |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, |
|||
{ 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, |
|||
{ 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
}, |
|||
{ |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, |
|||
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } |
|||
} |
|||
} |
|||
}; |
|||
|
|||
// Paragraph 13.5: Default Token Probability Table.
|
|||
public static readonly byte[,,,] DefaultCoeffsProba = |
|||
{ |
|||
{ |
|||
{ |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, |
|||
{ 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, |
|||
{ 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, |
|||
{ 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, |
|||
{ 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, |
|||
}, |
|||
{ |
|||
{ 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, |
|||
{ 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, |
|||
{ 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, |
|||
}, |
|||
{ |
|||
{ 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, |
|||
{ 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, |
|||
{ 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, |
|||
{ 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, |
|||
{ 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, |
|||
{ 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, |
|||
{ 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, |
|||
{ 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, |
|||
{ 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, |
|||
{ 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, |
|||
{ 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, |
|||
{ 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, |
|||
{ 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, |
|||
{ 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, |
|||
{ 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, |
|||
{ 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, |
|||
{ 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, |
|||
{ 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, |
|||
{ 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, |
|||
{ 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, |
|||
{ 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, |
|||
{ 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, |
|||
{ 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, |
|||
{ 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, |
|||
{ 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, |
|||
{ 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, |
|||
{ 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, |
|||
{ 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, |
|||
{ 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, |
|||
{ 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, |
|||
{ 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, |
|||
{ 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, |
|||
{ 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, |
|||
{ 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, |
|||
{ 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
{ 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, |
|||
{ 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, |
|||
{ 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, |
|||
{ 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, |
|||
{ 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, |
|||
{ 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, |
|||
{ 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, |
|||
{ 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, |
|||
{ 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, |
|||
{ 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, |
|||
{ 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, |
|||
{ 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, |
|||
{ 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, |
|||
{ 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, |
|||
{ 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } |
|||
}, |
|||
{ |
|||
{ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, |
|||
{ 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } |
|||
} |
|||
} |
|||
}; |
|||
|
|||
static WebPLookupTables() |
|||
{ |
|||
// TODO: maybe use hashset here
|
|||
Abs0 = new Dictionary<int, byte>(); |
|||
for (int i = -255; i <= 255; ++i) |
|||
{ |
|||
Abs0[i] = (byte)((i < 0) ? -i : i); |
|||
} |
|||
|
|||
Clip1 = new Dictionary<int, byte>(); |
|||
for (int i = -255; i <= 255 + 255; ++i) |
|||
{ |
|||
Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i); |
|||
} |
|||
|
|||
Sclip1 = new Dictionary<int, sbyte>(); |
|||
for (int i = -1020; i <= 1020; ++i) |
|||
{ |
|||
Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i); |
|||
} |
|||
|
|||
Sclip2 = new Dictionary<int, sbyte>(); |
|||
for (int i = -112; i <= 112; ++i) |
|||
{ |
|||
Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i); |
|||
} |
|||
|
|||
InitializeModesProbabilities(); |
|||
} |
|||
|
|||
private static void InitializeModesProbabilities() |
|||
{ |
|||
// Paragraph 11.5
|
|||
ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; |
|||
ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; |
|||
ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; |
|||
ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; |
|||
ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; |
|||
ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; |
|||
ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; |
|||
ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; |
|||
ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; |
|||
ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; |
|||
ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; |
|||
ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; |
|||
ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; |
|||
ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; |
|||
ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; |
|||
ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; |
|||
ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; |
|||
ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; |
|||
ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; |
|||
ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; |
|||
ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; |
|||
ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; |
|||
ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; |
|||
ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; |
|||
ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; |
|||
ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; |
|||
ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; |
|||
ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; |
|||
ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; |
|||
ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; |
|||
ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; |
|||
ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; |
|||
ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; |
|||
ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; |
|||
ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; |
|||
ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; |
|||
ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; |
|||
ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; |
|||
ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; |
|||
ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; |
|||
ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; |
|||
ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; |
|||
ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; |
|||
ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; |
|||
ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; |
|||
ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; |
|||
ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; |
|||
ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; |
|||
ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; |
|||
ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; |
|||
ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; |
|||
ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; |
|||
ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; |
|||
ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; |
|||
ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; |
|||
ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; |
|||
ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; |
|||
ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; |
|||
ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; |
|||
ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; |
|||
ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; |
|||
ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; |
|||
ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; |
|||
ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; |
|||
ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; |
|||
ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; |
|||
ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; |
|||
ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; |
|||
ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; |
|||
ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; |
|||
ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; |
|||
ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; |
|||
ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; |
|||
ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; |
|||
ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; |
|||
ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; |
|||
ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; |
|||
ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; |
|||
ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; |
|||
ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; |
|||
ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; |
|||
ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; |
|||
ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; |
|||
ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; |
|||
ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; |
|||
ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; |
|||
ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; |
|||
ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; |
|||
ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; |
|||
ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; |
|||
ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; |
|||
ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; |
|||
ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; |
|||
ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; |
|||
ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; |
|||
ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; |
|||
ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; |
|||
ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; |
|||
ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; |
|||
ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Provides WebP specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class WebPMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WebPMetadata"/> class.
|
|||
/// </summary>
|
|||
public WebPMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WebPMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private WebPMetadata(WebPMetadata other) |
|||
{ |
|||
this.Animated = other.Animated; |
|||
this.Format = other.Format; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the webp format used. Either lossless or lossy.
|
|||
/// </summary>
|
|||
public WebPFormatType Format { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets all found chunk types ordered by appearance.
|
|||
/// </summary>
|
|||
public Queue<WebPChunkType> ChunkTypes { get; set; } = new Queue<WebPChunkType>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the webp file contains a animation.
|
|||
/// </summary>
|
|||
public bool Animated { get; set; } = false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new WebPMetadata(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
internal static class WebPThrowHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">The error message for the exception.</param>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void ThrowImageFormatException(string errorMessage) |
|||
{ |
|||
throw new ImageFormatException(errorMessage); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">The error message for the exception.</param>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void ThrowNotSupportedException(string errorMessage) |
|||
{ |
|||
throw new NotSupportedException(errorMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the webp format.
|
|||
/// </summary>
|
|||
public sealed class WebPConfigurationModule : IConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder()); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector()); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,87 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using ImageMagick; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class DecodeWebp : BenchmarkBase |
|||
{ |
|||
private byte[] webpLossyBytes; |
|||
private byte[] webpLosslessBytes; |
|||
|
|||
private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); |
|||
private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); |
|||
|
|||
[Params(TestImages.WebP.Lossy.Bike)] |
|||
public string TestImageLossy { get; set; } |
|||
|
|||
[Params(TestImages.WebP.Lossless.BikeThreeTransforms)] |
|||
public string TestImageLossless { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.webpLossyBytes is null) |
|||
{ |
|||
this.webpLossyBytes = File.ReadAllBytes(this.TestImageLossyFullPath); |
|||
} |
|||
|
|||
if (this.webpLosslessBytes is null) |
|||
{ |
|||
this.webpLosslessBytes = File.ReadAllBytes(this.TestImageLosslessFullPath); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "Magick Lossy WebP")] |
|||
public int WebpLossyMagick() |
|||
{ |
|||
var settings = new MagickReadSettings { Format = MagickFormat.WebP }; |
|||
using (var image = new MagickImage(new MemoryStream(this.webpLossyBytes), settings)) |
|||
{ |
|||
return image.Width; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Lossy Webp")] |
|||
public int WebpLossy() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.webpLossyBytes)) |
|||
{ |
|||
using (var image = Image.Load<Rgba32>(memoryStream)) |
|||
{ |
|||
return image.Height; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "Magick Lossless WebP")] |
|||
public int WebpLosslessMagick() |
|||
{ |
|||
var settings = new MagickReadSettings { Format = MagickFormat.WebP }; |
|||
using (var image = new MagickImage(new MemoryStream(this.webpLosslessBytes), settings)) |
|||
{ |
|||
return image.Width; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Lossless Webp")] |
|||
public int WebpLossless() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.webpLosslessBytes)) |
|||
{ |
|||
using (var image = Image.Load<Rgba32>(memoryStream)) |
|||
{ |
|||
return image.Height; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,319 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Formats.WebP; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
|||
|
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.WebP |
|||
{ |
|||
using static SixLabors.ImageSharp.Tests.TestImages.WebP; |
|||
|
|||
public class WebPDecoderTests |
|||
{ |
|||
private static WebPDecoder WebpDecoder => new WebPDecoder(); |
|||
|
|||
private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); |
|||
|
|||
[Theory] |
|||
[InlineData(Lossless.GreenTransform1, 1000, 307, 32)] |
|||
[InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] |
|||
[InlineData(Lossless.NoTransform2, 128, 128, 32)] |
|||
[InlineData(Lossy.Alpha1, 1000, 307, 32)] |
|||
[InlineData(Lossy.Alpha2, 1000, 307, 32)] |
|||
[InlineData(Lossy.Bike, 250, 195, 24)] |
|||
public void Identify_DetectsCorrectDimensionsAndBitDepth( |
|||
string imagePath, |
|||
int expectedWidth, |
|||
int expectedHeight, |
|||
int expectedBitsPerPixel) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using (var stream = new MemoryStream(testFile.Bytes, false)) |
|||
{ |
|||
IImageInfo imageInfo = Image.Identify(stream); |
|||
Assert.NotNull(imageInfo); |
|||
Assert.Equal(expectedWidth, imageInfo.Width); |
|||
Assert.Equal(expectedHeight, imageInfo.Height); |
|||
Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.Bike, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.NoFilter01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.NoFilter02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationNoFilter01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationNoFilter02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationNoFilter03, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithoutFilter<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SimpleFilter03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SimpleFilter04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SimpleFilter05, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter08, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.ComplexFilter09, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithComplexFilter<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.Small01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Small02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Small03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Small04, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_VerySmall<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.SegmentationNoFilter04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationNoFilter05, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationNoFilter06, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationComplexFilter01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationComplexFilter02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationComplexFilter03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationComplexFilter04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.SegmentationComplexFilter05, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithPartitions<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.Partitions01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Partitions02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Partitions03, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithSegmentation<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.Sharpness01, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Sharpness02, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Sharpness03, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Sharpness04, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Sharpness05, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.Sharpness06, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] |
|||
[WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] |
|||
[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<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithoutTransforms<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.GreenTransform1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] |
|||
// TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work.
|
|||
// [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)]
|
|||
public void WebpDecoder_CanDecode_Lossless_WithSubstractGreenTransform<TPixel>( |
|||
TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.ColorIndexTransform1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ColorIndexTransform2, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.CrossColorTransform1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.CrossColorTransform2, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.TwoTransforms1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms2, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms3, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms4, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms5, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms6, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms7, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms8, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms9, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms10, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.ThreeTransforms1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms2, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms3, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms4, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(WebpDecoder)) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ReferenceDecoder); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] |
|||
[WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] |
|||
public void WebpDecoder_ThrowImageFormatException_OnInvalidImages<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Assert.Throws<ImageFormatException>(() => { using (provider.GetImage(WebpDecoder)) { } }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using SixLabors.ImageSharp.Formats.Bmp; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.WebP |
|||
{ |
|||
public class WebPFileHeaderTests |
|||
{ |
|||
[Fact] |
|||
public void TestWrite() |
|||
{ |
|||
var header = new BmpFileHeader(1, 2, 3, 4); |
|||
|
|||
var buffer = new byte[14]; |
|||
|
|||
header.WriteTo(buffer); |
|||
|
|||
Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 1fea1ceab89e87cc5f11376fa46164d3d27566c0 |
|||
Subproject commit 99a2bc523cd4eb00e37af20d1b2088fa11564c57 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:c4b9e2459858e6f6a1d919c2adeb73d7e2ad251d6bfbfb99303a6b4508ca757a |
|||
size 1838 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:150ff79f16e254281153d7e75e5968663c7f83ae58217b36c12d11088045eb07 |
|||
size 22038 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:96f514bce6faa65330eba17c06eb1cf120ba8c133288ab2633afa63d7c6c66ad |
|||
size 12162 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:53ad8a7038fed7bdfa90db1c1f987782a9f46903aaccd5ad04dd78d067632fba |
|||
size 114 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:4dc2d060c723aa0a855bc696191d220a60a36bad014cda9764ee93516fa9f073 |
|||
size 22038 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:79000c9c553f28e4ea589e772dd46a47a604f961050ca191a84a03d92d212eb8 |
|||
size 15592 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0a1eba20a9ba6a09735c2424d31e26c9282be64a0d7795dd689a42c4921d436b |
|||
size 114 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:3d169bfee11e65f1b5870142531d1e35539e2686640d19fa196b36a5b7b33a45 |
|||
size 22038 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:16ce80b417c8d5d95d895e3a50be00247262a3ab5700e2a22a408f5042884042 |
|||
size 15604 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:4052952ea401afa934b2d262f2852f615160c7cb82c4406c47622d26b343e95e |
|||
size 118 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:79c7f73faec6a9b0f7ec5d274a3dd10a7eb002ebab114122a49f9794c5b8541a |
|||
size 22038 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:e11b944fd8aa2e5f90c7bea05527a94e919492bef0bc464bac1493e00724ae01 |
|||
size 18266 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:864de77c2209a8346004f8fe5595ffb35cfaacb71f385cc8487236689056df7d |
|||
size 336 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:cf633cfad0fba9b53ef84f0319db15537868bbe75c7b3cd0f31add9c0d25addf |
|||
size 37341 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b17cfa1c0f484f1fc03f16d07684831585125817e5c7fb2c12cfed3d6ad863a8 |
|||
size 11840 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:68ba327459ac40a7a054dc5d8b237d3ce0154524854a4f2334e3b839524d13a9 |
|||
size 41063 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:54957c3daa3ab0bf258c00b170fcfc0578d909acd5dfc870b752688b9b64e406 |
|||
size 73772 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:8540997edf54f030201f7354f52438dd04bf248fb21a72b71a93095fc681fb5e |
|||
size 9682 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:4f2359f5425d78dbe16665d9e15cb56b84559eff527ab316c509a5dd1708c126 |
|||
size 16313 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5a552b43d45c77ece0ab4331f054fb183725420748656d47a49c5b672e42f4f9 |
|||
size 61782 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9f93883b8ba4ebc9c048c598b9294736baddfa756c4884e85f0d3b8e7f9d996c |
|||
size 39244 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:21612476f2d7668f773ce286af1a2a4c33da8718352c5a5c1dd839a4643de823 |
|||
size 9396 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d6ba1142638a7ae9df901bb7fc8e3a9e6afbe0ec8320fd28e35308a57a2e3e4f |
|||
size 3533772 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:37d98a0b3e2132f7b5bbc03935a88a8659735c61268d8ce6acdfccfa574f4166 |
|||
size 954 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a19dde0c51ce4c83d9bc05ea3b8f3cfed9cfac7ca19dcb23d85c56e465242350 |
|||
size 15822 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b |
|||
size 40765 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:30734501a0b1953c392762d6ea11652400efec3f891f0da37749e3674e15b6a0 |
|||
size 183280 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5c53fb4527509058a8a4caf72e03ee8634f4704ab5369a8e5d194e62359d6ad0 |
|||
size 117 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5eaf3d3e7f7a38487afa8d3f91062167eb061cd6a5dfa455d24a9a2004860311 |
|||
size 15368 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5131b5d7c0ba6bd7d6e6f74a325e0ffa2d388197b5132ed46a5c36ea8453cb22 |
|||
size 15898 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:2bcca2ea2a1a43d19c839528e9b831519e0a6875e4c9a2ce8bb9c34bb85ece3a |
|||
size 15734 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a96cc5243569ada325efadb3a6c78816b4a015a73283a400c5cc94893584901f |
|||
size 4332 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:40928dc5a6ca61e7008d212e66b24f5e62f43d5fe55f23add9843414168cbaa6 |
|||
size 13968249 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5b9557b7f3798bb9b511f2edad5dad330d7346f5f13440a70627488f9a53ec81 |
|||
size 163807 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:312aea5ac9557bdfa78ec95bab5c3446a97c980317f46c96a20a4b7837d0ae37 |
|||
size 68355 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:011089057caf7e11c9a59d3ec2b3448ea56d83545622e313f8584a22c322bc90 |
|||
size 50 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:482c1304367ede7a4b2e43e14aefced318c075e82e466473720d3bdabc0526fc |
|||
size 106 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f91a575ba29729357a612eb511a9ebab725c2d34a6a6eaaf6b6a16cee3ba25a2 |
|||
size 80 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:e598e8d2aef6a562a80c3fc9cb7cc6fdd979e210c26ed3a4defbdf895ae1c1cc |
|||
size 132 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:45fa843b9d374e1949f58e9d0d2a2ecf97d4a9cc2af55dfa3ef488d846ea3c80 |
|||
size 56 |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue