blob: df341c8b0126467ecc8b25544d9dd57b05ac67b9 [file] [log] [blame]
import 'dart:typed_data';
import '../animation.dart';
import '../image.dart';
import '../transform/copy_into.dart';
import '../util/input_buffer.dart';
import 'decoder.dart';
import 'gif/gif_color_map.dart';
import 'gif/gif_image_desc.dart';
import 'gif/gif_info.dart';
/// A decoder for the GIF image format. This supports both single frame and
/// animated GIF files, and transparency.
class GifDecoder extends Decoder {
GifInfo info;
GifDecoder([List<int> bytes]) {
if (bytes != null) {
startDecode(bytes);
}
}
/// Is the given file a valid Gif image?
bool isValidFile(List<int> bytes) {
_input = InputBuffer(bytes);
info = GifInfo();
return _getInfo();
}
/// How many frames are available to decode?
///
/// You should have prepared the decoder by either passing the file bytes
/// to the constructor, or calling getInfo.
int numFrames() => (info != null) ? info.numFrames : 0;
/// Validate the file is a Gif image and get information about it.
/// If the file is not a valid Gif image, null is returned.
GifInfo startDecode(List<int> bytes) {
_input = InputBuffer(bytes);
info = GifInfo();
if (!_getInfo()) {
return null;
}
try {
while (!_input.isEOS) {
int recordType = _input.readByte();
switch (recordType) {
case IMAGE_DESC_RECORD_TYPE:
InternalGifImageDesc gifImage = _skipImage();
if (gifImage == null) {
return info;
}
info.frames.add(gifImage);
break;
case EXTENSION_RECORD_TYPE:
int extCode = _input.readByte();
if (extCode == APPLICATION_EXT) {
_readApplicationExt(_input);
} else if (extCode == GRAPHIC_CONTROL_EXT) {
_readGraphicsControlExt(_input);
} else {
_skipRemainder();
}
break;
case TERMINATE_RECORD_TYPE:
//_numFrames = info.numFrames;
return info;
default:
break;
}
}
} catch (error) {
print(error);
}
//_numFrames = info.numFrames;
return info;
}
void _readApplicationExt(InputBuffer _input) {
int blockSize = _input.readByte();
String tag = _input.readString(blockSize);
if (tag == "NETSCAPE2.0") {
int b1 = _input.readByte();
int b2 = _input.readByte();
if (b1 == 0x03 && b2 == 0x01) {
_repeat = _input.readUint16();
}
} else {
_skipRemainder();
}
}
void _readGraphicsControlExt(InputBuffer _input) {
/*int blockSize =*/ _input.readByte();
int b = _input.readByte();
int duration = _input.readUint16();
int transparent = _input.readByte();
/*int endBlock =*/ _input.readByte();
int disposalMethod = (b >> 3) & 0x7;
//int userInput = (b >> 1) & 0x1;
int transparentFlag = b & 0x1;
int recordType = _input.peekBytes(1)[0];
if (recordType == IMAGE_DESC_RECORD_TYPE) {
_input.skip(1);
InternalGifImageDesc gifImage = _skipImage();
if (gifImage == null) {
return;
}
gifImage.duration = duration;
gifImage.clearFrame = disposalMethod == 2;
if (transparentFlag != 0) {
if (gifImage.colorMap != null) {
gifImage.colorMap.transparent = transparent;
} else if (info.globalColorMap != null) {
info.globalColorMap.transparent = transparent;
}
}
info.frames.add(gifImage);
}
}
Image decodeFrame(int frame) {
if (_input == null || info == null) {
return null;
}
if (frame >= info.frames.length || frame < 0) {
return null;
}
//_frame = frame;
InternalGifImageDesc gifImage = info.frames[frame] as InternalGifImageDesc;
_input.offset = gifImage.inputPosition;
return _decodeImage(info.frames[frame]);
}
Image decodeImage(List<int> bytes, {int frame = 0}) {
if (startDecode(bytes) == null) {
return null;
}
//_frame = 0;
//_numFrames = 1;
return decodeFrame(frame);
}
/// Decode all of the frames of an animated gif. For single image gifs,
/// this will return an animation with a single frame.
Animation decodeAnimation(List<int> bytes) {
if (startDecode(bytes) == null) {
return null;
}
Animation anim = Animation();
anim.width = info.width;
anim.height = info.height;
anim.loopCount = _repeat;
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);
}
GifImageDesc frame = info.frames[i];
Image image = decodeFrame(i);
if (image == null) {
return null;
}
GifColorMap colorMap =
(frame.colorMap != null) ? frame.colorMap : info.globalColorMap;
if (lastImage != null) {
if (frame.clearFrame) {
lastImage.fill(colorMap.color(info.backgroundColor));
}
copyInto(lastImage, image, dstX: frame.x, dstY: frame.y);
} else {
lastImage = image;
}
lastImage.duration = frame.duration;
anim.addFrame(lastImage);
}
return anim;
}
InternalGifImageDesc _skipImage() {
if (_input.isEOS) {
return null;
}
InternalGifImageDesc gifImage = InternalGifImageDesc(_input);
_input.skip(1);
_skipRemainder();
return gifImage;
}
/*bool _skipExtension() {
int extCode = _input.readByte();
int b = _input.readByte();
while (b != 0) {
_input.skip(b);
b = _input.readByte();
}
return true;
}*/
Image _decodeImage(GifImageDesc gifImage) {
if (_buffer == null) {
_initDecode();
}
_bitsPerPixel = _input.readByte();
_clearCode = 1 << _bitsPerPixel;
_eofCode = _clearCode + 1;
_runningCode = _eofCode + 1;
_runningBits = _bitsPerPixel + 1;
_maxCode1 = 1 << _runningBits;
_stackPtr = 0;
_lastCode = NO_SUCH_CODE;
_currentShiftState = 0;
_currentShiftDWord = 0;
_buffer[0] = 0;
_prefix.fillRange(0, _prefix.length, NO_SUCH_CODE);
int width = gifImage.width;
int height = gifImage.height;
if (gifImage.x + width > info.width || gifImage.y + height > info.height) {
return null;
}
GifColorMap colorMap =
(gifImage.colorMap != null) ? gifImage.colorMap : info.globalColorMap;
_pixelCount = width * height;
Image image = Image(width, height);
Uint8List line = Uint8List(width);
if (gifImage.interlaced) {
int row = gifImage.y;
for (int i = 0, j = 0; i < 4; ++i) {
for (int y = row + INTERLACED_OFFSET[i];
y < row + height;
y += INTERLACED_JUMP[i], ++j) {
if (!_getLine(line)) {
return image;
}
_updateImage(image, y, colorMap, line);
}
}
} else {
for (int y = 0; y < height; ++y) {
if (!_getLine(line)) {
return image;
}
_updateImage(image, y, colorMap, line);
}
}
return image;
}
void _updateImage(Image image, int y, GifColorMap colorMap, Uint8List line) {
if (colorMap != null) {
for (int x = 0, width = line.length; x < width; ++x) {
image.setPixel(x, y, colorMap.color(line[x]));
}
}
}
bool _getInfo() {
String tag = _input.readString(STAMP_SIZE);
if (tag != GIF87_STAMP && tag != GIF89_STAMP) {
return false;
}
info.width = _input.readUint16();
info.height = _input.readUint16();
int b = _input.readByte();
info.colorResolution = (((b & 0x70) + 1) >> 4) + 1;
int bitsPerPixel = (b & 0x07) + 1;
info.backgroundColor = _input.readByte();
_input.skip(1);
// Is there a global color map?
if (b & 0x80 != 0) {
info.globalColorMap = GifColorMap(1 << bitsPerPixel);
// Get the global color map:
for (int i = 0; i < info.globalColorMap.numColors; ++i) {
int r = _input.readByte();
int g = _input.readByte();
int b = _input.readByte();
info.globalColorMap.setColor(i, r, g, b);
}
}
info.isGif89 = tag == GIF89_STAMP;
return true;
}
bool _getLine(Uint8List line) {
_pixelCount -= line.length;
if (!_decompressLine(line)) {
return false;
}
// Flush any remainder blocks.
if (_pixelCount == 0) {
_skipRemainder();
}
return true;
}
/// Continue to get the image code in compressed form. This routine should be
/// called until NULL block is returned.
/// The block should NOT be freed by the user (not dynamically allocated).
bool _skipRemainder() {
if (_input.isEOS) {
return true;
}
int b = _input.readByte();
while (b != 0 && !_input.isEOS) {
_input.skip(b);
if (_input.isEOS) {
return true;
}
b = _input.readByte();
}
return true;
}
/// The LZ decompression routine:
/// This version decompress the given gif file into Line of length LineLen.
/// This routine can be called few times (one per scan line, for example), in
/// order the complete the whole image.
bool _decompressLine(Uint8List line) {
if (_stackPtr > LZ_MAX_CODE) {
return false;
}
int lineLen = line.length;
int i = 0;
if (_stackPtr != 0) {
// Let pop the stack off before continuing to read the gif file:
while (_stackPtr != 0 && i < lineLen) {
line[i++] = _stack[--_stackPtr];
}
}
int currentPrefix;
// Decode LineLen items.
while (i < lineLen) {
_currentCode = _decompressInput();
if (_currentCode == null) {
return false;
}
if (_currentCode == _eofCode) {
// Note however that usually we will not be here as we will stop
// decoding as soon as we got all the pixel, or EOF code will
// not be read at all, and DGifGetLine/Pixel clean everything.
return false;
}
if (_currentCode == _clearCode) {
// We need to start over again:
for (int j = 0; j <= LZ_MAX_CODE; j++) {
_prefix[j] = NO_SUCH_CODE;
}
_runningCode = _eofCode + 1;
_runningBits = _bitsPerPixel + 1;
_maxCode1 = 1 << _runningBits;
_lastCode = NO_SUCH_CODE;
} else {
// Its regular code - if in pixel range simply add it to output
// stream, otherwise trace to codes linked list until the prefix
// is in pixel range:
if (_currentCode < _clearCode) {
// This is simple - its pixel scalar, so add it to output:
line[i++] = _currentCode;
} else {
// Its a code to needed to be traced: trace the linked list
// until the prefix is a pixel, while pushing the suffix
// pixels on our stack. If we done, pop the stack in reverse
// (thats what stack is good for!) order to output. */
if (_prefix[_currentCode] == NO_SUCH_CODE) {
// Only allowed if CrntCode is exactly the running code:
// In that case CrntCode = XXXCode, CrntCode or the
// prefix code is last code and the suffix char is
// exactly the prefix of last code!
if (_currentCode == _runningCode - 2) {
currentPrefix = _lastCode;
_suffix[_runningCode - 2] = _stack[_stackPtr++] =
_getPrefixChar(_prefix, _lastCode, _clearCode);
} else {
return false;
}
} else {
currentPrefix = _currentCode;
}
// Now (if image is O.K.) we should not get an NO_SUCH_CODE
// During the trace. As we might loop forever, in case of
// defective image, we count the number of loops we trace
// and stop if we got LZ_MAX_CODE. obviously we can not
// loop more than that.
int j = 0;
while (j++ <= LZ_MAX_CODE &&
currentPrefix > _clearCode &&
currentPrefix <= LZ_MAX_CODE) {
_stack[_stackPtr++] = _suffix[currentPrefix];
currentPrefix = _prefix[currentPrefix];
}
if (j >= LZ_MAX_CODE || currentPrefix > LZ_MAX_CODE) {
return false;
}
// Push the last character on stack:
_stack[_stackPtr++] = currentPrefix;
// Now lets pop all the stack into output:
while (_stackPtr != 0 && i < lineLen) {
line[i++] = _stack[--_stackPtr];
}
}
if (_lastCode != NO_SUCH_CODE &&
_prefix[_runningCode - 2] == NO_SUCH_CODE) {
_prefix[_runningCode - 2] = _lastCode;
if (_currentCode == _runningCode - 2) {
// Only allowed if CrntCode is exactly the running code:
// In that case CrntCode = XXXCode, CrntCode or the
// prefix code is last code and the suffix char is
// exactly the prefix of last code!
_suffix[_runningCode - 2] =
_getPrefixChar(_prefix, _lastCode, _clearCode);
} else {
_suffix[_runningCode - 2] =
_getPrefixChar(_prefix, _currentCode, _clearCode);
}
}
_lastCode = _currentCode;
}
}
return true;
}
/// The LZ decompression input routine:
/// This routine is responsible for the decompression of the bit stream from
/// 8 bits (bytes) packets, into the real codes.
int _decompressInput() {
int code;
// The image can't contain more than LZ_BITS per code.
if (_runningBits > LZ_BITS) {
return null;
}
while (_currentShiftState < _runningBits) {
// Needs to get more bytes from input stream for next code:
int nextByte = _bufferedInput();
_currentShiftDWord |= nextByte << _currentShiftState;
_currentShiftState += 8;
}
code = _currentShiftDWord & CODE_MASKS[_runningBits];
_currentShiftDWord >>= _runningBits;
_currentShiftState -= _runningBits;
// If code cannot fit into RunningBits bits, must raise its size. Note
// however that codes above 4095 are used for special signaling.
// If we're using LZ_BITS bits already and we're at the max code, just
// keep using the table as it is, don't increment Private->RunningCode.
if (_runningCode < LZ_MAX_CODE + 2 &&
++_runningCode > _maxCode1 &&
_runningBits < LZ_BITS) {
_maxCode1 <<= 1;
_runningBits++;
}
return code;
}
/// Routine to trace the Prefixes linked list until we get a prefix which is
/// not code, but a pixel value (less than ClearCode). Returns that pixel value.
/// If image is defective, we might loop here forever, so we limit the loops to
/// the maximum possible if image O.k. - LZ_MAX_CODE times.
int _getPrefixChar(Uint32List prefix, int code, int clearCode) {
int i = 0;
while (code > clearCode && i++ <= LZ_MAX_CODE) {
if (code > LZ_MAX_CODE) {
return NO_SUCH_CODE;
}
code = prefix[code];
}
return code;
}
/// This routines read one gif data block at a time and buffers it internally
/// so that the decompression routine could access it.
/// The routine returns the next byte from its internal buffer (or read next
/// block in if buffer empty) and returns null on failure.
int _bufferedInput() {
int nextByte;
if (_buffer[0] == 0) {
// Needs to read the next buffer - this one is empty:
_buffer[0] = _input.readByte();
// There shouldn't be any empty data blocks here as the LZW spec
// says the LZW termination code should come first. Therefore we
// shouldn't be inside this routine at that point.
if (_buffer[0] == 0) {
return null;
}
_buffer.setRange(
1, 1 + _buffer[0], _input.readBytes(_buffer[0]).toUint8List());
nextByte = _buffer[1];
_buffer[1] = 2; // We use now the second place as last char read!
_buffer[0]--;
} else {
nextByte = _buffer[_buffer[1]++];
_buffer[0]--;
}
return nextByte;
}
void _initDecode() {
_buffer = Uint8List(256);
_stack = Uint8List(LZ_MAX_CODE);
_suffix = Uint8List(LZ_MAX_CODE + 1);
_prefix = Uint32List(LZ_MAX_CODE + 1);
}
InputBuffer _input;
//int _frame;
//int _numFrames;
int _repeat = 0;
Uint8List _buffer;
Uint8List _stack;
Uint8List _suffix;
Uint32List _prefix;
int _bitsPerPixel;
int _pixelCount;
int _currentShiftDWord;
int _currentShiftState;
int _stackPtr;
int _currentCode;
int _lastCode;
int _maxCode1;
int _runningBits;
int _runningCode;
int _eofCode;
int _clearCode;
static const int STAMP_SIZE = 6;
static const String GIF87_STAMP = 'GIF87a';
static const String GIF89_STAMP = 'GIF89a';
static const int IMAGE_DESC_RECORD_TYPE = 0x2c;
static const int EXTENSION_RECORD_TYPE = 0x21;
static const int TERMINATE_RECORD_TYPE = 0x3b;
static const int GRAPHIC_CONTROL_EXT = 0xf9;
static const int APPLICATION_EXT = 0xff;
static const int LZ_MAX_CODE = 4095;
static const int LZ_BITS = 12;
static const int NO_SUCH_CODE = 4098; // Impossible code, to signal empty.
static const List<int> CODE_MASKS = const [
0x0000,
0x0001,
0x0003,
0x0007,
0x000f,
0x001f,
0x003f,
0x007f,
0x00ff,
0x01ff,
0x03ff,
0x07ff,
0x0fff
];
static const List<int> INTERLACED_OFFSET = const [0, 4, 2, 1];
static const List<int> INTERLACED_JUMP = const [8, 8, 4, 2];
}