mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 1103 additions and 1053 deletions
@ -0,0 +1,137 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossy |
|||
{ |
|||
/// <summary>
|
|||
/// Quantization methods.
|
|||
/// </summary>
|
|||
internal static class QuantEnc |
|||
{ |
|||
private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; |
|||
|
|||
private const int MaxLevel = 2047; |
|||
|
|||
// Diffusion weights. We under-correct a bit (15/16th of the error is actually
|
|||
// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0.
|
|||
private const int C1 = 7; // fraction of error sent to the 4x4 block below
|
|||
private const int C2 = 8; // fraction of error sent to the 4x4 block on the right
|
|||
private const int DSHIFT = 4; |
|||
private const int DSCALE = 1; // storage descaling, needed to make the error fit byte
|
|||
|
|||
public static int Quantize2Blocks(Span<short> input, Span<short> output, Vp8Matrix mtx) |
|||
{ |
|||
int nz; |
|||
nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; |
|||
nz |= QuantEnc.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; |
|||
return nz; |
|||
} |
|||
|
|||
public static int QuantizeBlock(Span<short> input, Span<short> output, Vp8Matrix mtx) |
|||
{ |
|||
int last = -1; |
|||
int n; |
|||
for (n = 0; n < 16; ++n) |
|||
{ |
|||
int j = Zigzag[n]; |
|||
bool sign = input[j] < 0; |
|||
uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); |
|||
if (coeff > mtx.ZThresh[j]) |
|||
{ |
|||
uint q = mtx.Q[j]; |
|||
uint iQ = mtx.IQ[j]; |
|||
uint b = mtx.Bias[j]; |
|||
int level = QuantDiv(coeff, iQ, b); |
|||
if (level > MaxLevel) |
|||
{ |
|||
level = MaxLevel; |
|||
} |
|||
|
|||
if (sign) |
|||
{ |
|||
level = -level; |
|||
} |
|||
|
|||
input[j] = (short)(level * (int)q); |
|||
output[n] = (short)level; |
|||
if (level != 0) |
|||
{ |
|||
last = n; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
output[n] = 0; |
|||
input[j] = 0; |
|||
} |
|||
} |
|||
|
|||
return (last >= 0) ? 1 : 0; |
|||
} |
|||
|
|||
// Quantize as usual, but also compute and return the quantization error.
|
|||
// Error is already divided by DSHIFT.
|
|||
public static int QuantizeSingle(Span<short> v, Vp8Matrix mtx) |
|||
{ |
|||
int v0 = v[0]; |
|||
bool sign = v0 < 0; |
|||
if (sign) |
|||
{ |
|||
v0 = -v0; |
|||
} |
|||
|
|||
if (v0 > (int)mtx.ZThresh[0]) |
|||
{ |
|||
int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; |
|||
int err = v0 - qV; |
|||
v[0] = (short)(sign ? -qV : qV); |
|||
return (sign ? -err : err) >> DSCALE; |
|||
} |
|||
|
|||
v[0] = 0; |
|||
return (sign ? -v0 : v0) >> DSCALE; |
|||
} |
|||
|
|||
public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) |
|||
{ |
|||
#pragma warning disable SA1005 // Single line comments should begin with single space
|
|||
// | top[0] | top[1]
|
|||
// --------+--------+---------
|
|||
// left[0] | tmp[0] tmp[1] <-> err0 err1
|
|||
// left[1] | tmp[2] tmp[3] err2 err3
|
|||
//
|
|||
// Final errors {err1,err2,err3} are preserved and later restored
|
|||
// as top[]/left[] on the next block.
|
|||
#pragma warning restore SA1005 // Single line comments should begin with single space
|
|||
for (int ch = 0; ch <= 1; ++ch) |
|||
{ |
|||
Span<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); |
|||
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2); |
|||
int err0, err1, err2, err3; |
|||
Span<short> c = tmp.AsSpan(ch * 4 * 16, 4 * 16); |
|||
c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); |
|||
err0 = QuantEnc.QuantizeSingle(c, mtx); |
|||
c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); |
|||
err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); |
|||
c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); |
|||
err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); |
|||
c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); |
|||
err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); |
|||
|
|||
// TODO: set errors in rd
|
|||
// rd->derr[ch][0] = (int8_t)err1;
|
|||
// rd->derr[ch][1] = (int8_t)err2;
|
|||
// rd->derr[ch][2] = (int8_t)err3;
|
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int QuantDiv(uint n, uint iQ, uint b) |
|||
{ |
|||
return (int)(((n * iQ) + b) >> WebPConstants.QFix); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,664 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossy |
|||
{ |
|||
/// <summary>
|
|||
/// Methods for encoding a VP8 frame.
|
|||
/// </summary>
|
|||
internal static class Vp8Encoding |
|||
{ |
|||
private const int KC1 = 20091 + (1 << 16); |
|||
|
|||
private const int KC2 = 35468; |
|||
|
|||
private static readonly byte[] Clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255]
|
|||
|
|||
private const int I16DC16 = 0 * 16 * WebPConstants.Bps; |
|||
|
|||
private const int I16TM16 = I16DC16 + 16; |
|||
|
|||
private const int I16VE16 = 1 * 16 * WebPConstants.Bps; |
|||
|
|||
private const int I16HE16 = I16VE16 + 16; |
|||
|
|||
private const int C8DC8 = 2 * 16 * WebPConstants.Bps; |
|||
|
|||
private const int C8TM8 = C8DC8 + (1 * 16); |
|||
|
|||
private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); |
|||
|
|||
private const int C8HE8 = C8VE8 + (1 * 16); |
|||
|
|||
public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; |
|||
|
|||
public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; |
|||
|
|||
private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; |
|||
|
|||
private const int I4TM4 = I4DC4 + 4; |
|||
|
|||
private const int I4VE4 = I4DC4 + 8; |
|||
|
|||
private const int I4HE4 = I4DC4 + 12; |
|||
|
|||
private const int I4RD4 = I4DC4 + 16; |
|||
|
|||
private const int I4VR4 = I4DC4 + 20; |
|||
|
|||
private const int I4LD4 = I4DC4 + 24; |
|||
|
|||
private const int I4VL4 = I4DC4 + 28; |
|||
|
|||
private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); |
|||
|
|||
private const int I4HU4 = I4HD4 + 4; |
|||
|
|||
public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; |
|||
|
|||
static Vp8Encoding() |
|||
{ |
|||
for (int i = -255; i <= 255 + 255; ++i) |
|||
{ |
|||
Clip1[255 + i] = Clip8b(i); |
|||
} |
|||
} |
|||
|
|||
public static void ITransform(Span<byte> reference, Span<short> input, Span<byte> dst, bool doTwo) |
|||
{ |
|||
ITransformOne(reference, input, dst); |
|||
if (doTwo) |
|||
{ |
|||
ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); |
|||
} |
|||
} |
|||
|
|||
public static void ITransformOne(Span<byte> reference, Span<short> input, Span<byte> dst) |
|||
{ |
|||
int i; |
|||
#pragma warning disable SA1312 // Variable names should begin with lower-case letter
|
|||
var C = new int[4 * 4]; |
|||
#pragma warning restore SA1312 // Variable names should begin with lower-case letter
|
|||
Span<int> tmp = C.AsSpan(); |
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
// vertical pass.
|
|||
int a = input[0] + input[8]; |
|||
int b = input[0] - input[8]; |
|||
int c = Mul(input[4], KC2) - Mul(input[12], KC1); |
|||
int d = Mul(input[4], KC1) + Mul(input[12], KC2); |
|||
tmp[0] = a + d; |
|||
tmp[1] = b + c; |
|||
tmp[2] = b - c; |
|||
tmp[3] = a - d; |
|||
tmp = tmp.Slice(4); |
|||
input = input.Slice(1); |
|||
} |
|||
|
|||
tmp = C.AsSpan(); |
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
// horizontal pass.
|
|||
int dc = tmp[0] + 4; |
|||
int a = dc + tmp[8]; |
|||
int b = dc - tmp[8]; |
|||
int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); |
|||
int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); |
|||
Store(dst, reference, 0, i, a + d); |
|||
Store(dst, reference, 1, i, b + c); |
|||
Store(dst, reference, 2, i, b - c); |
|||
Store(dst, reference, 3, i, a - d); |
|||
tmp = tmp.Slice(1); |
|||
} |
|||
} |
|||
|
|||
public static void FTransform2(Span<byte> src, Span<byte> reference, Span<short> output, Span<short> output2) |
|||
{ |
|||
FTransform(src, reference, output); |
|||
FTransform(src.Slice(4), reference.Slice(4), output2); |
|||
} |
|||
|
|||
public static void FTransform(Span<byte> src, Span<byte> reference, Span<short> output) |
|||
{ |
|||
int i; |
|||
var tmp = new int[16]; |
|||
int srcIdx = 0; |
|||
int refIdx = 0; |
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255])
|
|||
int d1 = src[srcIdx + 1] - reference[refIdx + 1]; |
|||
int d2 = src[srcIdx + 2] - reference[refIdx + 2]; |
|||
int d3 = src[srcIdx + 3] - reference[refIdx + 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; |
|||
|
|||
srcIdx += WebPConstants.Bps; |
|||
refIdx += 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); |
|||
} |
|||
} |
|||
|
|||
public static void FTransformWht(Span<short> input, Span<short> output) |
|||
{ |
|||
var tmp = new int[16]; |
|||
int i; |
|||
int inputIdx = 0; |
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b
|
|||
int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; |
|||
int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; |
|||
int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; |
|||
tmp[0 + (i * 4)] = a0 + a1; // 14b
|
|||
tmp[1 + (i * 4)] = a3 + a2; |
|||
tmp[2 + (i * 4)] = a3 - a2; |
|||
tmp[3 + (i * 4)] = a0 - a1; |
|||
|
|||
inputIdx += 64; |
|||
} |
|||
|
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
int a0 = tmp[0 + i] + tmp[8 + i]; // 15b
|
|||
int a1 = tmp[4 + i] + tmp[12 + i]; |
|||
int a2 = tmp[4 + i] - tmp[12 + i]; |
|||
int a3 = tmp[0 + i] - tmp[8 + i]; |
|||
int b0 = a0 + a1; // 16b
|
|||
int b1 = a3 + a2; |
|||
int b2 = a3 - a2; |
|||
int b3 = a0 - a1; |
|||
output[0 + i] = (short)(b0 >> 1); // 15b
|
|||
output[4 + i] = (short)(b1 >> 1); |
|||
output[8 + i] = (short)(b2 >> 1); |
|||
output[12 + i] = (short)(b3 >> 1); |
|||
} |
|||
} |
|||
|
|||
// luma 16x16 prediction (paragraph 12.3).
|
|||
public static void EncPredLuma16(Span<byte> dst, Span<byte> left, Span<byte> top) |
|||
{ |
|||
DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); |
|||
VerticalPred(dst.Slice(I16VE16), top, 16); |
|||
HorizontalPred(dst.Slice(I16HE16), left, 16); |
|||
TrueMotion(dst.Slice(I16TM16), left, top, 16); |
|||
} |
|||
|
|||
// Chroma 8x8 prediction (paragraph 12.2).
|
|||
public static void EncPredChroma8(Span<byte> dst, Span<byte> left, Span<byte> top) |
|||
{ |
|||
// U block.
|
|||
DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); |
|||
VerticalPred(dst.Slice(C8VE8), top, 8); |
|||
HorizontalPred(dst.Slice(C8HE8), left, 8); |
|||
TrueMotion(dst.Slice(C8TM8), left, top, 8); |
|||
|
|||
// V block.
|
|||
dst = dst.Slice(8); |
|||
if (top != null) |
|||
{ |
|||
top = top.Slice(8); |
|||
} |
|||
|
|||
if (left != null) |
|||
{ |
|||
left = left.Slice(16); |
|||
} |
|||
|
|||
DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); |
|||
VerticalPred(dst.Slice(C8VE8), top, 8); |
|||
HorizontalPred(dst.Slice(C8HE8), left, 8); |
|||
TrueMotion(dst.Slice(C8TM8), left, top, 8); |
|||
} |
|||
|
|||
// Left samples are top[-5 .. -2], top_left is top[-1], top are
|
|||
// located at top[0..3], and top right is top[4..7]
|
|||
public static void EncPredLuma4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
Dc4(dst.Slice(I4DC4), top, topOffset); |
|||
Tm4(dst.Slice(I4TM4), top, topOffset); |
|||
Ve4(dst.Slice(I4VE4), top, topOffset); |
|||
He4(dst.Slice(I4HE4), top, topOffset); |
|||
Rd4(dst.Slice(I4RD4), top, topOffset); |
|||
Vr4(dst.Slice(I4VR4), top, topOffset); |
|||
Ld4(dst.Slice(I4LD4), top, topOffset); |
|||
Vl4(dst.Slice(I4VL4), top, topOffset); |
|||
Hd4(dst.Slice(I4HD4), top, topOffset); |
|||
Hu4(dst.Slice(I4HU4), top, topOffset); |
|||
} |
|||
|
|||
private static void VerticalPred(Span<byte> dst, Span<byte> top, int size) |
|||
{ |
|||
if (top != null) |
|||
{ |
|||
for (int j = 0; j < size; ++j) |
|||
{ |
|||
top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Fill(dst, 127, size); |
|||
} |
|||
} |
|||
|
|||
public static void HorizontalPred(Span<byte> dst, Span<byte> left, int size) |
|||
{ |
|||
if (left != null) |
|||
{ |
|||
left = left.Slice(1); // in the reference implementation, left starts at - 1.
|
|||
for (int j = 0; j < size; ++j) |
|||
{ |
|||
dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Fill(dst, 129, size); |
|||
} |
|||
} |
|||
|
|||
public static void TrueMotion(Span<byte> dst, Span<byte> left, Span<byte> top, int size) |
|||
{ |
|||
if (left != null) |
|||
{ |
|||
if (top != null) |
|||
{ |
|||
Span<byte> clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1
|
|||
for (int y = 0; y < size; ++y) |
|||
{ |
|||
Span<byte> clipTable = clip.Slice(left[y + 1]); // left[y]
|
|||
for (int x = 0; x < size; ++x) |
|||
{ |
|||
dst[x] = clipTable[top[x]]; |
|||
} |
|||
|
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Vp8Encoding.HorizontalPred(dst, left, size); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// true motion without left samples (hence: with default 129 value)
|
|||
// is equivalent to VE prediction where you just copy the top samples.
|
|||
// Note that if top samples are not available, the default value is
|
|||
// then 129, and not 127 as in the VerticalPred case.
|
|||
if (top != null) |
|||
{ |
|||
Vp8Encoding.VerticalPred(dst, top, size); |
|||
} |
|||
else |
|||
{ |
|||
Fill(dst, 129, size); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void DcMode(Span<byte> dst, Span<byte> left, Span<byte> top, int size, int round, int shift) |
|||
{ |
|||
int dc = 0; |
|||
int j; |
|||
if (top != null) |
|||
{ |
|||
for (j = 0; j < size; ++j) |
|||
{ |
|||
dc += top[j]; |
|||
} |
|||
|
|||
if (left != null) |
|||
{ |
|||
// top and left present.
|
|||
left = left.Slice(1); // in the reference implementation, left starts at -1.
|
|||
for (j = 0; j < size; ++j) |
|||
{ |
|||
dc += left[j]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// top, but no left.
|
|||
dc += dc; |
|||
} |
|||
|
|||
dc = (dc + round) >> shift; |
|||
} |
|||
else if (left != null) |
|||
{ |
|||
// left but no top.
|
|||
left = left.Slice(1); // in the reference implementation, left starts at -1.
|
|||
for (j = 0; j < size; ++j) |
|||
{ |
|||
dc += left[j]; |
|||
} |
|||
|
|||
dc += dc; |
|||
dc = (dc + round) >> shift; |
|||
} |
|||
else |
|||
{ |
|||
// no top, no left, nothing.
|
|||
dc = 0x80; |
|||
} |
|||
|
|||
Fill(dst, dc, size); |
|||
} |
|||
|
|||
private static void Dc4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
uint dc = 4; |
|||
int i; |
|||
for (i = 0; i < 4; ++i) |
|||
{ |
|||
dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); |
|||
} |
|||
|
|||
Fill(dst, (int)(dc >> 3), 4); |
|||
} |
|||
|
|||
private static void Tm4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
Span<byte> clip = Clip1.AsSpan(255 - top[topOffset - 1]); |
|||
for (int y = 0; y < 4; ++y) |
|||
{ |
|||
Span<byte> clipTable = clip.Slice(top[topOffset - 2 - y]); |
|||
for (int x = 0; x < 4; ++x) |
|||
{ |
|||
dst[x] = clipTable[top[topOffset + x]]; |
|||
} |
|||
|
|||
dst = dst.Slice(WebPConstants.Bps); |
|||
} |
|||
} |
|||
|
|||
private static void Ve4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
// vertical
|
|||
byte[] vals = |
|||
{ |
|||
LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), |
|||
LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), |
|||
LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), |
|||
LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) |
|||
}; |
|||
|
|||
for (int i = 0; i < 4; ++i) |
|||
{ |
|||
vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); |
|||
} |
|||
} |
|||
|
|||
private static void He4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
// horizontal
|
|||
byte x = top[topOffset - 1]; |
|||
byte i = top[topOffset - 2]; |
|||
byte j = top[topOffset - 3]; |
|||
byte k = top[topOffset - 4]; |
|||
byte l = top[topOffset - 5]; |
|||
|
|||
uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst, val); |
|||
val = 0x01010101U * LossyUtils.Avg3(i, j, k); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); |
|||
val = 0x01010101U * LossyUtils.Avg3(j, k, l); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); |
|||
val = 0x01010101U * LossyUtils.Avg3(k, l, l); |
|||
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); |
|||
} |
|||
|
|||
private static void Rd4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte x = top[topOffset - 1]; |
|||
byte i = top[topOffset - 2]; |
|||
byte j = top[topOffset - 3]; |
|||
byte k = top[topOffset - 4]; |
|||
byte l = top[topOffset - 5]; |
|||
byte a = top[topOffset]; |
|||
byte b = top[topOffset + 1]; |
|||
byte c = top[topOffset + 2]; |
|||
byte d = top[topOffset + 3]; |
|||
|
|||
LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); |
|||
var ijk = LossyUtils.Avg3(i, j, k); |
|||
LossyUtils.Dst(dst, 0, 2, ijk); |
|||
LossyUtils.Dst(dst, 1, 3, ijk); |
|||
var xij = LossyUtils.Avg3(x, i, j); |
|||
LossyUtils.Dst(dst, 0, 1, xij); |
|||
LossyUtils.Dst(dst, 1, 2, xij); |
|||
LossyUtils.Dst(dst, 2, 3, xij); |
|||
var axi = LossyUtils.Avg3(a, x, i); |
|||
LossyUtils.Dst(dst, 0, 0, axi); |
|||
LossyUtils.Dst(dst, 1, 1, axi); |
|||
LossyUtils.Dst(dst, 2, 2, axi); |
|||
LossyUtils.Dst(dst, 3, 3, axi); |
|||
var bax = LossyUtils.Avg3(b, a, x); |
|||
LossyUtils.Dst(dst, 1, 0, bax); |
|||
LossyUtils.Dst(dst, 2, 1, bax); |
|||
LossyUtils.Dst(dst, 3, 2, bax); |
|||
var cba = LossyUtils.Avg3(c, b, a); |
|||
LossyUtils.Dst(dst, 2, 0, cba); |
|||
LossyUtils.Dst(dst, 3, 1, cba); |
|||
LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); |
|||
} |
|||
|
|||
private static void Vr4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte x = top[topOffset - 1]; |
|||
byte i = top[topOffset - 2]; |
|||
byte j = top[topOffset - 3]; |
|||
byte k = top[topOffset - 4]; |
|||
byte a = top[topOffset]; |
|||
byte b = top[topOffset + 1]; |
|||
byte c = top[topOffset + 2]; |
|||
byte d = top[topOffset + 3]; |
|||
|
|||
var xa = LossyUtils.Avg2(x, a); |
|||
LossyUtils.Dst(dst, 0, 0, xa); |
|||
LossyUtils.Dst(dst, 1, 2, xa); |
|||
var ab = LossyUtils.Avg2(a, b); |
|||
LossyUtils.Dst(dst, 1, 0, ab); |
|||
LossyUtils.Dst(dst, 2, 2, ab); |
|||
var bc = LossyUtils.Avg2(b, c); |
|||
LossyUtils.Dst(dst, 2, 0, bc); |
|||
LossyUtils.Dst(dst, 3, 2, bc); |
|||
LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); |
|||
LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); |
|||
LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); |
|||
var ixa = LossyUtils.Avg3(i, x, a); |
|||
LossyUtils.Dst(dst, 0, 1, ixa); |
|||
LossyUtils.Dst(dst, 1, 3, ixa); |
|||
var xab = LossyUtils.Avg3(x, a, b); |
|||
LossyUtils.Dst(dst, 1, 1, xab); |
|||
LossyUtils.Dst(dst, 2, 3, xab); |
|||
var abc = LossyUtils.Avg3(a, b, c); |
|||
LossyUtils.Dst(dst, 2, 1, abc); |
|||
LossyUtils.Dst(dst, 3, 3, abc); |
|||
LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); |
|||
} |
|||
|
|||
private static void Ld4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte a = top[topOffset + 0]; |
|||
byte b = top[topOffset + 1]; |
|||
byte c = top[topOffset + 2]; |
|||
byte d = top[topOffset + 3]; |
|||
byte e = top[topOffset + 4]; |
|||
byte f = top[topOffset + 5]; |
|||
byte g = top[topOffset + 6]; |
|||
byte h = top[topOffset + 7]; |
|||
|
|||
LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); |
|||
var bcd = LossyUtils.Avg3(b, c, d); |
|||
LossyUtils.Dst(dst, 1, 0, bcd); |
|||
LossyUtils.Dst(dst, 0, 1, bcd); |
|||
var cde = LossyUtils.Avg3(c, d, e); |
|||
LossyUtils.Dst(dst, 2, 0, cde); |
|||
LossyUtils.Dst(dst, 1, 1, cde); |
|||
LossyUtils.Dst(dst, 0, 2, cde); |
|||
var def = LossyUtils.Avg3(d, e, f); |
|||
LossyUtils.Dst(dst, 3, 0, def); |
|||
LossyUtils.Dst(dst, 2, 1, def); |
|||
LossyUtils.Dst(dst, 1, 2, def); |
|||
LossyUtils.Dst(dst, 0, 3, def); |
|||
var efg = LossyUtils.Avg3(e, f, g); |
|||
LossyUtils.Dst(dst, 3, 1, efg); |
|||
LossyUtils.Dst(dst, 2, 2, efg); |
|||
LossyUtils.Dst(dst, 1, 3, efg); |
|||
var fgh = LossyUtils.Avg3(f, g, h); |
|||
LossyUtils.Dst(dst, 3, 2, fgh); |
|||
LossyUtils.Dst(dst, 2, 3, fgh); |
|||
LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); |
|||
} |
|||
|
|||
private static void Vl4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte a = top[topOffset + 0]; |
|||
byte b = top[topOffset + 1]; |
|||
byte c = top[topOffset + 2]; |
|||
byte d = top[topOffset + 3]; |
|||
byte e = top[topOffset + 4]; |
|||
byte f = top[topOffset + 5]; |
|||
byte g = top[topOffset + 6]; |
|||
byte h = top[topOffset + 7]; |
|||
|
|||
LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); |
|||
var bc = LossyUtils.Avg2(b, c); |
|||
LossyUtils.Dst(dst, 1, 0, bc); |
|||
LossyUtils.Dst(dst, 0, 2, bc); |
|||
var cd = LossyUtils.Avg2(c, d); |
|||
LossyUtils.Dst(dst, 2, 0, cd); |
|||
LossyUtils.Dst(dst, 1, 2, cd); |
|||
var de = LossyUtils.Avg2(d, e); |
|||
LossyUtils.Dst(dst, 3, 0, de); |
|||
LossyUtils.Dst(dst, 2, 2, de); |
|||
LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); |
|||
var bcd = LossyUtils.Avg3(b, c, d); |
|||
LossyUtils.Dst(dst, 1, 1, bcd); |
|||
LossyUtils.Dst(dst, 0, 3, bcd); |
|||
var cde = LossyUtils.Avg3(c, d, e); |
|||
LossyUtils.Dst(dst, 2, 1, cde); |
|||
LossyUtils.Dst(dst, 1, 3, cde); |
|||
var def = LossyUtils.Avg3(d, e, f); |
|||
LossyUtils.Dst(dst, 3, 1, def); |
|||
LossyUtils.Dst(dst, 2, 3, def); |
|||
LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); |
|||
LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); |
|||
} |
|||
|
|||
private static void Hd4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte x = top[topOffset - 1]; |
|||
byte i = top[topOffset - 2]; |
|||
byte j = top[topOffset - 3]; |
|||
byte k = top[topOffset - 4]; |
|||
byte l = top[topOffset - 5]; |
|||
byte a = top[topOffset]; |
|||
byte b = top[topOffset + 1]; |
|||
byte c = top[topOffset + 2]; |
|||
|
|||
var ix = LossyUtils.Avg2(i, x); |
|||
LossyUtils.Dst(dst, 0, 0, ix); |
|||
LossyUtils.Dst(dst, 2, 1, ix); |
|||
var ji = LossyUtils.Avg2(j, i); |
|||
LossyUtils.Dst(dst, 0, 1, ji); |
|||
LossyUtils.Dst(dst, 2, 2, ji); |
|||
var kj = LossyUtils.Avg2(k, j); |
|||
LossyUtils.Dst(dst, 0, 2, kj); |
|||
LossyUtils.Dst(dst, 2, 3, kj); |
|||
LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); |
|||
LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); |
|||
LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); |
|||
var ixa = LossyUtils.Avg3(i, x, a); |
|||
LossyUtils.Dst(dst, 1, 0, ixa); |
|||
LossyUtils.Dst(dst, 3, 1, ixa); |
|||
var jix = LossyUtils.Avg3(j, i, x); |
|||
LossyUtils.Dst(dst, 1, 1, jix); |
|||
LossyUtils.Dst(dst, 3, 2, jix); |
|||
var kji = LossyUtils.Avg3(k, j, i); |
|||
LossyUtils.Dst(dst, 1, 2, kji); |
|||
LossyUtils.Dst(dst, 3, 3, kji); |
|||
LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); |
|||
} |
|||
|
|||
private static void Hu4(Span<byte> dst, Span<byte> top, int topOffset) |
|||
{ |
|||
byte i = top[topOffset - 2]; |
|||
byte j = top[topOffset - 3]; |
|||
byte k = top[topOffset - 4]; |
|||
byte l = top[topOffset - 5]; |
|||
|
|||
LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); |
|||
var jk = LossyUtils.Avg2(j, k); |
|||
LossyUtils.Dst(dst, 2, 0, jk); |
|||
LossyUtils.Dst(dst, 0, 1, jk); |
|||
var kl = LossyUtils.Avg2(k, l); |
|||
LossyUtils.Dst(dst, 2, 1, kl); |
|||
LossyUtils.Dst(dst, 0, 2, kl); |
|||
LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); |
|||
var jkl = LossyUtils.Avg3(j, k, l); |
|||
LossyUtils.Dst(dst, 3, 0, jkl); |
|||
LossyUtils.Dst(dst, 1, 1, jkl); |
|||
var kll = LossyUtils.Avg3(k, l, l); |
|||
LossyUtils.Dst(dst, 3, 1, kll); |
|||
LossyUtils.Dst(dst, 1, 2, kll); |
|||
LossyUtils.Dst(dst, 3, 2, l); |
|||
LossyUtils.Dst(dst, 2, 2, l); |
|||
LossyUtils.Dst(dst, 0, 3, l); |
|||
LossyUtils.Dst(dst, 1, 3, l); |
|||
LossyUtils.Dst(dst, 2, 3, l); |
|||
LossyUtils.Dst(dst, 3, 3, l); |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void Fill(Span<byte> dst, int value, int size) |
|||
{ |
|||
for (int j = 0; j < size; ++j) |
|||
{ |
|||
dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static byte Clip8b(int v) |
|||
{ |
|||
return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void Store(Span<byte> dst, Span<byte> reference, int x, int y, int v) |
|||
{ |
|||
dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int Mul(int a, int b) |
|||
{ |
|||
return (a * b) >> 16; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,260 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossy |
|||
{ |
|||
internal static class YuvConversion |
|||
{ |
|||
/// <summary>
|
|||
/// Fixed-point precision for RGB->YUV.
|
|||
/// </summary>
|
|||
private const int YuvFix = 16; |
|||
|
|||
private const int YuvHalf = 1 << (YuvFix - 1); |
|||
|
|||
/// <summary>
|
|||
/// Checks if the image is not opaque.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type of the image,</typeparam>
|
|||
/// <param name="image">The image to check.</param>
|
|||
/// <returns>Returns true if alpha has non-0xff values.</returns>
|
|||
public static bool CheckNonOpaque<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Rgba32 rgba = default; |
|||
for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) |
|||
{ |
|||
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex); |
|||
for (int x = 0; x < image.Width; x++) |
|||
{ |
|||
TPixel color = rowSpan[x]; |
|||
color.ToRgba32(ref rgba); |
|||
if (rgba.A != 255) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public static void ConvertRgbaToY<TPixel>(Span<TPixel> rowSpan, Span<byte> y, int width) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Rgba32 rgba = default; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
TPixel color = rowSpan[x]; |
|||
color.ToRgba32(ref rgba); |
|||
y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); |
|||
} |
|||
} |
|||
|
|||
public static void ConvertRgbaToUv(Span<ushort> rgb, Span<byte> u, Span<byte> v, int width) |
|||
{ |
|||
int rgbIdx = 0; |
|||
for (int i = 0; i < width; i += 1, rgbIdx += 4) |
|||
{ |
|||
int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; |
|||
u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); |
|||
v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); |
|||
} |
|||
} |
|||
|
|||
public static void AccumulateRgb<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Rgba32 rgba0 = default; |
|||
Rgba32 rgba1 = default; |
|||
Rgba32 rgba2 = default; |
|||
Rgba32 rgba3 = default; |
|||
int i, j; |
|||
int dstIdx = 0; |
|||
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) |
|||
{ |
|||
TPixel color = rowSpan[j]; |
|||
color.ToRgba32(ref rgba0); |
|||
color = rowSpan[j + 1]; |
|||
color.ToRgba32(ref rgba1); |
|||
color = nextRowSpan[j]; |
|||
color.ToRgba32(ref rgba2); |
|||
color = nextRowSpan[j + 1]; |
|||
color.ToRgba32(ref rgba3); |
|||
|
|||
dst[dstIdx] = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.R) + |
|||
GammaToLinear(rgba1.R) + |
|||
GammaToLinear(rgba2.R) + |
|||
GammaToLinear(rgba3.R), 0); |
|||
dst[dstIdx + 1] = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.G) + |
|||
GammaToLinear(rgba1.G) + |
|||
GammaToLinear(rgba2.G) + |
|||
GammaToLinear(rgba3.G), 0); |
|||
dst[dstIdx + 2] = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.B) + |
|||
GammaToLinear(rgba1.B) + |
|||
GammaToLinear(rgba2.B) + |
|||
GammaToLinear(rgba3.B), 0); |
|||
} |
|||
|
|||
if ((width & 1) != 0) |
|||
{ |
|||
TPixel color = rowSpan[j]; |
|||
color.ToRgba32(ref rgba0); |
|||
color = nextRowSpan[j]; |
|||
color.ToRgba32(ref rgba1); |
|||
|
|||
dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); |
|||
dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); |
|||
dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); |
|||
} |
|||
} |
|||
|
|||
public static void AccumulateRgba<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Rgba32 rgba0 = default; |
|||
Rgba32 rgba1 = default; |
|||
Rgba32 rgba2 = default; |
|||
Rgba32 rgba3 = default; |
|||
int i, j; |
|||
int dstIdx = 0; |
|||
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) |
|||
{ |
|||
TPixel color = rowSpan[j]; |
|||
color.ToRgba32(ref rgba0); |
|||
color = rowSpan[j + 1]; |
|||
color.ToRgba32(ref rgba1); |
|||
color = nextRowSpan[j]; |
|||
color.ToRgba32(ref rgba2); |
|||
color = nextRowSpan[j + 1]; |
|||
color.ToRgba32(ref rgba3); |
|||
uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); |
|||
int r, g, b; |
|||
if (a == 4 * 0xff || a == 0) |
|||
{ |
|||
r = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.R) + |
|||
GammaToLinear(rgba1.R) + |
|||
GammaToLinear(rgba2.R) + |
|||
GammaToLinear(rgba3.R), 0); |
|||
g = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.G) + |
|||
GammaToLinear(rgba1.G) + |
|||
GammaToLinear(rgba2.G) + |
|||
GammaToLinear(rgba3.G), 0); |
|||
b = (ushort)LinearToGamma( |
|||
GammaToLinear(rgba0.B) + |
|||
GammaToLinear(rgba1.B) + |
|||
GammaToLinear(rgba2.B) + |
|||
GammaToLinear(rgba3.B), 0); |
|||
} |
|||
else |
|||
{ |
|||
r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
} |
|||
|
|||
dst[dstIdx] = (ushort)r; |
|||
dst[dstIdx + 1] = (ushort)g; |
|||
dst[dstIdx + 2] = (ushort)b; |
|||
dst[dstIdx + 3] = (ushort)a; |
|||
} |
|||
|
|||
if ((width & 1) != 0) |
|||
{ |
|||
TPixel color = rowSpan[j]; |
|||
color.ToRgba32(ref rgba0); |
|||
color = nextRowSpan[j]; |
|||
color.ToRgba32(ref rgba1); |
|||
uint a = (uint)(2u * (rgba0.A + rgba1.A)); |
|||
int r, g, b; |
|||
if (a == 4 * 0xff || a == 0) |
|||
{ |
|||
r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); |
|||
g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); |
|||
b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); |
|||
} |
|||
else |
|||
{ |
|||
r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); |
|||
} |
|||
|
|||
dst[dstIdx] = (ushort)r; |
|||
dst[dstIdx + 1] = (ushort)g; |
|||
dst[dstIdx + 2] = (ushort)b; |
|||
dst[dstIdx + 3] = (ushort)a; |
|||
} |
|||
} |
|||
|
|||
private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) |
|||
{ |
|||
uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); |
|||
return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); |
|||
} |
|||
|
|||
// Convert a linear value 'v' to YUV_FIX+2 fixed-point precision
|
|||
// U/V value, suitable for RGBToU/V calls.
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int LinearToGamma(uint baseValue, int shift) |
|||
{ |
|||
int y = Interpolate((int)(baseValue << shift)); // Final uplifted value.
|
|||
return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale.
|
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static uint GammaToLinear(byte v) |
|||
{ |
|||
return WebPLookupTables.GammaToLinearTab[v]; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int Interpolate(int v) |
|||
{ |
|||
int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part.
|
|||
int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part.
|
|||
int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; |
|||
int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; |
|||
int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate
|
|||
|
|||
return y; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int RgbToY(byte r, byte g, byte b, int rounding) |
|||
{ |
|||
int luma = (16839 * r) + (33059 * g) + (6420 * b); |
|||
return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip.
|
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int RgbToU(int r, int g, int b, int rounding) |
|||
{ |
|||
int u = (-9719 * r) - (19081 * g) + (28800 * b); |
|||
return ClipUv(u, rounding); |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int RgbToV(int r, int g, int b, int rounding) |
|||
{ |
|||
int v = (+28800 * r) - (24116 * g) - (4684 * b); |
|||
return ClipUv(v, rounding); |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static int ClipUv(int uv, int rounding) |
|||
{ |
|||
uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); |
|||
return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue