diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 73fb3fed23..060ec0f0f9 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -366,117 +366,14 @@ namespace SixLabors.ImageSharp.Formats.WebP sbyte clampType = (sbyte)bitReader.ReadValue(1); var vp8PictureHeader = new Vp8PictureHeader() { - Width = width, - Height = height, + Width = (uint)width, + Height = (uint)height, XScale = xScale, YScale = yScale, ColorSpace = colorSpace, ClampType = clampType }; - // Paragraph 9.3: Parse the segment header. - bool hasValue; - var proba = new Vp8Proba(); - Vp8SegmentHeader vp8SegmentHeader = ParseSegmentHeader(bitReader, proba); - - // Paragraph 9.4: Parse the filter specs. - Vp8FilterHeader vp8FilterHeader = ParseFilterHeader(bitReader); - - // TODO: Review Paragraph 9.5: ParsePartitions. - int numPartsMinusOne = (1 << (int)bitReader.ReadValue(2)) - 1; - int lastPart = numPartsMinusOne; - // TODO: check if we have enough data available here, throw exception if not - int partStart = bitReader.Pos + (lastPart * 3); - - // Paragraph 9.6: Dequantization Indices. - int baseQ0 = (int)bitReader.ReadValue(7); - hasValue = bitReader.ReadBool(); - int dqy1Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dqy2Dc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dqy2Ac = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dquvDc = hasValue ? bitReader.ReadSignedValue(4) : 0; - hasValue = bitReader.ReadBool(); - int dquvAc = hasValue ? bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebPConstants.NumMbSegments; ++i) - { - int q; - if (vp8SegmentHeader.UseSegment) - { - q = vp8SegmentHeader.Quantizer[i]; - if (!vp8SegmentHeader.Delta) - { - q += baseQ0; - } - } - else - { - if (i > 0) - { - // dec->dqm_[i] = dec->dqm_[0]; - continue; - } - else - { - q = baseQ0; - } - } - - var m = new Vp8QuantMatrix(); - m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; - m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; - - // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. - // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; - if (m.Y2Mat[1] < 8) - { - m.Y2Mat[1] = 8; - } - - m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; - - // For dithering strength evaluation. - m.UvQuant = q + dquvAc; - } - - // Ignore the value of update_proba - bitReader.ReadBool(); - - // Paragraph 13.4: Parse probabilities. - for (int t = 0; t < WebPConstants.NumTypes; ++t) - { - for (int b = 0; b < WebPConstants.NumBands; ++b) - { - for (int c = 0; c < WebPConstants.NumCtx; ++c) - { - for (int p = 0; p < WebPConstants.NumProbas; ++p) - { - var prob = WebPConstants.CoeffsUpdateProba[t, b, c, p]; - int v = bitReader.GetBit(prob) == 0 ? (int)bitReader.ReadValue(8) : WebPConstants.DefaultCoeffsProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (uint)v; - } - } - } - - for (int b = 0; b < 16 + 1; ++b) - { - // TODO: This needs to be reviewed and fixed. - // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; - } - } - - // TODO: those values needs to be stored somewhere - bool useSkipProba = bitReader.ReadBool(); - if (useSkipProba) - { - var skipP = bitReader.ReadValue(8); - } - return new WebPImageInfo() { Width = width, @@ -486,97 +383,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Features = features, Vp8Profile = (sbyte)version, Vp8FrameHeader = vp8FrameHeader, - Vp8SegmentHeader = vp8SegmentHeader, - Vp8FilterHeader = vp8FilterHeader, Vp8PictureHeader = vp8PictureHeader, Vp8BitReader = bitReader }; } - private static Vp8FilterHeader ParseFilterHeader(Vp8BitReader bitReader) - { - var vp8FilterHeader = new Vp8FilterHeader(); - vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.Level = (int)bitReader.ReadValue(6); - vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3); - vp8FilterHeader.UseLfDelta = bitReader.ReadBool(); - - // TODO: use enum here? - // 0 = 0ff, 1 = simple, 2 = complex - int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; - bool hasValue; - if (vp8FilterHeader.UseLfDelta) - { - // Update lf-delta? - if (bitReader.ReadBool()) - { - for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - - for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) - { - hasValue = bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6); - } - } - } - } - - return vp8FilterHeader; - } - - private static Vp8SegmentHeader ParseSegmentHeader(Vp8BitReader bitReader, Vp8Proba proba) - { - var vp8SegmentHeader = new Vp8SegmentHeader(); - vp8SegmentHeader.UseSegment = bitReader.ReadBool(); - bool hasValue; - if (vp8SegmentHeader.UseSegment) - { - vp8SegmentHeader.UpdateMap = bitReader.ReadBool(); - bool updateData = bitReader.ReadBool(); - if (updateData) - { - vp8SegmentHeader.Delta = bitReader.ReadBool(); - for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0; - vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; - } - - for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) - { - hasValue = bitReader.ReadBool(); - uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0; - vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; - } - - if (vp8SegmentHeader.UpdateMap) - { - for (int s = 0; s < proba.Segments.Length; ++s) - { - hasValue = bitReader.ReadBool(); - proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; - } - } - } - } - else - { - vp8SegmentHeader.UpdateMap = false; - } - - return vp8SegmentHeader; - } - /// /// Reads the header of a lossless webp image. /// @@ -695,10 +506,5 @@ namespace SixLabors.ImageSharp.Formats.WebP throw new ImageFormatException("Invalid WebP data."); } - - private int Clip(int v, int M) - { - return v < 0 ? 0 : v > M ? M : v; - } } } diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 1eeb4d2e0a..dace37aada 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -45,16 +45,6 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public Vp8PictureHeader Vp8PictureHeader { get; set; } - /// - /// Gets or sets the VP8 segment header. - /// - public Vp8SegmentHeader Vp8SegmentHeader { get; set; } - - /// - /// Gets or sets the VP8 filter header. - /// - public Vp8FilterHeader Vp8FilterHeader { 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 86fb099fd6..59528067e8 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -1,11 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; -using System.IO; -using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -36,6 +33,28 @@ namespace SixLabors.ImageSharp.Formats.WebP // TODO residue signal from DCT: 4x4 blocks of DCT transforms, 16Y, 4U, 4V Vp8Profile vp8Profile = this.DecodeProfile(vp8Version); + + // Paragraph 9.3: Parse the segment header. + var proba = new Vp8Proba(); + Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); + + // 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); + + // Paragraph 9.6: Dequantization Indices. + this.ParseDequantizationIndices(vp8SegmentHeader); + + // Ignore the value of update_proba + this.bitReader.ReadBool(); + + // Paragraph 13.4: Parse probabilities. + this.ParseProbabilities(proba); } private Vp8Profile DecodeProfile(int version) @@ -58,11 +77,191 @@ namespace SixLabors.ImageSharp.Formats.WebP } } + private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) + { + var vp8SegmentHeader = new Vp8SegmentHeader + { + UseSegment = this.bitReader.ReadBool() + }; + if (vp8SegmentHeader.UseSegment) + { + vp8SegmentHeader.UpdateMap = this.bitReader.ReadBool(); + bool updateData = this.bitReader.ReadBool(); + if (updateData) + { + vp8SegmentHeader.Delta = this.bitReader.ReadBool(); + bool hasValue; + for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + uint quantizeValue = hasValue ? this.bitReader.ReadValue(7) : 0; + vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue; + } + + for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + uint filterStrengthValue = hasValue ? this.bitReader.ReadValue(6) : 0; + vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue; + } + + if (vp8SegmentHeader.UpdateMap) + { + for (int s = 0; s < proba.Segments.Length; ++s) + { + hasValue = bitReader.ReadBool(); + proba.Segments[s] = hasValue ? bitReader.ReadValue(8) : 255; + } + } + } + } + else + { + vp8SegmentHeader.UpdateMap = false; + } + + return vp8SegmentHeader; + } + + private Vp8FilterHeader ParseFilterHeader() + { + var vp8FilterHeader = new Vp8FilterHeader(); + vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; + vp8FilterHeader.Level = (int)this.bitReader.ReadValue(6); + vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); + vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); + + // TODO: use enum here? + // 0 = 0ff, 1 = simple, 2 = complex + int filterType = (vp8FilterHeader.Level is 0) ? 0 : vp8FilterHeader.LoopFilter is LoopFilter.Simple ? 1 : 2; + if (vp8FilterHeader.UseLfDelta) + { + // Update lf-delta? + if (this.bitReader.ReadBool()) + { + bool hasValue; + for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.RefLfDelta[i] = this.bitReader.ReadSignedValue(6); + } + } + + for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + if (hasValue) + { + vp8FilterHeader.ModeLfDelta[i] = this.bitReader.ReadSignedValue(6); + } + } + } + } + + return vp8FilterHeader; + } + + private void ParseDequantizationIndices(Vp8SegmentHeader vp8SegmentHeader) + { + int baseQ0 = (int)this.bitReader.ReadValue(7); + bool hasValue = this.bitReader.ReadBool(); + int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Ac = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + for (int i = 0; i < WebPConstants.NumMbSegments; ++i) + { + int q; + if (vp8SegmentHeader.UseSegment) + { + q = vp8SegmentHeader.Quantizer[i]; + if (!vp8SegmentHeader.Delta) + { + q += baseQ0; + } + } + else + { + if (i > 0) + { + // dec->dqm_[i] = dec->dqm_[0]; + continue; + } + else + { + q = baseQ0; + } + } + + var m = new Vp8QuantMatrix(); + m.Y1Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebPConstants.AcTable[this.Clip(q + 0, 127)]; + m.Y2Mat[0] = WebPConstants.DcTable[this.Clip(q + dqy2Dc, 127)] * 2; + + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. + m.Y2Mat[1] = (WebPConstants.AcTable[this.Clip(q + dqy2Ac, 127)] * 101581) >> 16; + if (m.Y2Mat[1] < 8) + { + m.Y2Mat[1] = 8; + } + + m.UvMat[0] = WebPConstants.DcTable[this.Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebPConstants.AcTable[this.Clip(q + dquvAc, 127)]; + + // For dithering strength evaluation. + m.UvQuant = q + dquvAc; + } + } + + private void ParseProbabilities(Vp8Proba proba) + { + for (int t = 0; t < WebPConstants.NumTypes; ++t) + { + for (int b = 0; b < WebPConstants.NumBands; ++b) + { + for (int c = 0; c < WebPConstants.NumCtx; ++c) + { + for (int p = 0; p < WebPConstants.NumProbas; ++p) + { + var 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] = (uint)v; + } + } + } + + for (int b = 0; b < 16 + 1; ++b) + { + // TODO: This needs to be reviewed and fixed. + // proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + } + } + + // TODO: those values needs to be stored somewhere + bool useSkipProba = this.bitReader.ReadBool(); + if (useSkipProba) + { + var skipP = this.bitReader.ReadValue(8); + } + } + static bool Is8bOptimizable(Vp8LMetadata hdr) { int i; 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. @@ -70,15 +269,28 @@ namespace SixLabors.ImageSharp.Formats.WebP { List 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 int Clip(int v, int M) + { + return v < 0 ? 0 : v > M ? M : v; + } } struct YUVPixel