diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs index 1fc47180f7..d5579cbf1e 100644 --- a/src/ImageSharp/Formats/WebP/ColorCache.cs +++ b/src/ImageSharp/Formats/WebP/ColorCache.cs @@ -3,6 +3,9 @@ namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. + /// internal class ColorCache { private const uint KHashMul = 0x1e35a7bdu; @@ -22,6 +25,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public int HashBits { get; private set; } + /// + /// Initializes a new color cache. + /// + /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. public void Init(int hashBits) { int hashSize = 1 << hashBits; @@ -30,6 +37,10 @@ namespace SixLabors.ImageSharp.Formats.WebP this.HashShift = 32 - hashBits; } + /// + /// Inserts a new color into the cache. + /// + /// The color to insert. public void Insert(uint argb) { int key = this.HashPix(argb, this.HashShift); diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs index b76f41d23f..ac6c5bec46 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs @@ -5,6 +5,9 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.WebP { + /// + /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. + /// [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] internal class HuffmanCode { diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs index a52ec3984a..3cab68dd12 100644 --- a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Guard.NotNull(codeLengths, nameof(codeLengths)); Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); - // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length. + // 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. diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 7480318606..a0b11121d3 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -30,6 +30,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// 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. + /// + /// The transform data contains color table size and the entries in the color table. + /// The pixel data to apply the reverse transform on. public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) { int bitsPerPixel = 8 >> transform.Bits; @@ -80,6 +86,12 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// 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. + /// + /// The transform data. + /// The pixel data to apply the inverse transform on. public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) { int width = transform.XSize; @@ -124,6 +136,13 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + /// + /// Reverses the color space transform. + /// + /// The color transform element. + /// The pixel data to apply the inverse transform on. + /// The start index of reverse transform. + /// The number of pixels to apply the transform. public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels) { int end = start + numPixels; @@ -144,24 +163,14 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) - { - newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); - Span newData = MemoryMarshal.Cast(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. - } - } - + /// + /// 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. We divide the image into squares and all the pixels in a square use same prediction mode. + /// + /// The transform data. + /// The pixel data to apply the inverse transform. + /// The resulting pixel data with the reversed transformation data. public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output) { int processedPixels = 0; @@ -198,6 +207,8 @@ namespace SixLabors.ImageSharp.Formats.WebP 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) @@ -602,9 +613,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return (idx >> 8) & 0xff; } + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) + { + newColorMap[0] = transformData[0]; + Span data = MemoryMarshal.Cast(transformData); + Span newData = MemoryMarshal.Cast(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) { - int delta = ((sbyte)colorPred * color) >> 5; return ((int)colorPred * color) >> 5; } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index b12ed0e726..39d7ecdfc2 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -162,13 +162,20 @@ namespace SixLabors.ImageSharp.Formats.WebP return new WebPImageInfo(); } + /// + /// 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. + /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. + /// + /// Information about this webp image. private WebPImageInfo ReadVp8XHeader() { uint chunkSize = this.ReadChunkSize(); - // This byte contains information about the image features used. - // The first two bit should and the last bit should be 0. - // TODO: should an exception be thrown if its not the case, or just ignore it? + // 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. @@ -349,7 +356,9 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Parses optional metadata chunks. + /// 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. /// /// The webp features. private void ParseOptionalChunks(WebPFeatures features) diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index ecdaf606c3..64bbf5cc18 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -108,6 +108,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { transform.Data?.Dispose(); } + decoder.Metadata?.HuffmanImage?.Dispose(); } @@ -644,12 +645,11 @@ namespace SixLabors.ImageSharp.Formats.WebP case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: { + // The first 3 bits of prediction data define the block width and height in number of bits. transform.Bits = (int)this.bitReader.ReadBits(3) + 2; - IMemoryOwner transformData = this.DecodeImageStream( - decoder, - LosslessUtils.SubSampleSize(transform.XSize, transform.Bits), - LosslessUtils.SubSampleSize(transform.YSize, transform.Bits), - false); + int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); + int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); + IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); transform.Data = transformData; break; } @@ -659,7 +659,8 @@ namespace SixLabors.ImageSharp.Formats.WebP } /// - /// Reverses the transformations, if any are present. + /// A WebP lossless image can go through four different types of transformation before being entropy encoded. + /// This will reverses the transformations, if any are present. /// /// The decoder holding the transformation infos. /// The pixel data to apply the transformation.