blob: 50392ecf7a197677703747d67c5bee2de9428832 [file] [log] [blame]
import 'dart:typed_data';
import '../../image.dart';
import '../../util/input_buffer.dart';
import 'vp8_bit_reader.dart';
import 'vp8_filter.dart';
import 'vp8_types.dart';
import 'webp_alpha.dart';
import 'webp_info.dart';
/// WebP lossy format.
class VP8 {
InputBuffer input;
InternalWebPInfo _webp;
VP8(InputBuffer input, this._webp) : this.input = input;
WebPInfo get webp => _webp;
bool decodeHeader() {
int bits = input.readUint24();
final bool keyFrame = (bits & 1) == 0;
if (!keyFrame) {
return false;
}
if (((bits >> 1) & 7) > 3) {
return false; // unknown profile
}
if (((bits >> 4) & 1) == 0) {
return false; // first frame is invisible!
}
_frameHeader.keyFrame = (bits & 1) == 0;
_frameHeader.profile = (bits >> 1) & 7;
_frameHeader.show = (bits >> 4) & 1;
_frameHeader.partitionLength = (bits >> 5);
int signature = input.readUint24();
if (signature != VP8_SIGNATURE) {
return false;
}
webp.width = input.readUint16();
webp.height = input.readUint16();
return true;
}
Image decode() {
if (!_getHeaders()) {
return null;
}
output = Image(webp.width, webp.height);
// Will allocate memory and prepare everything.
if (!_initFrame()) {
return null;
}
// Main decoding loop
if (!_parseFrame()) {
return null;
}
return output;
}
bool _getHeaders() {
if (!decodeHeader()) {
return false;
}
_proba = VP8Proba();
for (int i = 0; i < NUM_MB_SEGMENTS; ++i) {
_dqm[i] = VP8QuantMatrix();
}
_picHeader.width = webp.width;
_picHeader.height = webp.height;
_picHeader.xscale = (webp.width >> 8) >> 6;
_picHeader.yscale = (webp.height >> 8) >> 6;
_cropTop = 0;
_cropLeft = 0;
_cropRight = webp.width;
_cropBottom = webp.height;
_mbWidth = (webp.width + 15) >> 4;
_mbHeight = (webp.height + 15) >> 4;
_segment = 0;
br = VP8BitReader(input.subset(_frameHeader.partitionLength));
input.skip(_frameHeader.partitionLength);
_picHeader.colorspace = br.get();
_picHeader.clampType = br.get();
if (!_parseSegmentHeader(_segmentHeader, _proba)) {
return false;
}
// Filter specs
if (!_parseFilterHeader()) {
return false;
}
if (!_parsePartitions(input)) {
return false;
}
// quantizer change
_parseQuant();
// Frame buffer marking
br.get(); // ignore the value of update_proba_
_parseProba();
return true;
}
bool _parseSegmentHeader(VP8SegmentHeader hdr, VP8Proba proba) {
hdr.useSegment = br.get() != 0;
if (hdr.useSegment) {
hdr.updateMap = br.get() != 0;
if (br.get() != 0) {
// update data
hdr.absoluteDelta = br.get() != 0;
for (int s = 0; s < NUM_MB_SEGMENTS; ++s) {
hdr.quantizer[s] = br.get() != 0 ? br.getSignedValue(7) : 0;
}
for (int s = 0; s < NUM_MB_SEGMENTS; ++s) {
hdr.filterStrength[s] = br.get() != 0 ? br.getSignedValue(6) : 0;
}
}
if (hdr.updateMap) {
for (int s = 0; s < MB_FEATURE_TREE_PROBS; ++s) {
proba.segments[s] = br.get() != 0 ? br.getValue(8) : 255;
}
}
} else {
hdr.updateMap = false;
}
return true;
}
bool _parseFilterHeader() {
VP8FilterHeader hdr = _filterHeader;
hdr.simple = br.get() != 0;
hdr.level = br.getValue(6);
hdr.sharpness = br.getValue(3);
hdr.useLfDelta = br.get() != 0;
if (hdr.useLfDelta) {
if (br.get() != 0) {
// update lf-delta?
for (int i = 0; i < NUM_REF_LF_DELTAS; ++i) {
if (br.get() != 0) {
hdr.refLfDelta[i] = br.getSignedValue(6);
}
}
for (int i = 0; i < NUM_MODE_LF_DELTAS; ++i) {
if (br.get() != 0) {
hdr.modeLfDelta[i] = br.getSignedValue(6);
}
}
}
}
_filterType = (hdr.level == 0) ? 0 : hdr.simple ? 1 : 2;
return true;
}
/// This function returns VP8_STATUS_SUSPENDED if we don't have all the
/// necessary data in 'buf'.
/// This case is not necessarily an error (for incremental decoding).
/// Still, no bitreader is ever initialized to make it possible to read
/// unavailable memory.
/// If we don't even have the partitions' sizes, than VP8_STATUS_NOT_ENOUGH_DATA
/// is returned, and this is an unrecoverable error.
/// If the partitions were positioned ok, VP8_STATUS_OK is returned.
bool _parsePartitions(InputBuffer input) {
int sz = 0;
int bufEnd = input.length;
_numPartitions = 1 << br.getValue(2);
int lastPart = _numPartitions - 1;
int partStart = lastPart * 3;
if (bufEnd < partStart) {
// we can't even read the sizes with sz[]! That's a failure.
return false;
}
for (int p = 0; p < lastPart; ++p) {
InputBuffer szb = input.peekBytes(3, sz);
final int psize = szb[0] | (szb[1] << 8) | (szb[2] << 16);
int partEnd = partStart + psize;
if (partEnd > bufEnd) {
partEnd = bufEnd;
}
InputBuffer pin = input.subset(partEnd - partStart, position: partStart);
_partitions[p] = VP8BitReader(pin);
partStart = partEnd;
sz += 3;
}
InputBuffer pin =
input.subset(bufEnd - partStart, position: input.position + partStart);
_partitions[lastPart] = VP8BitReader(pin);
// Init is ok, but there's not enough data
return (partStart < bufEnd) ? true : false;
}
void _parseQuant() {
final int base_q0 = br.getValue(7);
final int dqy1_dc = br.get() != 0 ? br.getSignedValue(4) : 0;
final int dqy2_dc = br.get() != 0 ? br.getSignedValue(4) : 0;
final int dqy2_ac = br.get() != 0 ? br.getSignedValue(4) : 0;
final int dquv_dc = br.get() != 0 ? br.getSignedValue(4) : 0;
final int dquv_ac = br.get() != 0 ? br.getSignedValue(4) : 0;
VP8SegmentHeader hdr = _segmentHeader;
for (int i = 0; i < NUM_MB_SEGMENTS; ++i) {
int q;
if (hdr.useSegment) {
q = hdr.quantizer[i];
if (!hdr.absoluteDelta) {
q += base_q0;
}
} else {
if (i > 0) {
_dqm[i] = _dqm[0];
continue;
} else {
q = base_q0;
}
}
VP8QuantMatrix m = _dqm[i];
m.y1Mat[0] = DC_TABLE[_clip(q + dqy1_dc, 127)];
m.y1Mat[1] = AC_TABLE[_clip(q + 0, 127)];
m.y2Mat[0] = DC_TABLE[_clip(q + dqy2_dc, 127)] * 2;
// For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16.
// The smallest precision for that is '(x*6349) >> 12' but 16 is a good
// word size.
m.y2Mat[1] = (AC_TABLE[_clip(q + dqy2_ac, 127)] * 101581) >> 16;
if (m.y2Mat[1] < 8) {
m.y2Mat[1] = 8;
}
m.uvMat[0] = DC_TABLE[_clip(q + dquv_dc, 117)];
m.uvMat[1] = AC_TABLE[_clip(q + dquv_ac, 127)];
m.uvQuant = q + dquv_ac; // for dithering strength evaluation
}
}
void _parseProba() {
VP8Proba proba = _proba;
for (int t = 0; t < NUM_TYPES; ++t) {
for (int b = 0; b < NUM_BANDS; ++b) {
for (int c = 0; c < NUM_CTX; ++c) {
for (int p = 0; p < NUM_PROBAS; ++p) {
final int v = br.getBit(COEFFS_UPDATE_PROBA[t][b][c][p]) != 0
? br.getValue(8)
: COEFFS_PROBA_0[t][b][c][p];
proba.bands[t][b].probas[c][p] = v;
}
}
}
}
_useSkipProba = br.get() != 0;
if (_useSkipProba) {
_skipP = br.getValue(8);
}
}
/// Precompute the filtering strength for each segment and each i4x4/i16x16
/// mode.
void _precomputeFilterStrengths() {
if (_filterType > 0) {
VP8FilterHeader hdr = _filterHeader;
for (int s = 0; s < NUM_MB_SEGMENTS; ++s) {
// First, compute the initial level
int baseLevel;
if (_segmentHeader.useSegment) {
baseLevel = _segmentHeader.filterStrength[s];
if (!_segmentHeader.absoluteDelta) {
baseLevel += hdr.level;
}
} else {
baseLevel = hdr.level;
}
for (int i4x4 = 0; i4x4 <= 1; ++i4x4) {
VP8FInfo info = _fStrengths[s][i4x4];
int level = baseLevel;
if (hdr.useLfDelta) {
level += hdr.refLfDelta[0];
if (i4x4 != 0) {
level += hdr.modeLfDelta[0];
}
}
level = (level < 0) ? 0 : (level > 63) ? 63 : level;
if (level > 0) {
int ilevel = level;
if (hdr.sharpness > 0) {
if (hdr.sharpness > 4) {
ilevel >>= 2;
} else {
ilevel >>= 1;
}
if (ilevel > 9 - hdr.sharpness) {
ilevel = 9 - hdr.sharpness;
}
}
if (ilevel < 1) {
ilevel = 1;
}
info.fInnerLevel = ilevel;
info.fLimit = 2 * level + ilevel;
info.hevThresh = (level >= 40) ? 2 : (level >= 15) ? 1 : 0;
} else {
info.fLimit = 0; // no filtering
}
info.fInner = i4x4 != 0;
}
}
}
}
bool _initFrame() {
if (_webp.alphaData != null) {
_alphaData = _webp.alphaData;
}
_fStrengths = List<List<VP8FInfo>>(NUM_MB_SEGMENTS);
for (int i = 0; i < NUM_MB_SEGMENTS; ++i) {
_fStrengths[i] = [new VP8FInfo(), new VP8FInfo()];
}
_yuvT = List<VP8TopSamples>(_mbWidth);
for (int i = 0; i < _mbWidth; ++i) {
_yuvT[i] = VP8TopSamples();
}
_yuvBlock = Uint8List(YUV_SIZE);
_intraT = Uint8List(4 * _mbWidth);
_cacheYStride = 16 * _mbWidth;
_cacheUVStride = 8 * _mbWidth;
final int extra_rows = FILTER_EXTRA_ROWS[_filterType];
final int extra_y = extra_rows * _cacheYStride;
final int extra_uv = (extra_rows ~/ 2) * _cacheUVStride;
_cacheY = InputBuffer(new Uint8List(16 * _cacheYStride + extra_y),
offset: extra_y);
_cacheU = InputBuffer(new Uint8List(8 * _cacheUVStride + extra_uv),
offset: extra_uv);
_cacheV = InputBuffer(new Uint8List(8 * _cacheUVStride + extra_uv),
offset: extra_uv);
_tmpY = InputBuffer(new Uint8List(webp.width));
final int uvWidth = (webp.width + 1) >> 1;
_tmpU = InputBuffer(new Uint8List(uvWidth));
_tmpV = InputBuffer(new Uint8List(uvWidth));
// Define the area where we can skip in-loop filtering, in case of cropping.
//
// 'Simple' filter reads two luma samples outside of the macroblock
// and filters one. It doesn't filter the chroma samples. Hence, we can
// avoid doing the in-loop filtering before crop_top/crop_left position.
// For the 'Complex' filter, 3 samples are read and up to 3 are filtered.
// Means: there's a dependency chain that goes all the way up to the
// top-left corner of the picture (MB #0). We must filter all the previous
// macroblocks.
{
final int extraPixels = FILTER_EXTRA_ROWS[_filterType];
if (_filterType == 2) {
// For complex filter, we need to preserve the dependency chain.
_tlMbX = 0;
_tlMbY = 0;
} else {
// For simple filter, we can filter only the cropped region.
// We include 'extra_pixels' on the other side of the boundary, since
// vertical or horizontal filtering of the previous macroblock can
// modify some abutting pixels.
_tlMbX = (_cropLeft - extraPixels) ~/ 16;
_tlMbY = (_cropTop - extraPixels) ~/ 16;
if (_tlMbX < 0) {
_tlMbX = 0;
}
if (_tlMbY < 0) {
_tlMbY = 0;
}
}
// We need some 'extra' pixels on the right/bottom.
_brMbY = (_cropBottom + 15 + extraPixels) ~/ 16;
_brMbX = (_cropRight + 15 + extraPixels) ~/ 16;
if (_brMbX > _mbWidth) {
_brMbX = _mbWidth;
}
if (_brMbY > _mbHeight) {
_brMbY = _mbHeight;
}
}
_mbInfo = List<VP8MB>(_mbWidth + 1);
_mbData = List<VP8MBData>(_mbWidth);
_fInfo = List<VP8FInfo>(_mbWidth);
for (int i = 0; i < _mbWidth; ++i) {
_mbInfo[i] = VP8MB();
_mbData[i] = VP8MBData();
}
_mbInfo[_mbWidth] = VP8MB();
_precomputeFilterStrengths();
// Init critical function pointers and look-up tables.
_dsp = VP8Filter();
return true;
}
bool _parseFrame() {
for (_mbY = 0; _mbY < _brMbY; ++_mbY) {
// Parse bitstream for this row.
VP8BitReader tokenBr = _partitions[_mbY & (_numPartitions - 1)];
for (; _mbX < _mbWidth; ++_mbX) {
if (!_decodeMB(tokenBr)) {
return false;
}
}
// Prepare for next scanline
VP8MB left = _mbInfo[0];
left.nz = 0;
left.nzDc = 0;
_intraL.fillRange(0, _intraL.length, B_DC_PRED);
_mbX = 0;
// Reconstruct, filter and emit the row.
if (!_processRow()) {
return false;
}
}
return true;
}
bool _processRow() {
_reconstructRow();
bool useFilter = (_filterType > 0) && (_mbY >= _tlMbY) && (_mbY <= _brMbY);
return _finishRow(useFilter);
}
void _reconstructRow() {
int mb_y = _mbY;
InputBuffer y_dst = InputBuffer(_yuvBlock, offset: Y_OFF);
InputBuffer u_dst = InputBuffer(_yuvBlock, offset: U_OFF);
InputBuffer v_dst = InputBuffer(_yuvBlock, offset: V_OFF);
for (int mb_x = 0; mb_x < _mbWidth; ++mb_x) {
VP8MBData block = _mbData[mb_x];
// Rotate in the left samples from previously decoded block. We move four
// pixels at a time for alignment reason, and because of in-loop filter.
if (mb_x > 0) {
for (int j = -1; j < 16; ++j) {
y_dst.memcpy(j * BPS - 4, 4, y_dst, j * BPS + 12);
}
for (int j = -1; j < 8; ++j) {
u_dst.memcpy(j * BPS - 4, 4, u_dst, j * BPS + 4);
v_dst.memcpy(j * BPS - 4, 4, v_dst, j * BPS + 4);
}
} else {
for (int j = 0; j < 16; ++j) {
y_dst[j * BPS - 1] = 129;
}
for (int j = 0; j < 8; ++j) {
u_dst[j * BPS - 1] = 129;
v_dst[j * BPS - 1] = 129;
}
// Init top-left sample on left column too
if (mb_y > 0) {
y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129;
}
}
// bring top samples into the cache
VP8TopSamples top_yuv = _yuvT[mb_x];
Int16List coeffs = block.coeffs;
int bits = block.nonZeroY;
if (mb_y > 0) {
y_dst.memcpy(-BPS, 16, top_yuv.y);
u_dst.memcpy(-BPS, 8, top_yuv.u);
v_dst.memcpy(-BPS, 8, top_yuv.v);
} else if (mb_x == 0) {
// we only need to do this init once at block (0,0).
// Afterward, it remains valid for the whole topmost row.
y_dst.memset(-BPS - 1, 16 + 4 + 1, 127);
u_dst.memset(-BPS - 1, 8 + 1, 127);
v_dst.memset(-BPS - 1, 8 + 1, 127);
}
// predict and add residuals
if (block.isIntra4x4) {
// 4x4
InputBuffer topRight = InputBuffer.from(y_dst, offset: -BPS + 16);
Uint32List topRight32 = topRight.toUint32List();
if (mb_y > 0) {
if (mb_x >= _mbWidth - 1) {
// on rightmost border
topRight.memset(0, 4, top_yuv.y[15]);
} else {
topRight.memcpy(0, 4, _yuvT[mb_x + 1].y);
}
}
// replicate the top-right pixels below
int p = topRight32[0];
topRight32[3 * BPS] = p;
topRight32[2 * BPS] = p;
topRight32[BPS] = p;
// predict and add residuals for all 4x4 blocks in turn.
for (int n = 0; n < 16; ++n, bits = (bits << 2) & 0xffffffff) {
InputBuffer dst = InputBuffer.from(y_dst, offset: kScan[n]);
VP8Filter.PredLuma4[block.imodes[n]](dst);
_doTransform(bits, new InputBuffer(coeffs, offset: n * 16), dst);
}
} else {
// 16x16
int predFunc = _checkMode(mb_x, mb_y, block.imodes[0]);
VP8Filter.PredLuma16[predFunc](y_dst);
if (bits != 0) {
for (int n = 0; n < 16; ++n, bits = (bits << 2) & 0xffffffff) {
InputBuffer dst = InputBuffer.from(y_dst, offset: kScan[n]);
_doTransform(bits, new InputBuffer(coeffs, offset: n * 16), dst);
}
}
}
// Chroma
int bits_uv = block.nonZeroUV;
int pred_func = _checkMode(mb_x, mb_y, block.uvmode);
VP8Filter.PredChroma8[pred_func](u_dst);
VP8Filter.PredChroma8[pred_func](v_dst);
InputBuffer c1 = InputBuffer(coeffs, offset: 16 * 16);
_doUVTransform(bits_uv, c1, u_dst);
InputBuffer c2 = InputBuffer(coeffs, offset: 20 * 16);
_doUVTransform(bits_uv >> 8, c2, v_dst);
// stash away top samples for next block
if (mb_y < _mbHeight - 1) {
top_yuv.y.setRange(0, 16, y_dst.toUint8List(), 15 * BPS);
top_yuv.u.setRange(0, 8, u_dst.toUint8List(), 7 * BPS);
top_yuv.v.setRange(0, 8, v_dst.toUint8List(), 7 * BPS);
}
// Transfer reconstructed samples from yuv_b_ cache to final destination.
int y_out = mb_x * 16; // dec->cache_y_ +
int u_out = mb_x * 8; // dec->cache_u_ +
int v_out = mb_x * 8; // _dec->cache_v_ +
for (int j = 0; j < 16; ++j) {
int start = y_out + j * _cacheYStride;
_cacheY.memcpy(start, 16, y_dst, j * BPS);
}
for (int j = 0; j < 8; ++j) {
int start = u_out + j * _cacheUVStride;
_cacheU.memcpy(start, 8, u_dst, j * BPS);
start = v_out + j * _cacheUVStride;
_cacheV.memcpy(start, 8, v_dst, j * BPS);
}
}
}
static const List<int> kScan = const [
0 + 0 * BPS,
4 + 0 * BPS,
8 + 0 * BPS,
12 + 0 * BPS,
0 + 4 * BPS,
4 + 4 * BPS,
8 + 4 * BPS,
12 + 4 * BPS,
0 + 8 * BPS,
4 + 8 * BPS,
8 + 8 * BPS,
12 + 8 * BPS,
0 + 12 * BPS,
4 + 12 * BPS,
8 + 12 * BPS,
12 + 12 * BPS
];
static int _checkMode(int mb_x, int mb_y, int mode) {
if (mode == B_DC_PRED) {
if (mb_x == 0) {
return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT;
} else {
return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED;
}
}
return mode;
}
void _doTransform(int bits, InputBuffer src, InputBuffer dst) {
switch (bits >> 30) {
case 3:
_dsp.transform(src, dst, false);
break;
case 2:
_dsp.transformAC3(src, dst);
break;
case 1:
_dsp.transformDC(src, dst);
break;
default:
break;
}
}
void _doUVTransform(int bits, InputBuffer src, InputBuffer dst) {
if (bits & 0xff != 0) {
// any non-zero coeff at all?
if (bits & 0xaa != 0) {
// any non-zero AC coefficient?
// note we don't use the AC3 variant for U/V
_dsp.transformUV(src, dst);
} else {
_dsp.transformDCUV(src, dst);
}
}
}
// vertical position of a MB
int MACROBLOCK_VPOS(int mb_y) => mb_y * 16;
/// kFilterExtraRows[] = How many extra lines are needed on the MB boundary
/// for caching, given a filtering level.
/// Simple filter: up to 2 luma samples are read and 1 is written.
/// Complex filter: up to 4 luma samples are read and 3 are written. Same for
/// U/V, so it's 8 samples total (because of the 2x upsampling).
static const List<int> kFilterExtraRows = const [0, 2, 8];
void _doFilter(int mbX, int mbY) {
final int yBps = _cacheYStride;
VP8FInfo fInfo = _fInfo[mbX];
InputBuffer yDst = InputBuffer.from(_cacheY, offset: mbX * 16);
final int ilevel = fInfo.fInnerLevel;
final int limit = fInfo.fLimit;
if (limit == 0) {
return;
}
if (_filterType == 1) {
// simple
if (mbX > 0) {
_dsp.simpleHFilter16(yDst, yBps, limit + 4);
}
if (fInfo.fInner) {
_dsp.simpleHFilter16i(yDst, yBps, limit);
}
if (mbY > 0) {
_dsp.simpleVFilter16(yDst, yBps, limit + 4);
}
if (fInfo.fInner) {
_dsp.simpleVFilter16i(yDst, yBps, limit);
}
} else {
// complex
final int uvBps = _cacheUVStride;
InputBuffer uDst = InputBuffer.from(_cacheU, offset: mbX * 8);
InputBuffer vDst = InputBuffer.from(_cacheV, offset: mbX * 8);
final int hevThresh = fInfo.hevThresh;
if (mbX > 0) {
_dsp.hFilter16(yDst, yBps, limit + 4, ilevel, hevThresh);
_dsp.hFilter8(uDst, vDst, uvBps, limit + 4, ilevel, hevThresh);
}
if (fInfo.fInner) {
_dsp.hFilter16i(yDst, yBps, limit, ilevel, hevThresh);
_dsp.hFilter8i(uDst, vDst, uvBps, limit, ilevel, hevThresh);
}
if (mbY > 0) {
_dsp.vFilter16(yDst, yBps, limit + 4, ilevel, hevThresh);
_dsp.vFilter8(uDst, vDst, uvBps, limit + 4, ilevel, hevThresh);
}
if (fInfo.fInner) {
_dsp.vFilter16i(yDst, yBps, limit, ilevel, hevThresh);
_dsp.vFilter8i(uDst, vDst, uvBps, limit, ilevel, hevThresh);
}
}
}
/// Filter the decoded macroblock row (if needed)
void _filterRow() {
for (int mbX = _tlMbX; mbX < _brMbX; ++mbX) {
_doFilter(mbX, _mbY);
}
}
void _ditherRow() {}
/// This function is called after a row of macroblocks is finished decoding.
/// It also takes into account the following restrictions:
///
/// * In case of in-loop filtering, we must hold off sending some of the bottom
/// pixels as they are yet unfiltered. They will be when the next macroblock
/// row is decoded. Meanwhile, we must preserve them by rotating them in the
/// cache area. This doesn't hold for the very bottom row of the uncropped
/// picture of course.
/// * we must clip the remaining pixels against the cropping area. The VP8Io
/// struct must have the following fields set correctly before calling put():
bool _finishRow(bool useFilter) {
final int extraYRows = kFilterExtraRows[_filterType];
final int ySize = extraYRows * _cacheYStride;
final int uvSize = (extraYRows ~/ 2) * _cacheUVStride;
InputBuffer yDst = InputBuffer.from(_cacheY, offset: -ySize);
InputBuffer uDst = InputBuffer.from(_cacheU, offset: -uvSize);
InputBuffer vDst = InputBuffer.from(_cacheV, offset: -uvSize);
final int mbY = _mbY;
final bool isFirstRow = (mbY == 0);
final bool isLastRow = (mbY >= _brMbY - 1);
int yStart = MACROBLOCK_VPOS(mbY);
int yEnd = MACROBLOCK_VPOS(mbY + 1);
if (useFilter) {
_filterRow();
}
if (_dither) {
_ditherRow();
}
if (!isFirstRow) {
yStart -= extraYRows;
_y = InputBuffer.from(yDst);
_u = InputBuffer.from(uDst);
_v = InputBuffer.from(vDst);
} else {
_y = InputBuffer.from(_cacheY);
_u = InputBuffer.from(_cacheU);
_v = InputBuffer.from(_cacheV);
}
if (!isLastRow) {
yEnd -= extraYRows;
}
if (yEnd > _cropBottom) {
yEnd = _cropBottom; // make sure we don't overflow on last row.
}
_a = null;
if (_alphaData != null && yStart < yEnd) {
_a = _decompressAlphaRows(yStart, yEnd - yStart);
if (_a == null) {
return false;
}
}
if (yStart < _cropTop) {
final int deltaY = _cropTop - yStart;
yStart = _cropTop;
_y.offset += _cacheYStride * deltaY;
_u.offset += _cacheUVStride * (deltaY >> 1);
_v.offset += _cacheUVStride * (deltaY >> 1);
if (_a != null) {
_a.offset += webp.width * deltaY;
}
}
if (yStart < yEnd) {
_y.offset += _cropLeft;
_u.offset += _cropLeft >> 1;
_v.offset += _cropLeft >> 1;
if (_a != null) {
_a.offset += _cropLeft;
}
_put(yStart - _cropTop, _cropRight - _cropLeft, yEnd - yStart);
}
// rotate top samples if needed
if (!isLastRow) {
_cacheY.memcpy(-ySize, ySize, yDst, 16 * _cacheYStride);
_cacheU.memcpy(-uvSize, uvSize, uDst, 8 * _cacheUVStride);
_cacheV.memcpy(-uvSize, uvSize, vDst, 8 * _cacheUVStride);
}
return true;
}
bool _put(int mbY, int mbW, int mbH) {
if (mbW <= 0 || mbH <= 0) {
return false;
}
/*int numLinesOut =*/ _emitFancyRGB(mbY, mbW, mbH);
_emitAlphaRGB(mbY, mbW, mbH);
//_lastY += numLinesOut;
return true;
}
int _clip8(int v) {
int d = ((v & XOR_YUV_MASK2) == 0) ? (v >> YUV_FIX2) : (v < 0) ? 0 : 255;
return d;
}
int _yuvToR(int y, int v) {
return _clip8(kYScale * y + kVToR * v + kRCst);
}
int _yuvToG(int y, int u, int v) {
return _clip8(kYScale * y - kUToG * u - kVToG * v + kGCst);
}
int _yuvToB(int y, int u) {
return _clip8(kYScale * y + kUToB * u + kBCst);
}
void _yuvToRgb(int y, int u, int v, InputBuffer rgb) {
rgb[2] = _yuvToR(y, v);
rgb[1] = _yuvToG(y, u, v);
rgb[0] = _yuvToB(y, u);
}
void _yuvToRgba(int y, int u, int v, InputBuffer rgba) {
_yuvToRgb(y, u, v, rgba);
rgba[3] = 0xff;
}
void _upsample(
InputBuffer topY,
InputBuffer bottomY,
InputBuffer topU,
InputBuffer topV,
InputBuffer curU,
InputBuffer curV,
InputBuffer topDst,
InputBuffer bottomDst,
int len) {
int LOAD_UV(int u, int v) => ((u) | ((v) << 16));
final int lastPixelPair = (len - 1) >> 1;
int tl_uv = LOAD_UV(topU[0], topV[0]); // top-left sample
int l_uv = LOAD_UV(curU[0], curV[0]); // left-sample
final int uv0 = (3 * tl_uv + l_uv + 0x00020002) >> 2;
_yuvToRgba(topY[0], uv0 & 0xff, (uv0 >> 16), topDst);
if (bottomY != null) {
final int uv0 = (3 * l_uv + tl_uv + 0x00020002) >> 2;
_yuvToRgba(bottomY[0], uv0 & 0xff, (uv0 >> 16), bottomDst);
}
for (int x = 1; x <= lastPixelPair; ++x) {
final int t_uv = LOAD_UV(topU[x], topV[x]); // top sample
final int uv = LOAD_UV(curU[x], curV[x]); // sample
// precompute invariant values associated with first and second diagonals
final int avg = tl_uv + t_uv + l_uv + uv + 0x00080008;
final int diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3;
final int diag_03 = (avg + 2 * (tl_uv + uv)) >> 3;
int uv0 = (diag_12 + tl_uv) >> 1;
int uv1 = (diag_03 + t_uv) >> 1;
_yuvToRgba(topY[2 * x - 1], uv0 & 0xff, (uv0 >> 16),
new InputBuffer.from(topDst, offset: (2 * x - 1) * 4));
_yuvToRgba(topY[2 * x - 0], uv1 & 0xff, (uv1 >> 16),
new InputBuffer.from(topDst, offset: (2 * x - 0) * 4));
if (bottomY != null) {
uv0 = (diag_03 + l_uv) >> 1;
uv1 = (diag_12 + uv) >> 1;
_yuvToRgba(bottomY[2 * x - 1], uv0 & 0xff, (uv0 >> 16),
new InputBuffer.from(bottomDst, offset: (2 * x - 1) * 4));
_yuvToRgba(bottomY[2 * x], uv1 & 0xff, (uv1 >> 16),
new InputBuffer.from(bottomDst, offset: (2 * x + 0) * 4));
}
tl_uv = t_uv;
l_uv = uv;
}
if ((len & 1) == 0) {
final int uv0 = (3 * tl_uv + l_uv + 0x00020002) >> 2;
_yuvToRgba(topY[len - 1], uv0 & 0xff, (uv0 >> 16),
new InputBuffer.from(topDst, offset: (len - 1) * 4));
if (bottomY != null) {
final int uv0 = (3 * l_uv + tl_uv + 0x00020002) >> 2;
_yuvToRgba(bottomY[len - 1], uv0 & 0xff, (uv0 >> 16),
new InputBuffer.from(bottomDst, offset: (len - 1) * 4));
}
}
}
void _emitAlphaRGB(int mbY, int mbW, int mbH) {
if (_a == null) {
return;
}
final int stride = webp.width * 4;
InputBuffer alpha = InputBuffer.from(_a);
int startY = mbY;
int numRows = mbH;
// Compensate for the 1-line delay of the fancy upscaler.
// This is similar to EmitFancyRGB().
if (startY == 0) {
// We don't process the last row yet. It'll be done during the next call.
--numRows;
} else {
--startY;
// Fortunately, *alpha data is persistent, so we can go back
// one row and finish alpha blending, now that the fancy upscaler
// completed the YUV->RGB interpolation.
alpha.offset -= webp.width;
}
InputBuffer dst =
InputBuffer(output.getBytes(), offset: startY * stride + 3);
if (_cropTop + mbY + mbH == _cropBottom) {
// If it's the very last call, we process all the remaining rows!
numRows = _cropBottom - _cropTop - startY;
}
for (int y = 0; y < numRows; ++y) {
for (int x = 0; x < mbW; ++x) {
final int alphaValue = alpha[x];
dst[4 * x] = alphaValue & 0xff;
}
alpha.offset += webp.width;
dst.offset += stride;
}
}
int _emitFancyRGB(int mbY, int mbW, int mbH) {
int numLinesOut = mbH; // a priori guess
InputBuffer dst =
InputBuffer(output.getBytes(), offset: mbY * webp.width * 4);
InputBuffer curY = InputBuffer.from(_y);
InputBuffer curU = InputBuffer.from(_u);
InputBuffer curV = InputBuffer.from(_v);
int y = mbY;
final int yEnd = mbY + mbH;
final int uvW = (mbW + 1) >> 1;
final int stride = webp.width * 4;
InputBuffer topU = InputBuffer.from(_tmpU);
InputBuffer topV = InputBuffer.from(_tmpV);
if (y == 0) {
// First line is special cased. We mirror the u/v samples at boundary.
_upsample(curY, null, curU, curV, curU, curV, dst, null, mbW);
} else {
// We can finish the left-over line from previous call.
_upsample(_tmpY, curY, topU, topV, curU, curV,
new InputBuffer.from(dst, offset: -stride), dst, mbW);
++numLinesOut;
}
// Loop over each output pairs of row.
topU.buffer = curU.buffer;
topV.buffer = curV.buffer;
for (; y + 2 < yEnd; y += 2) {
topU.offset = curU.offset;
topV.offset = curV.offset;
curU.offset += _cacheUVStride;
curV.offset += _cacheUVStride;
dst.offset += 2 * stride;
curY.offset += 2 * _cacheYStride;
_upsample(
new InputBuffer.from(curY, offset: -_cacheYStride),
curY,
topU,
topV,
curU,
curV,
new InputBuffer.from(dst, offset: -stride),
dst,
mbW);
}
// move to last row
curY.offset += _cacheYStride;
if (_cropTop + yEnd < _cropBottom) {
// Save the unfinished samples for next call (as we're not done yet).
_tmpY.memcpy(0, mbW, curY);
_tmpU.memcpy(0, uvW, curU);
_tmpV.memcpy(0, uvW, curV);
// The fancy upsampler leaves a row unfinished behind
// (except for the very last row)
numLinesOut--;
} else {
// Process the very last row of even-sized picture
if ((yEnd & 1) == 0) {
_upsample(curY, null, curU, curV, curU, curV,
new InputBuffer.from(dst, offset: stride), null, mbW);
}
}
return numLinesOut;
}
InputBuffer _decompressAlphaRows(int row, int numRows) {
final int width = webp.width;
final int height = webp.height;
if (row < 0 || numRows <= 0 || row + numRows > height) {
return null; // sanity check.
}
if (row == 0) {
_alphaPlane = Uint8List(width * height);
_alpha = WebPAlpha(_alphaData, width, height);
}
if (!_alpha.isAlphaDecoded) {
if (!_alpha.decode(row, numRows, _alphaPlane)) {
return null;
}
}
// Return a pointer to the current decoded row.
return new InputBuffer(_alphaPlane, offset: row * width);
}
bool _decodeMB(VP8BitReader tokenBr) {
VP8MB left = _mbInfo[0];
VP8MB mb = _mbInfo[1 + _mbX];
VP8MBData block = _mbData[_mbX];
bool skip;
// Note: we don't save segment map (yet), as we don't expect
// to decode more than 1 keyframe.
if (_segmentHeader.updateMap) {
// Hardcoded tree parsing
_segment = br.getBit(_proba.segments[0]) == 0
? br.getBit(_proba.segments[1])
: 2 + br.getBit(_proba.segments[2]);
}
skip = _useSkipProba ? br.getBit(_skipP) != 0 : false;
_parseIntraMode();
if (!skip) {
skip = _parseResiduals(mb, tokenBr);
} else {
left.nz = mb.nz = 0;
if (!block.isIntra4x4) {
left.nzDc = mb.nzDc = 0;
}
block.nonZeroY = 0;
block.nonZeroUV = 0;
}
if (_filterType > 0) {
// store filter info
_fInfo[_mbX] = _fStrengths[_segment][block.isIntra4x4 ? 1 : 0];
VP8FInfo finfo = _fInfo[_mbX];
finfo.fInner = finfo.fInner || !skip;
}
return true;
}
bool _parseResiduals(VP8MB mb, VP8BitReader tokenBr) {
var bands = _proba.bands;
List<VP8BandProbas> acProba;
VP8QuantMatrix q = _dqm[_segment];
VP8MBData block = _mbData[_mbX];
InputBuffer dst = InputBuffer(block.coeffs);
//int di = 0;
VP8MB leftMb = _mbInfo[0];
int tnz;
int lnz;
int nonZeroY = 0;
int nonZeroUV = 0;
int outTopNz;
int outLeftNz;
int first;
dst.memset(0, dst.length, 0);
if (!block.isIntra4x4) {
// parse DC
InputBuffer dc = InputBuffer(new Int16List(16));
final int ctx = mb.nzDc + leftMb.nzDc;
final int nz = _getCoeffs(tokenBr, bands[1], ctx, q.y2Mat, 0, dc);
mb.nzDc = leftMb.nzDc = (nz > 0) ? 1 : 0;
if (nz > 1) {
// more than just the DC -> perform the full transform
_transformWHT(dc, dst);
} else {
// only DC is non-zero -> inlined simplified transform
final int dc0 = (dc[0] + 3) >> 3;
for (int i = 0; i < 16 * 16; i += 16) {
dst[i] = dc0;
}
}
first = 1;
acProba = bands[0];
} else {
first = 0;
acProba = bands[3];
}
tnz = mb.nz & 0x0f;
lnz = leftMb.nz & 0x0f;
for (int y = 0; y < 4; ++y) {
int l = lnz & 1;
int nzCoeffs = 0;
for (int x = 0; x < 4; ++x) {
final int ctx = l + (tnz & 1);
final int nz = _getCoeffs(tokenBr, acProba, ctx, q.y1Mat, first, dst);
l = (nz > first) ? 1 : 0;
tnz = (tnz >> 1) | (l << 7);
nzCoeffs = _nzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0);
dst.offset += 16;
}
tnz >>= 4;
lnz = (lnz >> 1) | (l << 7);
nonZeroY = (nonZeroY << 8) | nzCoeffs;
}
outTopNz = tnz;
outLeftNz = lnz >> 4;
for (int ch = 0; ch < 4; ch += 2) {
int nzCoeffs = 0;
tnz = mb.nz >> (4 + ch);
lnz = leftMb.nz >> (4 + ch);
for (int y = 0; y < 2; ++y) {
int l = lnz & 1;
for (int x = 0; x < 2; ++x) {
final int ctx = l + (tnz & 1);
final int nz = _getCoeffs(tokenBr, bands[2], ctx, q.uvMat, 0, dst);
l = (nz > 0) ? 1 : 0;
tnz = (tnz >> 1) | (l << 3);
nzCoeffs = _nzCodeBits(nzCoeffs, nz, dst[0] != 0 ? 1 : 0);
dst.offset += 16;
}
tnz >>= 2;
lnz = (lnz >> 1) | (l << 5);
}
// Note: we don't really need the per-4x4 details for U/V blocks.
nonZeroUV |= nzCoeffs << (4 * ch);
outTopNz |= (tnz << 4) << ch;
outLeftNz |= (lnz & 0xf0) << ch;
}
mb.nz = outTopNz;
leftMb.nz = outLeftNz;
block.nonZeroY = nonZeroY;
block.nonZeroUV = nonZeroUV;
// We look at the mode-code of each block and check if some blocks have less
// than three non-zero coeffs (code < 2). This is to avoid dithering flat and
// empty blocks.
block.dither = (nonZeroUV & 0xaaaa) != 0 ? 0 : q.dither;
// will be used for further optimization
return (nonZeroY | nonZeroUV) == 0;
}
void _transformWHT(InputBuffer src, InputBuffer out) {
Int32List tmp = Int32List(16);
int oi = 0;
for (int i = 0; i < 4; ++i) {
final int a0 = src[0 + i] + src[12 + i];
final int a1 = src[4 + i] + src[8 + i];
final int a2 = src[4 + i] - src[8 + i];
final int a3 = src[0 + i] - src[12 + i];
tmp[0 + i] = a0 + a1;
tmp[8 + i] = a0 - a1;
tmp[4 + i] = a3 + a2;
tmp[12 + i] = a3 - a2;
}
for (int i = 0; i < 4; ++i) {
final int dc = tmp[0 + i * 4] + 3; // w/ rounder
final int a0 = dc + tmp[3 + i * 4];
final int a1 = tmp[1 + i * 4] + tmp[2 + i * 4];
final int a2 = tmp[1 + i * 4] - tmp[2 + i * 4];
final int a3 = dc - tmp[3 + i * 4];
out[oi + 0] = (a0 + a1) >> 3;
out[oi + 16] = (a3 + a2) >> 3;
out[oi + 32] = (a0 - a1) >> 3;
out[oi + 48] = (a3 - a2) >> 3;
oi += 64;
}
}
int _nzCodeBits(int nz_coeffs, int nz, int dc_nz) {
nz_coeffs <<= 2;
nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz;
return nz_coeffs;
}
static const List<int> kBands = const [
0,
1,
2,
3,
6,
4,
5,
6,
6,
6,
6,
6,
6,
6,
6,
7,
0
];
static const List<int> kCat3 = const [173, 148, 140];
static const List<int> kCat4 = const [176, 155, 140, 135];
static const List<int> kCat5 = const [180, 157, 141, 134, 130];
static const List<int> kCat6 = const [
254,
254,
243,
230,
196,
177,
153,
140,
133,
130,
129
];
static const List<List<int>> kCat3456 = const [kCat3, kCat4, kCat5, kCat6];
static const List<int> kZigzag = const [
0,
1,
4,
8,
5,
2,
3,
6,
9,
12,
13,
10,
7,
11,
14,
15
];
/// See section 13-2: http://tools.ietf.org/html/rfc6386#section-13.2
int _getLargeValue(VP8BitReader br, List<int> p) {
int v;
if (br.getBit(p[3]) == 0) {
if (br.getBit(p[4]) == 0) {
v = 2;
} else {
v = 3 + br.getBit(p[5]);
}
} else {
if (br.getBit(p[6]) == 0) {
if (br.getBit(p[7]) == 0) {
v = 5 + br.getBit(159);
} else {
v = 7 + 2 * br.getBit(165);
v += br.getBit(145);
}
} else {
final int bit1 = br.getBit(p[8]);
final int bit0 = br.getBit(p[9 + bit1]);
final int cat = 2 * bit1 + bit0;
v = 0;
List<int> tab = kCat3456[cat];
for (int i = 0, len = tab.length; i < len; ++i) {
v += v + br.getBit(tab[i]);
}
v += 3 + (8 << cat);
}
}
return v;
}
/// Returns the position of the last non-zero coeff plus one
int _getCoeffs(VP8BitReader br, List<VP8BandProbas> prob, int ctx,
List<int> dq, int n, InputBuffer out) {
// n is either 0 or 1 here. kBands[n] is not necessary for extracting '*p'.
List<int> p = prob[n].probas[ctx];
for (; n < 16; ++n) {
if (br.getBit(p[0]) == 0) {
return n; // previous coeff was last non-zero coeff
}
while (br.getBit(p[1]) == 0) {
// sequence of zero coeffs
p = prob[kBands[++n]].probas[0];
if (n == 16) {
return 16;
}
}
{
// non zero coeff
List<Uint8List> p_ctx = prob[kBands[n + 1]].probas;
int v;
if (br.getBit(p[2]) == 0) {
v = 1;
p = p_ctx[1];
} else {
v = _getLargeValue(br, p);
p = p_ctx[2];
}
out[kZigzag[n]] = br.getSigned(v) * dq[n > 0 ? 1 : 0];
}
}
return 16;
}
void _parseIntraMode() {
int ti = 4 * _mbX;
int li = 0;
Uint8List top = _intraT;
Uint8List left = _intraL;
VP8MBData block = _mbData[_mbX];
// decide for B_PRED first
block.isIntra4x4 = br.getBit(145) == 0;
if (!block.isIntra4x4) {
// Hardcoded 16x16 intra-mode decision tree.
final int ymode = br.getBit(156) != 0
? (br.getBit(128) != 0 ? TM_PRED : H_PRED)
: (br.getBit(163) != 0 ? V_PRED : DC_PRED);
block.imodes[0] = ymode;
top.fillRange(ti, ti + 4, ymode);
left.fillRange(li, li + 4, ymode);
} else {
Uint8List modes = block.imodes;
int mi = 0;
for (int y = 0; y < 4; ++y) {
int ymode = left[y];
for (int x = 0; x < 4; ++x) {
List<int> prob = kBModesProba[top[ti + x]][ymode];
// Generic tree-parsing
int b = br.getBit(prob[0]);
int i = kYModesIntra4[b];
while (i > 0) {
i = kYModesIntra4[2 * i + br.getBit(prob[i])];
}
ymode = -i;
top[ti + x] = ymode;
}
modes.setRange(mi, mi + 4, top, ti);
mi += 4;
left[y] = ymode;
}
}
// Hardcoded UVMode decision tree
block.uvmode = br.getBit(142) == 0
? DC_PRED
: br.getBit(114) == 0 ? V_PRED : br.getBit(183) != 0 ? TM_PRED : H_PRED;
}
// Main data source
VP8BitReader br;
Image output;
VP8Filter _dsp;
// headers
VP8FrameHeader _frameHeader = VP8FrameHeader();
VP8PictureHeader _picHeader = VP8PictureHeader();
VP8FilterHeader _filterHeader = VP8FilterHeader();
VP8SegmentHeader _segmentHeader = VP8SegmentHeader();
int _cropLeft;
int _cropRight;
int _cropTop;
int _cropBottom;
/// Width in macroblock units.
int _mbWidth;
/// Height in macroblock units.
int _mbHeight;
// Macroblock to process/filter, depending on cropping and filter_type.
int _tlMbX; // top-left MB that must be in-loop filtered
int _tlMbY;
int _brMbX; // last bottom-right MB that must be decoded
int _brMbY;
// number of partitions.
int _numPartitions;
// per-partition boolean decoders.
List<VP8BitReader> _partitions = List<VP8BitReader>(MAX_NUM_PARTITIONS);
// Dithering strength, deduced from decoding options
bool _dither = false; // whether to use dithering or not
//VP8Random _ditheringRand; // random generator for dithering
// dequantization (one set of DC/AC dequant factor per segment)
List<VP8QuantMatrix> _dqm = List<VP8QuantMatrix>(NUM_MB_SEGMENTS);
// probabilities
VP8Proba _proba;
bool _useSkipProba;
int _skipP;
// Boundary data cache and persistent buffers.
/// top intra modes values: 4 * _mbWidth
Uint8List _intraT;
/// left intra modes values
Uint8List _intraL = Uint8List(4);
/// uint8, segment of the currently parsed block
int _segment;
/// top y/u/v samples
List<VP8TopSamples> _yuvT;
/// contextual macroblock info (mb_w_ + 1)
List<VP8MB> _mbInfo;
/// filter strength info
List<VP8FInfo> _fInfo;
/// main block for Y/U/V (size = YUV_SIZE)
Uint8List _yuvBlock;
/// macroblock row for storing unfiltered samples
InputBuffer _cacheY;
InputBuffer _cacheU;
InputBuffer _cacheV;
int _cacheYStride;
int _cacheUVStride;
InputBuffer _tmpY;
InputBuffer _tmpU;
InputBuffer _tmpV;
InputBuffer _y;
InputBuffer _u;
InputBuffer _v;
InputBuffer _a;
/// main memory chunk for the above data. Persistent.
//Uint8List _mem;
// Per macroblock non-persistent infos.
/// current position, in macroblock units
int _mbX = 0;
int _mbY = 0;
/// parsed reconstruction data
List<VP8MBData> _mbData;
/// 0=off, 1=simple, 2=complex
int _filterType;
/// precalculated per-segment/type
List<List<VP8FInfo>> _fStrengths;
// Alpha
/// alpha-plane decoder object
WebPAlpha _alpha;
/// compressed alpha data (if present)
InputBuffer _alphaData;
/// true if alpha_data_ is decoded in alpha_plane_
//int _isAlphaDecoded;
/// output. Persistent, contains the whole data.
Uint8List _alphaPlane;
// extensions
//int _layerColorspace;
/// compressed layer data (if present)
//Uint8List _layerData;
static int _clip(int v, int M) {
return v < 0 ? 0 : v > M ? M : v;
}
static const kYModesIntra4 = [
-B_DC_PRED,
1,
-B_TM_PRED,
2,
-B_VE_PRED,
3,
4,
6,
-B_HE_PRED,
5,
-B_RD_PRED,
-B_VR_PRED,
-B_LD_PRED,
7,
-B_VL_PRED,
8,
-B_HD_PRED,
-B_HU_PRED
];
static const kBModesProba = [
[
[231, 120, 48, 89, 115, 113, 120, 152, 112],
[152, 179, 64, 126, 170, 118, 46, 70, 95],
[175, 69, 143, 80, 85, 82, 72, 155, 103],
[56, 58, 10, 171, 218, 189, 17, 13, 152],
[114, 26, 17, 163, 44, 195, 21, 10, 173],
[121, 24, 80, 195, 26, 62, 44, 64, 85],
[144, 71, 10, 38, 171, 213, 144, 34, 26],
[170, 46, 55, 19, 136, 160, 33, 206, 71],
[63, 20, 8, 114, 114, 208, 12, 9, 226],
[81, 40, 11, 96, 182, 84, 29, 16, 36]
],
[
[134, 183, 89, 137, 98, 101, 106, 165, 148],
[72, 187, 100, 130, 157, 111, 32, 75, 80],
[66, 102, 167, 99, 74, 62, 40, 234, 128],
[41, 53, 9, 178, 241, 141, 26, 8, 107],
[74, 43, 26, 146, 73, 166, 49, 23, 157],
[65, 38, 105, 160, 51, 52, 31, 115, 128],
[104, 79, 12, 27, 217, 255, 87, 17, 7],
[87, 68, 71, 44, 114, 51, 15, 186, 23],
[47, 41, 14, 110, 182, 183, 21, 17, 194],
[66, 45, 25, 102, 197, 189, 23, 18, 22]
],
[
[88, 88, 147, 150, 42, 46, 45, 196, 205],
[43, 97, 183, 117, 85, 38, 35, 179, 61],
[39, 53, 200, 87, 26, 21, 43, 232, 171],
[56, 34, 51, 104, 114, 102, 29, 93, 77],
[39, 28, 85, 171, 58, 165, 90, 98, 64],
[34, 22, 116, 206, 23, 34, 43, 166, 73],
[107, 54, 32, 26, 51, 1, 81, 43, 31],
[68, 25, 106, 22, 64, 171, 36, 225, 114],
[34, 19, 21, 102, 132, 188, 16, 76, 124],
[62, 18, 78, 95, 85, 57, 50, 48, 51]
],
[
[193, 101, 35, 159, 215, 111, 89, 46, 111],
[60, 148, 31, 172, 219, 228, 21, 18, 111],
[112, 113, 77, 85, 179, 255, 38, 120, 114],
[40, 42, 1, 196, 245, 209, 10, 25, 109],
[88, 43, 29, 140, 166, 213, 37, 43, 154],
[61, 63, 30, 155, 67, 45, 68, 1, 209],
[100, 80, 8, 43, 154, 1, 51, 26, 71],
[142, 78, 78, 16, 255, 128, 34, 197, 171],
[41, 40, 5, 102, 211, 183, 4, 1, 221],
[51, 50, 17, 168, 209, 192, 23, 25, 82]
],
[
[138, 31, 36, 171, 27, 166, 38, 44, 229],
[67, 87, 58, 169, 82, 115, 26, 59, 179],
[63, 59, 90, 180, 59, 166, 93, 73, 154],
[40, 40, 21, 116, 143, 209, 34, 39, 175],
[47, 15, 16, 183, 34, 223, 49, 45, 183],
[46, 17, 33, 183, 6, 98, 15, 32, 183],
[57, 46, 22, 24, 128, 1, 54, 17, 37],
[65, 32, 73, 115, 28, 128, 23, 128, 205],
[40, 3, 9, 115, 51, 192, 18, 6, 223],
[87, 37, 9, 115, 59, 77, 64, 21, 47]
],
[
[104, 55, 44, 218, 9, 54, 53, 130, 226],
[64, 90, 70, 205, 40, 41, 23, 26, 57],
[54, 57, 112, 184, 5, 41, 38, 166, 213],
[30, 34, 26, 133, 152, 116, 10, 32, 134],
[39, 19, 53, 221, 26, 114, 32, 73, 255],
[31, 9, 65, 234, 2, 15, 1, 118, 73],
[75, 32, 12, 51, 192, 255, 160, 43, 51],
[88, 31, 35, 67, 102, 85, 55, 186, 85],
[56, 21, 23, 111, 59, 205, 45, 37, 192],
[55, 38, 70, 124, 73, 102, 1, 34, 98]
],
[
[125, 98, 42, 88, 104, 85, 117, 175, 82],
[95, 84, 53, 89, 128, 100, 113, 101, 45],
[75, 79, 123, 47, 51, 128, 81, 171, 1],
[57, 17, 5, 71, 102, 57, 53, 41, 49],
[38, 33, 13, 121, 57, 73, 26, 1, 85],
[41, 10, 67, 138, 77, 110, 90, 47, 114],
[115, 21, 2, 10, 102, 255, 166, 23, 6],
[101, 29, 16, 10, 85, 128, 101, 196, 26],
[57, 18, 10, 102, 102, 213, 34, 20, 43],
[117, 20, 15, 36, 163, 128, 68, 1, 26]
],
[
[102, 61, 71, 37, 34, 53, 31, 243, 192],
[69, 60, 71, 38, 73, 119, 28, 222, 37],
[68, 45, 128, 34, 1, 47, 11, 245, 171],
[62, 17, 19, 70, 146, 85, 55, 62, 70],
[37, 43, 37, 154, 100, 163, 85, 160, 1],
[63, 9, 92, 136, 28, 64, 32, 201, 85],
[75, 15, 9, 9, 64, 255, 184, 119, 16],
[86, 6, 28, 5, 64, 255, 25, 248, 1],
[56, 8, 17, 132, 137, 255, 55, 116, 128],
[58, 15, 20, 82, 135, 57, 26, 121, 40]
],
[
[164, 50, 31, 137, 154, 133, 25, 35, 218],
[51, 103, 44, 131, 131, 123, 31, 6, 158],
[86, 40, 64, 135, 148, 224, 45, 183, 128],
[22, 26, 17, 131, 240, 154, 14, 1, 209],
[45, 16, 21, 91, 64, 222, 7, 1, 197],
[56, 21, 39, 155, 60, 138, 23, 102, 213],
[83, 12, 13, 54, 192, 255, 68, 47, 28],
[85, 26, 85, 85, 128, 128, 32, 146, 171],
[18, 11, 7, 63, 144, 171, 4, 4, 246],
[35, 27, 10, 146, 174, 171, 12, 26, 128]
],
[
[190, 80, 35, 99, 180, 80, 126, 54, 45],
[85, 126, 47, 87, 176, 51, 41, 20, 32],
[101, 75, 128, 139, 118, 146, 116, 128, 85],
[56, 41, 15, 176, 236, 85, 37, 9, 62],
[71, 30, 17, 119, 118, 255, 17, 18, 138],
[101, 38, 60, 138, 55, 70, 43, 26, 142],
[146, 36, 19, 30, 171, 255, 97, 27, 20],
[138, 45, 61, 62, 219, 1, 81, 188, 64],
[32, 41, 20, 117, 151, 142, 20, 21, 163],
[112, 19, 12, 61, 195, 128, 48, 4, 24]
]
];
static const COEFFS_PROBA_0 = [
[
[
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]
],
[
[253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128],
[189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128],
[106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128]
],
[
[1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128],
[181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128],
[78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128],
],
[
[1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128],
[184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128],
[77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128],
],
[
[1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128],
[170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128],
[37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128]
],
[
[1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128],
[207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128],
[102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128]
],
[
[1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128],
[177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128],
[80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128]
],
[
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]
]
],
[
[
[198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62],
[131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1],
[68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128]
],
[
[1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128],
[184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128],
[81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128]
],
[
[1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128],
[99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128],
[23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128]
],
[
[1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128],
[109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128],
[44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128]
],
[
[1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128],
[94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128],
[22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128]
],
[
[1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128],
[124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128],
[35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128]
],
[
[1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128],
[121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128],
[45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128]
],
[
[1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128],
[203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128],
[137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128]
]
],
[
[
[253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128],
[175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128],
[73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128]
],
[
[1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128],
[239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128],
[155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128]
],
[
[1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128],
[201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128],
[69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128]
],
[
[1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128],
[223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128],
[141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128]
],
[
[1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128],
[190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128],
[149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128]
],
[
[1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128]
],
[
[1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128],
[213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128],
[55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128]
],
[
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]
]
],
[
[
[202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255],
[126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128],
[61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128]
],
[
[1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128],
[166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128],
[39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128]
],
[
[1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128],
[124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128],
[24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128]
],
[
[1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128],
[149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128],
[28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128]
],
[
[1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128],
[123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128],
[20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128]
],
[
[1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128],
[168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128],
[47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128]
],
[
[1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128],
[141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128],
[42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128]
],
[
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128]
]
]
];
static const COEFFS_UPDATE_PROBA = [
[
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255],
[250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
]
],
[
[
[217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255],
[234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255]
],
[
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
]
],
[
[
[186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255],
[234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255],
[251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255]
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
]
],
[
[
[248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255],
[248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255]
],
[
[255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
]
]
];
// Paragraph 14.1
static const DC_TABLE = [
// uint8
4, 5, 6, 7, 8, 9, 10, 10,
11, 12, 13, 14, 15, 16, 17, 17,
18, 19, 20, 20, 21, 21, 22, 22,
23, 23, 24, 25, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36,
37, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 76, 77, 78, 79, 80, 81,
82, 83, 84, 85, 86, 87, 88, 89,
91, 93, 95, 96, 98, 100, 101, 102,
104, 106, 108, 110, 112, 114, 116, 118,
122, 124, 126, 128, 130, 132, 134, 136,
138, 140, 143, 145, 148, 151, 154, 157
];
static const AC_TABLE = [
// uint16
4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 60,
62, 64, 66, 68, 70, 72, 74, 76,
78, 80, 82, 84, 86, 88, 90, 92,
94, 96, 98, 100, 102, 104, 106, 108,
110, 112, 114, 116, 119, 122, 125, 128,
131, 134, 137, 140, 143, 146, 149, 152,
155, 158, 161, 164, 167, 170, 173, 177,
181, 185, 189, 193, 197, 201, 205, 209,
213, 217, 221, 225, 229, 234, 239, 245,
249, 254, 259, 264, 269, 274, 279, 284
];
/// FILTER_EXTRA_ROWS = How many extra lines are needed on the MB boundary
/// for caching, given a filtering level.
/// Simple filter: up to 2 luma samples are read and 1 is written.
/// Complex filter: up to 4 luma samples are read and 3 are written. Same for
/// U/V, so it's 8 samples total (because of the 2x upsampling).
static const FILTER_EXTRA_ROWS = [0, 2, 8];
static const int VP8_SIGNATURE = 0x2a019d;
static const int MB_FEATURE_TREE_PROBS = 3;
static const int NUM_MB_SEGMENTS = 4;
static const int NUM_REF_LF_DELTAS = 4;
static const int NUM_MODE_LF_DELTAS = 4; // I4x4, ZERO, *, SPLIT
static const int MAX_NUM_PARTITIONS = 8;
static const int B_DC_PRED = 0; // 4x4 modes
static const int B_TM_PRED = 1;
static const int B_VE_PRED = 2;
static const int B_HE_PRED = 3;
static const int B_RD_PRED = 4;
static const int B_VR_PRED = 5;
static const int B_LD_PRED = 6;
static const int B_VL_PRED = 7;
static const int B_HD_PRED = 8;
static const int B_HU_PRED = 9;
static const int NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED;
// Luma16 or UV modes
static const int DC_PRED = B_DC_PRED;
static const int V_PRED = B_VE_PRED;
static const int H_PRED = B_HE_PRED;
static const int TM_PRED = B_TM_PRED;
static const int B_PRED = NUM_BMODES;
// special modes
static const int B_DC_PRED_NOTOP = 4;
static const int B_DC_PRED_NOLEFT = 5;
static const int B_DC_PRED_NOTOPLEFT = 6;
static const int NUM_B_DC_MODES = 7;
// Probabilities
static const int NUM_TYPES = 4;
static const int NUM_BANDS = 8;
static const int NUM_CTX = 3;
static const int NUM_PROBAS = 11;
static const int BPS = 32; // this is the common stride used by yuv[]
static const int YUV_SIZE = (BPS * 17 + BPS * 9);
static const int Y_SIZE = (BPS * 17);
static const int Y_OFF = (BPS * 1 + 8);
static const int U_OFF = (Y_OFF + BPS * 16 + BPS);
static const int V_OFF = (U_OFF + 16);
static const int YUV_FIX = 16; // fixed-point precision for RGB->YUV
static const int YUV_HALF = 1 << (YUV_FIX - 1);
static const int YUV_MASK = (256 << YUV_FIX) - 1;
static const int YUV_RANGE_MIN = -227; // min value of r/g/b output
static const int YUV_RANGE_MAX = 256 + 226; // max value of r/g/b output
static const int YUV_FIX2 = 14; // fixed-point precision for YUV->RGB
static const int YUV_HALF2 = 1 << (YUV_FIX2 - 1);
static const int YUV_MASK2 = (256 << YUV_FIX2) - 1;
static const int XOR_YUV_MASK2 = (-YUV_MASK2 - 1);
// These constants are 14b fixed-point version of ITU-R BT.601 constants.
static const int kYScale = 19077; // 1.164 = 255 / 219
static const int kVToR = 26149; // 1.596 = 255 / 112 * 0.701
static const int kUToG = 6419; // 0.391 = 255 / 112 * 0.886 * 0.114 / 0.587
static const int kVToG = 13320; // 0.813 = 255 / 112 * 0.701 * 0.299 / 0.587
static const int kUToB = 33050; // 2.018 = 255 / 112 * 0.886
static const int kRCst = (-kYScale * 16 - kVToR * 128 + YUV_HALF2);
static const int kGCst =
(-kYScale * 16 + kUToG * 128 + kVToG * 128 + YUV_HALF2);
static const int kBCst = (-kYScale * 16 - kUToB * 128 + YUV_HALF2);
}