From 9838e2e512a918b9bf9762cd800610f9f331ffd5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 5 Feb 2020 20:57:40 +0100 Subject: [PATCH] Implement Parse Partitions --- src/ImageSharp/Formats/WebP/BitReaderBase.cs | 4 +- src/ImageSharp/Formats/WebP/VP8BandProbas.cs | 4 + src/ImageSharp/Formats/WebP/Vp8BitReader.cs | 44 +++- src/ImageSharp/Formats/WebP/Vp8Decoder.cs | 60 +++-- .../Formats/WebP/Vp8MacroBlockData.cs | 3 +- src/ImageSharp/Formats/WebP/Vp8Proba.cs | 16 ++ src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs | 6 +- src/ImageSharp/Formats/WebP/Vp8TopSamples.cs | 14 ++ src/ImageSharp/Formats/WebP/WebPConstants.cs | 3 + .../Formats/WebP/WebPDecoderCore.cs | 22 +- src/ImageSharp/Formats/WebP/WebPImageInfo.cs | 9 +- .../Formats/WebP/WebPLossyDecoder.cs | 227 +++++++++++++++--- 12 files changed, 325 insertions(+), 87 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Vp8TopSamples.cs diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs index 0a62299ea..8ef93a4d7 100644 --- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs +++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.WebP internal abstract class BitReaderBase { /// - /// Gets raw encoded image data. + /// Gets or sets the raw encoded image data. /// - protected byte[] Data { get; private set; } + public byte[] Data { get; set; } /// /// Copies the raw encoded image data from the stream into a byte array. diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs index 6a48eef55..a9c9420d1 100644 --- a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs +++ b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs @@ -11,6 +11,10 @@ namespace SixLabors.ImageSharp.Formats.WebP 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; } diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs index 2fdd4959f..d7b1fe100 100644 --- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -32,9 +32,11 @@ namespace SixLabors.ImageSharp.Formats.WebP private int bits; /// - /// The next byte to be read. + /// Max packed-read position on buffer. /// - private byte buf; + private uint bufferMax; + + private uint bufferEnd; /// /// True if input is exhausted. @@ -53,10 +55,20 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. /// Start index in the data array. Defaults to 0. - public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, int startPos = 0) + 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(startPos); + this.InitBitreader(partitionLength, startPos); + } + + 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 @@ -64,6 +76,12 @@ namespace SixLabors.ImageSharp.Formats.WebP get { return (int)this.pos; } } + public uint ImageDataSize { get; } + + public uint PartitionLength { get; } + + public uint Remaining { get; set; } + public int GetBit(int prob) { Guard.MustBeGreaterThan(prob, 0, nameof(prob)); @@ -123,26 +141,26 @@ namespace SixLabors.ImageSharp.Formats.WebP return this.ReadValue(1) != 0 ? -value : value; } - private void InitBitreader(int pos = 0) + private void InitBitreader(uint size, int pos = 0) { this.range = 255 - 1; this.value = 0; - this.bits = -8; // to load the very first 8bits. + this.bits = -8; // to load the very first 8 bits. this.eof = false; - this.pos = 0; + 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.Data.Length) + if (this.pos < this.bufferMax) { - ulong bits; - ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan().Slice((int)this.pos, 8)); + ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8)); this.pos += BitsCount >> 3; - this.buf = this.Data[this.pos]; - bits = this.ByteSwap64(inBits); + ulong bits = this.ByteSwap64(inBits); bits >>= 64 - BitsCount; this.value = bits | (this.value << BitsCount); this.bits += BitsCount; @@ -156,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void LoadFinalBytes() { // Only read 8bits at a time. - if (this.pos < this.Data.Length) + if (this.pos < this.bufferEnd) { this.bits += 8; this.value = this.Data[this.pos++] | (this.value << 8); diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs index 5643b798b..24d329e5b 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs @@ -15,10 +15,32 @@ namespace SixLabors.ImageSharp.Formats.WebP this.FilterHeader = filterHeader; this.SegmentHeader = segmentHeader; this.Probabilities = probabilities; + this.IntraL = new byte[4]; + this.YuvBuffer = new byte[(WebPConstants.Bps * 17) + (WebPConstants.Bps * 9)]; + this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); + this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); + this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth]; + this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; + this.YuvTopSamples = new Vp8TopSamples[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.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments]; this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2]; - this.IntraL = new byte[4]; + 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(); + } + } + this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions]; this.Init(io); } @@ -30,14 +52,20 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8SegmentHeader SegmentHeader { get; } + // number of partitions minus one. + public uint NumPartsMinusOne { get; } + + // per-partition boolean decoders. + public Vp8BitReader[] Vp8BitReaders { get; } + public bool Dither { get; set; } /// /// Gets or sets dequantization matrices (one set of DC/AC dequant factor per segment). /// - public Vp8QuantMatrix[] DeQuantMatrices { get; private set; } + public Vp8QuantMatrix[] DeQuantMatrices { get; } - public bool UseSkipProba { get; set; } + public bool UseSkipProbability { get; set; } public byte SkipProbability { get; set; } @@ -52,12 +80,12 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the width in macroblock units. /// - public int MbWidth { get; set; } + public int MbWidth { get; } /// /// Gets or sets the height in macroblock units. /// - public int MbHeight { get; set; } + public int MbHeight { get; } /// /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. @@ -72,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. /// - public int BotomRightMbX { get; set; } + public int BottomRightMbX { get; set; } /// /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. @@ -90,14 +118,14 @@ namespace SixLabors.ImageSharp.Formats.WebP public int MbY { get; set; } /// - /// Gets or sets the parsed reconstruction data. + /// Gets the parsed reconstruction data. /// - public Vp8MacroBlockData[] MacroBlockData { get; set; } + public Vp8MacroBlockData[] MacroBlockData { get; } /// - /// Gets or sets contextual macroblock infos. + /// Gets contextual macroblock infos. /// - public Vp8MacroBlock[] MacroBlockInfo { get; set; } + public Vp8MacroBlock[] MacroBlockInfo { get; } public int MacroBlockIdx { get; set; } @@ -105,6 +133,10 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8FilterInfo[,] FilterStrength { get; } + public byte[] YuvBuffer { get; } + + public Vp8TopSamples[] YuvTopSamples { get; } + /// /// Gets or sets filter strength info. /// @@ -112,8 +144,6 @@ namespace SixLabors.ImageSharp.Formats.WebP public void Init(Vp8Io io) { - this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); - this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); int intraPredModeSize = 4 * this.MbWidth; this.IntraT = new byte[intraPredModeSize]; @@ -157,10 +187,10 @@ namespace SixLabors.ImageSharp.Formats.WebP // We need some 'extra' pixels on the right/bottom. this.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4; - this.BotomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; - if (this.BotomRightMbX > this.MbWidth) + this.BottomRightMbX = (io.CropRight + 15 + extraPixels) >> 4; + if (this.BottomRightMbX > this.MbWidth) { - this.BotomRightMbX = this.MbWidth; + this.BottomRightMbX = this.MbWidth; } if (this.BottomRightMbY > this.MbHeight) diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs index 5dbeb2358..f2ab8ff7f 100644 --- a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs @@ -11,12 +11,13 @@ namespace SixLabors.ImageSharp.Formats.WebP public Vp8MacroBlockData() { this.Modes = new byte[16]; + this.Coeffs = new short[384]; } /// /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4. /// - public short Coeffs { get; set; } + public short[] Coeffs { get; set; } /// /// Gets or sets a value indicating whether its intra4x4. diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs index a884bd3c7..ae34f936d 100644 --- a/src/ImageSharp/Formats/WebP/Vp8Proba.cs +++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs @@ -15,6 +15,22 @@ namespace SixLabors.ImageSharp.Formats.WebP 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; } diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs index a5d718a48..ca9f4b69e 100644 --- a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs @@ -5,11 +5,11 @@ namespace SixLabors.ImageSharp.Formats.WebP { internal class Vp8QuantMatrix { - public int[] Y1Mat { get; set; } + public int[] Y1Mat { get; } = new int[2]; - public int[] Y2Mat { get; set; } + public int[] Y2Mat { get; } = new int[2]; - public int[] UvMat { get; set; } + public int[] UvMat { get; } = new int[2]; /// /// Gets or sets the U/V quantizer value. diff --git a/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs new file mode 100644 index 000000000..c6382b5c6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs @@ -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]; + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index f06310d23..854b9674b 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -121,6 +121,9 @@ namespace SixLabors.ImageSharp.Formats.WebP 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; public const int TmPred = 1; diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 22a370592..6e9631729 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // remaining counts the available image data payload. uint remaining = dataSize; - // See paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 + // 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. @@ -359,31 +359,21 @@ namespace SixLabors.ImageSharp.Formats.WebP var bitReader = new Vp8BitReader( this.currentStream, remaining, - this.memoryAllocator); - - // Paragraph 9.2: color space and clamp type follow. - sbyte colorSpace = (sbyte)bitReader.ReadValue(1); - sbyte clampType = (sbyte)bitReader.ReadValue(1); - var vp8PictureHeader = new Vp8PictureHeader() - { - Width = (uint)width, - Height = (uint)height, - XScale = xScale, - YScale = yScale, - ColorSpace = colorSpace, - ClampType = clampType - }; + 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, - Vp8PictureHeader = vp8PictureHeader, Vp8BitReader = bitReader }; } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index dace37aad..19a72c0c2 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -15,6 +15,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public uint Height { get; set; } + public sbyte XScale { get; set; } + + public sbyte YScale { get; set; } + /// /// Gets or sets the bits per pixel. /// @@ -40,11 +44,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8FrameHeader Vp8FrameHeader { get; set; } - /// - /// Gets or sets the VP8 picture header. - /// - public Vp8PictureHeader Vp8PictureHeader { get; set; } - /// /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image. /// diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f1d06531e..852b94c0f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -36,6 +36,19 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V Vp8Profile vp8Profile = this.DecodeProfile(info.Vp8Profile); + // Paragraph 9.2: color space and clamp type follow. + sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); + sbyte clampType = (sbyte)this.bitReader.ReadValue(1); + var vp8PictureHeader = new Vp8PictureHeader() + { + Width = (uint)width, + Height = (uint)height, + XScale = info.XScale, + YScale = info.YScale, + ColorSpace = colorSpace, + ClampType = clampType + }; + // Paragraph 9.3: Parse the segment header. var proba = new Vp8Proba(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); @@ -43,23 +56,21 @@ namespace SixLabors.ImageSharp.Formats.WebP // Paragraph 9.4: Parse the filter specs. Vp8FilterHeader vp8FilterHeader = this.ParseFilterHeader(); - // TODO: Review Paragraph 9.5: ParsePartitions. - int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; - int lastPart = numPartsMinusOne; - // TODO: check if we have enough data available here, throw exception if not - int partStart = this.bitReader.Pos + (lastPart * 3); + var vp8Io = default(Vp8Io); + var decoder = new Vp8Decoder(info.Vp8FrameHeader, vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); + + // Paragraph 9.5: Parse partitions. + this.ParsePartitions(decoder); // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(vp8SegmentHeader); + this.ParseDequantizationIndices(decoder.SegmentHeader); // Ignore the value of update_proba this.bitReader.ReadBool(); // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(proba); + this.ParseProbabilities(decoder, decoder.Probabilities); - var vp8Io = default(Vp8Io); - var decoder = new Vp8Decoder(info.Vp8FrameHeader, info.Vp8PictureHeader, vp8FilterHeader, vp8SegmentHeader, proba, vp8Io); this.ParseFrame(decoder, vp8Io); } @@ -81,21 +92,9 @@ namespace SixLabors.ImageSharp.Formats.WebP // Prepare for next scanline. this.InitScanline(dec); - // TODO: Reconstruct, filter and emit the row. - } - } - - private void InitScanline(Vp8Decoder dec) - { - Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; - left.NoneZeroAcDcCoeffs = 0; - left.NoneZeroDcCoeffs = 0; - for (int i = 0; i < dec.IntraL.Length; i++) - { - dec.IntraL[i] = 0; + // Reconstruct, filter and emit the row. + this.ProcessRow(dec); } - - dec.MbX = 0; } private void ParseIntraMode(Vp8Decoder dec, int mbX) @@ -117,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.WebP block.Segment = 0; } - if (dec.UseSkipProba) + if (dec.UseSkipProbability) { block.Skip = (byte)this.bitReader.GetBit(dec.SkipProbability); } @@ -165,7 +164,144 @@ namespace SixLabors.ImageSharp.Formats.WebP block.UvMode = (byte)(this.bitReader.GetBit(142) is 0 ? 0 : this.bitReader.GetBit(114) is 0 ? 2 : this.bitReader.GetBit(183) > 0 ? 1 : 3); + } + private void InitScanline(Vp8Decoder dec) + { + Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; + left.NoneZeroAcDcCoeffs = 0; + left.NoneZeroDcCoeffs = 0; + for (int i = 0; i < dec.IntraL.Length; i++) + { + dec.IntraL[i] = 0; + } + + dec.MbX = 0; + } + + private void ProcessRow(Vp8Decoder dec) + { + bool filterRow = (dec.Filter != LoopFilter.None) && + (dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY); + + this.ReconstructRow(dec, filterRow); + } + + private void ReconstructRow(Vp8Decoder dec, bool filterRow) + { + int mby = dec.MbY; + + int yOff = (WebPConstants.Bps * 1) + 8; + int uOff = yOff + (WebPConstants.Bps * 16) + WebPConstants.Bps; + int vOff = uOff + 16; + + Span yDst = dec.YuvBuffer.AsSpan(yOff); + Span uDst = dec.YuvBuffer.AsSpan(uOff); + Span vDst = dec.YuvBuffer.AsSpan(vOff); + + // Initialize left-most block. + for (int i = 0; i < 16; ++i) + { + yDst[(i * WebPConstants.Bps) - 1] = 129; + } + + for (int i = 0; i < 8; ++i) + { + uDst[(i * WebPConstants.Bps) - 1] = 129; + vDst[(i * WebPConstants.Bps) - 1] = 129; + } + + // Init top-left sample on left column too. + if (mby > 0) + { + yDst[-1 - WebPConstants.Bps] = uDst[-1 - WebPConstants.Bps] = vDst[-1 - WebPConstants.Bps] = 129; + } + else + { + // We only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + Span tmp = dec.YuvBuffer.AsSpan(yOff - WebPConstants.Bps - 1, 16 + 4 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + + tmp = dec.YuvBuffer.AsSpan(uOff - WebPConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + + tmp = dec.YuvBuffer.AsSpan(vOff - WebPConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; ++i) + { + tmp[i] = 127; + } + } + + // Reconstruct one row. + for (int mbx = 0; mbx < dec.MbWidth; ++mbx) + { + Vp8MacroBlockData block = dec.MacroBlockData[mbx]; + + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mbx > 0) + { + for (int i = -1; i < 16; ++i) + { + // Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + } + + for (int i = -1; i < 8; ++i) + { + // Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); + // Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + } + + // Bring top samples into the cache. + Vp8TopSamples topSamples = dec.YuvTopSamples[mbx]; + short[] coeffs = block.Coeffs; + uint bits = block.NonZeroY; + if (mby > 0) + { + //memcpy(y_dst - BPS, top_yuv[0].y, 16); + //memcpy(u_dst - BPS, top_yuv[0].u, 8); + //memcpy(v_dst - BPS, top_yuv[0].v, 8); + } + + // Predict and add residuals. + if (block.IsI4x4) + { + if (mby > 0) + { + if (mbx >= dec.MbWidth - 1) + { + // On rightmost border. + //memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } + else + { + // memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } + } + + // Replicate the top-right pixels below. + + + // Predict and add residuals for all 4x4 blocks in turn. + for (int n = 0; n < 16; ++n, bits <<= 2) + { + + } + } + else + { + // 16x16 + + } + } + } } private Vp8Profile DecodeProfile(int version) @@ -193,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockIdx - 1]; // TODO: not sure if this - 1 is correct here Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockIdx + dec.MbX]; Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockIdx + dec.MbX]; - int skip = dec.UseSkipProba ? blockData.Skip : 0; + int skip = dec.UseSkipProbability ? blockData.Skip : 0; if (skip is 0) { @@ -555,6 +691,34 @@ namespace SixLabors.ImageSharp.Formats.WebP return vp8FilterHeader; } + private void ParsePartitions(Vp8Decoder dec) + { + uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; + int startIdx = (int)this.bitReader.PartitionLength; + Span sz = this.bitReader.Data.AsSpan(startIdx); + int sizeLeft = (int)size; + int numPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; + int lastPart = numPartsMinusOne; + + int partStart = startIdx + (lastPart * 3); + sizeLeft -= lastPart * 3; + for (int p = 0; p < lastPart; ++p) + { + int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (pSize > sizeLeft) + { + pSize = sizeLeft; + } + + dec.Vp8BitReaders[p] = new Vp8BitReader(this.bitReader.Data, (uint)pSize, partStart); + partStart += pSize; + sizeLeft -= pSize; + sz = sz.Slice(3); + } + + dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); + } + private void ParseDequantizationIndices(Vp8SegmentHeader vp8SegmentHeader) { int baseQ0 = (int)this.bitReader.ReadValue(7); @@ -613,7 +777,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - private void ParseProbabilities(Vp8Proba proba) + private void ParseProbabilities(Vp8Decoder dec, Vp8Proba proba) { for (int t = 0; t < WebPConstants.NumTypes; ++t) { @@ -623,8 +787,8 @@ namespace SixLabors.ImageSharp.Formats.WebP { for (int p = 0; p < WebPConstants.NumProbas; ++p) { - var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; - int v = this.bitReader.GetBit(prob) == 0 + byte prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; + int v = this.bitReader.GetBit(prob) > 0 ? (int)this.bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)v; @@ -638,11 +802,10 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // TODO: those values needs to be stored somewhere - bool useSkipProba = this.bitReader.ReadBool(); - if (useSkipProba) + dec.UseSkipProbability = this.bitReader.ReadBool(); + if (dec.UseSkipProbability) { - uint skipP = this.bitReader.ReadValue(8); + dec.SkipProbability = (byte)this.bitReader.ReadValue(8); } }