forked from LeenkxTeam/LNXSDK
526 lines
12 KiB
Haxe
526 lines
12 KiB
Haxe
package format.gif;
|
|
import format.gif.Data;
|
|
import haxe.ds.Vector;
|
|
import haxe.io.Bytes;
|
|
import haxe.io.Output;
|
|
import haxe.io.UInt8Array;
|
|
|
|
/**
|
|
* ...
|
|
* @author Yanrishatum
|
|
*/
|
|
class Writer
|
|
{
|
|
|
|
private var o:Output;
|
|
private var lzw:LZWEncoder;
|
|
private var gctSize:Int;
|
|
|
|
public function new(o:Output)
|
|
{
|
|
this.o = o;
|
|
this.lzw = new LZWEncoder();
|
|
o.bigEndian = false;
|
|
}
|
|
|
|
/**
|
|
* Write entire Data at once.
|
|
* @param data Input gif file data
|
|
*/
|
|
public function write(data:Data):Void
|
|
{
|
|
// Header
|
|
writeHeader(data.version);
|
|
|
|
// Logical screen descriptor.
|
|
writeLogicalScreenDescriptor(data.logicalScreenDescriptor, data.globalColorTable);
|
|
|
|
for (block in data.blocks)
|
|
{
|
|
switch (block)
|
|
{
|
|
case Block.BEOF:
|
|
writeEOF();
|
|
return;
|
|
case Block.BExtension(ext):
|
|
switch (ext)
|
|
{
|
|
case Extension.EUnknown(id, bytes):
|
|
writeUnknownExtension(id, bytes);
|
|
case Extension.EComment(text):
|
|
writeComment(text);
|
|
case Extension.EText(textExt):
|
|
writeText(textExt);
|
|
case Extension.EGraphicControl(gce):
|
|
writeGraphicControl(gce);
|
|
case Extension.EApplicationExtension(appExt):
|
|
writeAppExtension(appExt);
|
|
}
|
|
case Block.BFrame(frame):
|
|
writeFrame(frame);
|
|
}
|
|
}
|
|
writeEOF(); // If we doesn't encountered EOF block - write it.
|
|
}
|
|
|
|
/**
|
|
* Writes header of Gif file. Must be first.
|
|
* @param version
|
|
*/
|
|
public function writeHeader(version:Version):Void
|
|
{
|
|
o.writeString("GIF");
|
|
switch(version)
|
|
{
|
|
case Version.GIF87a: o.writeString("87a");
|
|
case Version.GIF89a: o.writeString("89a");
|
|
case Version.Unknown(v):
|
|
if (v.length == 3) o.writeString(v);
|
|
else if (v.length > 3) o.writeString(v.substr(0, 3));
|
|
else
|
|
{
|
|
while (v.length < 3) v += "-";
|
|
o.writeString(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes Logical Screen Descriptor block. Must go right after header.
|
|
* @param lsd Logical Screen Descriptor object.
|
|
* @param globalColorTable Global color table. Required only if LSD contains hasGlobalColorTable flag.
|
|
* Color table must be a RGB-aligned Bytes with 3 bytes per color.
|
|
*/
|
|
public function writeLogicalScreenDescriptor(lsd:LogicalScreenDescriptor, globalColorTable:Bytes = null):Void
|
|
{
|
|
o.writeUInt16(lsd.width);
|
|
o.writeUInt16(lsd.height);
|
|
|
|
var packed:Int = 0;
|
|
if (lsd.hasGlobalColorTable) packed |= 128;
|
|
packed |= (lsd.colorResolution << 4) & 112;
|
|
if (lsd.sorted) packed |= 8;
|
|
packed |= Math.round(Tools.log2(lsd.globalColorTableSize) - 1) & 7;
|
|
o.writeByte(packed);
|
|
|
|
o.writeByte(lsd.backgroundColorIndex);
|
|
if (lsd.pixelAspectRatio == 1) o.writeByte(0);
|
|
else o.writeByte(Std.int(lsd.pixelAspectRatio) * 64 - 15);
|
|
|
|
if (lsd.hasGlobalColorTable)
|
|
{
|
|
if (globalColorTable != null)
|
|
{
|
|
o.writeBytes(globalColorTable, 0, globalColorTable.length);
|
|
gctSize = lsd.globalColorTableSize;
|
|
}
|
|
else throw "hasGlobalColorTable flag present, but there is no global color table!";
|
|
}
|
|
}
|
|
|
|
public function writeComment(text:String):Void
|
|
{
|
|
o.writeByte(0x21);
|
|
o.writeByte(0xFE);
|
|
writeStringBlocks(text);
|
|
}
|
|
|
|
public function writeText(textExt:PlainTextExtension):Void
|
|
{
|
|
o.writeByte(0x21);
|
|
o.writeByte(0x01);
|
|
o.writeByte(12);
|
|
o.writeUInt16(textExt.textGridX);
|
|
o.writeUInt16(textExt.textGridY);
|
|
o.writeUInt16(textExt.textGridWidth);
|
|
o.writeUInt16(textExt.textGridHeight);
|
|
o.writeByte(textExt.charCellWidth);
|
|
o.writeByte(textExt.charCellHeight);
|
|
o.writeByte(textExt.textForegroundColorIndex);
|
|
o.writeByte(textExt.textForegroundColorIndex);
|
|
writeStringBlocks(textExt.text);
|
|
}
|
|
|
|
public function writeGraphicControl(gce:GraphicControlExtension):Void
|
|
{
|
|
o.writeByte(0x21);
|
|
o.writeByte(0xF9);
|
|
o.writeByte(4);
|
|
var packed:Int = 0;
|
|
|
|
switch (gce.disposalMethod)
|
|
{
|
|
case DisposalMethod.UNSPECIFIED: // 0
|
|
case DisposalMethod.NO_ACTION: packed |= 4;
|
|
case DisposalMethod.FILL_BACKGROUND: packed |= 8;
|
|
case DisposalMethod.RENDER_PREVIOUS: packed |= 12;
|
|
case DisposalMethod.UNDEFINED(idx): packed |= (idx & 7) << 2;
|
|
}
|
|
if (gce.userInput) packed |= 2;
|
|
if (gce.hasTransparentColor) packed |= 1;
|
|
|
|
o.writeByte(packed);
|
|
o.writeUInt16(gce.delay);
|
|
o.writeByte(gce.transparentIndex);
|
|
o.writeByte(0); // Terminator
|
|
}
|
|
|
|
public function writeAppExtension(appExt:ApplicationExtension):Void
|
|
{
|
|
o.writeByte(0x21);
|
|
o.writeByte(0xFF);
|
|
o.writeByte(11);
|
|
switch (appExt)
|
|
{
|
|
case ApplicationExtension.AENetscapeLooping(loops):
|
|
o.writeString("NETSCAPE2.0");
|
|
o.writeByte(3);
|
|
o.writeByte(1); // Looping
|
|
o.writeUInt16(loops);
|
|
o.writeByte(0);
|
|
case ApplicationExtension.AEUnknown(name, version, bytes):
|
|
o.writeString(name);
|
|
o.writeString(version);
|
|
writeBlocks(bytes);
|
|
}
|
|
}
|
|
|
|
public function writeUnknownExtension(id:Int, bytes:Bytes):Void
|
|
{
|
|
o.writeByte(0x21);
|
|
o.writeByte(id);
|
|
writeBlocks(bytes);
|
|
}
|
|
|
|
public function writeFrame(frame:Frame):Void
|
|
{
|
|
|
|
o.writeByte(0x2C);
|
|
o.writeUInt16(frame.x);
|
|
o.writeUInt16(frame.y);
|
|
o.writeUInt16(frame.width);
|
|
o.writeUInt16(frame.height);
|
|
|
|
var packed:Int = 0;
|
|
if (frame.localColorTable) packed |= 128;
|
|
if (frame.interlaced) packed |= 64;
|
|
if (frame.sorted) packed |= 32;
|
|
packed |= Math.round(Tools.log2(frame.localColorTableSize) - 1) & 7;
|
|
o.writeByte(packed);
|
|
if (frame.localColorTable)
|
|
{
|
|
if (frame.colorTable != null) o.writeBytes(frame.colorTable, 0, frame.colorTable.length);
|
|
else throw "localColorTable flag is set, but there is no local color table!";
|
|
}
|
|
|
|
lzw.encode(frame.width, frame.height, frame.pixels, frame.localColorTable ? frame.localColorTableSize : gctSize, o, frame.interlaced);
|
|
}
|
|
|
|
/**
|
|
* Writes EndOfFile block.
|
|
*/
|
|
public function writeEOF():Void
|
|
{
|
|
o.writeByte(0x3B);
|
|
}
|
|
|
|
private function writeStringBlocks(text:String):Void
|
|
{
|
|
var len:Int;
|
|
var caret:Int = 0;
|
|
while (caret < text.length)
|
|
{
|
|
len = text.length - caret;
|
|
if (len > 0xFF) len = 0xFF;
|
|
o.writeByte(len);
|
|
for (i in 0...len) o.writeByte(text.charCodeAt(i + caret));
|
|
caret += len;
|
|
}
|
|
o.writeByte(0);
|
|
}
|
|
|
|
private function writeBlocks(bytes:Bytes):Void
|
|
{
|
|
var len:Int;
|
|
var caret:Int = 0;
|
|
while (caret < bytes.length)
|
|
{
|
|
len = bytes.length - caret;
|
|
if (len > 0xFF) len = 0xFF;
|
|
o.writeByte(len);
|
|
o.writeBytes(bytes, caret, len);
|
|
caret += 0xFF;
|
|
}
|
|
o.writeByte(0); // Terminator
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class LZWEncoder
|
|
{
|
|
private var EOF:Int = -1;
|
|
private static inline var BITS:Int = 12;
|
|
private static inline var HSIZE:Int = 5003;
|
|
private var masks:Array<Int> = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
|
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
|
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
|
|
|
|
private var out:Output;
|
|
private var bits:Int;
|
|
private var bitsCount:Int;
|
|
|
|
private var minCodeSize:Int;
|
|
private var codeSize:Int;
|
|
private var codeSizeLimit:Int;
|
|
private var clearFlag:Bool;
|
|
|
|
private var clearCode:Int;
|
|
private var eofCode:Int;
|
|
|
|
// Dict
|
|
private var htab:Vector<Int>;
|
|
private var codetab:Vector<Int>;
|
|
private var freeEnt:Int;
|
|
|
|
// Block buffer
|
|
private var blockBuffer:Bytes;
|
|
private var blockBufferCaret:Int;
|
|
|
|
// Input data
|
|
private var pixels:Bytes;
|
|
private var width:Int;
|
|
private var height:Int;
|
|
private var remaining:Int;
|
|
|
|
// Non-interlaced
|
|
private var pixelsCaret:Int;
|
|
// Interlaced
|
|
private var interlaced:Bool;
|
|
private var pixelsX:Int;
|
|
private var pixelsY:Int;
|
|
private var interlacingStage:Int;
|
|
private var interlacingStep:Int;
|
|
|
|
public function new()
|
|
{
|
|
blockBuffer = Bytes.alloc(256);
|
|
}
|
|
|
|
public function encode(width:Int, height:Int, pixels:Bytes, colorsCount:Int, out:Output, interlaced:Bool):Void
|
|
{
|
|
minCodeSize = Math.round(Tools.log2(colorsCount));
|
|
|
|
this.pixels = pixels;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.out = out;
|
|
|
|
htab = new Vector(HSIZE);
|
|
codetab = new Vector(HSIZE);
|
|
|
|
blockBufferCaret = 0;
|
|
bits = 0;
|
|
bitsCount = 0;
|
|
|
|
clearCode = 1 << minCodeSize;
|
|
eofCode = clearCode + 1;
|
|
freeEnt = clearCode + 2;
|
|
|
|
out.writeByte(minCodeSize);
|
|
remaining = width * height;
|
|
|
|
this.interlaced = interlaced;
|
|
if (interlaced)
|
|
{
|
|
pixelsX = 0;
|
|
pixelsY = 0;
|
|
interlacingStage = 0;
|
|
interlacingStep = 8;
|
|
}
|
|
else pixelsCaret = 0;
|
|
|
|
compress();
|
|
out.writeByte(0);
|
|
}
|
|
|
|
private function char_out(c:Int):Void
|
|
{
|
|
blockBuffer.set(blockBufferCaret++, c);
|
|
if (blockBufferCaret >= 254) flush_char();
|
|
}
|
|
|
|
private function cl_block():Void
|
|
{
|
|
cl_hash(HSIZE);
|
|
freeEnt = clearCode + 2;
|
|
clearFlag = true;
|
|
output(clearCode);
|
|
}
|
|
|
|
private function cl_hash(hsize:Int):Void
|
|
{
|
|
for (i in 0...hsize) htab[i] = -1;
|
|
}
|
|
|
|
private function compress():Void
|
|
{
|
|
var disp:Int;
|
|
var i:Int;
|
|
|
|
clearFlag = false;
|
|
codeSize = minCodeSize + 1;
|
|
codeSizeLimit = MAXCODE(codeSize);
|
|
|
|
var ent:Int = nextPixel();
|
|
|
|
var hshift:Int = 0;
|
|
var fcode:Int = HSIZE;
|
|
while (fcode < 65536)
|
|
{
|
|
++hshift;
|
|
fcode *= 2;
|
|
}
|
|
hshift = 8 - hshift;
|
|
var hsize_reg:Int = HSIZE;
|
|
cl_hash(hsize_reg);
|
|
|
|
output(clearCode);
|
|
|
|
var c:Int;
|
|
while ((c = nextPixel()) != EOF)
|
|
{
|
|
fcode = (c << BITS) + ent;
|
|
i = (c << hshift) ^ ent;
|
|
if (htab[i] == fcode)
|
|
{
|
|
ent = codetab[i];
|
|
continue;
|
|
}
|
|
else if (htab[i] >= 0)
|
|
{
|
|
disp = hsize_reg - i;
|
|
if (i == 0) disp = 1;
|
|
var skip:Bool = false;
|
|
do
|
|
{
|
|
if ((i -= disp) < 0) i += hsize_reg;
|
|
if (htab[i] == fcode)
|
|
{
|
|
ent = codetab[i];
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
while (htab[i] >= 0);
|
|
if (skip) continue;
|
|
}
|
|
|
|
output(ent);
|
|
ent = c;
|
|
if (freeEnt < (1 << BITS))
|
|
{
|
|
codetab[i] = freeEnt++;
|
|
htab[i] = fcode;
|
|
}
|
|
else
|
|
{
|
|
cl_block();
|
|
}
|
|
}
|
|
|
|
output(ent);
|
|
output(eofCode);
|
|
}
|
|
|
|
private function flush_char():Void
|
|
{
|
|
if (blockBufferCaret > 0)
|
|
{
|
|
out.writeByte(blockBufferCaret);
|
|
out.writeBytes(blockBuffer, 0, blockBufferCaret);
|
|
blockBufferCaret = 0;
|
|
}
|
|
}
|
|
|
|
private inline function MAXCODE(n_bits:Int):Int
|
|
{
|
|
return (1 << n_bits) - 1;
|
|
}
|
|
|
|
private function nextPixel():Int
|
|
{
|
|
if (remaining == 0) return EOF;
|
|
remaining--;
|
|
if (interlaced)
|
|
{
|
|
if (++pixelsX == width)
|
|
{
|
|
pixelsX = 0;
|
|
pixelsY += interlacingStep;
|
|
if (pixelsY >= height)
|
|
{
|
|
switch (interlacingStage)
|
|
{
|
|
// first: Every 8 line with start at 0
|
|
case 0: pixelsY = 4; // Every 8 line with start at 4
|
|
case 1: pixelsY = 2; interlacingStep = 4; // Every 4 line with start at 2
|
|
case 2: pixelsY = 1; interlacingStep = 2; // Every 2 line with start at 1
|
|
default: return -1; // EOF
|
|
}
|
|
interlacingStage++;
|
|
}
|
|
}
|
|
return pixels.get(pixelsY * width + pixelsX);
|
|
}
|
|
else
|
|
{
|
|
return pixels.get(pixelsCaret++);
|
|
}
|
|
}
|
|
|
|
private function output(code:Int):Void
|
|
{
|
|
bits &= masks[bitsCount];
|
|
|
|
if (bitsCount > 0) bits |= (code << bitsCount);
|
|
else bits = code;
|
|
|
|
bitsCount += codeSize;
|
|
|
|
while (bitsCount >= 8)
|
|
{
|
|
char_out(bits & 0xFF);
|
|
bits >>= 8;
|
|
bitsCount -= 8;
|
|
}
|
|
|
|
if (freeEnt > codeSizeLimit || clearFlag)
|
|
{
|
|
if (clearFlag)
|
|
{
|
|
codeSizeLimit = MAXCODE(codeSize = minCodeSize + 1);
|
|
clearFlag = false;
|
|
}
|
|
else
|
|
{
|
|
codeSize++;
|
|
if (codeSize == BITS) codeSizeLimit = 1 << BITS;
|
|
else codeSizeLimit = MAXCODE(codeSize);
|
|
}
|
|
}
|
|
|
|
if (code == eofCode)
|
|
{
|
|
while (bitsCount > 0)
|
|
{
|
|
char_out(bits & 0xFF);
|
|
bits >>= 8;
|
|
bitsCount -= 8;
|
|
}
|
|
flush_char();
|
|
}
|
|
}
|
|
|
|
}
|