Files
LNXSDK/leenkx/Sources/iron/format/gif/Reader.hx

347 lines
9.4 KiB
Haxe

package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import haxe.io.Input;
/**
* ...
* @author Yanrishatum
*/
class Reader
{
private var i:Input;
public function new(i:Input)
{
this.i = i;
i.bigEndian = false;
}
public function read():Data
{
for (b in [71, 73, 70])
{
if (i.readByte() != b) throw "Invalid header";
}
var gifVer:String = i.readString(3);
var version:Version = Version.GIF89a;
switch(gifVer)
{
case "87a": version = Version.GIF87a;
case "89a": version = Version.GIF89a;
default: version = Version.Unknown(gifVer);
}
// Logical screen descriptor.
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packedField:Int = i.readByte();
var bgIndex:Int = i.readByte();
var pixelAspectRatio:Float = i.readByte();
if (pixelAspectRatio != 0) pixelAspectRatio = (pixelAspectRatio + 15) / 64;
else pixelAspectRatio = 1;
var lsd:LogicalScreenDescriptor =
{
width: width,
height: height,
hasGlobalColorTable: (packedField & 128) == 128,
colorResolution: (packedField & 112) >>> 4,
sorted: (packedField & 8) == 8,
globalColorTableSize: 2 << (packedField & 7),
backgroundColorIndex: bgIndex,
pixelAspectRatio: pixelAspectRatio
}
var gct:ColorTable = null;
if (lsd.hasGlobalColorTable) gct = readColorTable(lsd.globalColorTableSize);
var blocks:List<Block> = new List();
while (true)
{
var b:Block = readBlock();
blocks.add(b);
if (b == Block.BEOF) break;
}
return
{
version: version,
logicalScreenDescriptor: lsd,
globalColorTable: gct,
blocks: blocks
}
}
private function readBlock():Block
{
var blockID:Int = i.readByte();
switch(blockID)
{
case 0x2C:
// Image
return readImage();
case 0x21:
// Extension
return readExtension();
case 0x3B:
return Block.BEOF;
}
// The behaviour of taking unknown block ID is unspecified.
return Block.BEOF;
}
private function readImage():Block
{
var x:Int = i.readUInt16();
var y:Int = i.readUInt16();
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packed:Int = i.readByte();
var localColorTable:Bool = (packed & 128) == 128;
var interlaced:Bool = (packed & 64) == 64;
var sorted:Bool = (packed & 32) == 32;
var localColorTableSize:Int = 2 << (packed & 7);
var lct:ColorTable = null;
if (localColorTable) lct = readColorTable(localColorTableSize);
return Block.BFrame(
{
x: x,
y: y,
width: width,
height: height,
localColorTable: localColorTable,
interlaced:interlaced,
sorted:sorted,
localColorTableSize:localColorTableSize,
pixels:readPixels(width, height, interlaced),
colorTable:lct
});
}
private function readPixels(width:Int, height:Int, interlaced:Bool):Bytes
{
var input:Input = this.i;
var pixelsCount:Int = width * height;
var pixels:Bytes = Bytes.alloc(pixelsCount);
var minCodeSize:Int = input.readByte();
var blockSize:Int = input.readByte() - 1;
var bits:Int = input.readByte();
var bitsCount:Int = 8;
var clearCode:Int = 1 << minCodeSize;
var eoiCode:Int = clearCode + 1;
var codeSize:Int = minCodeSize + 1;
var codeSizeLimit:Int = 1 << codeSize;
var codeMask = codeSizeLimit - 1;
var baseDict:Array<Array<Int>> = new Array();
for (i in 0...clearCode) baseDict[i] = [i];
var dict:Array<Array<Int>> = new Array();
var dictLen:Int = clearCode + 2;
var newRecord:Array<Int>;
var i:Int = 0;
var code:Int = 0;
var last:Int;
while (i < pixelsCount)
{
last = code;
while (bitsCount < codeSize)
{
if (blockSize == 0) break;
bits |= input.readByte() << bitsCount;
bitsCount += 8;
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
code = bits & codeMask;
bits >>= codeSize;
bitsCount -= codeSize;
if (code == clearCode)
{
dict = baseDict.copy();
dictLen = clearCode + 2;
codeSize = minCodeSize + 1;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
continue;
}
if (code == eoiCode) break;
if (code < dictLen)
{
if (last != clearCode)
{
newRecord = dict[last].copy();
newRecord.push(dict[code][0]);
dict[dictLen++] = newRecord;
}
}
else
{
if (code != dictLen) throw 'Invalid LZW code. Excepted: $dictLen, got: $code';
newRecord = dict[last].copy();
newRecord.push(newRecord[0]);
dict[dictLen++] = newRecord;
}
newRecord = dict[code];
for (item in newRecord) pixels.set(i++, item);
if (dictLen == codeSizeLimit && codeSize < 12)
{
codeSize++;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
}
}
// Just in case
while (blockSize > 0)
{
input.readByte();
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
while (i < pixelsCount) pixels.set(i++, 0);
if (interlaced)
{
var buffer:Bytes = Bytes.alloc(pixelsCount);
var offset:Int = deinterlace(pixels, buffer, 8, 0, 0 , width, height); // Every 8 line with start at 0
offset = deinterlace(pixels, buffer, 8, 4, offset, width, height); // Every 8 line with start at 4
offset = deinterlace(pixels, buffer, 4, 2, offset, width, height); // Every 4 line with start at 2
deinterlace(pixels, buffer, 2, 1, offset, width, height); // Every 2 line with start at 1
pixels = buffer;
}
return pixels;
}
private function deinterlace(input:Bytes, output:Bytes, step:Int, y:Int, offset:Int, width:Int, height:Int):Int
{
while (y < height)
{
output.blit(y * width, input, offset, width);
offset += width;
y += step;
}
return offset;
}
private function readExtension():Block
{
var subId:Int = i.readByte();
switch(subId)
{
case 0xF9:
// Graphics Control Extension
if (i.readByte() != 4) throw "Incorrect Graphic Control Extension block size!";
var packed:Int = i.readByte();
var disposalMethod:DisposalMethod = switch ( (packed & 28) >> 2)
{
case 0: DisposalMethod.UNSPECIFIED;
case 1: DisposalMethod.NO_ACTION;
case 2: DisposalMethod.FILL_BACKGROUND;
case 3: DisposalMethod.RENDER_PREVIOUS;
default: DisposalMethod.UNDEFINED((packed & 28) >> 2);
};
var b:Block = Block.BExtension(Extension.EGraphicControl(
{
disposalMethod:disposalMethod,
userInput: (packed & 2) == 2,
hasTransparentColor: (packed & 1) == 1,
delay: i.readUInt16(),
transparentIndex: i.readByte()
}));
i.readByte(); // Terminator
return b;
case 0x01:
// Text block
// Exists only on paper, nobody ever used it.
if (i.readByte() != 12) throw "Incorrect size of Plain Text Extension introducer block.";
return Block.BExtension(Extension.EText(
{
textGridX: i.readUInt16(),
textGridY: i.readUInt16(),
textGridWidth: i.readUInt16(),
textGridHeight: i.readUInt16(),
charCellWidth: i.readByte(),
charCellHeight: i.readByte(),
textForegroundColorIndex: i.readByte(),
textBackgroundColorIndex: i.readByte(),
text: readBlocks().toString()
}));
case 0xFE:
// Commentary
return Block.BExtension(Extension.EComment(readBlocks().toString()));
case 0xFF:
// Application extension
return readApplicationExtension();
default:
return Block.BExtension(Extension.EUnknown(subId, readBlocks()));
}
}
private function readApplicationExtension():Block
{
if (i.readByte() != 11) throw "Incorrect size of Application Extension introducer block.";
var name:String = i.readString(8);
var version:String = i.readString(3);
var data:Bytes = readBlocks();
if (name == "NETSCAPE" && version == "2.0" && data.get(0) == 1)
{
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(data.get(1) | (data.get(2) << 8))));
}
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AEUnknown(name, version, data)));
}
private inline function readBlocks():Bytes
{
var buffer:BytesOutput = new BytesOutput();
var bytes:Bytes = Bytes.alloc(255);
var len:Int = i.readByte();
while (len != 0)
{
i.readBytes(bytes, 0, len);
buffer.writeBytes(bytes, 0, len);
len = i.readByte();
}
buffer.flush();
bytes = buffer.getBytes();
buffer.close();
return bytes;
}
private function readColorTable(size:Int):ColorTable
{
size *= 3;
var output:ColorTable = ColorTable.alloc(size);
var c:Int = 0;
while (c < size)
{
output.set(c , i.readByte()); // R
output.set(c + 1, i.readByte()); // G
output.set(c + 2, i.readByte()); // B
c += 3;
}
return output;
}
}