Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9

This commit is contained in:
2026-09-04 10:46:13 -07:00
parent c915312901
commit 0460b9d587
482 changed files with 27797 additions and 3226 deletions

View File

@ -0,0 +1,357 @@
package iron.format.gif;
import haxe.io.Bytes;
/**
* Gif data.
*/
typedef Data =
{
/**
* Gif version. There is only 2 Gif version exists. 87a and 89a.
* 87a have less features and does not support any extensions.
* Unknown version is adviced to be interpreted as newest (89a) official version.
*/
var version:Version;
/**
* Information about logical screen of Gif that provides basic information about Gif.
*/
var logicalScreenDescriptor:LogicalScreenDescriptor;
/**
* Global color table used for Gif. Present only if Logical Screen Descriptor contained global color table flag.
* Note that this color table not always present since frames can contain local color tables that overrides global color table.
*/
@:optional var globalColorTable:Null<ColorTable>;
/**
* List of Gif data blocks.
*/
var blocks:List<Block>;
}
/**
* Gif data block. Custom blocks are not supported.
*/
enum Block
{
/**
* Gif frame block.
* Note that this block does not contain link to graphic control extension of Frame even if it is present. GraphicControl extension Block commonly present right before frame Block.
*/
BFrame(frame:Frame);
/**
* Additional extension block. This Block does not supported in 87a Gif specification version.
*/
BExtension(extension:Extension);
/**
* End of File block. Represents end of Gif data.
*/
BEOF;
}
/**
* Extension block contains additional data about Gif image. This block does not supported by 87a version.
*/
enum Extension
{
/**
* Graphic Control extension gives additional control over next frame, like frame delay, disposal method, alpha channel and other information.
*/
EGraphicControl(gce:GraphicControlExtension);
/**
* Commentary extension. Not show up as any visual, just a text in file.
*/
EComment(text:String);
/**
* Text extension. Must work as text rendering on the image, but ignored by all major Gif decoders.
*/
EText(pte:PlainTextExtension);
/**
* Application extension allow to insert additional application data into Gif. Mostly used app extension is NETSCAPE2.0 looping extension, used to set up amount of loops in frame.
*/
EApplicationExtension(ext:ApplicationExtension);
/**
* Unknown extension.
*/
EUnknown(id:Int, data:Bytes);
}
/**
* Application extension. Mostly used only for one reason - setting up loops count. There is exist other app extensions but they are really rare.
*/
enum ApplicationExtension
{
/**
* NETSCAPE2.0 looping extension. Contains only amount of animation repeats.
* Note that there is two NETSCAPE2.0 app extensions for Gif format and the type of extension is stored in first byte of data. Looping extension have ID 1.
*/
AENetscapeLooping(loops:Int);
/**
* Unknown or unsupported app extension.
*/
AEUnknown(name:String, version:String, data:Bytes);
}
/**
* Typical color table for Gif image.
* Can contain 2, 4, 8, 16, 32, 64, 128 or 256 colors.
* Data stored in RGB format. Information about alpha channel provided by Graohic Control Extension.
*/
typedef ColorTable = Bytes;
/**
* Single frame of the image.
* Actually it's a merge of 3 consequent blocks:
* 1. Image Descriptor.
* Contains frame informations like position, size, existing of local color table and interlaced flag.
* 2. [Local color table].
* Only present if Image Descriptor contains local color table flag. Overrides global color table.
* 3. Pixel data blocks.
* LZW compressed pixel data.
*/
typedef Frame =
{
/**
* X position of image on the Logical Screen
*/
var x:Int;
/**
* Y position of image on the Logical Screen
*/
var y:Int;
/**
* Width of image in pixels
*/
var width:Int;
/**
* Height of image in pixels
*/
var height:Int;
/**
* Is this image uses local color table?
*/
var localColorTable:Bool;
/**
* Is this image written in interlace mode?
* Note: The pixel data already deinterlaced and this flag presented only for information purpose (and for Writer when there is one).
*/
var interlaced:Bool;
/**
* Is local color table sorted in order of decreasing priority?
*/
var sorted:Bool;
/**
* Size of local color table
*/
var localColorTableSize:Int;
/**
* Pixel data of frame. Stored as Indexed colors, 1 byte per pixel.
*/
var pixels:Bytes;
/**
* Local color table used by frame. Stored as 3-byte RGB colors. If value is null, must be used global color table.
*/
var colorTable:ColorTable;
}
/**
* Graphic Control Extension block, used for setting up disposal method, transparency, delay and user input.
*/
typedef GraphicControlExtension =
{
/**
* Disposal method of frame.
*/
var disposalMethod:DisposalMethod;
/**
* Is image must wait for user input, before dispose?
* This flag may be used by user-defined program but absolutely ignored by any Gif players.
*/
var userInput:Bool;
/**
* Is image have transparency?
*/
var hasTransparentColor:Bool;
/**
* Delay, before next image appears. Delay is in centiseconds (1 centisecond = 1/100 seconds).
* Note: Some players (like FastStone) cut fraction of elapsed time when progressing to next frame which results in small timing error.
* Recommended to use `time -= delay` instead of `time = 0`.
*/
var delay:Int;
/**
* Index in color table that used as transparent.
*/
var transparentIndex:Int;
}
/**
* Extension for rendering text on Gif logical screen. It does not supported by major Gif decoders.
* Font and text size decision is left to decoder. (recommended to decide based on grid/cell size)
* Text must be rendered with one character at cell.
* It's recommended to replace any characters less than 0x20 and greater than 0xf7 to be rendered as Space (0x20)
*/
typedef PlainTextExtension =
{
/**
* X position of text grid on Logical Screen.
*/
var textGridX:Int;
/**
* Y position of text grid on Logical Screen.
*/
var textGridY:Int;
/**
* Width of text grid in pixels.
*/
var textGridWidth:Int;
/**
* Height of text grid in pixels.
*/
var textGridHeight:Int;
/**
* Width of character cell in text grid.
*/
var charCellWidth:Int;
/**
* Height of character cell in text grid.
*/
var charCellHeight:Int;
/**
* Foreground/character color index.
*/
var textForegroundColorIndex:Int;
/**
* Background color index.
*/
var textBackgroundColorIndex:Int;
/**
* Text to render.
*/
var text:String;
}
/**
* Logical screen descriptor of GIF file.
* Contains very basic information about Gif.
*/
typedef LogicalScreenDescriptor =
{
/**
* Width of GIF image in pixels
*/
var width:Int;
/**
* Height of GIF image in pixels
*/
var height:Int;
/**
* Is this file uses global color table?
*/
var hasGlobalColorTable:Bool;
/**
* Specification:
* Number of bits per primary color available
to the original image, minus 1. This value represents the size of
the entire palette from which the colors in the graphic were
selected, not the number of colors actually used in the graphic.
For example, if the value in this field is 3, then the palette of
the original image had 4 bits per primary color available to create
the image. This value should be set to indicate the richness of
the original palette, even if not every color from the whole
palette is available on the source machine.
*/
var colorResolution:Int;
/**
* Specification:
* Indicates whether the Global Color Table is sorted.
If the flag is set, the Global Color Table is sorted, in order of
decreasing importance. Typically, the order would be decreasing
frequency, with most frequent color first. This assists a decoder,
with fewer available colors, in choosing the best subset of colors;
the decoder may use an initial segment of the table to render the
graphic.
*/
var sorted:Bool;
/**
* Size of global color table.
*/
var globalColorTableSize:Int;
/**
* Background color index in global color table
*/
var backgroundColorIndex:Int;
/**
* Factor used to compute an approximation of the aspect ratio of the pixel in the original image.
*/
var pixelAspectRatio:Float;
}
/**
* Version of Gif file.
* The only 2 official versions is GIF87a and GIF89a.
*/
enum Version
{
/**
* First version of Gif file format from May 1987.
*
* Note: The checking of unsupported blocks disabled by default to save some time. To enable supported blocks check set `yagp_strict_version_check` debug variable.
*/
GIF87a;
/**
* Second and actual version of Gif file format from July 1989.
*/
GIF89a;
/**
* Unknown version of Gif file.
*/
Unknown(version:String);
}
/**
* Disposal method of GIF frame.
*/
enum DisposalMethod
{
/**
* The disposal method is unspecified. Action on demand of viewer.
*
* Mostly interpreted as NO_ACTION.
*/
UNSPECIFIED;
/**
* No action required.
*/
NO_ACTION;
/**
* Fill frame rectangle with background color.
*
* Usage note:
* Most renderers clears to transparency instead of filling background color, when frame's transparent color index not equals to background color index.
*/
FILL_BACKGROUND;
/**
* Render previous state of gif as it before rendering disposing frame.
*/
RENDER_PREVIOUS;
/**
* Reserved disposal methods.
*/
UNDEFINED(index:Int);
}

View File

@ -0,0 +1,359 @@
package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose.
*
* Original code by Kevin Weiner, FM Software.
* Adapted by Thomas Hourdel (https://github.com/Chman/Moments)
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*/
import haxe.io.UInt8Array;
import haxe.io.BytesOutput;
@:enum abstract GifRepeat(Int)
from Int to Int {
var None = 0;
var Infinite = -1;
}
@:enum abstract GifQuality(Int)
from Int to Int {
var Best = 1;
var VeryHigh = 10;
var QuiteHigh = 20;
var High = 35;
var Mid = 50;
var Low = 65;
var QuiteLow = 80;
var VeryLow = 90;
var Worst = 100;
}
class GifEncoder {
var width: Int;
var height: Int;
var framerate: Float = 24; // used if frame.delay < 0
var repeat: Int = -1; // -1: infinite, 0: none, >0: repeat count
var colorDepth: Int = 8; // Number of bit planes
var paletteSize: Int = 7; // Color table size (bits-1)
var sampleInterval: Int = 10; // Default sample interval for quantizer
//caches
var pixels: UInt8Array;
var indexedPixels: UInt8Array; // Converted frame indexed to palette
var colorTab: UInt8Array; // RGB palette
var usedEntry: Array<Bool>; // Active palette entries
//
var nq: NeuQuant;
var lzwEncoder: LzwEncoder;
//internal
var started: Bool = false;
var first_frame: Bool = true;
//:todo: error handling could be better - but throw inside of another thread on cpp is too quiet
/** Allows a custom print handler for error messages.
Defaults to Sys.println on sys targets, and trace otherwise. */
public var print: Dynamic->Void;
// Public API
/** Construct a gif encoder with options:
frame width/height:
Default is 0, required
framerate:
This is used if an added frame has a delay that is negative.
repeat:
Default is 0 (no repeat); -1 means play indefinitely.
Use GifRepeat for clarity
quality:
Sets quality of color quantization (conversion of images to
the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1)
produce better colors, but slow processing significantly. Higher values will speed
up the quantization pass at the cost of lower image quality (maximum = 100). */
public function new(
_frame_width:Int,
_frame_height:Int,
_framerate:Float,
_repeat:Int = GifRepeat.Infinite,
_quality:Int = 10
) {
#if sys
print = Sys.println;
#else
print = function(v) { trace(v); }
#end
width = _frame_width;
height = _frame_height;
framerate = _framerate;
repeat = _repeat;
sampleInterval = Std.int(clamp(_quality, 1, 100));
usedEntry = [for (i in 0...256) false];
pixels = new UInt8Array(width * height * 3);
indexedPixels = new UInt8Array(width * height);
nq = new NeuQuant();
lzwEncoder = new LzwEncoder();
} //new
public function start(output:BytesOutput) : Void {
if(output == null) {
print("gif: start() output must not be null.");
return;
}
output.writeString("GIF89a");
write_LSD(output);
started = true;
} //start
public function add(output:BytesOutput, frame:GifFrame) : Void {
if(output == null) {
print("gif: add() output must not be null.");
return;
}
if(!started) {
print("gif: add() requires start to be called before adding frames.");
return;
}
var pixels = get_pixels(frame);
analyze(pixels);
if(first_frame) {
write_palette(output);
if(repeat != GifRepeat.None) {
write_NetscapeExt(output);
}
first_frame = false;
} //first_frame
var delay = if(frame.delay < 0) {
1.0/framerate;
} else {
frame.delay;
}
write_GraphicControlExt(output, delay);
write_image_desc(output, first_frame);
if(!first_frame) {
write_palette(output);
}
write_pixels(output);
} //add
public function commit(output:BytesOutput) : Void {
if(output == null) {
print("gif: commit() output must be not null.");
return;
}
if(!started) {
print("gif: commit() called without start() being called first.");
return;
}
output.writeByte(0x3b); // Gif trailer
output.flush();
output.close();
started = false;
first_frame = true;
} //commit
//helpers
function get_pixels(frame:GifFrame):UInt8Array {
//if not flipped we can use the data as is
if (!frame.flippedY) return frame.data;
//otherwise flip it, and return the cached array
var stride = width * 3;
for(y in 0...height) {
var begin = (height - 1 - y) * stride;
pixels.view.buffer.blit(y * stride, frame.data.view.buffer, begin, stride);
}
return pixels;
} //get_pixels
function analyze(pixels:UInt8Array) {
// Create reduced palette
nq.reset(pixels, pixels.length, sampleInterval);
colorTab = nq.process();
// Map image pixels to new palette
var k:Int = 0;
for (i in 0...(width * height)) {
var r = pixels[k++] & 0xff;
var g = pixels[k++] & 0xff;
var b = pixels[k++] & 0xff;
var index = nq.map(r, g,b);
usedEntry[index] = true;
indexedPixels[i] = index;
}
} //analyze
//writers
//
/** Writes Logical Screen Descriptor. */
function write_LSD(output:BytesOutput) {
//
// Logical screen size
output.writeInt16(width);
output.writeInt16(height);
// Packed fields
output.writeByte(0x80 | // 1 : global color table flag = 1 (gct used)
0x70 | // 2-4 : color resolution = 7
0x00 | // 5 : gct sort flag = 0
paletteSize); // 6-8 : gct size
output.writeByte(0); // Background color index
output.writeByte(0); // Pixel aspect ratio - assume 1:1
} //write_LSD
/** Writes Netscape application extension to define repeat count. */
function write_NetscapeExt(output:BytesOutput):Void {
var repeats = repeat;
if(repeats == GifRepeat.Infinite || repeats < 0) repeats = 0;
if(repeats == GifRepeat.None) repeats = -1;
output.writeByte(0x21); // Extension introducer
output.writeByte(0xff); // App extension label
output.writeByte(11); // Block size
output.writeString("NETSCAPE" + "2.0"); // App id + auth code
output.writeByte(3); // Sub-block size
output.writeByte(1); // Loop sub-block id
output.writeInt16(repeats); // Loop count (extra iterations, 0=repeat forever)
output.writeByte(0); // Block terminator
} //write_NetscapeExt
/** Write color table. */
function write_palette(output:BytesOutput):Void {
output.write(colorTab.view.buffer);
var n:Int = (3 * 256) - colorTab.length;
for (i in 0...n) {
output.writeByte(0);
}
} //write_palette
/** Encodes and writes pixel data. */
function write_pixels(output:BytesOutput):Void {
lzwEncoder.reset(indexedPixels, colorDepth);
lzwEncoder.encode(output);
} //write_pixels
/** Writes Image Descriptor. */
function write_image_desc(output:BytesOutput, first:Bool):Void {
output.writeByte(0x2c); // Image separator
output.writeInt16(0); // Image position x = 0
output.writeInt16(0); // Image position y = 0
output.writeInt16(width); // Image width
output.writeInt16(height); // Image height
//Write LCT, or GCT
if(first) {
output.writeByte(0); // No LCT - GCT is used for first (or only) frame
} else {
output.writeByte(0x80 | // 1 local color table 1=yes
0 | // 2 interlace - 0=no
0 | // 3 sorted - 0=no
0 | // 4-5 reserved
paletteSize); // 6-8 size of color table
} //else
} //write_image_desc
/** Writes Graphic Control Extension. Delay is in seconds, floored and converted to 1/100 of a second */
function write_GraphicControlExt(output:BytesOutput, delay:Float):Void {
output.writeByte(0x21); // Extension introducer
output.writeByte(0xf9); // GCE label
output.writeByte(4); // data block size
// Packed fields
output.writeByte(0 | // 1:3 reserved
0 | // 4:6 disposal
0 | // 7 user input - 0 = none
0 ); // 8 transparency flag
//convert to 1/100 sec
var delay_val = Math.floor(delay * 100);
output.writeInt16(delay_val); // Delay x 1/100 sec
output.writeByte(0); // Transparent color index
output.writeByte(0); // Block terminator
} //write_GraphicControlExt
/** Clamp a value between a and b and return the clamped version */
static inline public function clamp(value:Float, a:Float, b:Float):Float
{
return ( value < a ) ? a : ( ( value > b ) ? b : value );
}
} //GifEncoder
typedef GifFrame = {
/** Delay of the frame in seconds. This value gets floored
when encoded due to gif format requirements. If this value is negative,
the default encoder frame rate will be used. */
var delay: Float;
/** Whether or not this frame should be flipped on the Y axis */
var flippedY: Bool;
/** Pixels data in unsigned bytes, rgb format */
var data: UInt8Array;
}

View File

@ -0,0 +1,350 @@
 package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose, however, refer to the Unisys LZW patent for restrictions
* on use of the associated LZWEncoder class :
*
* The Unisys patent expired on 20 June 2003 in the USA, in Europe it expired
* on 18 June 2004, in Japan the patent expired on 20 June 2004 and in Canada
* it expired on 7 July 2004. The U.S. IBM patent expired 11 August 2006, The
* Software Freedom Law Center says that after 1 October 2006, there will be
* no significant patent claims interfering with employment of the GIF format.
*
* Original code by Kevin Weiner, FM Software.
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class LzwEncoder {
static var EOF(default, never):Int = -1;
var pixAry:UInt8Array;
var initCodeSize:Int;
var curPixel:Int;
// GIFCOMPR.C - GIF Image compression routines
//
// Lempel-Ziv compression based on 'compress'. GIF modifications by
// David Rowley (mgardi@watdcsu.waterloo.edu)
// General DEFINEs
static var BITS(default, never):Int = 12;
static var HSIZE(default, never):Int = 5003; // 80% occupancy
// GIF Image compression - modified 'compress'
//
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
//
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
// Jim McKie (decvax!mcvax!jim)
// Steve Davies (decvax!vax135!petsd!peora!srd)
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
// James A. Woods (decvax!ihnp4!ames!jaw)
// Joe Orost (decvax!vax135!petsd!joe)
var n_bits:Int; // number of bits/code
var maxbits:Int = BITS; // user settable max # bits/code
var maxcode:Int; // maximum code, given n_bits
var maxmaxcode:Int = 1 << BITS; // should NEVER generate this code
var htab:Int32Array;
var codetab:Int32Array;
var hsize:Int = HSIZE; // for dynamic table sizing
var free_ent:Int = 0; // first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
var clear_flg:Bool = false;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
var g_init_bits:Int;
var ClearCode:Int;
var EOFCode:Int;
// output
//
// output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// outputs:
// outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
var cur_accum:Int = 0;
var cur_bits:Int = 0;
var masks:Array<Int> =
[
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF,
0x07FF,
0x0FFF,
0x1FFF,
0x3FFF,
0x7FFF,
0xFFFF ];
// Number of characters so far in this 'packet'
var a_count:Int;
// Define the storage for the packet accumulator
var accum:UInt8Array;
//----------------------------------------------------------------------------
public function new()
{
htab = new Int32Array(HSIZE);
codetab = new Int32Array(HSIZE);
accum = new UInt8Array(256);
}
//Reset the encoder to new pixel data and default values
public function reset(pixels:UInt8Array, color_depth:Int) { //width and height used to be passed in though they were never used
pixAry = pixels;
initCodeSize = Std.int(Math.max(2, color_depth));
maxbits = BITS;
maxmaxcode = 1 << BITS;
hsize = HSIZE;
free_ent = 0;
clear_flg = false;
cur_accum = 0;
cur_bits = 0;
}
// add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
function add(c:UInt, out:haxe.io.Output):Void
{
accum[a_count++] = c;
if (a_count >= 254)
flush(out);
}
// Clear out the hash table
// table clear for block compress
function clearTable(out:haxe.io.Output):Void
{
resetCodeTable(hsize);
free_ent = ClearCode + 2;
clear_flg = true;
output(ClearCode, out);
}
// reset code table
function resetCodeTable(hsize:Int):Void
{
for (i in 0...hsize)
htab[i] = -1;
}
function compress(init_bits:Int, out:haxe.io.Output):Void
{
var fcode:Int;
var i:Int /* = 0 */;
var c:Int;
var ent:Int;
var disp:Int;
var hsize_reg:Int;
var hshift:Int;
// Set up the globals: g_init_bits - initial number of bits
g_init_bits = init_bits;
// Set up the necessary values
clear_flg = false;
n_bits = g_init_bits;
maxcode = maxCode(n_bits);
ClearCode = 1 << (init_bits - 1);
EOFCode = ClearCode + 1;
free_ent = ClearCode + 2;
a_count = 0; // clear packet
ent = nextPixel();
hshift = 0;
fcode = hsize;
while (fcode < 65536) {
++hshift;
fcode *= 2;
}
hshift = 8 - hshift; // set hash code range bound
hsize_reg = hsize;
resetCodeTable(hsize_reg); // clear hash table
output(ClearCode, out);
while ((c = nextPixel()) != EOF)
{
fcode = (c << maxbits) + ent;
i = (c << hshift) ^ ent; // xor hashing
if (htab[i] == fcode)
{
ent = codetab[i];
continue;
}
else if (htab[i] >= 0) // non-empty slot
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if (i == 0)
disp = 1;
do
{
if ((i -= disp) < 0)
i += hsize_reg;
if (htab[i] == fcode)
{
ent = codetab[i];
break;
}
} while (htab[i] >= 0);
if (htab[i] == fcode) continue;
}
output(ent, out);
ent = c;
if (free_ent < maxmaxcode)
{
codetab[i] = free_ent++; // code -> hashtable
htab[i] = fcode;
}
else
clearTable(out);
}
// Put out the final code.
output(ent, out);
output(EOFCode, out);
}
//----------------------------------------------------------------------------
public function encode(os:haxe.io.Output):Void
{
os.writeByte( initCodeSize ); // write "initial code size" byte
curPixel = 0;
compress(initCodeSize + 1, os); // compress and write the pixel data
os.writeByte(0); // write block terminator
}
// flush the packet to disk, and reset the accumulator
function flush(out:haxe.io.Output):Void
{
if (a_count > 0)
{
out.writeByte(a_count);
out.writeBytes(accum.view.buffer, 0, a_count);
a_count = 0;
}
}
inline function maxCode(n_bits:Int):Int
{
return (1 << n_bits) - 1;
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
function nextPixel():Int
{
if (curPixel == pixAry.length)
return EOF;
curPixel++;
return pixAry[curPixel - 1] & 0xff;
}
function output(code:Int, out:haxe.io.Output):Void
{
cur_accum &= masks[cur_bits];
if (cur_bits > 0)
cur_accum |= (code << cur_bits);
else
cur_accum = code;
cur_bits += n_bits;
while (cur_bits >= 8)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (free_ent > maxcode || clear_flg)
{
if (clear_flg)
{
maxcode = maxCode(n_bits = g_init_bits);
clear_flg = false;
}
else
{
++n_bits;
if (n_bits == maxbits)
maxcode = maxmaxcode;
else
maxcode = maxCode(n_bits);
}
}
if (code == EOFCode)
{
// At EOF, write the rest of the buffer.
while (cur_bits > 0)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
flush(out);
}
}
}

View File

@ -0,0 +1,541 @@
package iron.format.gif;
/*
* Copyright (c) 1994 Anthony Dekker
* Ported to Java by Kevin Weiner, FM Software
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
* See "Kohonen neural networks for optimal colour quantization"
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
* for a discussion of the algorithm.
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
* in this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons who receive
* copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class NeuQuant {
inline static var netsize : Int = 256; // Number of colours used
// Four primes near 500 - assume no image has a length so large that it is divisible by all four primes
inline static var prime1 : Int = 499;
inline static var prime2 : Int = 491;
inline static var prime3 : Int = 487;
inline static var prime4 : Int = 503;
inline static var minpicturebytes : Int = (3 * prime4); // Minimum size for input image
// Network Definitions
inline static var netbiasshift : Int = 4; // Bias for colour values
inline static var ncycles : Int = 100; // No. of learning cycles
// Defs for freq and bias
inline static var intbiasshift : Int = 16; // Bias for fractions
inline static var intbias : Int = (1 << intbiasshift);
inline static var gammashift : Int = 10; // Gamma = 1024
inline static var gamma : Int = (1 << gammashift);
inline static var betashift : Int = 10;
inline static var beta : Int = (intbias >> betashift); // Beta = 1/1024
inline static var betagamma : Int = (intbias << (gammashift - betashift));
// Defs for decreasing radius factor
inline static var initrad : Int = (netsize >> 3); // For 256 cols, radius starts
inline static var radiusbiasshift : Int = 6; // At 32.0 biased by 6 bits
inline static var radiusbias : Int = (1 << radiusbiasshift);
inline static var initradius : Int = (initrad * radiusbias); // And decreases by a
inline static var radiusdec : Int = 30; // Factor of 1/30 each cycle
// Defs for decreasing alpha factor
inline static var alphabiasshift : Int = 10; /* alpha starts at 1.0 */
inline static var initalpha : Int = (1 << alphabiasshift);
// Radbias and alpharadbias used for radpower calculation
inline static var radbiasshift : Int = 8;
inline static var radbias : Int = (1 << radbiasshift);
inline static var alpharadbshift : Int = (alphabiasshift + radbiasshift);
inline static var alpharadbias : Int = (1 << alpharadbshift);
var alphadec:Int; // Biased by 10 bits
// Types and Global Variables
var thepicture: UInt8Array; // The input image itself
var lengthcount: Int; // Lengthcount = H*W*3
var samplefac: Int; // Sampling factor 1..30
var network: Int32Array; // The network itself - [netsize][4]
var netindex: Int32Array; // For network lookup - really 256
var bias: Int32Array; // Bias array for learning
var freq: Int32Array; // Frequency array for learning
var radpower: Int32Array; // Radpower for precomputation
var colormap_map: UInt8Array; // Cached color map array
var colormap_index: Int32Array; // Cached color map index
public function new()
{
netindex = new Int32Array(256);
bias = new Int32Array(netsize);
freq = new Int32Array(netsize);
radpower = new Int32Array(initrad);
network = new Int32Array(netsize * 4);
colormap_map = new UInt8Array(3 * netsize);
colormap_index = new Int32Array(netsize);
}
// Reset network in range (0,0,0) to (255,255,255) and set parameters
public function reset(thepic:UInt8Array, len:Int, sample:Int):Void {
thepicture = thepic;
lengthcount = len;
samplefac = sample;
for (i in 0...netsize) {
network[i*4 + 0] = network[i*4 + 1] = network[i*4 + 2] = Std.int((i << (netbiasshift + 8)) / netsize);
freq[i] = Std.int(intbias / netsize); // 1 / netsize
bias[i] = 0; // allocated to zero?
}
}
public function colormap():UInt8Array
{
for(i in 0...netsize) {
colormap_index[network[i * 4 + 3]] = i;
}
var k:Int = 0;
for (i in 0...netsize)
{
var j = colormap_index[i];
colormap_map[k++] = network[j * 4];
colormap_map[k++] = network[j * 4 + 1];
colormap_map[k++] = network[j * 4 + 2];
}
return colormap_map;
}
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
public function inxbuild():Void
{
var i:Int;
var j:Int;
var smallpos:Int;
var smallval:Int;
var previouscol:Int;
var startpos:Int;
previouscol = 0;
startpos = 0;
for (i in 0...netsize)
{
smallpos = i;
smallval = network[i*4 + 1]; // Index on g
// Find smallest in i..netsize-1
for (j in (i + 1)...netsize)
{
if (network[j*4 + 1] < smallval)
{
smallpos = j;
smallval = network[j*4 + 1]; // Index on g
}
}
// Swap p (i) and q (smallpos) entries
if (i != smallpos)
{
j = network[smallpos*4 + 0];
network[smallpos*4 + 0] = network[i*4 + 0];
network[i*4 + 0] = j;
j = network[smallpos*4 + 1];
network[smallpos*4 + 1] = network[i*4 + 1];
network[i*4 + 1] = j;
j = network[smallpos*4 + 2];
network[smallpos*4 + 2] = network[i*4 + 2];
network[i*4 + 2] = j;
j = network[smallpos*4 + 3];
network[smallpos*4 + 3] = network[i*4 + 3];
network[i*4 + 3] = j;
}
// Smallval entry is now in position i
if (smallval != previouscol)
{
netindex[previouscol] = (startpos + i) >> 1;
for (j in (previouscol + 1)...smallval)
netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
var maxnetpos = netsize - 1;
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (j in (previouscol + 1)...256)
netindex[j] = maxnetpos;
}
// Main learning Loop
public function learn():Void
{
var i:Int;
var j:Int;
var b:Int;
var g:Int;
var r:Int;
var radius:Int;
var rad:Int;
var alpha:Int;
var step:Int;
var delta:Int;
var samplepixels:Int;
var p:UInt8Array;
var pix:Int;
var lim:Int;
if (lengthcount < minpicturebytes)
samplefac = 1;
alphadec = 30 + Std.int((samplefac - 1) / 3);
p = thepicture;
pix = 0;
lim = lengthcount;
samplepixels = Std.int(lengthcount / (3 * samplefac));
delta = Std.int(samplepixels / ncycles);
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (i in 0...rad)
radpower[i] = Std.int(alpha * (((rad * rad - i * i) * radbias) / (rad * rad)));
if (lengthcount < minpicturebytes)
{
step = 3;
}
else if ((lengthcount % prime1) != 0)
{
step = 3 * prime1;
}
else
{
if ((lengthcount % prime2) != 0)
{
step = 3 * prime2;
}
else
{
if ((lengthcount % prime3) != 0)
step = 3 * prime3;
else
step = 3 * prime4;
}
}
i = 0;
while (i < samplepixels)
{
b = (p[pix + 0] & 0xff) << netbiasshift;
g = (p[pix + 1] & 0xff) << netbiasshift;
r = (p[pix + 2] & 0xff) << netbiasshift;
j = contest(b, g, r);
altersingle(alpha, j, b, g, r);
if (rad != 0)
alterneigh(rad, j, b, g, r); // Alter neighbours
pix += step;
if (pix >= lim)
pix -= lengthcount;
i++;
if (delta == 0)
delta = 1;
if (i % delta == 0)
{
alpha -= Std.int(alpha / alphadec);
radius -= Std.int(radius / radiusdec);
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (j in 0...rad)
radpower[j] = Std.int(alpha * (((rad * rad - j * j) * radbias) / (rad * rad)));
}
}
}
// Search for BGR values 0..255 (after net is unbiased) and return colour index
public function map(b:Int, g:Int, r:Int):Int
{
var i:Int;
var j:Int;
var dist:Int;
var a:Int;
var bestd:Int;
var best:Int;
bestd = 1000; // Biggest possible dist is 256*3
best = -1;
i = netindex[g]; // Index on g
j = i - 1; // Start at netindex[g] and work outwards
while ((i < netsize) || (j >= 0))
{
if (i < netsize)
{
dist = network[i*4 + 1] - g; // Inx key
if (dist >= bestd)
{
i = netsize; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[i*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[i*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[i*4 + 3];
}
}
i++;
}
}
if (j >= 0)
{
dist = g - network[j*4 + 1]; // Inx key - reverse dif
if (dist >= bestd)
{
j = -1; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[j*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[j*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[j*4 + 3];
}
}
j--;
}
}
}
return best;
}
public function process():UInt8Array
{
learn();
unbiasnet();
inxbuild();
return colormap();
}
// Unbias network to give byte values 0..255 and record position i to prepare for sort
public function unbiasnet():Void
{
for (i in 0...netsize)
{
network[i*4] >>= netbiasshift;
network[i*4 + 1] >>= netbiasshift;
network[i*4 + 2] >>= netbiasshift;
network[i*4 + 3] = i; // Record colour no
}
}
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
function alterneigh(rad:Int, i:Int, b:Int, g:Int, r:Int):Void
{
var j:Int;
var k:Int;
var lo:Int;
var hi:Int;
var a:Int;
var m:Int;
lo = i - rad;
if (lo < -1)
lo = -1;
hi = i + rad;
if (hi > netsize)
hi = netsize;
j = i + 1;
k = i - 1;
m = 1;
while ((j < hi) || (k > lo))
{
a = radpower[m++];
if (j < hi)
{
network[j * 4 + 0] -= Std.int((a * (network[j * 4 + 0] - b)) / alpharadbias);
network[j * 4 + 1] -= Std.int((a * (network[j * 4 + 1] - g)) / alpharadbias);
network[j * 4 + 2] -= Std.int((a * (network[j * 4 + 2] - r)) / alpharadbias);
j++;
}
if (k > lo)
{
network[k * 4 + 0] -= Std.int((a * (network[k * 4 + 0] - b)) / alpharadbias);
network[k * 4 + 1] -= Std.int((a * (network[k * 4 + 1] - g)) / alpharadbias);
network[k * 4 + 2] -= Std.int((a * (network[k * 4 + 2] - r)) / alpharadbias);
k--;
}
}
}
// Move neuron i towards biased (b,g,r) by factor alpha
function altersingle(alpha:Int, i:Int, b:Int, g:Int, r:Int):Void
{
/* Alter hit neuron */
network[i*4 + 0] -= Std.int((alpha * (network[i*4 + 0] - b)) / initalpha);
network[i*4 + 1] -= Std.int((alpha * (network[i*4 + 1] - g)) / initalpha);
network[i*4 + 2] -= Std.int((alpha * (network[i*4 + 2] - r)) / initalpha);
}
inline function make_abs(value:Int) : Int {
var tmp = value >> 31;
value ^= tmp;
value += tmp & 1;
return value;
}
// Search for biased BGR values
static inline var bestd_init = ~(1 << 31);
function contest(b:Int, g:Int, r:Int):Int
{
// Finds closest neuron (min dist) and updates freq
// Finds best neuron (min dist-bias) and returns position
// For frequently chosen neurons, freq[i] is high and bias[i] is negative
// bias[i] = gamma*((1/netsize)-freq[i])
var i:Int;
var dist:Int;
var a:Int;
var biasdist:Int;
var betafreq:Int;
var bestpos:Int;
var bestbiaspos:Int;
var bestd:Int;
var bestbiasd:Int;
bestd = bestd_init;
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
for (i in 0...netsize)
{
var i_n = i * 4;
var b_i = i_n + 0;
var g_i = i_n + 1;
var r_i = i_n + 2;
var b_a = network[b_i];
var g_a = network[g_i];
var r_a = network[r_i];
b_a = make_abs(b_a - b);
g_a = make_abs(g_a - g);
r_a = make_abs(r_a - r);
dist = b_a + g_a + r_a;
if (dist < bestd)
{
bestd = dist;
bestpos = i;
}
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
if (biasdist < bestbiasd)
{
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (freq[i] >> betashift);
freq[i] -= betafreq;
bias[i] += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return bestbiaspos;
}
}

View File

@ -0,0 +1,346 @@
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;
}
}

View File

@ -0,0 +1,390 @@
package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesData;
/**
* Tools for gif data.
* @author Yanrishatum
*/
class Tools
{
/**
* Returns amount of frames in Gif data.
*/
public static function framesCount(data:Data):Int
{
var frames:Int = 0;
for (block in data.blocks)
{
switch(block)
{
case Block.BFrame(_):
frames++;
default :
}
}
return frames;
}
/**
* Returns frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return Frame at given index or null, if there is no frame at that index.
*/
public static function frame(data:Data, frameIndex:Int):Frame
{
var counter:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return frame;
counter++;
default :
}
}
return null;
}
/**
* Returns Graphic Control extension for frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return GCE extension if it is exists for given frame, null otherwise.
*/
public static function graphicControl(data:Data, frameIndex:Int):GraphicControlExtension
{
var counter:Int = 0;
var gce:GraphicControlExtension = null;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return gce;
gce = null;
counter++;
case Block.BExtension(Extension.EGraphicControl(g)):
gce = g;
default :
}
}
return null;
}
//==========================================================
// Extracting.
//==========================================================
/**
* Extracts frame pixel data in Blue-Green-Red-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts frame pixel data in Red-Green-Blue-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts full Gif pixel data to specified frame in Blue-Green-Red-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // B
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // R
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Extracts full Gif pixel data to specified frame in Red-Green-Blue-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // R
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // B
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Returns amount of animation repeats stored in Gif data.
* This is link to Netscape Looping application extension. If this extension does not present amount of loops equals to 1.
* @param data Gif data.
* @return Amount of animation repeats. Zero equals to infinite amount of repeats.
*/
public static function loopCount(data:Data):Int
{
for (block in data.blocks)
{
switch(block)
{
case Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(loops))): return loops;
default :
}
}
return 1;
}
//==========================================================
// In-Dev writer tools.
//==========================================================
//public static function buildFrameFromTrueColor(pixels:Bytes, width:Int, height:Int):Void
//{
//
//}
private static var LN2:Float = Math.log(2);
@:noCompletion public static inline function log2(val:Float):Float
{
return Math.log(val) / LN2;
}
}

View File

@ -0,0 +1,525 @@
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();
}
}
}