mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 1580 additions and 32 deletions
@ -0,0 +1,908 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the GNU Affero General Public License, Version 3.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.WebP.Lossless |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image transform methods for the lossless webp encoder.
|
||||
|
/// </summary>
|
||||
|
internal static class PredictorEncoder |
||||
|
{ |
||||
|
private const int GreenRedToBlueNumAxis = 8; |
||||
|
|
||||
|
private const int GreenRedToBlueMaxIters = 7; |
||||
|
|
||||
|
private const float MaxDiffCost = 1e30f; |
||||
|
|
||||
|
private const uint MaskAlpha = 0xff000000; |
||||
|
|
||||
|
private const float SpatialPredictorBias = 15.0f; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Finds the best predictor for each tile, and converts the image to residuals
|
||||
|
/// with respect to predictions. If nearLosslessQuality < 100, applies
|
||||
|
/// near lossless processing, shaving off more bits of residuals for lower qualities.
|
||||
|
/// </summary>
|
||||
|
public static void ResidualImage(int width, int height, int bits, Span<uint> argb, Span<uint> argbScratch, Span<uint> image, int nearLosslessQuality, bool exact, bool usedSubtractGreen) |
||||
|
{ |
||||
|
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); |
||||
|
int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); |
||||
|
int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); |
||||
|
int[][] histo = new int[4][]; |
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
histo[i] = new int[256]; |
||||
|
} |
||||
|
|
||||
|
for (int tileY = 0; tileY < tilesPerCol; tileY++) |
||||
|
{ |
||||
|
for (int tileX = 0; tileX < tilesPerRow; tileX++) |
||||
|
{ |
||||
|
int pred = GetBestPredictorForTile(width, height, tileX, tileY, bits, histo, argbScratch, argb, maxQuantization, exact, usedSubtractGreen, image); |
||||
|
image[(tileY * tilesPerRow) + tileX] = (uint)(WebPConstants.ArgbBlack | (pred << 8)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
CopyImageWithPrediction(width, height, bits, image, argbScratch, argb, maxQuantization, exact, usedSubtractGreen); |
||||
|
} |
||||
|
|
||||
|
public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> argb, Span<uint> image) |
||||
|
{ |
||||
|
int maxTileSize = 1 << bits; |
||||
|
int tileXSize = LosslessUtils.SubSampleSize(width, bits); |
||||
|
int tileYSize = LosslessUtils.SubSampleSize(height, bits); |
||||
|
int[] accumulatedRedHisto = new int[256]; |
||||
|
int[] accumulatedBlueHisto = new int[256]; |
||||
|
var prevX = default(Vp8LMultipliers); |
||||
|
var prevY = default(Vp8LMultipliers); |
||||
|
for (int tileY = 0; tileY < tileYSize; tileY++) |
||||
|
{ |
||||
|
for (int tileX = 0; tileX < tileXSize; tileX++) |
||||
|
{ |
||||
|
int tileXOffset = tileX * maxTileSize; |
||||
|
int tileYOffset = tileY * maxTileSize; |
||||
|
int allXMax = GetMin(tileXOffset + maxTileSize, width); |
||||
|
int allYMax = GetMin(tileYOffset + maxTileSize, height); |
||||
|
int offset = (tileY * tileXSize) + tileX; |
||||
|
if (tileY != 0) |
||||
|
{ |
||||
|
LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); |
||||
|
} |
||||
|
|
||||
|
prevX = GetBestColorTransformForTile(tileX, tileY, bits, |
||||
|
prevX, prevY, |
||||
|
quality, width, height, |
||||
|
accumulatedRedHisto, |
||||
|
accumulatedBlueHisto, |
||||
|
argb); |
||||
|
|
||||
|
image[offset] = MultipliersToColorCode(prevX); |
||||
|
CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, argb); |
||||
|
|
||||
|
// Gather accumulated histogram data.
|
||||
|
for (int y = tileYOffset; y < allYMax; y++) |
||||
|
{ |
||||
|
int ix = (y * width) + tileXOffset; |
||||
|
int ixEnd = ix + allXMax - tileXOffset; |
||||
|
|
||||
|
for (; ix < ixEnd; ix++) |
||||
|
{ |
||||
|
uint pix = argb[ix]; |
||||
|
if (ix >= 2 && pix == argb[ix - 2] && pix == argb[ix - 1]) |
||||
|
{ |
||||
|
continue; // Repeated pixels are handled by backward references.
|
||||
|
} |
||||
|
|
||||
|
if (ix >= width + 2 && argb[ix - 2] == argb[ix - width - 2] && argb[ix - 1] == argb[ix - width - 1] && pix == argb[ix - width]) |
||||
|
{ |
||||
|
continue; // Repeated pixels are handled by backward references.
|
||||
|
} |
||||
|
|
||||
|
accumulatedRedHisto[(pix >> 16) & 0xff]++; |
||||
|
accumulatedBlueHisto[(pix >> 0) & 0xff]++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns best predictor and updates the accumulated histogram.
|
||||
|
/// If max_quantization > 1, assumes that near lossless processing will be
|
||||
|
/// applied, quantizing residuals to multiples of quantization levels up to
|
||||
|
/// maxQuantization (the actual quantization level depends on smoothness near
|
||||
|
/// the given pixel).
|
||||
|
/// </summary>
|
||||
|
/// <returns>Best predictor.</returns>
|
||||
|
private static int GetBestPredictorForTile(int width, int height, int tileX, int tileY, |
||||
|
int bits, int[][] accumulated, Span<uint> argbScratch, Span<uint> argb, |
||||
|
int maxQuantization, bool exact, bool usedSubtractGreen, Span<uint> modes) |
||||
|
{ |
||||
|
const int numPredModes = 14; |
||||
|
int startX = tileX << bits; |
||||
|
int startY = tileY << bits; |
||||
|
int tileSize = 1 << bits; |
||||
|
int maxY = GetMin(tileSize, height - startY); |
||||
|
int maxX = GetMin(tileSize, width - startX); |
||||
|
|
||||
|
// Whether there exist columns just outside the tile.
|
||||
|
int haveLeft = (startX > 0) ? 1 : 0; |
||||
|
|
||||
|
// Position and size of the strip covering the tile and adjacent columns if they exist.
|
||||
|
int contextStartX = startX - haveLeft; |
||||
|
int contextWidth = maxX + haveLeft + (maxX < width ? 1 : 0) - startX; |
||||
|
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); |
||||
|
|
||||
|
// Prediction modes of the left and above neighbor tiles.
|
||||
|
int leftMode = (int)((tileX > 0) ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); |
||||
|
int aboveMode = (int)((tileY > 0) ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); |
||||
|
|
||||
|
// The width of upper_row and current_row is one pixel larger than image width
|
||||
|
// to allow the top right pixel to point to the leftmost pixel of the next row
|
||||
|
// when at the right edge.
|
||||
|
Span<uint> upperRow = argbScratch; |
||||
|
Span<uint> currentRow = upperRow.Slice(width + 1); |
||||
|
Span<byte> maxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow.Slice(width + 1)); |
||||
|
float bestDiff = MaxDiffCost; |
||||
|
int bestMode = 0; |
||||
|
uint[] residuals = new uint[1 << WebPConstants.MaxTransformBits]; |
||||
|
int[][] histoArgb = new int[4][]; |
||||
|
int[][] bestHisto = new int[4][]; |
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
histoArgb[i] = new int[256]; |
||||
|
bestHisto[i] = new int[256]; |
||||
|
} |
||||
|
|
||||
|
for (int mode = 0; mode < numPredModes; mode++) |
||||
|
{ |
||||
|
float curDiff; |
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
histoArgb[i].AsSpan().Fill(0); |
||||
|
} |
||||
|
|
||||
|
if (startY > 0) |
||||
|
{ |
||||
|
// Read the row above the tile which will become the first upper_row.
|
||||
|
// Include a pixel to the left if it exists; include a pixel to the right
|
||||
|
// in all cases (wrapping to the leftmost pixel of the next row if it does
|
||||
|
// not exist).
|
||||
|
Span<uint> src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1); |
||||
|
Span<uint> dst = currentRow.Slice(contextStartX); |
||||
|
src.CopyTo(dst); |
||||
|
} |
||||
|
|
||||
|
for (int relativeY = 0; relativeY < maxY; relativeY++) |
||||
|
{ |
||||
|
int y = startY + relativeY; |
||||
|
Span<uint> tmp = upperRow; |
||||
|
upperRow = currentRow; |
||||
|
currentRow = tmp; |
||||
|
|
||||
|
// Read current_row. Include a pixel to the left if it exists; include a
|
||||
|
// pixel to the right in all cases except at the bottom right corner of
|
||||
|
// the image (wrapping to the leftmost pixel of the next row if it does
|
||||
|
// not exist in the current row).
|
||||
|
Span<uint> src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0)); |
||||
|
Span<uint> dst = currentRow.Slice(contextStartX); |
||||
|
src.CopyTo(dst); |
||||
|
if (maxQuantization > 1 && y >= 1 && y + 1 < height) |
||||
|
{ |
||||
|
MaxDiffsForRow(contextWidth, width, argb.Slice((y * width) + contextStartX), maxDiffs.Slice(contextStartX), usedSubtractGreen); |
||||
|
} |
||||
|
|
||||
|
GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, residuals); |
||||
|
for (int relativeX = 0; relativeX < maxX; relativeX++) |
||||
|
{ |
||||
|
UpdateHisto(histoArgb, residuals[relativeX]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); |
||||
|
|
||||
|
// Favor keeping the areas locally similar.
|
||||
|
if (mode == leftMode) |
||||
|
{ |
||||
|
curDiff -= SpatialPredictorBias; |
||||
|
} |
||||
|
|
||||
|
if (mode == aboveMode) |
||||
|
{ |
||||
|
curDiff -= SpatialPredictorBias; |
||||
|
} |
||||
|
|
||||
|
if (curDiff < bestDiff) |
||||
|
{ |
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
histoArgb[i].AsSpan().CopyTo(bestHisto[i]); |
||||
|
} |
||||
|
|
||||
|
bestDiff = curDiff; |
||||
|
bestMode = mode; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
for (int j = 0; j < 256; j++) |
||||
|
{ |
||||
|
accumulated[i][j] += bestHisto[i][j]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return bestMode; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores the difference between the pixel and its prediction in "output".
|
||||
|
/// In case of a lossy encoding, updates the source image to avoid propagating
|
||||
|
/// the deviation further to pixels which depend on the current pixel for their
|
||||
|
/// predictions.
|
||||
|
/// </summary>
|
||||
|
private static void GetResidual(int width, int height, Span<uint> upperRow, Span<uint> currentRow, Span<byte> maxDiffs, int mode, int xStart, int xEnd, int y, int maxQuantization, bool exact, bool usedSubtractGreen, Span<uint> output) |
||||
|
{ |
||||
|
if (exact) |
||||
|
{ |
||||
|
PredictBatch(mode, xStart, y, xEnd - xStart, currentRow, upperRow, output); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int x = xStart; x < xEnd; x++) |
||||
|
{ |
||||
|
uint predict = 0; |
||||
|
uint residual; |
||||
|
if (y == 0) |
||||
|
{ |
||||
|
predict = (x == 0) ? WebPConstants.ArgbBlack : currentRow[x - 1]; // Left.
|
||||
|
} |
||||
|
else if (x == 0) |
||||
|
{ |
||||
|
predict = upperRow[x]; // Top.
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
switch (mode) |
||||
|
{ |
||||
|
case 0: |
||||
|
predict = WebPConstants.ArgbBlack; |
||||
|
break; |
||||
|
case 1: |
||||
|
predict = currentRow[x - 1]; |
||||
|
break; |
||||
|
case 2: |
||||
|
predict = LosslessUtils.Predictor2(upperRow, x); |
||||
|
break; |
||||
|
case 3: |
||||
|
predict = LosslessUtils.Predictor3(upperRow, x); |
||||
|
break; |
||||
|
case 4: |
||||
|
predict = LosslessUtils.Predictor4(upperRow, x); |
||||
|
break; |
||||
|
case 5: |
||||
|
predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 6: |
||||
|
predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 7: |
||||
|
predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 8: |
||||
|
predict = LosslessUtils.Predictor8(upperRow, x); |
||||
|
break; |
||||
|
case 9: |
||||
|
predict = LosslessUtils.Predictor9(upperRow, x); |
||||
|
break; |
||||
|
case 10: |
||||
|
predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 11: |
||||
|
predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 12: |
||||
|
predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
case 13: |
||||
|
predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow, x); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) |
||||
|
{ |
||||
|
residual = LosslessUtils.SubPixels(currentRow[x], predict); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); |
||||
|
|
||||
|
// Update the source image.
|
||||
|
currentRow[x] = LosslessUtils.AddPixels(predict, residual); |
||||
|
|
||||
|
// x is never 0 here so we do not need to update upper_row like below.
|
||||
|
} |
||||
|
|
||||
|
if ((currentRow[x] & MaskAlpha) == 0) |
||||
|
{ |
||||
|
// If alpha is 0, cleanup RGB. We can choose the RGB values of the
|
||||
|
// residual for best compression. The prediction of alpha itself can be
|
||||
|
// non-zero and must be kept though. We choose RGB of the residual to be
|
||||
|
// 0.
|
||||
|
residual &= MaskAlpha; |
||||
|
|
||||
|
// Update the source image.
|
||||
|
currentRow[x] = predict & ~MaskAlpha; |
||||
|
|
||||
|
// The prediction for the rightmost pixel in a row uses the leftmost
|
||||
|
// pixel
|
||||
|
// in that row as its top-right context pixel. Hence if we change the
|
||||
|
// leftmost pixel of current_row, the corresponding change must be
|
||||
|
// applied
|
||||
|
// to upper_row as well where top-right context is being read from.
|
||||
|
if (x == 0 && y != 0) |
||||
|
{ |
||||
|
upperRow[width] = currentRow[0]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
output[x - xStart] = residual; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Quantize every component of the difference between the actual pixel value and
|
||||
|
/// its prediction to a multiple of a quantization (a power of 2, not larger than
|
||||
|
/// maxQuantization which is a power of 2, smaller than maxDiff). Take care if
|
||||
|
/// value and predict have undergone subtract green, which means that red and
|
||||
|
/// blue are represented as offsets from green.
|
||||
|
/// </summary>
|
||||
|
private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) |
||||
|
{ |
||||
|
int quantization; |
||||
|
byte newGreen = 0; |
||||
|
byte greenDiff = 0; |
||||
|
byte a, r, g, b; |
||||
|
if (maxDiff <= 2) |
||||
|
{ |
||||
|
return LosslessUtils.SubPixels(value, predict); |
||||
|
} |
||||
|
|
||||
|
quantization = maxQuantization; |
||||
|
while (quantization >= maxDiff) |
||||
|
{ |
||||
|
quantization >>= 1; |
||||
|
} |
||||
|
|
||||
|
if ((value >> 24) == 0 || (value >> 24) == 0xff) |
||||
|
{ |
||||
|
// Preserve transparency of fully transparent or fully opaque pixels.
|
||||
|
a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); |
||||
|
} |
||||
|
|
||||
|
g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); |
||||
|
|
||||
|
if (usedSubtractGreen) |
||||
|
{ |
||||
|
// The green offset will be added to red and blue components during decoding
|
||||
|
// to obtain the actual red and blue values.
|
||||
|
newGreen = (byte)(((predict >> 8) + g) & 0xff); |
||||
|
|
||||
|
// The amount by which green has been adjusted during quantization. It is
|
||||
|
// subtracted from red and blue for compensation, to avoid accumulating two
|
||||
|
// quantization errors in them.
|
||||
|
greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); |
||||
|
} |
||||
|
|
||||
|
r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); |
||||
|
b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); |
||||
|
|
||||
|
return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Quantize the difference between the actual component value and its prediction
|
||||
|
/// to a multiple of quantization, working modulo 256, taking care not to cross
|
||||
|
/// a boundary (inclusive upper limit).
|
||||
|
/// </summary>
|
||||
|
private static byte NearLosslessComponent(byte value, byte predict, byte boundary, int quantization) |
||||
|
{ |
||||
|
int residual = (value - predict) & 0xff; |
||||
|
int boundaryResidual = (boundary - predict) & 0xff; |
||||
|
int lower = residual & ~(quantization - 1); |
||||
|
int upper = lower + quantization; |
||||
|
|
||||
|
// Resolve ties towards a value closer to the prediction (i.e. towards lower
|
||||
|
// if value comes after prediction and towards upper otherwise).
|
||||
|
int bias = ((boundary - value) & 0xff) < boundaryResidual ? 1 : 0; |
||||
|
|
||||
|
if (residual - lower < upper - residual + bias) |
||||
|
{ |
||||
|
// lower is closer to residual than upper.
|
||||
|
if (residual > boundaryResidual && lower <= boundaryResidual) |
||||
|
{ |
||||
|
// Halve quantization step to avoid crossing boundary. This midpoint is
|
||||
|
// on the same side of boundary as residual because midpoint >= residual
|
||||
|
// (since lower is closer than upper) and residual is above the boundary.
|
||||
|
return (byte)(lower + (quantization >> 1)); |
||||
|
} |
||||
|
|
||||
|
return (byte)lower; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// upper is closer to residual than lower.
|
||||
|
if (residual <= boundaryResidual && upper > boundaryResidual) |
||||
|
{ |
||||
|
// Halve quantization step to avoid crossing boundary. This midpoint is
|
||||
|
// on the same side of boundary as residual because midpoint <= residual
|
||||
|
// (since upper is closer than lower) and residual is below the boundary.
|
||||
|
return (byte)(lower + (quantization >> 1)); |
||||
|
} |
||||
|
|
||||
|
return (byte)(upper & 0xff); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts pixels of the image to residuals with respect to predictions.
|
||||
|
/// If max_quantization > 1, applies near lossless processing, quantizing
|
||||
|
/// residuals to multiples of quantization levels up to max_quantization
|
||||
|
/// (the actual quantization level depends on smoothness near the given pixel).
|
||||
|
/// </summary>
|
||||
|
private static void CopyImageWithPrediction(int width, int height, int bits, Span<uint> modes, Span<uint> argbScratch, Span<uint> argb, int maxQuantization, bool exact, bool usedSubtractGreen) |
||||
|
{ |
||||
|
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); |
||||
|
|
||||
|
// The width of upper_row and current_row is one pixel larger than image width
|
||||
|
// to allow the top right pixel to point to the leftmost pixel of the next row
|
||||
|
// when at the right edge.
|
||||
|
Span<uint> upperRow = argbScratch; |
||||
|
Span<uint> currentRow = upperRow.Slice(width + 1); |
||||
|
Span<byte> currentMaxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow.Slice(width + 1)); |
||||
|
Span<byte> lowerMaxDiffs = currentMaxDiffs.Slice(width); |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<uint> tmp32 = upperRow; |
||||
|
upperRow = currentRow; |
||||
|
currentRow = tmp32; |
||||
|
argb.Slice(y * width, width + y + (1 < height ? 1 : 0)).CopyTo(currentRow); |
||||
|
if (maxQuantization > 1) |
||||
|
{ |
||||
|
// Compute max_diffs for the lower row now, because that needs the
|
||||
|
// contents of argb for the current row, which we will overwrite with
|
||||
|
// residuals before proceeding with the next row.
|
||||
|
Span<byte> tmp8 = currentMaxDiffs; |
||||
|
currentMaxDiffs = lowerMaxDiffs; |
||||
|
lowerMaxDiffs = tmp8; |
||||
|
if (y + 2 < height) |
||||
|
{ |
||||
|
MaxDiffsForRow(width, width, argb.Slice((y + 1) * width), lowerMaxDiffs, usedSubtractGreen); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int x = 0; x < width;) |
||||
|
{ |
||||
|
int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); |
||||
|
int xEnd = x + (1 << bits); |
||||
|
if (xEnd > width) |
||||
|
{ |
||||
|
xEnd = width; |
||||
|
} |
||||
|
|
||||
|
GetResidual(width, height, upperRow, currentRow, currentMaxDiffs, |
||||
|
mode, x, xEnd, y, maxQuantization, exact, |
||||
|
usedSubtractGreen, argb.Slice((y * width) + x)); |
||||
|
x = xEnd; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictBatch(int mode, int xStart, int y, int numPixels, Span<uint> current, Span<uint> upper, Span<uint> output) |
||||
|
{ |
||||
|
if (xStart == 0) |
||||
|
{ |
||||
|
if (y == 0) |
||||
|
{ |
||||
|
// ARGB_BLACK.
|
||||
|
LosslessUtils.PredictorSub0(current, 1, output); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Top one.
|
||||
|
LosslessUtils.PredictorSub2(current, 0, upper, 1, output); |
||||
|
} |
||||
|
|
||||
|
xStart++; |
||||
|
output = output.Slice(1); |
||||
|
numPixels--; |
||||
|
} |
||||
|
|
||||
|
if (y == 0) |
||||
|
{ |
||||
|
// Left one.
|
||||
|
LosslessUtils.PredictorSub1(current, xStart, numPixels, output); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
switch (mode) |
||||
|
{ |
||||
|
case 0: |
||||
|
LosslessUtils.PredictorSub0(current, numPixels, output); |
||||
|
break; |
||||
|
case 1: |
||||
|
LosslessUtils.PredictorSub1(current, xStart, numPixels, output); |
||||
|
break; |
||||
|
case 2: |
||||
|
LosslessUtils.PredictorSub2(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 3: |
||||
|
LosslessUtils.PredictorSub3(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 4: |
||||
|
LosslessUtils.PredictorSub4(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 5: |
||||
|
LosslessUtils.PredictorSub5(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 6: |
||||
|
LosslessUtils.PredictorSub6(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 7: |
||||
|
LosslessUtils.PredictorSub7(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 8: |
||||
|
LosslessUtils.PredictorSub8(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 9: |
||||
|
LosslessUtils.PredictorSub9(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 10: |
||||
|
LosslessUtils.PredictorSub10(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 11: |
||||
|
LosslessUtils.PredictorSub11(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 12: |
||||
|
LosslessUtils.PredictorSub12(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
case 13: |
||||
|
LosslessUtils.PredictorSub13(current, xStart, upper.Slice(xStart), numPixels, output); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void MaxDiffsForRow(int width, int stride, Span<uint> argb, Span<byte> maxDiffs, bool usedSubtractGreen) |
||||
|
{ |
||||
|
if (width <= 2) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint current = argb[0]; |
||||
|
uint right = argb[1]; |
||||
|
if (usedSubtractGreen) |
||||
|
{ |
||||
|
current = AddGreenToBlueAndRed(current); |
||||
|
right = AddGreenToBlueAndRed(right); |
||||
|
} |
||||
|
|
||||
|
for (int x = 1; x < width - 1; x++) |
||||
|
{ |
||||
|
uint up = argb[-stride + x]; // TODO: -stride!
|
||||
|
uint down = argb[stride + x]; |
||||
|
uint left = current; |
||||
|
current = right; |
||||
|
right = argb[x + 1]; |
||||
|
if (usedSubtractGreen) |
||||
|
{ |
||||
|
up = AddGreenToBlueAndRed(up); |
||||
|
down = AddGreenToBlueAndRed(down); |
||||
|
right = AddGreenToBlueAndRed(right); |
||||
|
} |
||||
|
|
||||
|
maxDiffs[x] = (byte)MaxDiffAroundPixel(current, up, down, left, right); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int MaxDiffBetweenPixels(uint p1, uint p2) |
||||
|
{ |
||||
|
int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); |
||||
|
int diffR = Math.Abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); |
||||
|
int diffG = Math.Abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); |
||||
|
int diffB = Math.Abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); |
||||
|
return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); |
||||
|
} |
||||
|
|
||||
|
private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) |
||||
|
{ |
||||
|
int diffUp = MaxDiffBetweenPixels(current, up); |
||||
|
int diffDown = MaxDiffBetweenPixels(current, down); |
||||
|
int diffLeft = MaxDiffBetweenPixels(current, left); |
||||
|
int diffRight = MaxDiffBetweenPixels(current, right); |
||||
|
return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); |
||||
|
} |
||||
|
|
||||
|
private static void UpdateHisto(int[][] histoArgb, uint argb) |
||||
|
{ |
||||
|
++histoArgb[0][argb >> 24]; |
||||
|
++histoArgb[1][(argb >> 16) & 0xff]; |
||||
|
++histoArgb[2][(argb >> 8) & 0xff]; |
||||
|
++histoArgb[3][argb & 0xff]; |
||||
|
} |
||||
|
|
||||
|
private static uint AddGreenToBlueAndRed(uint argb) |
||||
|
{ |
||||
|
uint green = (argb >> 8) & 0xff; |
||||
|
uint redBlue = argb & 0x00ff00ffu; |
||||
|
redBlue += (green << 16) | green; |
||||
|
redBlue &= 0x00ff00ffu; |
||||
|
return (argb & 0xff00ff00u) | redBlue; |
||||
|
} |
||||
|
|
||||
|
private static void CopyTileWithColorTransform(int xSize, int ySize, int tileX, int tileY, int maxTileSize, Vp8LMultipliers colorTransform, Span<uint> argb) |
||||
|
{ |
||||
|
int xScan = GetMin(maxTileSize, xSize - tileX); |
||||
|
int yScan = GetMin(maxTileSize, ySize - tileY); |
||||
|
argb = argb.Slice((tileY * xSize) + tileX); |
||||
|
while (yScan-- > 0) |
||||
|
{ |
||||
|
LosslessUtils.TransformColor(colorTransform, argb, xScan); |
||||
|
argb = argb.Slice(xSize); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Vp8LMultipliers GetBestColorTransformForTile(int tile_x, int tile_y, int bits, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int xSize, int ySize, int[] accumulatedRedHisto, int[] accumulatedBlueHisto, Span<uint> argb) |
||||
|
{ |
||||
|
int maxTileSize = 1 << bits; |
||||
|
int tileYOffset = tile_y * maxTileSize; |
||||
|
int tileXOffset = tile_x * maxTileSize; |
||||
|
int allXMax = GetMin(tileXOffset + maxTileSize, xSize); |
||||
|
int allYMax = GetMin(tileYOffset + maxTileSize, ySize); |
||||
|
int tileWidth = allXMax - tileXOffset; |
||||
|
int tileHeight = allYMax - tileYOffset; |
||||
|
Span<uint> tileArgb = argb.Slice((tileYOffset * xSize) + tileXOffset); |
||||
|
|
||||
|
var bestTx = default(Vp8LMultipliers); |
||||
|
|
||||
|
GetBestGreenToRed(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); |
||||
|
|
||||
|
GetBestGreenRedToBlue(tileArgb, xSize, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); |
||||
|
|
||||
|
return bestTx; |
||||
|
} |
||||
|
|
||||
|
private static void GetBestGreenToRed(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedRedHisto, ref Vp8LMultipliers bestTx) |
||||
|
{ |
||||
|
int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6]
|
||||
|
int greenToRedBest = 0; |
||||
|
float bestDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); |
||||
|
for (int iter = 0; iter < maxIters; iter++) |
||||
|
{ |
||||
|
// ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to
|
||||
|
// one in color computation. Having initial delta here as 1 is sufficient
|
||||
|
// to explore the range of (-2, 2).
|
||||
|
int delta = 32 >> iter; |
||||
|
|
||||
|
// Try a negative and a positive delta from the best known value.
|
||||
|
for (int offset = -delta; offset <= delta; offset += 2 * delta) |
||||
|
{ |
||||
|
int greenToRedCur = offset + greenToRedBest; |
||||
|
float curDiff = GetPredictionCostCrossColorRed(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); |
||||
|
if (curDiff < bestDiff) |
||||
|
{ |
||||
|
bestDiff = curDiff; |
||||
|
greenToRedBest = greenToRedCur; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); |
||||
|
} |
||||
|
|
||||
|
private static void GetBestGreenRedToBlue(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) |
||||
|
{ |
||||
|
int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; |
||||
|
int greenToBlueBest = 0; |
||||
|
int redToBlueBest = 0; |
||||
|
sbyte[][] offset = { new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 } }; |
||||
|
sbyte[] deltaLut = { 16, 16, 8, 4, 2, 2, 2 }; |
||||
|
|
||||
|
// Initial value at origin:
|
||||
|
float bestDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); |
||||
|
for (int iter = 0; iter < iters; iter++) |
||||
|
{ |
||||
|
int delta = deltaLut[iter]; |
||||
|
for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) |
||||
|
{ |
||||
|
int greenToBlueCur = (offset[axis][0] * delta) + greenToBlueBest; |
||||
|
int redToBlueCur = (offset[axis][1] * delta) + redToBlueBest; |
||||
|
float curDiff = GetPredictionCostCrossColorBlue(argb, stride, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); |
||||
|
if (curDiff < bestDiff) |
||||
|
{ |
||||
|
bestDiff = curDiff; |
||||
|
greenToBlueBest = greenToBlueCur; |
||||
|
redToBlueBest = redToBlueCur; |
||||
|
} |
||||
|
|
||||
|
if (quality < 25 && iter == 4) |
||||
|
{ |
||||
|
// Only axis aligned diffs for lower quality.
|
||||
|
break; // next iter.
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (delta == 2 && greenToBlueBest == 0 && redToBlueBest == 0) |
||||
|
{ |
||||
|
// Further iterations would not help.
|
||||
|
break; // out of iter-loop.
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bestTx.GreenToBlue = (byte)(greenToBlueBest & 0xff); |
||||
|
bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); |
||||
|
} |
||||
|
|
||||
|
private static float GetPredictionCostCrossColorRed(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToRed, int[] accumulatedRedHisto) |
||||
|
{ |
||||
|
int[] histo = new int[256]; |
||||
|
|
||||
|
CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); |
||||
|
float curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); |
||||
|
|
||||
|
if ((byte)greenToRed == prevX.GreenToRed) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if ((byte)greenToRed == prevY.GreenToRed) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if (greenToRed == 0) |
||||
|
{ |
||||
|
curDiff -= 3; |
||||
|
} |
||||
|
|
||||
|
return curDiff; |
||||
|
} |
||||
|
|
||||
|
private static float GetPredictionCostCrossColorBlue(Span<uint> argb, int stride, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int greenToBlue, int redToBlue, int[] accumulatedBlueHisto) |
||||
|
{ |
||||
|
int[] histo = new int[256]; |
||||
|
|
||||
|
CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); |
||||
|
float curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); |
||||
|
if ((byte)greenToBlue == prevX.GreenToBlue) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if ((byte)greenToBlue == prevY.GreenToBlue) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if ((byte)redToBlue == prevX.RedToBlue) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if ((byte)redToBlue == prevY.RedToBlue) |
||||
|
{ |
||||
|
curDiff -= 3; // Favor keeping the areas locally similar.
|
||||
|
} |
||||
|
|
||||
|
if (greenToBlue == 0) |
||||
|
{ |
||||
|
curDiff -= 3; |
||||
|
} |
||||
|
|
||||
|
if (redToBlue == 0) |
||||
|
{ |
||||
|
curDiff -= 3; |
||||
|
} |
||||
|
|
||||
|
return curDiff; |
||||
|
} |
||||
|
|
||||
|
private static void CollectColorRedTransforms(Span<uint> argb, int stride, int tileWidth, int tileHeight, int greenToRed, int[] histo) |
||||
|
{ |
||||
|
int pos = 0; |
||||
|
while (tileHeight-- > 0) |
||||
|
{ |
||||
|
for (int x = 0; x < tileWidth; x++) |
||||
|
{ |
||||
|
++histo[LosslessUtils.TransformColorRed((sbyte)greenToRed, argb[pos + x])]; |
||||
|
} |
||||
|
|
||||
|
pos += stride; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void CollectColorBlueTransforms(Span<uint> argb, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, int[] histo) |
||||
|
{ |
||||
|
int pos = 0; |
||||
|
while (tileHeight-- > 0) |
||||
|
{ |
||||
|
for (int x = 0; x < tileWidth; x++) |
||||
|
{ |
||||
|
++histo[LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, argb[pos + x])]; |
||||
|
} |
||||
|
|
||||
|
pos += stride; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) |
||||
|
{ |
||||
|
double retVal = 0.0d; |
||||
|
for (int i = 0; i < 4; i++) |
||||
|
{ |
||||
|
double kExpValue = 0.94; |
||||
|
retVal += PredictionCostSpatial(tile[i], 1, kExpValue); |
||||
|
retVal += LosslessUtils.CombinedShannonEntropy(tile[i], accumulated[i]); |
||||
|
} |
||||
|
|
||||
|
return (float)retVal; |
||||
|
} |
||||
|
|
||||
|
private static float PredictionCostCrossColor(int[] accumulated, int[] counts) |
||||
|
{ |
||||
|
// Favor low entropy, locally and globally.
|
||||
|
// Favor small absolute values for PredictionCostSpatial.
|
||||
|
const double expValue = 2.4d; |
||||
|
return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); |
||||
|
} |
||||
|
|
||||
|
private static float PredictionCostSpatial(int[] counts, int weight0, double expVal) |
||||
|
{ |
||||
|
int significantSymbols = 256 >> 4; |
||||
|
double expDecayFactor = 0.6; |
||||
|
double bits = weight0 * counts[0]; |
||||
|
for (int i = 1; i < significantSymbols; i++) |
||||
|
{ |
||||
|
bits += expVal * (counts[i] + counts[256 - i]); |
||||
|
expVal *= expDecayFactor; |
||||
|
} |
||||
|
|
||||
|
return (float)(-0.1 * bits); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static byte NearLosslessDiff(byte a, byte b) |
||||
|
{ |
||||
|
return (byte)((a - b) & 0xff); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static uint MultipliersToColorCode(Vp8LMultipliers m) |
||||
|
{ |
||||
|
return 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static int GetMin(int a, int b) |
||||
|
{ |
||||
|
return (a > b) ? b : a; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static int GetMax(int a, int b) |
||||
|
{ |
||||
|
return (a < b) ? b : a; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the GNU Affero General Public License, Version 3.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.WebP.Lossless |
||||
|
{ |
||||
|
internal struct Vp8LMultipliers |
||||
|
{ |
||||
|
public byte GreenToRed; |
||||
|
|
||||
|
public byte GreenToBlue; |
||||
|
|
||||
|
public byte RedToBlue; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue