Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9
This commit is contained in:
357
leenkx/Sources/iron/format/gif/Data.hx
Normal file
357
leenkx/Sources/iron/format/gif/Data.hx
Normal 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);
|
||||
}
|
||||
359
leenkx/Sources/iron/format/gif/GifEncoder.hx
Normal file
359
leenkx/Sources/iron/format/gif/GifEncoder.hx
Normal 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;
|
||||
|
||||
}
|
||||
350
leenkx/Sources/iron/format/gif/LzwEncoder.hx
Normal file
350
leenkx/Sources/iron/format/gif/LzwEncoder.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
541
leenkx/Sources/iron/format/gif/NeuQuant.hx
Normal file
541
leenkx/Sources/iron/format/gif/NeuQuant.hx
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
346
leenkx/Sources/iron/format/gif/Reader.hx
Normal file
346
leenkx/Sources/iron/format/gif/Reader.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
390
leenkx/Sources/iron/format/gif/Tools.hx
Normal file
390
leenkx/Sources/iron/format/gif/Tools.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
525
leenkx/Sources/iron/format/gif/Writer.hx
Normal file
525
leenkx/Sources/iron/format/gif/Writer.hx
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user