Browse Source

Start with parsing huffman codes

pull/1552/head
Brian Popow 7 years ago
parent
commit
89d8d50a26
  1. 18
      src/ImageSharp/Formats/WebP/HuffmanCode.cs
  2. 55
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  3. 23
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  4. 232
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs

18
src/ImageSharp/Formats/WebP/HuffmanCode.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.WebP
{
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 int Value { get; set; }
}
}

55
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -27,16 +27,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
0x2A
};
/// <summary>
/// Signature byte which identifies a VP8L header.
/// </summary>
public static byte Vp8LMagicByte = 0x2F;
/// <summary>
/// Bits for width and height infos of a VPL8 image.
/// </summary>
public static int Vp8LImageSizeBits = 14;
/// <summary>
/// The header bytes identifying RIFF file.
/// </summary>
@ -58,5 +48,50 @@ namespace SixLabors.ImageSharp.Formats.WebP
0x42, // B
0x50 // P
};
/// <summary>
/// Signature byte which identifies a VP8L header.
/// </summary>
public static byte Vp8LMagicByte = 0x2F;
/// <summary>
/// 3 bits reserved for version.
/// </summary>
public static int Vp8LVersionBits = 3;
/// <summary>
/// Bits for width and height infos of a VPL8 image.
/// </summary>
public static int Vp8LImageSizeBits = 14;
/// <summary>
/// Maximum number of color cache bits.
/// </summary>
public static int MaxColorCacheBits = 11;
/// <summary>
/// The maximum number of allowed transforms in a bitstream.
/// </summary>
public static int MaxNumberOfTransforms = 4;
public static int MaxAllowedCodeLength = 15;
public static int HuffmanCodesPerMetaCode = 5;
public static int NumLiteralCodes = 256;
public static int NumLengthCodes = 24;
public static int NumDistanceCodes = 40;
public static int NumCodeLengthCodes = 19;
public static byte[] KCodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
public static int[] kAlphabetSize = {
NumLiteralCodes + NumLengthCodes,
NumLiteralCodes, NumLiteralCodes, NumLiteralCodes,
NumDistanceCodes
};
}
}

23
src/ImageSharp/Formats/WebP/WebPDecoderCore.cs

@ -80,11 +80,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (imageInfo.IsLossLess)
{
ReadSimpleLossless(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize);
var losslessDecoder = new WebPLosslessDecoder(this.currentStream, (int)imageInfo.ImageDataSize);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
ReadSimpleLossy(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize);
var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream);
lossyDecoder.Decode(pixels, image.Width, image.Height, (int)imageInfo.ImageDataSize);
}
// There can be optional chunks after the image data, like EXIF, XMP etc.
@ -310,10 +312,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
// The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise.
bool alphaIsUsed = bitReader.ReadBit();
// The next 3 bytes are the version. The version_number is a 3 bit code that must be set to 0.
// 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.
// TODO: should we throw here when version number is != 0?
uint version = bitReader.Read(3);
uint version = bitReader.Read(WebPConstants.Vp8LVersionBits);
return new WebPImageInfo()
{
@ -328,18 +330,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void ReadSimpleLossy<TPixel>(Buffer2D<TPixel> pixels, int width, int height, int imageDataSize)
where TPixel : struct, IPixel<TPixel>
{
var lossyDecoder = new WebPLossyDecoder(this.configuration, this.currentStream);
lossyDecoder.Decode(pixels, width, height, imageDataSize);
}
private void ReadSimpleLossless<TPixel>(Buffer2D<TPixel> pixels, int width, int height, int imageDataSize)
where TPixel : struct, IPixel<TPixel>
{
var losslessDecoder = new WebPLosslessDecoder(this.currentStream);
losslessDecoder.Decode(pixels, width, height, imageDataSize);
// TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes.
this.currentStream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment.
}
private void ReadExtended<TPixel>(Buffer2D<TPixel> pixels, int width, int height)

232
src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Memory;
@ -11,29 +12,226 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Decoder for lossless webp images.
/// </summary>
/// <remarks>
/// The lossless specification can be found here:
/// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
/// </remarks>
internal sealed class WebPLosslessDecoder
{
private Vp8LBitReader bitReader;
private readonly Vp8LBitReader bitReader;
public WebPLosslessDecoder(Stream stream)
private readonly int imageDataSize;
public WebPLosslessDecoder(Stream stream, int imageDataSize)
{
this.bitReader = new Vp8LBitReader(stream);
this.imageDataSize = imageDataSize;
// TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes.
//stream.Skip(imageDataSize + 34); // TODO: Not sure why the additional data starts at offset +34 at the moment.
}
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height, int imageDataSize)
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
//ReadTransformations();
this.ReadTransformations();
int xsize = 0, ysize = 0;
this.ReadHuffmanCodes(xsize, ysize);
}
private void ReadHuffmanCodes(int xsize, int ysize)
{
int maxAlphabetSize = 0;
int colorCacheBits = 0;
int numHtreeGroups = 1;
int numHtreeGroupsMax = 1;
// Read color cache, if present.
bool colorCachePresent = this.bitReader.ReadBit();
if (colorCachePresent)
{
colorCacheBits = (int)this.bitReader.Read(4);
int colorCacheSize = 1 << colorCacheBits;
if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits))
{
WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found");
}
}
// Read the Huffman codes.
// If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored.
// If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image.
bool isEntropyImage = this.bitReader.ReadBit();
if (isEntropyImage)
{
uint huffmanPrecision = this.bitReader.Read(3) + 2;
int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision);
int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision);
// TODO: decode entropy image
return;
}
// Find maximum alphabet size for the htree group.
for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++)
{
int alphabetSize = WebPConstants.kAlphabetSize[j];
if (j == 0 && colorCacheBits > 0)
{
alphabetSize += 1 << colorCacheBits;
}
if (maxAlphabetSize < alphabetSize)
{
maxAlphabetSize = alphabetSize;
}
}
for (int i = 0; i < numHtreeGroupsMax; i++)
{
int size;
int totalSize = 0;
int isTrivialLiteral = 1;
int maxBits = 0;
var codeLengths = new int[maxAlphabetSize];
for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++)
{
int alphabetSize = WebPConstants.kAlphabetSize[j];
if (j == 0 && colorCacheBits > 0)
{
alphabetSize += 1 << colorCacheBits;
size = this.ReadHuffmanCode(alphabetSize, codeLengths);
if (size is 0)
{
WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero");
}
}
}
}
}
private int ReadHuffmanCode(int alphabetSize, int[] codeLengths)
{
bool simpleCode = this.bitReader.ReadBit();
if (simpleCode)
{
// (i) Simple Code Length Code.
// This variant is used in the special case when only 1 or 2 Huffman code lengths are non - zero,
// and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros.
// Read symbols, codes & code lengths directly.
uint numSymbols = this.bitReader.Read(1) + 1;
uint firstSymbolLenCode = this.bitReader.Read(1);
// The first code is either 1 bit or 8 bit code.
uint symbol = this.bitReader.Read((firstSymbolLenCode == 0) ? 1 : 8);
codeLengths[symbol] = 1;
// The second code (if present), is always 8 bit long.
if (numSymbols == 2)
{
symbol = this.bitReader.Read(8);
codeLengths[symbol] = 1;
}
}
else
{
// (ii)Normal Code Length Code:
// The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths;
// the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros.
var codeLengthCodeLengths = new int[WebPConstants.NumLengthCodes];
uint numCodes = this.bitReader.Read(4) + 4;
if (numCodes > WebPConstants.NumCodeLengthCodes)
{
WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value");
}
for (int i = 0; i < numCodes; i++)
{
codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3);
}
// TODO: ReadHuffmanCodeLengths
}
int size = 0;
// TODO: VP8LBuildHuffmanTable
return size;
}
private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize,
int[] sorted) // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length.
{
// total size root table + 2nd level table
int totalSize = 1 << rootBits;
// current code length
int len;
// symbol index in original or sorted table
int symbol;
// number of codes of each length:
var count = new int[WebPConstants.MaxAllowedCodeLength + 1];
// offsets in sorted table for each length
var offset = new int[WebPConstants.MaxAllowedCodeLength + 1];
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
{
return 0;
}
++count[codeLengths[symbol]];
}
// 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 = sorted[0]
};
return totalSize;
}
return 0;
}
private void ReadTransformations()
{
// Next bit indicates, if a transformation is present.
bool transformPresent = bitReader.ReadBit();
bool transformPresent = this.bitReader.ReadBit();
int numberOfTransformsPresent = 0;
var transforms = new List<WebPTransformType>(WebPConstants.MaxNumberOfTransforms);
while (transformPresent)
{
var transformType = (WebPTransformType)bitReader.Read(2);
var transformType = (WebPTransformType)this.bitReader.Read(2);
transforms.Add(transformType);
switch (transformType)
{
case WebPTransformType.SubtractGreen:
@ -42,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
case WebPTransformType.ColorIndexingTransform:
// The transform data contains color table size and the entries in the color table.
// 8 bit value for color table size.
uint colorTableSize = bitReader.Read(8) + 1;
uint colorTableSize = this.bitReader.Read(8) + 1;
// TODO: color table should follow here?
break;
@ -51,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
// The first 3 bits of prediction data define the block width and height in number of bits.
// The number of block columns, block_xsize, is used in indexing two-dimensionally.
uint sizeBits = bitReader.Read(3) + 2;
uint sizeBits = this.bitReader.Read(3) + 2;
int blockWidth = 1 << (int)sizeBits;
int blockHeight = 1 << (int)sizeBits;
@ -62,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
// The first 3 bits of the color transform data contain the width and height of the image block in number of bits,
// just like the predictor transform:
uint sizeBits = bitReader.Read(3) + 2;
uint sizeBits = this.bitReader.Read(3) + 2;
int blockWidth = 1 << (int)sizeBits;
int blockHeight = 1 << (int)sizeBits;
break;
@ -70,15 +268,23 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
numberOfTransformsPresent++;
if (numberOfTransformsPresent == 4)
transformPresent = this.bitReader.ReadBit();
if (numberOfTransformsPresent == WebPConstants.MaxNumberOfTransforms && transformPresent)
{
break;
WebPThrowHelper.ThrowImageFormatException("The maximum number of transforms was exceeded");
}
transformPresent = bitReader.ReadBit();
}
// TODO: return transformation in an appropriate form.
}
/// <summary>
/// Computes sampled size of 'size' when sampling using 'sampling bits'.
/// </summary>
private int SubSampleSize(int size, int samplingBits)
{
return (size + (1 << samplingBits) - 1) >> samplingBits;
}
}
}

Loading…
Cancel
Save