diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs index 20e502ef6a..5eac30f1fb 100644 --- a/src/ImageSharp/Formats/WebP/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs @@ -1,3 +1,6 @@ +using System; +using System.Runtime.InteropServices; + namespace SixLabors.ImageSharp.Formats.WebP { /// @@ -22,6 +25,57 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + public static void ColorIndexInverseTransform(Vp8LTransform transform, uint[] pixelData) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + int height = transform.YSize; + uint[] colorMap = transform.Data; + int decodedPixels = 0; + if (bitsPerPixel < 8) + { + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; + + // TODO: use memoryAllocator here + 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[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[colorMapIndex]; + decodedPixels++; + } + } + } + public static void ColorSpaceInverseTransform(Vp8LTransform transform, uint[] pixelData) { int width = transform.XSize; @@ -78,11 +132,36 @@ namespace SixLabors.ImageSharp.Formats.WebP newBlue += ColorTransformDelta(m.GreenToBlue, (sbyte)green); newBlue += ColorTransformDelta(m.RedToBlue, (sbyte)newRed); newBlue &= 0xff; - var pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); + // uint pixelValue = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); pixelData[i] = (uint)((argb & 0xff00ff00u) | (newRed << 16) | newBlue); } } + public static uint[] ExpandColorMap(int numColors, Vp8LTransform transform, uint[] transformData) + { + int finalNumColors = 1 << (8 >> transform.Bits); + + // TODO: use memoryAllocator here + var newColorMap = new uint[finalNumColors]; + 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 * finalNumColors; ++i) + { + newData[i] = 0; // black tail. + } + + return newColorMap; + } + /// /// Computes sampled size of 'size' when sampling using 'sampling bits'. /// @@ -91,6 +170,39 @@ namespace SixLabors.ImageSharp.Formats.WebP return (size + (1 << samplingBits) - 1) >> samplingBits; } + /// + /// Sum of each component, mod 256. + /// + 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); + } + + /// + /// Difference of each component, mod 256. + /// + 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 void PredictorAdd1(uint[] pixelData, int numPixels) + { + /*for (int i = 0; i < num_pixels; ++i) + { + pixelData[i] = VP8LAddPixels(in[i], left); + }*/ + } + + private static uint GetARGBIndex(uint idx) + { + return (idx >> 8) & 0xff; + } + private static int ColorTransformDelta(sbyte colorPred, sbyte color) { return ((int)colorPred * color) >> 5; diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index 341af8843e..24104b494a 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -603,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.WebP : 3; transform.XSize = LosslessUtils.SubSampleSize(transform.XSize, bits); transform.Bits = bits; - transform.Data = this.DecodeImageStream(decoder, (int)numColors, 1, false); + uint[] colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false); + transform.Data = LosslessUtils.ExpandColorMap((int)numColors, transform, colorMap); break; case Vp8LTransformType.PredictorTransform: @@ -635,12 +636,18 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8LTransformType transformType = transforms[i].TransformType; switch (transformType) { + case Vp8LTransformType.PredictorTransform: + // LosslessUtils.PredictorInverseTransform(transforms[i], pixelData); + break; case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); break; case Vp8LTransformType.CrossColorTransform: LosslessUtils.ColorSpaceInverseTransform(transforms[i], pixelData); break; + case Vp8LTransformType.ColorIndexingTransform: + LosslessUtils.ColorIndexInverseTransform(transforms[i], pixelData); + break; } } }