private bool Decimate(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8RdLevel rdOpt) { rd.InitScore(); // We can perform predictions for Luma16x16 and Chroma8x8 already. // Luma4x4 predictions needs to be done as-we-go. it.MakeLuma16Preds(); it.MakeChroma8Preds(); if (rdOpt > Vp8RdLevel.RdOptNone) { QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); if (this.method >= WebpEncodingMethod.Level2) { QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); } else { // At this point we have heuristically decided intra16 / intra4. // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= WebpEncodingMethod.Level2, this.method >= WebpEncodingMethod.Level1, this.MbHeaderLimit); } bool isSkipped = rd.Nz == 0; it.SetSkip(isSkipped); return(isSkipped); }
private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) { Span <byte> y = this.Y.GetSpan(); Span <byte> u = this.U.GetSpan(); Span <byte> v = this.V.GetSpan(); var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); long size = 0; long sizeP0 = 0; long distortion = 0; long pixelCount = nbMbs * 384; it.Init(); this.SetLoopParams(stats.Q); var info = new Vp8ModeScore(); do { info.Clear(); it.Import(y, u, v, yStride, uvStride, width, height, false); if (this.Decimate(it, ref info, rdOpt)) { // Just record the number of skips and act like skipProba is not used. ++this.Proba.NbSkip; } this.RecordResiduals(it, info); size += info.R + info.H; sizeP0 += info.H; distortion += info.D; it.SaveBoundary(); }while (it.Next() && --nbMbs > 0); sizeP0 += this.SegmentHeader.Size; if (stats.DoSizeSearch) { size += this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); size += this.Proba.FinalizeTokenProbas(); size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; stats.Value = size; } else { stats.Value = GetPsnr(distortion, pixelCount); } return(sizeP0); }
/// <summary> /// Only collect statistics(number of skips, token usage, ...). /// This is used for deciding optimal probabilities. It also modifies the /// quantizer value if some target (size, PSNR) was specified. /// </summary> private void StatLoop(int width, int height, int yStride, int uvStride) { int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. bool doSearch = targetSize > 0 || targetPsnr > 0; bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; int numPassLeft = this.entropyPasses; Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); this.Proba.ResetTokenStats(); // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) { if (this.method == WebpEncodingMethod.Level3) { // We need more stats for method 3 to be reliable. nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; } else { nbMbs = nbMbs > 200 ? nbMbs >> 2 : 50; } } while (numPassLeft-- > 0) { bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); long sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); if (sizeP0 == 0) { return; } if (this.maxI4HeaderBits > 0 && sizeP0 > (long)Partition0SizeLimit) { ++numPassLeft; this.maxI4HeaderBits >>= 1; // strengthen header bit limitation... continue; // ...and start over } if (isLastPass) { break; } // If no target size: just do several pass without changing 'q' if (doSearch) { stats.ComputeNextQ(); if (MathF.Abs(stats.Dq) <= DqLimit) { break; } } } if (!doSearch || !stats.DoSizeSearch) { // Need to finalize probas now, since it wasn't done during the search. this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); this.Proba.FinalizeTokenProbas(); } // Finalize costs. this.Proba.CalculateLevelCosts(); }
/// <summary> /// Initializes a new instance of the <see cref="Vp8Encoder" /> class. /// </summary> /// <param name="memoryAllocator">The memory allocator.</param> /// <param name="configuration">The global configuration.</param> /// <param name="width">The width of the input image.</param> /// <param name="height">The height of the input image.</param> /// <param name="quality">The encoding quality.</param> /// <param name="method">Quality/speed trade-off (0=fast, 6=slower-better).</param> /// <param name="entropyPasses">Number of entropy-analysis passes (in [1..10]).</param> /// <param name="filterStrength">The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).</param> /// <param name="spatialNoiseShaping">The spatial noise shaping. 0=off, 100=maximum.</param> public Vp8Encoder( MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, WebpEncodingMethod method, int entropyPasses, int filterStrength, int spatialNoiseShaping) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.Width = width; this.Height = height; this.quality = Numerics.Clamp(quality, 0, 100); this.method = method; this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll : method >= WebpEncodingMethod.Level5 ? Vp8RdLevel.RdOptTrellis : method >= WebpEncodingMethod.Level3 ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int pixelCount = width * height; this.Mbw = (width + 15) >> 4; this.Mbh = (height + 15) >> 4; int uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); this.Y = this.memoryAllocator.Allocate <byte>(pixelCount); this.U = this.memoryAllocator.Allocate <byte>(uvSize); this.V = this.memoryAllocator.Allocate <byte>(uvSize); this.YTop = new byte[this.Mbw * 16]; this.UvTop = new byte[this.Mbw * 16 * 2]; this.Nz = new uint[this.Mbw + 1]; this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.Mbw * this.Mbh); this.TopDerr = new sbyte[this.Mbw * 4]; // TODO: make partition_limit configurable? int limit = 100; // original code: limit = 100 - config->partition_limit; this.maxI4HeaderBits = 256 * 16 * 16 * limit * limit / (100 * 100); // ... modulated with a quadratic curve. this.MbInfo = new Vp8MacroBlockInfo[this.Mbw * this.Mbh]; for (int i = 0; i < this.MbInfo.Length; i++) { this.MbInfo[i] = new Vp8MacroBlockInfo(); } this.SegmentInfos = new Vp8SegmentInfo[4]; for (int i = 0; i < 4; i++) { this.SegmentInfos[i] = new Vp8SegmentInfo(); } this.FilterHeader = new Vp8FilterHeader(); int predSize = (((4 * this.Mbw) + 1) * ((4 * this.Mbh) + 1)) + this.PredsWidth + 1; this.PredsWidth = (4 * this.Mbw) + 1; this.Proba = new Vp8EncProba(); this.Preds = new byte[predSize + this.PredsWidth + this.Mbw]; // Initialize with default values, which the reference c implementation uses, // to be able to compare to the original and spot differences. this.Preds.AsSpan().Fill(205); this.Nz.AsSpan().Fill(3452816845); this.ResetBoundaryPredictions(); }