import 'package:archive/archive.dart'; | |
import '../animation.dart'; | |
import '../color.dart'; | |
import '../icc_profile_data.dart'; | |
import '../image.dart'; | |
import '../image_exception.dart'; | |
import '../transform/copy_into.dart'; | |
import '../util/input_buffer.dart'; | |
import 'decode_info.dart'; | |
import 'decoder.dart'; | |
import 'png/png_frame.dart'; | |
import 'png/png_info.dart'; | |
/// Decode a PNG encoded image. | |
class PngDecoder extends Decoder { | |
InternalPngInfo _info; | |
/// Is the given file a valid PNG image? | |
bool isValidFile(List<int> data) { | |
InputBuffer input = InputBuffer(data, bigEndian: true); | |
InputBuffer pngHeader = input.readBytes(8); | |
const PNG_HEADER = const [137, 80, 78, 71, 13, 10, 26, 10]; | |
for (int i = 0; i < 8; ++i) { | |
if (pngHeader[i] != PNG_HEADER[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
PngInfo get info => _info; | |
/// Start decoding the data as an animation sequence, but don't actually | |
/// process the frames until they are requested with decodeFrame. | |
DecodeInfo startDecode(List<int> data) { | |
_input = InputBuffer(data, bigEndian: true); | |
InputBuffer pngHeader = _input.readBytes(8); | |
const PNG_HEADER = const [137, 80, 78, 71, 13, 10, 26, 10]; | |
for (int i = 0; i < 8; ++i) { | |
if (pngHeader[i] != PNG_HEADER[i]) { | |
return null; | |
} | |
} | |
while (true) { | |
int inputPos = _input.position; | |
int chunkSize = _input.readUint32(); | |
String chunkType = _input.readString(4); | |
switch (chunkType) { | |
case 'IHDR': | |
InputBuffer hdr = InputBuffer.from(_input.readBytes(chunkSize)); | |
List<int> hdrBytes = hdr.toUint8List(); | |
_info = InternalPngInfo(); | |
_info.width = hdr.readUint32(); | |
_info.height = hdr.readUint32(); | |
_info.bits = hdr.readByte(); | |
_info.colorType = hdr.readByte(); | |
_info.compressionMethod = hdr.readByte(); | |
_info.filterMethod = hdr.readByte(); | |
_info.interlaceMethod = hdr.readByte(); | |
// Validate some of the info in the header to make sure we support | |
// the proposed image data. | |
if (![GRAYSCALE, RGB, INDEXED, GRAYSCALE_ALPHA, RGBA] | |
.contains(_info.colorType)) { | |
return null; | |
} | |
if (_info.filterMethod != 0) { | |
return null; | |
} | |
switch (_info.colorType) { | |
case GRAYSCALE: | |
if (![1, 2, 4, 8, 16].contains(_info.bits)) { | |
return null; | |
} | |
break; | |
case RGB: | |
if (![8, 16].contains(_info.bits)) { | |
return null; | |
} | |
break; | |
case INDEXED: | |
if (![1, 2, 4, 8].contains(_info.bits)) { | |
return null; | |
} | |
break; | |
case GRAYSCALE_ALPHA: | |
if (![8, 16].contains(_info.bits)) { | |
return null; | |
} | |
break; | |
case RGBA: | |
if (![8, 16].contains(_info.bits)) { | |
return null; | |
} | |
break; | |
} | |
int crc = _input.readUint32(); | |
int computedCrc = _crc(chunkType, hdrBytes); | |
if (crc != computedCrc) { | |
throw new ImageException('Invalid $chunkType checksum'); | |
} | |
break; | |
case 'PLTE': | |
_info.palette = _input.readBytes(chunkSize).toUint8List(); | |
int crc = _input.readUint32(); | |
int computedCrc = _crc(chunkType, _info.palette); | |
if (crc != computedCrc) { | |
throw new ImageException('Invalid $chunkType checksum'); | |
} | |
break; | |
case 'tRNS': | |
_info.transparency = _input.readBytes(chunkSize).toUint8List(); | |
int crc = _input.readUint32(); | |
int computedCrc = _crc(chunkType, _info.transparency); | |
if (crc != computedCrc) { | |
throw new ImageException('Invalid $chunkType checksum'); | |
} | |
break; | |
case 'IEND': | |
// End of the image. | |
_input.skip(4); // CRC | |
break; | |
case 'gAMA': | |
if (chunkSize != 4) { | |
throw new ImageException('Invalid gAMA chunk'); | |
} | |
int gammaInt = _input.readUint32(); | |
_input.skip(4); // CRC | |
// A gamma of 1.0 doesn't have any affect, so pretend we didn't get | |
// a gamma in that case. | |
if (gammaInt != 100000) { | |
_info.gamma = gammaInt / 100000.0; | |
} | |
break; | |
case 'IDAT': | |
_info.idat.add(inputPos); | |
_input.skip(chunkSize); | |
_input.skip(4); // CRC | |
break; | |
case 'acTL': // Animation control chunk | |
_info.numFrames = _input.readUint32(); | |
_info.repeat = _input.readUint32(); | |
_input.skip(4); // CRC | |
break; | |
case 'fcTL': // Frame control chunk | |
PngFrame frame = InternalPngFrame(); | |
_info.frames.add(frame); | |
frame.sequenceNumber = _input.readUint32(); | |
frame.width = _input.readUint32(); | |
frame.height = _input.readUint32(); | |
frame.xOffset = _input.readUint32(); | |
frame.yOffset = _input.readUint32(); | |
frame.delayNum = _input.readUint16(); | |
frame.delayDen = _input.readUint16(); | |
frame.dispose = _input.readByte(); | |
frame.blend = _input.readByte(); | |
_input.skip(4); // CRC | |
break; | |
case 'fdAT': | |
/*int sequenceNumber =*/ _input.readUint32(); | |
InternalPngFrame frame = _info.frames.last as InternalPngFrame; | |
frame.fdat.add(inputPos); | |
_input.skip(chunkSize - 4); | |
_input.skip(4); // CRC | |
break; | |
case 'bKGD': | |
if (_info.colorType == 3) { | |
int paletteIndex = _input.readByte(); | |
chunkSize--; | |
int p3 = paletteIndex * 3; | |
int r = _info.palette[p3]; | |
int g = _info.palette[p3 + 1]; | |
int b = _info.palette[p3 + 2]; | |
_info.backgroundColor = Color.fromRgb(r, g, b); | |
} else if (_info.colorType == 0 || _info.colorType == 4) { | |
/*int gray =*/ _input.readUint16(); | |
chunkSize -= 2; | |
} else if (_info.colorType == 2 || _info.colorType == 6) { | |
/*int r =*/ _input.readUint16(); | |
/*int g =*/ _input.readUint16(); | |
/*int b =*/ _input.readUint16(); | |
chunkSize -= 24; | |
} | |
if (chunkSize > 0) { | |
_input.skip(chunkSize); | |
} | |
_input.skip(4); // CRC | |
break; | |
case 'iCCP': | |
_info.iCCPName = _input.readString(); | |
_info.iCCPCompression = _input.readByte(); // 0: deflate | |
chunkSize -= _info.iCCPName.length + 2; | |
final profile = _input.readBytes(chunkSize); | |
_info.iCCPData = profile.toUint8List(); | |
_input.skip(4); // CRC | |
break; | |
default: | |
_input.skip(chunkSize); | |
_input.skip(4); // CRC | |
break; | |
} | |
if (chunkType == 'IEND') { | |
break; | |
} | |
if (_input.isEOS) { | |
return null; | |
} | |
} | |
return _info; | |
} | |
/// The number of frames that can be decoded. | |
int numFrames() => _info != null ? _info.numFrames : 0; | |
/// Decode the frame (assuming [startDecode] has already been called). | |
Image decodeFrame(int frame) { | |
if (_info == null) { | |
return null; | |
} | |
List<int> imageData = []; | |
int width = _info.width; | |
int height = _info.height; | |
if (!_info.isAnimated || frame == 0) { | |
for (int i = 0, len = _info.idat.length; i < len; ++i) { | |
_input.offset = _info.idat[i]; | |
int chunkSize = _input.readUint32(); | |
String chunkType = _input.readString(4); | |
List<int> data = _input.readBytes(chunkSize).toUint8List(); | |
imageData.addAll(data); | |
int crc = _input.readUint32(); | |
int computedCrc = _crc(chunkType, data); | |
if (crc != computedCrc) { | |
throw new ImageException('Invalid $chunkType checksum'); | |
} | |
} | |
} else { | |
if (frame < 0 || frame >= _info.frames.length) { | |
throw new ImageException('Invalid Frame Number: $frame'); | |
} | |
InternalPngFrame f = _info.frames[frame] as InternalPngFrame; | |
width = f.width; | |
height = f.height; | |
for (int i = 0; i < f.fdat.length; ++i) { | |
_input.offset = f.fdat[i]; | |
int chunkSize = _input.readUint32(); | |
/*String chunkType =*/ _input.readString(4); | |
_input.skip(4); // sequence number | |
List<int> data = _input.readBytes(chunkSize).toUint8List(); | |
imageData.addAll(data); | |
} | |
//_frame = frame; | |
//_numFrames = _info.numFrames; | |
} | |
int format; | |
if (_info.colorType == GRAYSCALE_ALPHA || | |
_info.colorType == RGBA || | |
_info.transparency != null) { | |
format = Image.RGBA; | |
} else { | |
format = Image.RGB; | |
} | |
Image image = Image(width, height, format); | |
List<int> uncompressed = ZLibDecoder().decodeBytes(imageData); | |
// input is the decompressed data. | |
InputBuffer input = InputBuffer(uncompressed, bigEndian: true); | |
_resetBits(); | |
// Set up a LUT to transform colors for gamma correction. | |
if (_info.colorLut == null) { | |
_info.colorLut = List<int>(256); | |
for (int i = 0; i < 256; ++i) { | |
int c = i; | |
/*if (info.gamma != null) { | |
c = (Math.pow((c / 255.0), info.gamma) * 255.0).toInt(); | |
}*/ | |
_info.colorLut[i] = c; | |
} | |
// Apply the LUT to the palette, if necessary. | |
if (_info.palette != null && _info.gamma != null) { | |
for (int i = 0; i < _info.palette.length; ++i) { | |
_info.palette[i] = _info.colorLut[_info.palette[i]]; | |
} | |
} | |
} | |
int origW = _info.width; | |
int origH = _info.height; | |
_info.width = width; | |
_info.height = height; | |
int w = width; | |
int h = height; | |
_progressY = 0; | |
if (_info.interlaceMethod != 0) { | |
_processPass(input, image, 0, 0, 8, 8, (w + 7) >> 3, (h + 7) >> 3); | |
_processPass(input, image, 4, 0, 8, 8, (w + 3) >> 3, (h + 7) >> 3); | |
_processPass(input, image, 0, 4, 4, 8, (w + 3) >> 2, (h + 3) >> 3); | |
_processPass(input, image, 2, 0, 4, 4, (w + 1) >> 2, (h + 3) >> 2); | |
_processPass(input, image, 0, 2, 2, 4, (w + 1) >> 1, (h + 1) >> 2); | |
_processPass(input, image, 1, 0, 2, 2, w >> 1, (h + 1) >> 1); | |
_processPass(input, image, 0, 1, 1, 2, w, h >> 1); | |
} else { | |
_process(input, image); | |
} | |
_info.width = origW; | |
_info.height = origH; | |
image.iccProfile = | |
ICCProfileData(_info.iCCPName, ICCPCompression.deflate, _info.iCCPData); | |
return image; | |
} | |
Image decodeImage(List<int> data, {int frame = 0}) { | |
if (startDecode(data) == null) { | |
return null; | |
} | |
return decodeFrame(frame); | |
} | |
Animation decodeAnimation(List<int> data) { | |
if (startDecode(data) == null) { | |
return null; | |
} | |
Animation anim = Animation(); | |
anim.width = _info.width; | |
anim.height = _info.height; | |
if (!_info.isAnimated) { | |
Image image = decodeFrame(0); | |
anim.addFrame(image); | |
return anim; | |
} | |
int dispose = PngFrame.APNG_DISPOSE_OP_BACKGROUND; | |
Image lastImage = Image(_info.width, _info.height); | |
for (int i = 0; i < _info.numFrames; ++i) { | |
//_frame = i; | |
if (lastImage == null) { | |
lastImage = Image(_info.width, _info.height); | |
} else { | |
lastImage = Image.from(lastImage); | |
} | |
PngFrame frame = _info.frames[i]; | |
Image image = decodeFrame(i); | |
if (image == null) { | |
continue; | |
} | |
if (lastImage != null) { | |
if (dispose == PngFrame.APNG_DISPOSE_OP_BACKGROUND || | |
dispose == PngFrame.APNG_DISPOSE_OP_PREVIOUS) { | |
lastImage.fill(_info.backgroundColor); | |
} | |
copyInto(lastImage, image, | |
dstX: frame.xOffset, | |
dstY: frame.yOffset, | |
blend: frame.blend == PngFrame.APNG_BLEND_OP_OVER); | |
} else { | |
lastImage = image; | |
} | |
anim.addFrame(lastImage); | |
dispose = frame.dispose; | |
} | |
return anim; | |
} | |
/// Process a pass of an interlaced image. | |
void _processPass(InputBuffer input, Image image, int xOffset, int yOffset, | |
int xStep, int yStep, int passWidth, int passHeight) { | |
final int channels = (_info.colorType == GRAYSCALE_ALPHA) | |
? 2 | |
: (_info.colorType == RGB) ? 3 : (_info.colorType == RGBA) ? 4 : 1; | |
final int pixelDepth = channels * _info.bits; | |
final int bpp = (pixelDepth + 7) >> 3; | |
final int rowBytes = (pixelDepth * passWidth + 7) >> 3; | |
final List<int> line = List<int>.filled(rowBytes, 0); | |
final List<List<int>> inData = [line, line]; | |
final List<int> pixel = [0, 0, 0, 0]; | |
//int pi = 0; | |
for (int srcY = 0, dstY = yOffset, ri = 0; | |
srcY < passHeight; | |
++srcY, dstY += yStep, ri = 1 - ri, _progressY++) { | |
int filterType = input.readByte(); | |
inData[ri] = input.readBytes(rowBytes).toUint8List(); | |
final List<int> row = inData[ri]; | |
final List<int> prevRow = inData[1 - ri]; | |
// Before the image is compressed, it was filtered to improve compression. | |
// Reverse the filter now. | |
_unfilter(filterType, bpp, row, prevRow); | |
// Scanlines are always on byte boundaries, so for bit depths < 8, | |
// reset the bit stream counter. | |
_resetBits(); | |
InputBuffer rowInput = InputBuffer(row, bigEndian: true); | |
final int blockHeight = xStep; | |
final int blockWidth = xStep - xOffset; | |
//int yMax = Math.min(dstY + blockHeight, _info.height); | |
for (int srcX = 0, dstX = xOffset; | |
srcX < passWidth; | |
++srcX, dstX += xStep) { | |
_readPixel(rowInput, pixel); | |
int c = _getColor(pixel); | |
image.setPixel(dstX, dstY, c); | |
if (blockWidth > 1 || blockHeight > 1) { | |
//int xMax = Math.min(dstX + blockWidth, _info.width); | |
//int xPixels = xMax - dstX; | |
for (int i = 0; i < blockHeight; ++i) { | |
for (int j = 0; j < blockWidth; ++j) { | |
image.setPixelSafe(dstX + j, dstY + j, c); | |
} | |
} | |
} | |
} | |
} | |
} | |
void _process(InputBuffer input, Image image) { | |
final int channels = (_info.colorType == GRAYSCALE_ALPHA) | |
? 2 | |
: (_info.colorType == RGB) ? 3 : (_info.colorType == RGBA) ? 4 : 1; | |
final int pixelDepth = channels * _info.bits; | |
final int w = _info.width; | |
final int h = _info.height; | |
final int rowBytes = (((w * pixelDepth + 7)) >> 3); | |
final int bpp = (pixelDepth + 7) >> 3; | |
final List<int> line = List<int>.filled(rowBytes, 0); | |
final List<List<int>> inData = [line, line]; | |
final List<int> pixel = [0, 0, 0, 0]; | |
for (int y = 0, pi = 0, ri = 0; y < h; ++y, ri = 1 - ri) { | |
int filterType = input.readByte(); | |
inData[ri] = input.readBytes(rowBytes).toUint8List(); | |
List<int> row = inData[ri]; | |
List<int> prevRow = inData[1 - ri]; | |
// Before the image is compressed, it was filtered to improve compression. | |
// Reverse the filter now. | |
_unfilter(filterType, bpp, row, prevRow); | |
// Scanlines are always on byte boundaries, so for bit depths < 8, | |
// reset the bit stream counter. | |
_resetBits(); | |
InputBuffer rowInput = InputBuffer(inData[ri], bigEndian: true); | |
for (int x = 0; x < w; ++x) { | |
_readPixel(rowInput, pixel); | |
image[pi++] = _getColor(pixel); | |
} | |
} | |
} | |
void _unfilter(int filterType, int bpp, List<int> row, List<int> prevRow) { | |
final int rowBytes = row.length; | |
switch (filterType) { | |
case FILTER_NONE: | |
break; | |
case FILTER_SUB: | |
for (int x = bpp; x < rowBytes; ++x) { | |
row[x] = (row[x] + row[x - bpp]) & 0xff; | |
} | |
break; | |
case FILTER_UP: | |
for (int x = 0; x < rowBytes; ++x) { | |
row[x] = (row[x] + prevRow[x]) & 0xff; | |
} | |
break; | |
case FILTER_AVERAGE: | |
for (int x = 0; x < rowBytes; ++x) { | |
int a = x < bpp ? 0 : row[x - bpp]; | |
int b = prevRow[x]; | |
row[x] = (row[x] + ((a + b) >> 1)) & 0xff; | |
} | |
break; | |
case FILTER_PAETH: | |
for (int x = 0; x < rowBytes; ++x) { | |
int a = x < bpp ? 0 : row[x - bpp]; | |
int b = prevRow[x]; | |
int c = x < bpp ? 0 : prevRow[x - bpp]; | |
int p = a + b - c; | |
int pa = (p - a).abs(); | |
int pb = (p - b).abs(); | |
int pc = (p - c).abs(); | |
int paeth = 0; | |
if (pa <= pb && pa <= pc) { | |
paeth = a; | |
} else if (pb <= pc) { | |
paeth = b; | |
} else { | |
paeth = c; | |
} | |
row[x] = (row[x] + paeth) & 0xff; | |
} | |
break; | |
default: | |
throw new ImageException('Invalid filter value: ${filterType}'); | |
} | |
} | |
int _convert16to8(int c) { | |
return c >> 8; | |
} | |
int _convert1to8(int c) { | |
return (c == 0) ? 0 : 255; | |
} | |
int _convert2to8(int c) { | |
return c * 85; | |
} | |
int _convert4to8(int c) { | |
return c << 4; | |
} | |
/// Return the CRC of the bytes | |
int _crc(String type, List<int> bytes) { | |
int crc = getCrc32(type.codeUnits); | |
return getCrc32(bytes, crc); | |
} | |
int _bitBuffer = 0; | |
int _bitBufferLen = 0; | |
void _resetBits() { | |
_bitBuffer = 0; | |
_bitBufferLen = 0; | |
} | |
/// Read a number of bits from the input stream. | |
int _readBits(InputBuffer input, int numBits) { | |
if (numBits == 0) { | |
return 0; | |
} | |
if (numBits == 8) { | |
return input.readByte(); | |
} | |
if (numBits == 16) { | |
return input.readUint16(); | |
} | |
// not enough buffer | |
while (_bitBufferLen < numBits) { | |
if (input.isEOS) { | |
throw new ImageException('Invalid PNG data.'); | |
} | |
// input byte | |
int octet = input.readByte(); | |
// concat octet | |
_bitBuffer = octet << _bitBufferLen; | |
_bitBufferLen += 8; | |
} | |
// output byte | |
int mask = (numBits == 1) | |
? 1 | |
: (numBits == 2) | |
? 3 | |
: (numBits == 4) | |
? 0xf | |
: (numBits == 8) ? 0xff : (numBits == 16) ? 0xffff : 0; | |
int octet = (_bitBuffer >> (_bitBufferLen - numBits)) & mask; | |
_bitBufferLen -= numBits; | |
return octet; | |
} | |
/// Read the next pixel from the input stream. | |
void _readPixel(InputBuffer input, List<int> pixel) { | |
switch (_info.colorType) { | |
case GRAYSCALE: | |
pixel[0] = _readBits(input, _info.bits); | |
return; | |
case RGB: | |
pixel[0] = _readBits(input, _info.bits); | |
pixel[1] = _readBits(input, _info.bits); | |
pixel[2] = _readBits(input, _info.bits); | |
return; | |
case INDEXED: | |
pixel[0] = _readBits(input, _info.bits); | |
return; | |
case GRAYSCALE_ALPHA: | |
pixel[0] = _readBits(input, _info.bits); | |
pixel[1] = _readBits(input, _info.bits); | |
return; | |
case RGBA: | |
pixel[0] = _readBits(input, _info.bits); | |
pixel[1] = _readBits(input, _info.bits); | |
pixel[2] = _readBits(input, _info.bits); | |
pixel[3] = _readBits(input, _info.bits); | |
return; | |
} | |
throw new ImageException('Invalid color type: ${_info.colorType}.'); | |
} | |
/// Get the color with the list of components. | |
int _getColor(List<int> raw) { | |
switch (_info.colorType) { | |
case GRAYSCALE: | |
int g; | |
switch (_info.bits) { | |
case 1: | |
g = _convert1to8(raw[0]); | |
break; | |
case 2: | |
g = _convert2to8(raw[0]); | |
break; | |
case 4: | |
g = _convert4to8(raw[0]); | |
break; | |
case 8: | |
g = raw[0]; | |
break; | |
case 16: | |
g = _convert16to8(raw[0]); | |
break; | |
} | |
g = _info.colorLut[g]; | |
if (_info.transparency != null) { | |
int a = ((_info.transparency[0] & 0xff) << 24) | | |
(_info.transparency[1] & 0xff); | |
if (raw[0] == a) { | |
return getColor(g, g, g, 0); | |
} | |
} | |
return getColor(g, g, g, 255); | |
case RGB: | |
int r, g, b; | |
switch (_info.bits) { | |
case 1: | |
r = _convert1to8(raw[0]); | |
g = _convert1to8(raw[1]); | |
b = _convert1to8(raw[2]); | |
break; | |
case 2: | |
r = _convert2to8(raw[0]); | |
g = _convert2to8(raw[1]); | |
b = _convert2to8(raw[2]); | |
break; | |
case 4: | |
r = _convert4to8(raw[0]); | |
g = _convert4to8(raw[1]); | |
b = _convert4to8(raw[2]); | |
break; | |
case 8: | |
r = raw[0]; | |
g = raw[1]; | |
b = raw[2]; | |
break; | |
case 16: | |
r = _convert16to8(raw[0]); | |
g = _convert16to8(raw[1]); | |
b = _convert16to8(raw[2]); | |
break; | |
} | |
r = _info.colorLut[r]; | |
g = _info.colorLut[g]; | |
b = _info.colorLut[b]; | |
if (_info.transparency != null) { | |
int tr = ((_info.transparency[0] & 0xff) << 8) | | |
(_info.transparency[1] & 0xff); | |
int tg = ((_info.transparency[2] & 0xff) << 8) | | |
(_info.transparency[3] & 0xff); | |
int tb = ((_info.transparency[4] & 0xff) << 8) | | |
(_info.transparency[5] & 0xff); | |
if (raw[0] == tr && raw[1] == tg && raw[2] == tb) { | |
return getColor(r, g, b, 0); | |
} | |
} | |
return getColor(r, g, b, 255); | |
case INDEXED: | |
int p = raw[0] * 3; | |
int a = _info.transparency != null && raw[0] < _info.transparency.length | |
? _info.transparency[raw[0]] | |
: 255; | |
if (p >= _info.palette.length) { | |
return getColor(255, 255, 255, a); | |
} | |
int r = _info.palette[p]; | |
int g = _info.palette[p + 1]; | |
int b = _info.palette[p + 2]; | |
return getColor(r, g, b, a); | |
case GRAYSCALE_ALPHA: | |
int g, a; | |
switch (_info.bits) { | |
case 1: | |
g = _convert1to8(raw[0]); | |
a = _convert1to8(raw[1]); | |
break; | |
case 2: | |
g = _convert2to8(raw[0]); | |
a = _convert2to8(raw[1]); | |
break; | |
case 4: | |
g = _convert4to8(raw[0]); | |
a = _convert4to8(raw[1]); | |
break; | |
case 8: | |
g = raw[0]; | |
a = raw[1]; | |
break; | |
case 16: | |
g = _convert16to8(raw[0]); | |
a = _convert16to8(raw[1]); | |
break; | |
} | |
g = _info.colorLut[g]; | |
return getColor(g, g, g, a); | |
case RGBA: | |
int r, g, b, a; | |
switch (_info.bits) { | |
case 1: | |
r = _convert1to8(raw[0]); | |
g = _convert1to8(raw[1]); | |
b = _convert1to8(raw[2]); | |
a = _convert1to8(raw[3]); | |
break; | |
case 2: | |
r = _convert2to8(raw[0]); | |
g = _convert2to8(raw[1]); | |
b = _convert2to8(raw[2]); | |
a = _convert2to8(raw[3]); | |
break; | |
case 4: | |
r = _convert4to8(raw[0]); | |
g = _convert4to8(raw[1]); | |
b = _convert4to8(raw[2]); | |
a = _convert4to8(raw[3]); | |
break; | |
case 8: | |
r = raw[0]; | |
g = raw[1]; | |
b = raw[2]; | |
a = raw[3]; | |
break; | |
case 16: | |
r = _convert16to8(raw[0]); | |
g = _convert16to8(raw[1]); | |
b = _convert16to8(raw[2]); | |
a = _convert16to8(raw[3]); | |
break; | |
} | |
r = _info.colorLut[r]; | |
g = _info.colorLut[g]; | |
b = _info.colorLut[b]; | |
return getColor(r, g, b, a); | |
} | |
throw new ImageException('Invalid color type: ${_info.colorType}.'); | |
} | |
InputBuffer _input; | |
int _progressY; | |
//int _frame = 0; | |
//int _numFrames = 1; | |
static const int GRAYSCALE = 0; | |
static const int RGB = 2; | |
static const int INDEXED = 3; | |
static const int GRAYSCALE_ALPHA = 4; | |
static const int RGBA = 6; | |
static const int FILTER_NONE = 0; | |
static const int FILTER_SUB = 1; | |
static const int FILTER_UP = 2; | |
static const int FILTER_AVERAGE = 3; | |
static const int FILTER_PAETH = 4; | |
} |