diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index d49653a054..ffd9d6cf36 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -11,22 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { private const uint NonTrivialSym = 0xffffffff; - /// - /// Size of histogram used by CollectHistogram. - /// - private const int MaxCoeffThresh = 31; - - private int maxValue; - - private int lastNonZero; - - /// - /// Initializes a new instance of the class. - /// - public Vp8LHistogram() - { - } - /// /// Initializes a new instance of the class. /// @@ -317,114 +300,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return true; } - public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) - { - int j; - var distribution = new int[MaxCoeffThresh + 1]; - for (j = startBlock; j < endBlock; ++j) - { - var output = new short[16]; - - this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output); - - // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) - { - int v = Math.Abs(output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++distribution[clippedValue]; - } - } - - this.SetHistogramData(distribution); - } - - public int GetAlpha() - { - // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer - // values which happen to be mostly noise. This leaves the maximum precision - // for handling the useful small values which contribute most. - int maxValue = this.maxValue; - int lastNonZero = this.lastNonZero; - int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; - return alpha; - } - - public void Merge(Vp8LHistogram other) - { - if (this.maxValue > other.maxValue) - { - other.maxValue = this.maxValue; - } - - if (this.lastNonZero > other.lastNonZero) - { - other.lastNonZero = this.lastNonZero; - } - } - - private void SetHistogramData(int[] distribution) - { - int maxValue = 0; - int lastNonZero = 1; - for (int k = 0; k <= MaxCoeffThresh; ++k) - { - int value = distribution[k]; - if (value > 0) - { - if (value > maxValue) - { - maxValue = value; - } - - lastNonZero = k; - } - } - - this.maxValue = maxValue; - this.lastNonZero = lastNonZero; - } - - private void Vp8FTransform(Span src, Span reference, Span output) - { - int i; - var tmp = new int[16]; - for (i = 0; i < 4; ++i) - { - int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) - int d1 = src[1] - reference[1]; - int d2 = src[2] - reference[2]; - int d3 = src[3] - reference[3]; - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - - // Do not change the span in the last iteration. - if (i < 3) - { - src = src.Slice(WebpConstants.Bps); - reference = reference.Slice(WebpConstants.Bps); - } - } - - for (i = 0; i < 4; ++i) - { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); - } - } - private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) { if (this.IsUsed[0]) @@ -636,8 +511,5 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless output[i] = a[i] + b[i]; } } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) => (v > max) ? max : v; } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 340263f7ef..ac582fd424 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Formats.WebP.Lossy; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -383,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.MakeLuma16Preds(); for (mode = 0; mode < maxMode; ++mode) { - var histo = new Vp8LHistogram(); + var histo = new Vp8Histogram(); histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) @@ -402,21 +402,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy byte[] modes = new byte[16]; int maxMode = MaxIntra4Mode; int i4Alpha; - var totalHisto = new Vp8LHistogram(); + var totalHisto = new Vp8Histogram(); int curHisto = 0; this.StartI4(); do { int mode; int bestModeAlpha = DefaultAlpha; - var histos = new Vp8LHistogram[2]; + var histos = new Vp8Histogram[2]; Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); this.MakeIntra4Preds(); for (mode = 0; mode < maxMode; ++mode) { int alpha; - histos[curHisto] = new Vp8LHistogram(); + histos[curHisto] = new Vp8Histogram(); histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); alpha = histos[curHisto].GetAlpha(); @@ -456,7 +456,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.MakeChroma8Preds(); for (mode = 0; mode < maxMode; ++mode) { - var histo = new Vp8LHistogram(); + var histo = new Vp8Histogram(); histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs new file mode 100644 index 0000000000..e569ab0d9c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Histogram.cs @@ -0,0 +1,132 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Webp; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal class Vp8Histogram + { + /// + /// Size of histogram used by CollectHistogram. + /// + private const int MaxCoeffThresh = 31; + + private int maxValue; + + private int lastNonZero; + + public int GetAlpha() + { + // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer + // values which happen to be mostly noise. This leaves the maximum precision + // for handling the useful small values which contribute most. + int maxValue = this.maxValue; + int lastNonZero = this.lastNonZero; + int alpha = (maxValue > 1) ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; + return alpha; + } + + public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) + { + int j; + int[] distribution = new int[MaxCoeffThresh + 1]; + for (j = startBlock; j < endBlock; ++j) + { + short[] output = new short[16]; + + this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), output); + + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + int v = Math.Abs(output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++distribution[clippedValue]; + } + } + + this.SetHistogramData(distribution); + } + + public void Merge(Vp8Histogram other) + { + if (this.maxValue > other.maxValue) + { + other.maxValue = this.maxValue; + } + + if (this.lastNonZero > other.lastNonZero) + { + other.lastNonZero = this.lastNonZero; + } + } + + private void SetHistogramData(int[] distribution) + { + int maxValue = 0; + int lastNonZero = 1; + for (int k = 0; k <= MaxCoeffThresh; ++k) + { + int value = distribution[k]; + if (value > 0) + { + if (value > maxValue) + { + maxValue = value; + } + + lastNonZero = k; + } + } + + this.maxValue = maxValue; + this.lastNonZero = lastNonZero; + } + + private void Vp8FTransform(Span src, Span reference, Span output) + { + int i; + int[] tmp = new int[16]; + for (i = 0; i < 4; ++i) + { + int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) + int d1 = src[1] - reference[1]; + int d2 = src[2] - reference[2]; + int d3 = src[3] - reference[3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + + // Do not change the span in the last iteration. + if (i < 3) + { + src = src.Slice(WebpConstants.Bps); + reference = reference.Slice(WebpConstants.Bps); + } + } + + for (i = 0; i < 4; ++i) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + ((a3 != 0) ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipMax(int v, int max) => (v > max) ? max : v; + } +}