import 'dart:typed_data'; | |
import '../../internal/bit_operators.dart'; | |
import '../../util/input_buffer.dart'; | |
import 'vp8l.dart'; | |
class VP8LTransform { | |
// enum VP8LImageTransformType | |
static const int PREDICTOR_TRANSFORM = 0; | |
static const int CROSS_COLOR_TRANSFORM = 1; | |
static const int SUBTRACT_GREEN = 2; | |
static const int COLOR_INDEXING_TRANSFORM = 3; | |
int type = 0; | |
int xsize = 0; | |
int ysize = 0; | |
Uint32List data; | |
int bits = 0; | |
void inverseTransform(int rowStart, int rowEnd, Uint32List inData, int rowsIn, | |
Uint32List outData, int rowsOut) { | |
final int width = xsize; | |
switch (type) { | |
case SUBTRACT_GREEN: | |
addGreenToBlueAndRed( | |
outData, rowsOut, rowsOut + (rowEnd - rowStart) * width); | |
break; | |
case PREDICTOR_TRANSFORM: | |
predictorInverseTransform(rowStart, rowEnd, outData, rowsOut); | |
if (rowEnd != ysize) { | |
// The last predicted row in this iteration will be the top-pred row | |
// for the first row in next iteration. | |
int start = rowsOut - width; | |
int end = start + width; | |
int offset = rowsOut + (rowEnd - rowStart - 1) * width; | |
outData.setRange(start, end, inData, offset); | |
} | |
break; | |
case CROSS_COLOR_TRANSFORM: | |
colorSpaceInverseTransform(rowStart, rowEnd, outData, rowsOut); | |
break; | |
case COLOR_INDEXING_TRANSFORM: | |
if (rowsIn == rowsOut && bits > 0) { | |
// Move packed pixels to the end of unpacked region, so that unpacking | |
// can occur seamlessly. | |
// Also, note that this is the only transform that applies on | |
// the effective width of VP8LSubSampleSize(xsize_, bits_). All other | |
// transforms work on effective width of xsize_. | |
final int outStride = (rowEnd - rowStart) * width; | |
final int inStride = | |
(rowEnd - rowStart) * InternalVP8L.subSampleSize(xsize, bits); | |
int src = rowsOut + outStride - inStride; | |
outData.setRange(src, src + inStride, inData, rowsOut); | |
colorIndexInverseTransform( | |
rowStart, rowEnd, inData, src, outData, rowsOut); | |
} else { | |
colorIndexInverseTransform( | |
rowStart, rowEnd, inData, rowsIn, outData, rowsOut); | |
} | |
break; | |
} | |
} | |
void colorIndexInverseTransformAlpha( | |
int yStart, int yEnd, InputBuffer src, InputBuffer dst) { | |
final int bitsPerPixel = 8 >> bits; | |
final int width = xsize; | |
Uint32List colorMap = this.data; | |
if (bitsPerPixel < 8) { | |
final int pixelsPerByte = 1 << bits; | |
final int countMask = pixelsPerByte - 1; | |
final bit_mask = (1 << bitsPerPixel) - 1; | |
for (int y = yStart; y < yEnd; ++y) { | |
int packed_pixels = 0; | |
for (int x = 0; x < width; ++x) { | |
// We need to load fresh 'packed_pixels' once every | |
// 'pixels_per_byte' increments of x. Fortunately, pixels_per_byte | |
// is a power of 2, so can just use a mask for that, instead of | |
// decrementing a counter. | |
if ((x & countMask) == 0) { | |
packed_pixels = _getAlphaIndex(src[0]); | |
src.offset++; | |
} | |
int p = _getAlphaValue(colorMap[packed_pixels & bit_mask]); | |
; | |
dst[0] = p; | |
dst.offset++; | |
packed_pixels >>= bitsPerPixel; | |
} | |
} | |
} else { | |
for (int y = yStart; y < yEnd; ++y) { | |
for (int x = 0; x < width; ++x) { | |
int index = _getAlphaIndex(src[0]); | |
src.offset++; | |
dst[0] = _getAlphaValue(colorMap[index]); | |
dst.offset++; | |
} | |
} | |
} | |
} | |
void colorIndexInverseTransform(int yStart, int yEnd, Uint32List inData, | |
int src, Uint32List outData, int dst) { | |
final int bitsPerPixel = 8 >> bits; | |
final int width = xsize; | |
Uint32List colorMap = this.data; | |
if (bitsPerPixel < 8) { | |
final int pixelsPerByte = 1 << bits; | |
final int countMask = pixelsPerByte - 1; | |
final bit_mask = (1 << bitsPerPixel) - 1; | |
for (int y = yStart; y < yEnd; ++y) { | |
int packed_pixels = 0; | |
for (int x = 0; x < width; ++x) { | |
// We need to load fresh 'packed_pixels' once every | |
// 'pixels_per_byte' increments of x. Fortunately, pixels_per_byte | |
// is a power of 2, so can just use a mask for that, instead of | |
// decrementing a counter. | |
if ((x & countMask) == 0) { | |
packed_pixels = _getARGBIndex(inData[src++]); | |
} | |
outData[dst++] = _getARGBValue(colorMap[packed_pixels & bit_mask]); | |
packed_pixels >>= bitsPerPixel; | |
} | |
} | |
} else { | |
for (int y = yStart; y < yEnd; ++y) { | |
for (int x = 0; x < width; ++x) { | |
outData[dst++] = | |
_getARGBValue(colorMap[_getARGBIndex(inData[src++])]); | |
} | |
} | |
} | |
} | |
/// Color space inverse transform. | |
void colorSpaceInverseTransform( | |
int yStart, int yEnd, Uint32List outData, int data) { | |
final int width = xsize; | |
final int mask = (1 << bits) - 1; | |
final int tilesPerRow = InternalVP8L.subSampleSize(width, bits); | |
int y = yStart; | |
int predRow = (y >> bits) * tilesPerRow; //this.data + | |
while (y < yEnd) { | |
int pred = predRow; // this.data+ | |
_VP8LMultipliers m = _VP8LMultipliers(); | |
for (int x = 0; x < width; ++x) { | |
if ((x & mask) == 0) { | |
m.colorCode = this.data[pred++]; | |
} | |
outData[data + x] = m.transformColor(outData[data + x], true); | |
} | |
data += width; | |
++y; | |
if ((y & mask) == 0) { | |
predRow += tilesPerRow; | |
; | |
} | |
} | |
} | |
/// Inverse prediction. | |
void predictorInverseTransform( | |
int yStart, int yEnd, Uint32List outData, int data) { | |
final int width = xsize; | |
if (yStart == 0) { | |
// First Row follows the L (mode=1) mode. | |
final int pred0 = _predictor0(outData, outData[data - 1], 0); | |
_addPixelsEq(outData, data, pred0); | |
for (int x = 1; x < width; ++x) { | |
final int pred1 = _predictor1(outData, outData[data + x - 1], 0); | |
_addPixelsEq(outData, data + x, pred1); | |
} | |
data += width; | |
++yStart; | |
} | |
int y = yStart; | |
final int mask = (1 << bits) - 1; | |
final int tilesPerRow = InternalVP8L.subSampleSize(width, bits); | |
int predModeBase = (y >> bits) * tilesPerRow; //this.data + | |
while (y < yEnd) { | |
final int pred2 = _predictor2(outData, outData[data - 1], data - width); | |
int predModeSrc = predModeBase; //this.data + | |
// First pixel follows the T (mode=2) mode. | |
_addPixelsEq(outData, data, pred2); | |
// .. the rest: | |
int k = (this.data[predModeSrc++] >> 8) & 0xf; | |
var predFunc = PREDICTORS[k]; | |
for (int x = 1; x < width; ++x) { | |
if ((x & mask) == 0) { | |
// start of tile. Read predictor function. | |
int k = ((this.data[predModeSrc++]) >> 8) & 0xf; | |
predFunc = PREDICTORS[k]; | |
} | |
int d = outData[data + x - 1]; | |
int pred = predFunc(outData, d, data + x - width); | |
_addPixelsEq(outData, data + x, pred); | |
} | |
data += width; | |
++y; | |
if ((y & mask) == 0) { | |
// Use the same mask, since tiles are squares. | |
predModeBase += tilesPerRow; | |
} | |
} | |
} | |
/// Add green to blue and red channels (i.e. perform the inverse transform of | |
/// 'subtract green'). | |
void addGreenToBlueAndRed(Uint32List pixels, int data, int dataEnd) { | |
while (data < dataEnd) { | |
final int argb = pixels[data]; | |
final int green = ((argb >> 8) & 0xff); | |
int redBlue = (argb & 0x00ff00ff); | |
redBlue += (green << 16) | green; | |
redBlue &= 0x00ff00ff; | |
pixels[data++] = (argb & 0xff00ff00) | redBlue; | |
} | |
} | |
static int _getARGBIndex(int idx) { | |
return (idx >> 8) & 0xff; | |
} | |
static int _getAlphaIndex(int idx) { | |
return idx; | |
} | |
static int _getARGBValue(int val) { | |
return val; | |
} | |
static int _getAlphaValue(int val) { | |
return (val >> 8) & 0xff; | |
} | |
/// In-place sum of each component with mod 256. | |
static void _addPixelsEq(Uint32List pixels, int a, int b) { | |
int pa = pixels[a]; | |
final int alphaAndGreen = (pa & 0xff00ff00) + (b & 0xff00ff00); | |
final int redAndBlue = (pa & 0x00ff00ff) + (b & 0x00ff00ff); | |
pixels[a] = (alphaAndGreen & 0xff00ff00) | (redAndBlue & 0x00ff00ff); | |
} | |
static int _average2(int a0, int a1) { | |
return (((a0 ^ a1) & 0xfefefefe) >> 1) + (a0 & a1); | |
} | |
static int _average3(int a0, int a1, int a2) { | |
return _average2(_average2(a0, a2), a1); | |
} | |
static int _average4(int a0, int a1, int a2, int a3) { | |
return _average2(_average2(a0, a1), _average2(a2, a3)); | |
} | |
/// Return 0, when a is a negative integer. | |
/// Return 255, when a is positive. | |
static int _clip255(int a) { | |
if (a < 0) { | |
return 0; | |
} | |
if (a > 255) { | |
return 255; | |
} | |
return a; | |
} | |
static int _addSubtractComponentFull(int a, int b, int c) { | |
return _clip255(a + b - c); | |
} | |
static int _clampedAddSubtractFull(int c0, int c1, int c2) { | |
final int a = _addSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); | |
final int r = _addSubtractComponentFull( | |
(c0 >> 16) & 0xff, (c1 >> 16) & 0xff, (c2 >> 16) & 0xff); | |
final int g = _addSubtractComponentFull( | |
(c0 >> 8) & 0xff, (c1 >> 8) & 0xff, (c2 >> 8) & 0xff); | |
final int b = _addSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); | |
return (a << 24) | (r << 16) | (g << 8) | b; | |
} | |
static int _addSubtractComponentHalf(int a, int b) { | |
return _clip255(a + (a - b) ~/ 2); | |
} | |
static int _clampedAddSubtractHalf(int c0, int c1, int c2) { | |
final int avg = _average2(c0, c1); | |
final int a = _addSubtractComponentHalf(avg >> 24, c2 >> 24); | |
final int r = | |
_addSubtractComponentHalf((avg >> 16) & 0xff, (c2 >> 16) & 0xff); | |
final int g = | |
_addSubtractComponentHalf((avg >> 8) & 0xff, (c2 >> 8) & 0xff); | |
final int b = | |
_addSubtractComponentHalf((avg >> 0) & 0xff, (c2 >> 0) & 0xff); | |
return (a << 24) | (r << 16) | (g << 8) | b; | |
} | |
static int _sub3(int a, int b, int c) { | |
final int pb = b - c; | |
final int pa = a - c; | |
return pb.abs() - pa.abs(); | |
} | |
static int _select(int a, int b, int c) { | |
final int pa_minus_pb = _sub3((a >> 24), (b >> 24), (c >> 24)) + | |
_sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + | |
_sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + | |
_sub3((a) & 0xff, (b) & 0xff, (c) & 0xff); | |
return (pa_minus_pb <= 0) ? a : b; | |
} | |
//-------------------------------------------------------------------------- | |
// Predictors | |
static int _predictor0(Uint32List pixels, int left, int top) { | |
return VP8L.ARGB_BLACK; | |
} | |
static int _predictor1(Uint32List pixels, int left, int top) { | |
return left; | |
} | |
static int _predictor2(Uint32List pixels, int left, int top) { | |
return pixels[top]; | |
} | |
static int _predictor3(Uint32List pixels, int left, int top) { | |
return pixels[top + 1]; | |
} | |
static int _predictor4(Uint32List pixels, int left, int top) { | |
return pixels[top - 1]; | |
} | |
static int _predictor5(Uint32List pixels, int left, int top) { | |
return _average3(left, pixels[top], pixels[top + 1]); | |
} | |
static int _predictor6(Uint32List pixels, int left, int top) { | |
return _average2(left, pixels[top - 1]); | |
} | |
static int _predictor7(Uint32List pixels, int left, int top) { | |
return _average2(left, pixels[top]); | |
} | |
static int _predictor8(Uint32List pixels, int left, int top) { | |
return _average2(pixels[top - 1], pixels[top]); | |
} | |
static int _predictor9(Uint32List pixels, int left, int top) { | |
return _average2(pixels[top], pixels[top + 1]); | |
} | |
static int _predictor10(Uint32List pixels, int left, int top) { | |
return _average4(left, pixels[top - 1], pixels[top], pixels[top + 1]); | |
} | |
static int _predictor11(Uint32List pixels, int left, int top) { | |
return _select(pixels[top], left, pixels[top - 1]); | |
} | |
static int _predictor12(Uint32List pixels, int left, int top) { | |
return _clampedAddSubtractFull(left, pixels[top], pixels[top - 1]); | |
} | |
static int _predictor13(Uint32List pixels, int left, int top) { | |
return _clampedAddSubtractHalf(left, pixels[top], pixels[top - 1]); | |
} | |
static final PREDICTORS = [ | |
_predictor0, | |
_predictor1, | |
_predictor2, | |
_predictor3, | |
_predictor4, | |
_predictor5, | |
_predictor6, | |
_predictor7, | |
_predictor8, | |
_predictor9, | |
_predictor10, | |
_predictor11, | |
_predictor12, | |
_predictor13, | |
_predictor0, | |
_predictor0 | |
]; | |
} | |
class _VP8LMultipliers { | |
final data = Uint8List(3); | |
// Note: the members are uint8_t, so that any negative values are | |
// automatically converted to "mod 256" values. | |
int get greenToRed => data[0]; | |
set greenToRed(int m) => data[0] = m; | |
int get greenToBlue => data[1]; | |
set greenToBlue(int m) => data[1] = m; | |
int get redToBlue => data[2]; | |
set redToBlue(int m) => data[2] = m; | |
void clear() { | |
data[0] = 0; | |
data[1] = 0; | |
data[2] = 0; | |
} | |
set colorCode(int colorCode) { | |
data[0] = (colorCode >> 0) & 0xff; | |
data[1] = (colorCode >> 8) & 0xff; | |
data[2] = (colorCode >> 16) & 0xff; | |
} | |
int get colorCode => 0xff000000 | (data[2] << 16) | (data[1] << 8) | data[0]; | |
int transformColor(int argb, bool inverse) { | |
final int green = (argb >> 8) & 0xff; | |
final int red = (argb >> 16) & 0xff; | |
int newRed = red; | |
int newBlue = argb & 0xff; | |
if (inverse) { | |
int g = colorTransformDelta(greenToRed, green); | |
newRed = (newRed + g) & 0xffffffff; | |
newRed &= 0xff; | |
newBlue = | |
(newBlue + colorTransformDelta(greenToBlue, green)) & 0xffffffff; | |
newBlue = (newBlue + colorTransformDelta(redToBlue, newRed)) & 0xffffffff; | |
newBlue &= 0xff; | |
} else { | |
newRed -= colorTransformDelta(greenToRed, green); | |
newRed &= 0xff; | |
newBlue -= colorTransformDelta(greenToBlue, green); | |
newBlue -= colorTransformDelta(redToBlue, red); | |
newBlue &= 0xff; | |
} | |
int c = (argb & 0xff00ff00) | ((newRed << 16) & 0xffffffff) | (newBlue); | |
return c; | |
} | |
int colorTransformDelta(int colorPred, int color) { | |
// There's a bug in dart2js (issue 16497) that requires I do this a bit | |
// convoluted to avoid having the optimizer butcher the code. | |
int a = uint8ToInt8(colorPred); | |
int b = uint8ToInt8(color); | |
int d = int32ToUint32(a * b); | |
return d >> 5; | |
} | |
} |