Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
dc84968
Initial Commit towards WebP
jongleur1983 Oct 11, 2019
8eed360
Add empty implementation of IImageDecoder to test github colaboration
brianpopow Oct 11, 2019
7f3af8e
introduce decoderCore classes (without implementation)
jongleur1983 Oct 12, 2019
d54cbc3
First attempt parsing minimal image info
brianpopow Oct 12, 2019
e9969ef
Identify works at least for reading the image dimensions so far
brianpopow Oct 12, 2019
3441e56
WebP test input files added, test skeletons added (copied from Bmp te…
martonl Oct 12, 2019
89080b9
First attempt parsing minimal image info, infoIdentify works at least…
brianpopow Oct 12, 2019
4e77257
fix typo
jongleur1983 Oct 14, 2019
c5474e9
WebP tests WIP
martonl Oct 14, 2019
30712d4
Add reading of VP8X header
brianpopow Oct 16, 2019
0d8e813
Additional webp constants, first attempt parsing VP8L header
brianpopow Oct 17, 2019
19563f0
WebP Identify test added, webp sample files copied from official samp…
martonl Oct 17, 2019
3bc1bbf
Two sample images missed
martonl Oct 18, 2019
cb92eea
Add bitreader for a VP8L stream. Determining image size for VP8L shou…
brianpopow Oct 19, 2019
f35465a
height comes before width, switch order.
jongleur1983 Oct 20, 2019
ccc7e5e
Parsing the transforms (still WIP)
brianpopow Oct 20, 2019
72e273e
Add additional chunk header constants
brianpopow Oct 21, 2019
10b3443
Parsing of image features of VP8X header
brianpopow Oct 21, 2019
acd80c9
Use the width and height from the VP8X information, if present
brianpopow Oct 21, 2019
e69ea45
Refactor determining chunk types
brianpopow Oct 21, 2019
2ced96a
Some Lossy files added to WebPDecoder Identify test. Test is broken w…
martonl Oct 21, 2019
df96513
Animated webp sample added to Identify test
martonl Oct 21, 2019
6d71a0c
Skipping over optional chunks with VP8X images (still does not seem t…
brianpopow Oct 22, 2019
39a6bf5
Fix mistake with parsing VP8X header
brianpopow Oct 22, 2019
5a4fbec
BitsPerPixel Assert added to webp Identify_DetectsCorrectDimensions test
martonx Oct 23, 2019
6e94934
Initialize metadata
brianpopow Oct 23, 2019
762c5bc
Add parsing optional chunks at the end
brianpopow Oct 23, 2019
5ff81d4
Add to the metadata, if the image is lossy or lossless and also if a …
brianpopow Oct 24, 2019
9ea185e
Throwing not supported exception for animated images
brianpopow Oct 24, 2019
d9b7e55
Add all found chunk types to the metadata
brianpopow Oct 24, 2019
dabb141
Move decoding of lossless into separate class
brianpopow Oct 25, 2019
d041d80
Add additional image features into separate class
brianpopow Oct 25, 2019
e9630ed
introduce WebPLossyDecoder
Oct 26, 2019
5a24e61
Decode verison of lossy bitstream to determine Reconstruction- and Lo…
Oct 28, 2019
9b7e461
More animeted webp test images added
martonl Oct 28, 2019
1d9c764
Fix Bitreader issue: offset was not initialized correctly
brianpopow Oct 30, 2019
450a632
Start with parsing huffman codes
brianpopow Oct 30, 2019
d525f66
Add ReadHuffmanCodeLengths
brianpopow Nov 1, 2019
83cdeb5
Move huffman code into separate class, introduce HTreeGroup
brianpopow Nov 3, 2019
949c3ef
Continue with BuildHuffmanTable
brianpopow Nov 4, 2019
8eccf53
Overhaul bitmap reader to be similar to libwebp bitreader
brianpopow Nov 15, 2019
d4fea75
Use kTableSize array to determine tableSize
brianpopow Nov 15, 2019
94c22eb
Continue with ReadHuffmanCodes
brianpopow Nov 18, 2019
8b0ed63
Setup color cache
brianpopow Nov 19, 2019
bae8272
Add additional helper methods for decoding the image, start with deco…
brianpopow Nov 25, 2019
486edbc
Using color cache, first version of decoding a lossless image
brianpopow Nov 28, 2019
23ee03f
Refactor lossless decoding to match better original implementation
brianpopow Nov 30, 2019
f666db7
Fix some decoding bugs
brianpopow Dec 1, 2019
ddd5f66
Fix issue with creating huffman tables
brianpopow Dec 5, 2019
0dcc43e
Add tests for lossless images without transforms
brianpopow Dec 5, 2019
d44b442
Fix more decoding bugs, lossless_vec_2_0.webp.webp now decodes withou…
brianpopow Dec 7, 2019
ad58d36
Add tests for lossless images with one transform
brianpopow Dec 8, 2019
4987893
SubtractGreen transform works now
brianpopow Dec 8, 2019
e3946d8
Reading transformation data
brianpopow Dec 8, 2019
c54f921
Fix an issue reading the transformations and fixed the testimages acc…
brianpopow Dec 9, 2019
a66c4cf
Additional tests for images with more than one transform
brianpopow Dec 10, 2019
89bdf89
Implemented ColorSpaceInverseTransform
brianpopow Dec 10, 2019
c2d5acc
Add ColorIndexInverseTransform
brianpopow Dec 14, 2019
a077518
Fix some warnings
brianpopow Dec 15, 2019
488492c
Add helper methods for PredictorInverseTransform
brianpopow Dec 15, 2019
f7dad0d
Add PredictorAdd methods
brianpopow Dec 17, 2019
0f5f746
Add PredictorInverseTransform
brianpopow Dec 30, 2019
6de5ae9
Fix wrong start and end index in PredictorAdd01
brianpopow Dec 31, 2019
541aa85
Fix bug in ColorSpaceInverseTransform
brianpopow Jan 5, 2020
026e658
Move corrupted images into separate test
brianpopow Jan 6, 2020
1cea0c2
Remove none webp images, except for two example images
brianpopow Jan 8, 2020
2bd350e
Fix bug in PredictorInverseTransform
brianpopow Jan 9, 2020
8361526
Fix warnings, add additional comments
brianpopow Jan 10, 2020
4726b81
Use memory allocator
brianpopow Jan 10, 2020
4a839a6
Use memory allocator where possible in lossless decoder
brianpopow Jan 11, 2020
4b0603d
Add additional comments
brianpopow Jan 12, 2020
06e7d4e
Add parsing of EXIF chunk
brianpopow Jan 13, 2020
4e23462
Add parsing of ICCP chunk
brianpopow Jan 13, 2020
5fdf1f7
Fix parsing VP8 header
brianpopow Jan 14, 2020
354d8c0
Throw exception, if a transform is present more than once
brianpopow Jan 14, 2020
4505f1e
Add bits per pixel in webp image info
brianpopow Jan 15, 2020
20cc8f8
Lossless WebP: avoid duplicate if by moving the negated condition to …
jongleur1983 Jan 14, 2020
badece2
LosslessWebP: use logical connections between fields to make it less …
jongleur1983 Jan 14, 2020
bbfdff7
move some methods from LosslessDecoder to base class
jongleur1983 Jan 15, 2020
0a60fc3
Add data classes for decoding VP8 images
brianpopow Jan 16, 2020
a9a4b9e
code cleanup
jongleur1983 Jan 16, 2020
4d5ceca
Introduce bitreader base class
brianpopow Jan 17, 2020
d02a6d3
Fix checking the magick bytes in parsing the VP8 header
brianpopow Jan 18, 2020
72d2428
Introduce VP8 Profile which contains the reconstruction filter and th…
brianpopow Jan 18, 2020
d2b76c7
Fix image data size in webpinfo for VP8: 10 bytes are already read du…
brianpopow Jan 18, 2020
9487288
Add VP8 bitreader to image info, remove image data size (not used exc…
brianpopow Jan 19, 2020
458fa2f
WIP: Parse further VP8 header: SegmentHeader, FilterHeader, implement…
brianpopow Jan 19, 2020
5ac15d7
remove obsolete comments
jongleur1983 Jan 19, 2020
723a0a7
WebP: Move ReadSymbol to WebPDecoderBase
jongleur1983 Jan 19, 2020
658f07f
introduce function Is8bOptimizable
jongleur1983 Jan 19, 2020
f749880
introduce WebPFilterType enum
jongleur1983 Jan 19, 2020
5012084
WebP: replace WebPFilterType enum by classes and implement them
jongleur1983 Jan 19, 2020
febfb01
WebP: implement AlphaDecoder and AlphaApplyFilter, start implementing…
jongleur1983 Jan 20, 2020
2ad6bd2
WebP: split Filter classes to different files
jongleur1983 Jan 23, 2020
a25eb03
Fix some issues in Vp8BitReader
brianpopow Jan 24, 2020
10459dc
Paragraph 9.6: Dequantization Indices
brianpopow Jan 24, 2020
ab743a1
Rename constants
brianpopow Jan 24, 2020
9444fe2
Paragraph 13.4: Parse probabilities
brianpopow Jan 27, 2020
09b2cf3
Move parsing VP8 infos into separate methods
brianpopow Jan 27, 2020
011719a
Move Vp8 specific parsing into LossyDecoder
brianpopow Jan 27, 2020
653bbb3
code cleanup, make method static
jongleur1983 Jan 27, 2020
daf1f30
Add ParseResiduals
brianpopow Jan 31, 2020
a6a7fbe
Implement VP8 decoder init
brianpopow Jan 31, 2020
80b224d
Start implementing ParseFrame, ParseIntraMode
brianpopow Feb 3, 2020
a1d13e2
Finish ParseIntraMode (still untested)
brianpopow Feb 3, 2020
1d5f57f
Implement Parse Partitions
brianpopow Feb 5, 2020
1e80ba9
Fix BitsLog2Floor
brianpopow Feb 7, 2020
5359bcc
Store filter info
brianpopow Feb 7, 2020
33ff66c
Start implementing ReconstructRow
brianpopow Feb 12, 2020
045dacc
Fix some parsing mistakes
brianpopow Feb 13, 2020
6e8acc6
Use memset equivalent in LossyUtils
brianpopow Feb 13, 2020
0947f73
Fix a bug in TransformDcuv
brianpopow Feb 13, 2020
6bd5ebd
Implementing FinishRow, EmitRgb
brianpopow Feb 15, 2020
1d532f4
Fix modes probabilities
brianpopow Feb 16, 2020
6e60e3b
Implement functions for luma modes
brianpopow Feb 17, 2020
a801c52
Fix setting Y/UV-Stride
brianpopow Feb 20, 2020
01d7f2e
Implement TrueMotion
brianpopow Feb 21, 2020
1640a7f
Implement macroblock filtering (still not working: the extra rows in …
brianpopow Feb 21, 2020
325c29e
Add lossy webp specification
brianpopow Feb 26, 2020
059b07a
Replicate the top-right pixels in 4x4 block
brianpopow Feb 26, 2020
4cea3a8
Move lookup tables to separate file
brianpopow Feb 26, 2020
0e21c6a
Add webp lossy tests
brianpopow Feb 26, 2020
65cf078
Fix webp lossy decoding bug
brianpopow Feb 27, 2020
c274276
Additional webp lossy tests
brianpopow Feb 28, 2020
2ab691c
Enable macroblock filtering
brianpopow Feb 28, 2020
ff701c0
Fix some lossy decoding bugs
brianpopow Feb 29, 2020
bf758f7
Add more webp lossy test cases
brianpopow Feb 29, 2020
a26d86c
Fix decoding very small lossy webp images
brianpopow Mar 1, 2020
240b156
Additional test cases for webp lossy images
brianpopow Mar 1, 2020
4c66366
Add decode webp benchmark
brianpopow Mar 2, 2020
e2cd0d8
A little cleanup and refactoring
brianpopow Mar 2, 2020
fc1003d
Start implementing alpha decoding: uncompressed alpha without filteri…
brianpopow Mar 2, 2020
3f3b445
Start implementing compressed alpha decoding
brianpopow Mar 5, 2020
592e5b1
Add filtering for uncompressed alpha
brianpopow Mar 9, 2020
3f0a1cd
Add filtering for compressed alpha
brianpopow Mar 9, 2020
885b3fb
Fix decoding issues
brianpopow Mar 10, 2020
d806e75
Vp8Decoder reserves only needed memory
brianpopow Mar 10, 2020
4cf09ce
Vp8Decoder now uses memory allocator
brianpopow Mar 10, 2020
e510e41
Use bulk pixel conversion
brianpopow Mar 10, 2020
87a8099
Merge remote-tracking branch 'jongleur_upstream/WebP' into webp
brianpopow Mar 13, 2020
7f90c02
Fix solution file due to merge error
brianpopow Mar 13, 2020
27c7149
Add tests for reading webp metadata, fix some test images with metadata
brianpopow Mar 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions ImageSharp.sln

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/ImageSharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.WebP;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing;
Expand Down Expand Up @@ -162,7 +163,8 @@ internal static Configuration CreateDefaultInstance()
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
new TgaConfigurationModule());
new TgaConfigurationModule(),
new WebPConfigurationModule());
}
}
}
309 changes: 309 additions & 0 deletions src/ImageSharp/Formats/WebP/AlphaDecoder.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
49 changes: 49 additions & 0 deletions src/ImageSharp/Formats/WebP/BitReaderBase.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
Loading