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

526 lines
12 KiB
Haxe
Raw Normal View History

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();
}
}
}