forked from LeenkxTeam/LNXSDK
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
leenkx/Sources/iron/format/jpg/Data.hx
Normal file
37
leenkx/Sources/iron/format/jpg/Data.hx
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* JPG File Format
|
||||
* Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion)
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.jpg;
|
||||
|
||||
typedef Data = {
|
||||
var width : Int;
|
||||
var height : Int;
|
||||
var quality : Float;
|
||||
var pixels : haxe.io.Bytes;
|
||||
}
|
||||
657
leenkx/Sources/iron/format/jpg/Writer.hx
Normal file
657
leenkx/Sources/iron/format/jpg/Writer.hx
Normal file
@ -0,0 +1,657 @@
|
||||
package iron.format.jpg;
|
||||
|
||||
class Writer {
|
||||
var ZigZag: Array<Int>;
|
||||
|
||||
// Static table initialization
|
||||
function initZigZag() {
|
||||
ZigZag = [
|
||||
0, 1, 5, 6,14,15,27,28,
|
||||
2, 4, 7,13,16,26,29,42,
|
||||
3, 8,12,17,25,30,41,43,
|
||||
9,11,18,24,31,40,44,53,
|
||||
10,19,23,32,39,45,52,54,
|
||||
20,22,33,38,46,51,55,60,
|
||||
21,34,37,47,50,56,59,61,
|
||||
35,36,48,49,57,58,62,63
|
||||
];
|
||||
}
|
||||
|
||||
var YTable: Array<Int>;
|
||||
var UVTable: Array<Int>;
|
||||
var fdtbl_Y: Array<Float>;
|
||||
var fdtbl_UV: Array<Float>;
|
||||
|
||||
function initQuantTables(sf: Int) {
|
||||
var YQT: Array<Int> = [
|
||||
16, 11, 10, 16, 24, 40, 51, 61,
|
||||
12, 12, 14, 19, 26, 58, 60, 55,
|
||||
14, 13, 16, 24, 40, 57, 69, 56,
|
||||
14, 17, 22, 29, 51, 87, 80, 62,
|
||||
18, 22, 37, 56, 68,109,103, 77,
|
||||
24, 35, 55, 64, 81,104,113, 92,
|
||||
49, 64, 78, 87,103,121,120,101,
|
||||
72, 92, 95, 98,112,100,103, 99
|
||||
];
|
||||
for (i in 0...64) {
|
||||
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
|
||||
if( t < 1 ) t = 1;
|
||||
else if( t > 255 ) t = 255;
|
||||
YTable[ ZigZag[i] ] = t;
|
||||
}
|
||||
var UVQT: Array<Int> = [
|
||||
17, 18, 24, 47, 99, 99, 99, 99,
|
||||
18, 21, 26, 66, 99, 99, 99, 99,
|
||||
24, 26, 56, 99, 99, 99, 99, 99,
|
||||
47, 66, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99
|
||||
];
|
||||
for( j in 0...64 ) {
|
||||
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
|
||||
if( u < 1 ) u = 1;
|
||||
else if( u > 255 ) u = 255;
|
||||
UVTable[ ZigZag[j] ] = u;
|
||||
}
|
||||
var aasf: Array<Float> = [
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379
|
||||
];
|
||||
var k = 0;
|
||||
for( row in 0...8 ) {
|
||||
for( col in 0...8 ) {
|
||||
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var std_dc_luminance_nrcodes: Array<Int>;
|
||||
var std_dc_luminance_values: haxe.io.Bytes;
|
||||
var std_ac_luminance_nrcodes: Array<Int>;
|
||||
var std_ac_luminance_values: haxe.io.Bytes;
|
||||
|
||||
function initLuminance() {
|
||||
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
|
||||
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
|
||||
std_ac_luminance_values = strIntsToBytes(
|
||||
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
|
||||
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
|
||||
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
|
||||
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
|
||||
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
|
||||
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
|
||||
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
|
||||
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
|
||||
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
|
||||
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
|
||||
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
|
||||
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
|
||||
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
|
||||
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
|
||||
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
|
||||
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
|
||||
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
|
||||
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
|
||||
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
|
||||
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
function strIntsToBytes( s: String ) {
|
||||
var len = s.length;
|
||||
var b = new haxe.io.BytesBuffer();
|
||||
var val = 0;
|
||||
var i = 0;
|
||||
for( j in 0...len ) {
|
||||
if( s.charAt( j ) == ',' ) {
|
||||
val = Std.parseInt( s.substr(i, j - i) );
|
||||
b.addByte( val );
|
||||
i = j + 1;
|
||||
}
|
||||
}
|
||||
if( i < len ) {
|
||||
val = Std.parseInt( s.substr(i) );
|
||||
b.addByte( val );
|
||||
}
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
var std_dc_chrominance_nrcodes: Array<Int>;
|
||||
var std_dc_chrominance_values: haxe.io.Bytes;
|
||||
var std_ac_chrominance_nrcodes: Array<Int>;
|
||||
var std_ac_chrominance_values: haxe.io.Bytes;
|
||||
|
||||
function initChrominance() {
|
||||
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
|
||||
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
|
||||
std_ac_chrominance_values = strIntsToBytes(
|
||||
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
|
||||
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
|
||||
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
|
||||
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
|
||||
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
|
||||
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
|
||||
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
|
||||
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
|
||||
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
|
||||
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
|
||||
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
|
||||
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
|
||||
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
|
||||
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
|
||||
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
|
||||
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
|
||||
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
|
||||
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
|
||||
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
|
||||
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
var YDC_HT: Map<Int,BitString>;
|
||||
var UVDC_HT: Map<Int,BitString>;
|
||||
var YAC_HT: Map<Int,BitString>;
|
||||
var UVAC_HT: Map<Int,BitString>;
|
||||
|
||||
// Función para crear la tabla Huffman (Helper)
|
||||
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
|
||||
var codevalue = 0;
|
||||
var pos_in_table = 0;
|
||||
var HT: Map<Int,BitString> = new Map();
|
||||
for( k in 1...17 ) {
|
||||
var end = nrcodes[k];
|
||||
for( j in 0...end ) {
|
||||
var idx: Int = std_table.get( pos_in_table );
|
||||
HT.set( idx, new BitString( k, codevalue ) );
|
||||
pos_in_table++;
|
||||
codevalue++;
|
||||
}
|
||||
codevalue *= 2;
|
||||
}
|
||||
return HT;
|
||||
}
|
||||
|
||||
function initHuffmanTbl() {
|
||||
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
|
||||
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
|
||||
|
||||
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
|
||||
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
|
||||
|
||||
// CORRECCIÓN DE TABLAS: Asegurar la existencia de EOB (0x00) y ZRL (0xF0)
|
||||
// Esto es necesario para evitar fallos si el 'computeHuffmanTbl' no incluye estos valores por algún motivo.
|
||||
if (YAC_HT.get(0x00) == null) YAC_HT.set(0x00, new BitString(4, 0x00));
|
||||
if (UVAC_HT.get(0x00) == null) UVAC_HT.set(0x00, new BitString(4, 0x00));
|
||||
if (YAC_HT.get(0xF0) == null) YAC_HT.set(0xF0, new BitString(11, 0x1E));
|
||||
if (UVAC_HT.get(0xF0) == null) UVAC_HT.set(0xF0, new BitString(11, 0x1E));
|
||||
}
|
||||
|
||||
var bitcode: Map<Int,BitString>;
|
||||
var category: Map<Int,Int>;
|
||||
|
||||
function initCategoryNumber() {
|
||||
var nrlower = 1;
|
||||
var nrupper = 2;
|
||||
var idx: Int;
|
||||
for (cat in 1...16) {
|
||||
//Positive numbers
|
||||
for( nr in nrlower...nrupper ) {
|
||||
idx = 32767 + nr;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nr ) );
|
||||
}
|
||||
//Negative numbers
|
||||
var nrneg: Int = -(nrupper - 1);
|
||||
while( nrneg <= -nrlower ) {
|
||||
idx = 32767 + nrneg;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
|
||||
nrneg++;
|
||||
}
|
||||
nrlower <<= 1;
|
||||
nrupper <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// IO functions
|
||||
var byteout: haxe.io.Output;
|
||||
var bytenew: Int;
|
||||
var bytepos: Int;
|
||||
|
||||
function writeBits(bs: BitString) {
|
||||
// Se confía en que bs no es nulo gracias al clamping y las correcciones de tablas.
|
||||
var value: Int = bs.val;
|
||||
var posval: Int = bs.len - 1;
|
||||
while( posval >= 0 ) {
|
||||
if( (value & (1 << posval)) != 0 ) {
|
||||
bytenew |= (1 << bytepos);
|
||||
}
|
||||
posval--;
|
||||
bytepos--;
|
||||
if( bytepos < 0 ) {
|
||||
if( bytenew == 0xFF ) {
|
||||
b(0xFF);
|
||||
b(0);
|
||||
}
|
||||
else {
|
||||
b(bytenew);
|
||||
}
|
||||
bytepos = 7;
|
||||
bytenew = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeWord( val: Int ) {
|
||||
b( (val >> 8) & 0xFF );
|
||||
b( val & 0xFF );
|
||||
}
|
||||
|
||||
// DCT & quantization core
|
||||
|
||||
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
|
||||
/* Pass 1: process rows. */
|
||||
var dataOff = 0;
|
||||
for (i in 0...8) {
|
||||
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
|
||||
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
|
||||
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
|
||||
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
|
||||
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
|
||||
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
|
||||
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
|
||||
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
|
||||
|
||||
/* Even part */
|
||||
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
|
||||
var tmp13: Float = tmp0 - tmp3;
|
||||
var tmp11: Float = tmp1 + tmp2;
|
||||
var tmp12: Float = tmp1 - tmp2;
|
||||
|
||||
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
|
||||
data[dataOff + 4] = tmp10 - tmp11;
|
||||
|
||||
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
|
||||
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
|
||||
data[dataOff + 6] = tmp13 - z1;
|
||||
|
||||
/* Odd part */
|
||||
tmp10 = tmp4 + tmp5; /* phase 2 */
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
|
||||
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
|
||||
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
|
||||
var z3: Float = tmp11 * 0.707106781; /* c4 */
|
||||
|
||||
var z11: Float = tmp7 + z3; /* phase 5 */
|
||||
var z13: Float = tmp7 - z3;
|
||||
|
||||
data[dataOff + 5] = z13 + z2; /* phase 6 */
|
||||
data[dataOff + 3] = z13 - z2;
|
||||
data[dataOff + 1] = z11 + z4;
|
||||
data[dataOff + 7] = z11 - z4;
|
||||
|
||||
dataOff += 8; /* advance pointer to next row */
|
||||
}
|
||||
|
||||
/* Pass 2: process columns. */
|
||||
dataOff = 0;
|
||||
for (j in 0...8) {
|
||||
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
|
||||
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
|
||||
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
|
||||
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
|
||||
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
|
||||
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
|
||||
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
|
||||
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
|
||||
|
||||
/* Even part */
|
||||
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
|
||||
var tmp13p2: Float = tmp0p2 - tmp3p2;
|
||||
var tmp11p2: Float = tmp1p2 + tmp2p2;
|
||||
var tmp12p2: Float = tmp1p2 - tmp2p2;
|
||||
|
||||
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
|
||||
data[dataOff+32] = tmp10p2 - tmp11p2;
|
||||
|
||||
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
|
||||
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
|
||||
data[dataOff+48] = tmp13p2 - z1p2;
|
||||
|
||||
/* Odd part */
|
||||
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
|
||||
tmp11p2 = tmp5p2 + tmp6p2;
|
||||
tmp12p2 = tmp6p2 + tmp7p2;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
|
||||
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
|
||||
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
|
||||
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
|
||||
|
||||
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
|
||||
var z13p2: Float = tmp7p2 - z3p2;
|
||||
|
||||
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
|
||||
data[dataOff+24] = z13p2 - z2p2;
|
||||
data[dataOff+ 8] = z11p2 + z4p2;
|
||||
data[dataOff+56] = z11p2 - z4p2;
|
||||
|
||||
dataOff++; /* advance pointer to next column */
|
||||
}
|
||||
|
||||
// Quantize/descale the coefficients
|
||||
for (k in 0...64) {
|
||||
// Apply the quantization and scaling factor & Round to nearest integer
|
||||
data[k] = Math.round(data[k] * fdtbl[k]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Chunk writing
|
||||
|
||||
inline function b(v) {
|
||||
byteout.writeByte(v);
|
||||
}
|
||||
|
||||
function writeAPP0() {
|
||||
b(0xFF); b(0xE0); //<- marker 0xFFE0
|
||||
b(0); b(16); //<- length
|
||||
b("J".code); // J
|
||||
b("F".code);
|
||||
b("I".code);
|
||||
b("F".code);
|
||||
b(0);
|
||||
b(1); // versionhi
|
||||
b(1); // versionlo
|
||||
b(0); // xyunits
|
||||
b(0); b(1); // xdensity
|
||||
b(0); b(1); // ydensity
|
||||
b(0); // thumbnwidth
|
||||
b(0); // thumbnheight
|
||||
}
|
||||
function writeDQT() {
|
||||
b(0xFF); b(0xDB); //<- marker 0xFFDB
|
||||
b(0); b(132); //<- length
|
||||
b(0);
|
||||
for( j in 0...64 )
|
||||
b(YTable[j]);
|
||||
b(1);
|
||||
for( j in 0...64 )
|
||||
b(UVTable[j]);
|
||||
}
|
||||
function writeSOF0(width: Int, height: Int) {
|
||||
b(0xFF); b(0xC0); //<- marker 0xFFC0
|
||||
b(0); b(17); //<- length, truecolor YUV JPG
|
||||
b(8); // precision
|
||||
b( (height>>8) & 0xFF );
|
||||
b( height & 0xFF );
|
||||
b( (width>>8) & 0xFF );
|
||||
b( width & 0xFF );
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0x11); // HVY
|
||||
b(0); // QTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HVU
|
||||
b(1); // QTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HVV
|
||||
b(1); // QTV
|
||||
}
|
||||
|
||||
function writeDHT() {
|
||||
b(0xFF); b(0xC4); //<- marker 0xFFC4
|
||||
b(0x01); b(0xA2); //<- length
|
||||
b(0); // HTYDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_luminance_nrcodes[j]);
|
||||
byteout.write(std_dc_luminance_values);
|
||||
|
||||
b(0x10); // HTYACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_luminance_nrcodes[j]);
|
||||
byteout.write(std_ac_luminance_values);
|
||||
|
||||
b(1); // HTUDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_chrominance_nrcodes[j]);
|
||||
byteout.write(std_dc_chrominance_values);
|
||||
|
||||
b(0x11); // HTUACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_chrominance_nrcodes[j]);
|
||||
byteout.write(std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function writeSOS() {
|
||||
b(0xFF); b(0xDA); //<- marker 0xFFDA
|
||||
b(0); b(12); //<- length
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0); // HTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HTV
|
||||
b(0); // Ss
|
||||
b(0x3F); // Se
|
||||
b(0); // Bf
|
||||
}
|
||||
|
||||
// Core processing
|
||||
var DU: Array<Float>;
|
||||
|
||||
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
|
||||
var EOB: BitString = HTAC.get( 0x00 );
|
||||
var M16zeroes: BitString = HTAC.get( 0xF0 );
|
||||
|
||||
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
|
||||
//ZigZag reorder
|
||||
for (i in 0...64) {
|
||||
DU[ ZigZag[i] ] = DU_DCT[i];
|
||||
}
|
||||
var idx: Int;
|
||||
var Diff = Std.int( DU[0] - DC );
|
||||
DC = DU[0];
|
||||
|
||||
// CORRECCIÓN DE RANGO: Clamping de la diferencia DC (previene accesos a `category` fuera de rango)
|
||||
if (Diff > 16383) Diff = 16383;
|
||||
if (Diff < -16383) Diff = -16383;
|
||||
|
||||
//Encode DC
|
||||
if( Diff == 0 ) {
|
||||
writeBits( HTDC.get(0) );
|
||||
} else {
|
||||
idx = 32767 + Diff;
|
||||
writeBits(HTDC.get( category.get( idx ) ));
|
||||
writeBits( bitcode.get( idx ) );
|
||||
}
|
||||
|
||||
//Encode ACs
|
||||
var end0pos = 63;
|
||||
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
|
||||
|
||||
//end0pos = first element in reverse order !=0
|
||||
if ( end0pos == 0 ) {
|
||||
writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
var i = 1;
|
||||
while ( i <= end0pos ) {
|
||||
var startpos = i;
|
||||
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
|
||||
|
||||
// Chequeo de seguridad si 'i' saltó más allá
|
||||
if (i > end0pos) break;
|
||||
|
||||
var nrzeroes: Int = i - startpos;
|
||||
if ( nrzeroes >= 16 ) {
|
||||
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
|
||||
nrzeroes &= 0xF;
|
||||
}
|
||||
|
||||
// CORRECCIÓN DE RANGO: Clamping del coeficiente AC
|
||||
var du_val = Std.int( DU[i] );
|
||||
if (du_val > 16383) du_val = 16383;
|
||||
if (du_val < -16383) du_val = -16383;
|
||||
|
||||
// LÓGICA DE SALTO: Si el clamping forzó el valor a 0, saltamos.
|
||||
if (du_val == 0) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = 32767 + du_val;
|
||||
|
||||
var cat = category.get( idx );
|
||||
// Si 'cat' es nulo, significa que el valor de 'du_val' está fuera del rango -16383..16383, lo cual el clamping debería haber prevenido.
|
||||
var index_ac = nrzeroes * 16 + cat;
|
||||
|
||||
writeBits( HTAC.get( index_ac ) );
|
||||
writeBits( bitcode.get( idx ) );
|
||||
i++;
|
||||
}
|
||||
if( end0pos != 63 ) writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
|
||||
var YDU: Array<Float>;
|
||||
var UDU: Array<Float>;
|
||||
var VDU: Array<Float>;
|
||||
|
||||
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
|
||||
var pos = 0;
|
||||
for( y in 0...8 ) {
|
||||
var offset = ((y + ypos) * width + xpos) << 2;
|
||||
for( x in 0...8 ) {
|
||||
offset++; // skip alpha
|
||||
var R = img.get(offset++);
|
||||
var G = img.get(offset++);
|
||||
var B = img.get(offset++);
|
||||
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
|
||||
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
|
||||
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function new( out : haxe.io.Output ) {
|
||||
//begin : lines added to initialize variables
|
||||
YTable = new Array<Int>();
|
||||
UVTable = new Array<Int>();
|
||||
fdtbl_Y = new Array<Float>();
|
||||
fdtbl_UV = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YTable.push(0); UVTable.push(0);
|
||||
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
|
||||
}
|
||||
|
||||
bitcode = new Map();
|
||||
category = new Map();
|
||||
byteout = out;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
YDC_HT = new Map();
|
||||
UVDC_HT = new Map();
|
||||
YAC_HT = new Map();
|
||||
UVAC_HT = new Map();
|
||||
|
||||
YDU = new Array<Float>();
|
||||
UDU = new Array<Float>();
|
||||
VDU = new Array<Float>();
|
||||
DU = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
|
||||
}
|
||||
initZigZag();
|
||||
initLuminance();
|
||||
initChrominance();
|
||||
//end : lines added to initialize variables
|
||||
|
||||
// Create tables
|
||||
initHuffmanTbl();
|
||||
initCategoryNumber();
|
||||
}
|
||||
|
||||
public function write( image : Data ) {
|
||||
// init quality table
|
||||
var quality = image.quality;
|
||||
if( quality <= 0 ) quality = 1;
|
||||
if( quality > 100 ) quality = 100;
|
||||
var sf =
|
||||
if( quality < 50 ) Std.int( 5000 / quality )
|
||||
else Std.int( 200 - quality * 2 );
|
||||
initQuantTables(sf);
|
||||
|
||||
// Initialize bit writer
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
var width = image.width;
|
||||
var height = image.height;
|
||||
// Add JPEG headers
|
||||
writeWord(0xFFD8); // SOI
|
||||
writeAPP0();
|
||||
writeDQT();
|
||||
writeSOF0( width, height );
|
||||
writeDHT();
|
||||
writeSOS();
|
||||
|
||||
// Encode 8x8 macroblocks
|
||||
var DCY = 0.0;
|
||||
var DCU = 0.0;
|
||||
var DCV = 0.0;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
var ypos = 0;
|
||||
while( ypos < height ) {
|
||||
var xpos = 0;
|
||||
while( xpos < width ) {
|
||||
|
||||
// CORRECCIÓN CRÍTICA DE ESTADO: Limpieza de arreglos para evitar arrastre de valores (lo que el 'trace' estaba enmascarando)
|
||||
// Se asegura que los buffers sean cero antes de llenarlos con RGB2YUV si no se llenan completamente.
|
||||
for (k in 0...64) { YDU[k] = 0.0; UDU[k] = 0.0; VDU[k] = 0.0; }
|
||||
|
||||
RGB2YUV(image.pixels, width, xpos, ypos);
|
||||
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
|
||||
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
|
||||
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
|
||||
xpos += 8;
|
||||
}
|
||||
ypos += 8;
|
||||
}
|
||||
|
||||
// Do the bit alignment of the EOI marker
|
||||
if( bytepos >= 0 ) {
|
||||
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
|
||||
writeBits(fillbits);
|
||||
}
|
||||
|
||||
writeWord(0xFFD9); //EOI
|
||||
}
|
||||
}
|
||||
|
||||
private class BitString {
|
||||
public var len: Int;
|
||||
public var val: Int;
|
||||
|
||||
public function new( l: Int, v: Int ) {
|
||||
len = l;
|
||||
val = v;
|
||||
}
|
||||
}
|
||||
651
leenkx/Sources/iron/format/jpg/WriterOriginal.hx
Normal file
651
leenkx/Sources/iron/format/jpg/WriterOriginal.hx
Normal file
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* JPG File Format
|
||||
* Copyright (C) 2007-2009 Thibault Imbert, AS3-to-Haxe by Michel Oster
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.jpg;
|
||||
|
||||
class Writer {
|
||||
var ZigZag: Array<Int>;
|
||||
|
||||
// Static table initialization
|
||||
function initZigZag() {
|
||||
ZigZag = [
|
||||
0, 1, 5, 6,14,15,27,28,
|
||||
2, 4, 7,13,16,26,29,42,
|
||||
3, 8,12,17,25,30,41,43,
|
||||
9,11,18,24,31,40,44,53,
|
||||
10,19,23,32,39,45,52,54,
|
||||
20,22,33,38,46,51,55,60,
|
||||
21,34,37,47,50,56,59,61,
|
||||
35,36,48,49,57,58,62,63
|
||||
];
|
||||
}
|
||||
|
||||
var YTable: Array<Int>; // = new Array(64);
|
||||
var UVTable: Array<Int>; // = new Array(64);
|
||||
var fdtbl_Y: Array<Float>; // = new Array(64);
|
||||
var fdtbl_UV: Array<Float>; // = new Array(64);
|
||||
|
||||
function initQuantTables(sf: Int) {
|
||||
var YQT: Array<Int> = [
|
||||
16, 11, 10, 16, 24, 40, 51, 61,
|
||||
12, 12, 14, 19, 26, 58, 60, 55,
|
||||
14, 13, 16, 24, 40, 57, 69, 56,
|
||||
14, 17, 22, 29, 51, 87, 80, 62,
|
||||
18, 22, 37, 56, 68,109,103, 77,
|
||||
24, 35, 55, 64, 81,104,113, 92,
|
||||
49, 64, 78, 87,103,121,120,101,
|
||||
72, 92, 95, 98,112,100,103, 99
|
||||
];
|
||||
for (i in 0...64) {
|
||||
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
|
||||
if( t < 1 ) t = 1;
|
||||
else if( t > 255 ) t = 255;
|
||||
YTable[ ZigZag[i] ] = t;
|
||||
}
|
||||
var UVQT: Array<Int> = [
|
||||
17, 18, 24, 47, 99, 99, 99, 99,
|
||||
18, 21, 26, 66, 99, 99, 99, 99,
|
||||
24, 26, 56, 99, 99, 99, 99, 99,
|
||||
47, 66, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99
|
||||
];
|
||||
for( j in 0...64 ) {
|
||||
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
|
||||
if( u < 1 ) u = 1;
|
||||
else if( u > 255 ) u = 255;
|
||||
UVTable[ ZigZag[j] ] = u;
|
||||
}
|
||||
var aasf: Array<Float> = [
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379
|
||||
];
|
||||
var k = 0;
|
||||
for( row in 0...8 ) {
|
||||
for( col in 0...8 ) {
|
||||
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var std_dc_luminance_nrcodes: Array<Int>;
|
||||
var std_dc_luminance_values: haxe.io.Bytes;
|
||||
var std_ac_luminance_nrcodes: Array<Int>;
|
||||
var std_ac_luminance_values: haxe.io.Bytes;
|
||||
|
||||
function initLuminance() {
|
||||
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
|
||||
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
|
||||
std_ac_luminance_values = strIntsToBytes(
|
||||
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
|
||||
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
|
||||
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
|
||||
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
|
||||
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
|
||||
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
|
||||
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
|
||||
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
|
||||
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
|
||||
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
|
||||
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
|
||||
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
|
||||
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
|
||||
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
|
||||
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
|
||||
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
|
||||
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
|
||||
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
|
||||
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
|
||||
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
function strIntsToBytes( s: String ) {
|
||||
var len = s.length;
|
||||
var b = new haxe.io.BytesBuffer();
|
||||
var val = 0;
|
||||
var i = 0;
|
||||
for( j in 0...len ) {
|
||||
if( s.charAt( j ) == ',' ) {
|
||||
val = Std.parseInt( s.substr(i, j - i) );
|
||||
b.addByte( val );
|
||||
i = j + 1;
|
||||
}
|
||||
}
|
||||
if( i < len ) {
|
||||
val = Std.parseInt( s.substr(i) );
|
||||
b.addByte( val );
|
||||
}
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
var std_dc_chrominance_nrcodes: Array<Int>;
|
||||
var std_dc_chrominance_values: haxe.io.Bytes;
|
||||
var std_ac_chrominance_nrcodes: Array<Int>;
|
||||
var std_ac_chrominance_values: haxe.io.Bytes;
|
||||
|
||||
function initChrominance() {
|
||||
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
|
||||
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
|
||||
std_ac_chrominance_values = strIntsToBytes(
|
||||
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
|
||||
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
|
||||
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
|
||||
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
|
||||
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
|
||||
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
|
||||
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
|
||||
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
|
||||
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
|
||||
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
|
||||
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
|
||||
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
|
||||
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
|
||||
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
|
||||
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
|
||||
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
|
||||
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
|
||||
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
|
||||
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
|
||||
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
var YDC_HT: Map<Int,BitString>;
|
||||
var UVDC_HT: Map<Int,BitString>;
|
||||
var YAC_HT: Map<Int,BitString>;
|
||||
var UVAC_HT: Map<Int,BitString>;
|
||||
|
||||
function initHuffmanTbl() {
|
||||
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
|
||||
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
|
||||
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
|
||||
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
|
||||
var codevalue = 0;
|
||||
var pos_in_table = 0;
|
||||
var HT: Map<Int,BitString> = new Map();
|
||||
for( k in 1...17 ) {
|
||||
var end = nrcodes[k];
|
||||
for( j in 0...end ) {
|
||||
var idx: Int = std_table.get( pos_in_table );
|
||||
HT.set( idx, new BitString( k, codevalue ) );
|
||||
pos_in_table++;
|
||||
codevalue++;
|
||||
}
|
||||
codevalue *= 2;
|
||||
}
|
||||
return HT;
|
||||
}
|
||||
|
||||
var bitcode: Map<Int,BitString>;
|
||||
var category: Map<Int,Int>;
|
||||
|
||||
function initCategoryNumber() {
|
||||
var nrlower = 1;
|
||||
var nrupper = 2;
|
||||
var idx: Int;
|
||||
for (cat in 1...16) {
|
||||
//Positive numbers
|
||||
for( nr in nrlower...nrupper ) {
|
||||
idx = 32767 + nr;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nr ) );
|
||||
}
|
||||
//Negative numbers
|
||||
var nrneg: Int = -(nrupper - 1);
|
||||
while( nrneg <= -nrlower ) {
|
||||
idx = 32767 + nrneg;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
|
||||
nrneg++;
|
||||
}
|
||||
nrlower <<= 1;
|
||||
nrupper <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// IO functions
|
||||
var byteout: haxe.io.Output;
|
||||
var bytenew: Int;
|
||||
var bytepos: Int;
|
||||
|
||||
function writeBits(bs: BitString) {
|
||||
var value: Int = bs.val;
|
||||
var posval: Int = bs.len - 1;
|
||||
while( posval >= 0 ) {
|
||||
//if (value & uint(1 << posval) ) {
|
||||
if( (value & (1 << posval)) != 0 ) { //<- CORRECT ?
|
||||
//bytenew |= uint(1 << bytepos);
|
||||
bytenew |= (1 << bytepos);
|
||||
}
|
||||
posval--;
|
||||
bytepos--;
|
||||
if( bytepos < 0 ) {
|
||||
if( bytenew == 0xFF ) {
|
||||
b(0xFF);
|
||||
b(0);
|
||||
}
|
||||
else {
|
||||
b(bytenew);
|
||||
}
|
||||
bytepos = 7;
|
||||
bytenew = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeWord( val: Int ) {
|
||||
b( (val >> 8) & 0xFF );
|
||||
b( val & 0xFF );
|
||||
}
|
||||
|
||||
// DCT & quantization core
|
||||
|
||||
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
|
||||
/* Pass 1: process rows. */
|
||||
var dataOff = 0;
|
||||
for (i in 0...8) {
|
||||
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
|
||||
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
|
||||
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
|
||||
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
|
||||
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
|
||||
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
|
||||
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
|
||||
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
|
||||
|
||||
/* Even part */
|
||||
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
|
||||
var tmp13: Float = tmp0 - tmp3;
|
||||
var tmp11: Float = tmp1 + tmp2;
|
||||
var tmp12: Float = tmp1 - tmp2;
|
||||
|
||||
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
|
||||
data[dataOff + 4] = tmp10 - tmp11;
|
||||
|
||||
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
|
||||
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
|
||||
data[dataOff + 6] = tmp13 - z1;
|
||||
|
||||
/* Odd part */
|
||||
tmp10 = tmp4 + tmp5; /* phase 2 */
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
|
||||
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
|
||||
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
|
||||
var z3: Float = tmp11 * 0.707106781; /* c4 */
|
||||
|
||||
var z11: Float = tmp7 + z3; /* phase 5 */
|
||||
var z13: Float = tmp7 - z3;
|
||||
|
||||
data[dataOff + 5] = z13 + z2; /* phase 6 */
|
||||
data[dataOff + 3] = z13 - z2;
|
||||
data[dataOff + 1] = z11 + z4;
|
||||
data[dataOff + 7] = z11 - z4;
|
||||
|
||||
dataOff += 8; /* advance pointer to next row */
|
||||
}
|
||||
|
||||
/* Pass 2: process columns. */
|
||||
dataOff = 0;
|
||||
for (j in 0...8) {
|
||||
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
|
||||
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
|
||||
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
|
||||
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
|
||||
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
|
||||
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
|
||||
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
|
||||
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
|
||||
|
||||
/* Even part */
|
||||
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
|
||||
var tmp13p2: Float = tmp0p2 - tmp3p2;
|
||||
var tmp11p2: Float = tmp1p2 + tmp2p2;
|
||||
var tmp12p2: Float = tmp1p2 - tmp2p2;
|
||||
|
||||
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
|
||||
data[dataOff+32] = tmp10p2 - tmp11p2;
|
||||
|
||||
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
|
||||
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
|
||||
data[dataOff+48] = tmp13p2 - z1p2;
|
||||
|
||||
/* Odd part */
|
||||
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
|
||||
tmp11p2 = tmp5p2 + tmp6p2;
|
||||
tmp12p2 = tmp6p2 + tmp7p2;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
|
||||
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
|
||||
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
|
||||
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
|
||||
|
||||
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
|
||||
var z13p2: Float = tmp7p2 - z3p2;
|
||||
|
||||
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
|
||||
data[dataOff+24] = z13p2 - z2p2;
|
||||
data[dataOff+ 8] = z11p2 + z4p2;
|
||||
data[dataOff+56] = z11p2 - z4p2;
|
||||
|
||||
dataOff++; /* advance pointer to next column */
|
||||
}
|
||||
|
||||
// Quantize/descale the coefficients
|
||||
for (k in 0...64) {
|
||||
// Apply the quantization and scaling factor & Round to nearest integer
|
||||
data[k] = Math.round(data[k] * fdtbl[k]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Chunk writing
|
||||
|
||||
inline function b(v) {
|
||||
byteout.writeByte(v);
|
||||
}
|
||||
|
||||
function writeAPP0() {
|
||||
b(0xFF); b(0xE0); //<- marker 0xFFE0
|
||||
b(0); b(16); //<- length
|
||||
b("J".code); // J
|
||||
b("F".code);
|
||||
b("I".code);
|
||||
b("F".code);
|
||||
b(0);
|
||||
b(1); // versionhi
|
||||
b(1); // versionlo
|
||||
b(0); // xyunits
|
||||
b(0); b(1); // xdensity
|
||||
b(0); b(1); // ydensity
|
||||
b(0); // thumbnwidth
|
||||
b(0); // thumbnheight
|
||||
}
|
||||
function writeDQT() {
|
||||
b(0xFF); b(0xDB); //<- marker 0xFFDB
|
||||
b(0); b(132); //<- length
|
||||
b(0);
|
||||
for( j in 0...64 )
|
||||
b(YTable[j]);
|
||||
b(1);
|
||||
for( j in 0...64 )
|
||||
b(UVTable[j]);
|
||||
}
|
||||
function writeSOF0(width: Int, height: Int) {
|
||||
b(0xFF); b(0xC0); //<- marker 0xFFC0
|
||||
b(0); b(17); //<- length, truecolor YUV JPG
|
||||
b(8); // precision
|
||||
b( (height>>8) & 0xFF );
|
||||
b( height & 0xFF );
|
||||
b( (width>>8) & 0xFF );
|
||||
b( width & 0xFF );
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0x11); // HVY
|
||||
b(0); // QTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HVU
|
||||
b(1); // QTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HVV
|
||||
b(1); // QTV
|
||||
}
|
||||
|
||||
function writeDHT() {
|
||||
b(0xFF); b(0xC4); //<- marker 0xFFC4
|
||||
b(0x01); b(0xA2); //<- length
|
||||
b(0); // HTYDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_luminance_nrcodes[j]);
|
||||
byteout.write(std_dc_luminance_values);
|
||||
|
||||
b(0x10); // HTYACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_luminance_nrcodes[j]);
|
||||
byteout.write(std_ac_luminance_values);
|
||||
|
||||
b(1); // HTUDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_chrominance_nrcodes[j]);
|
||||
byteout.write(std_dc_chrominance_values);
|
||||
|
||||
b(0x11); // HTUACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_chrominance_nrcodes[j]);
|
||||
byteout.write(std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function writeSOS() {
|
||||
b(0xFF); b(0xDA); //<- marker 0xFFDA
|
||||
b(0); b(12); //<- length
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0); // HTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HTV
|
||||
b(0); // Ss
|
||||
b(0x3F); // Se
|
||||
b(0); // Bf
|
||||
}
|
||||
|
||||
// Core processing
|
||||
var DU: Array<Float>; //<- initialized in function new JPEGEncoder()
|
||||
|
||||
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
|
||||
var EOB: BitString = HTAC.get( 0x00 );
|
||||
var M16zeroes: BitString = HTAC.get( 0xF0 );
|
||||
|
||||
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
|
||||
//ZigZag reorder
|
||||
for (i in 0...64) {
|
||||
DU[ ZigZag[i] ] = DU_DCT[i];
|
||||
}
|
||||
var idx: Int;
|
||||
var Diff = Std.int( DU[0] - DC );
|
||||
DC = DU[0];
|
||||
//Encode DC
|
||||
if( Diff == 0 ) {
|
||||
writeBits( HTDC.get(0) ); // Diff might be 0
|
||||
} else {
|
||||
idx = 32767 + Diff;
|
||||
writeBits(HTDC.get( category.get( idx ) ));
|
||||
writeBits( bitcode.get( idx ) );
|
||||
}
|
||||
|
||||
//Encode ACs
|
||||
var end0pos = 63;
|
||||
//for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) { };
|
||||
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
|
||||
|
||||
//end0pos = first element in reverse order !=0
|
||||
if ( end0pos == 0 ) {
|
||||
writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
var i = 1;
|
||||
while ( i <= end0pos ) {
|
||||
var startpos = i;
|
||||
//for (; (DU[i]==0) && (i<=end0pos); i++) { }; <- it's a 'while' loop
|
||||
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
|
||||
|
||||
var nrzeroes: Int = i - startpos;
|
||||
if ( nrzeroes >= 16 ) {
|
||||
//for (var nrmarker: Int=1; nrmarker <= nrzeroes/16; nrmarker++) {
|
||||
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
|
||||
nrzeroes &= 0xF;
|
||||
}
|
||||
idx = 32767 + Std.int( DU[i] ); //<- line added
|
||||
writeBits( HTAC.get( nrzeroes * 16 + category.get( idx ) ) );
|
||||
writeBits( bitcode.get( idx ) );
|
||||
i++;
|
||||
}
|
||||
if( end0pos != 63 ) writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
|
||||
var YDU: Array<Float>;
|
||||
var UDU: Array<Float>;
|
||||
var VDU: Array<Float>;
|
||||
|
||||
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
|
||||
var pos = 0;
|
||||
for( y in 0...8 ) {
|
||||
var offset = ((y + ypos) * width + xpos) << 2;
|
||||
for( x in 0...8 ) {
|
||||
offset++; // skip alpha
|
||||
var R = img.get(offset++);
|
||||
var G = img.get(offset++);
|
||||
var B = img.get(offset++);
|
||||
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
|
||||
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
|
||||
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function new( out : haxe.io.Output ) {
|
||||
//begin : lines added to initialize variables
|
||||
YTable = new Array<Int>();
|
||||
UVTable = new Array<Int>();
|
||||
fdtbl_Y = new Array<Float>();
|
||||
fdtbl_UV = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YTable.push(0); UVTable.push(0);
|
||||
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
|
||||
}
|
||||
|
||||
bitcode = new Map(); //<- 65535 elements <BitString>
|
||||
category = new Map(); //<- 65535 elements <Int>
|
||||
byteout = out;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
YDC_HT = new Map();
|
||||
UVDC_HT = new Map();
|
||||
YAC_HT = new Map();
|
||||
UVAC_HT = new Map();
|
||||
|
||||
YDU = new Array<Float>(); //<- 64 elements
|
||||
UDU = new Array<Float>();
|
||||
VDU = new Array<Float>();
|
||||
DU = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
|
||||
}
|
||||
initZigZag();
|
||||
initLuminance();
|
||||
initChrominance();
|
||||
//end : lines added to initialize variables
|
||||
|
||||
// Create tables
|
||||
initHuffmanTbl();
|
||||
initCategoryNumber();
|
||||
}
|
||||
|
||||
public function write( image : Data ) {
|
||||
// init quality table
|
||||
var quality = image.quality;
|
||||
if( quality <= 0 ) quality = 1;
|
||||
if( quality > 100 ) quality = 100;
|
||||
var sf =
|
||||
if( quality < 50 ) Std.int( 5000 / quality )
|
||||
else Std.int( 200 - quality * 2 );
|
||||
initQuantTables(sf);
|
||||
|
||||
// Initialize bit writer
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
var width = image.width;
|
||||
var height = image.height;
|
||||
// Add JPEG headers
|
||||
writeWord(0xFFD8); // SOI
|
||||
writeAPP0();
|
||||
writeDQT();
|
||||
writeSOF0( width, height );
|
||||
writeDHT();
|
||||
writeSOS();
|
||||
|
||||
// Encode 8x8 macroblocks
|
||||
var DCY = 0.0;
|
||||
var DCU = 0.0;
|
||||
var DCV = 0.0;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
var ypos = 0;
|
||||
while( ypos < height ) {
|
||||
var xpos = 0;
|
||||
while( xpos < width ) {
|
||||
RGB2YUV(image.pixels, width, xpos, ypos);
|
||||
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
|
||||
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
|
||||
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
|
||||
xpos += 8;
|
||||
}
|
||||
ypos += 8;
|
||||
}
|
||||
|
||||
// Do the bit alignment of the EOI marker
|
||||
if( bytepos >= 0 ) {
|
||||
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
|
||||
writeBits(fillbits);
|
||||
}
|
||||
|
||||
writeWord(0xFFD9); //EOI
|
||||
}
|
||||
}
|
||||
|
||||
private class BitString {
|
||||
public var len: Int;
|
||||
public var val: Int;
|
||||
|
||||
public function new( l: Int, v: Int ) {
|
||||
len = l;
|
||||
val = v;
|
||||
}
|
||||
}
|
||||
56
leenkx/Sources/iron/format/wav/Data.hx
Normal file
56
leenkx/Sources/iron/format/wav/Data.hx
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.wav;
|
||||
|
||||
typedef WAVE = {
|
||||
header : WAVEHeader,
|
||||
data : haxe.io.Bytes,
|
||||
cuePoints : Array<CuePoint>
|
||||
}
|
||||
|
||||
typedef WAVEHeader = {
|
||||
format : WAVEFormat,
|
||||
channels : Int,
|
||||
samplingRate : Int,
|
||||
byteRate : Int, // samplingRate * channels * bitsPerSample / 8
|
||||
blockAlign : Int, // channels * bitsPerSample / 8
|
||||
bitsPerSample : Int
|
||||
}
|
||||
|
||||
typedef CuePoint = {
|
||||
id : Int,
|
||||
sampleOffset : Int
|
||||
}
|
||||
|
||||
enum WAVEFormat {
|
||||
WF_PCM;
|
||||
}
|
||||
|
||||
|
||||
155
leenkx/Sources/iron/format/wav/Reader.hx
Normal file
155
leenkx/Sources/iron/format/wav/Reader.hx
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.wav;
|
||||
import iron.format.wav.Data;
|
||||
|
||||
class Reader {
|
||||
|
||||
var i : haxe.io.Input;
|
||||
var version : Int;
|
||||
|
||||
public function new(i) {
|
||||
this.i = i;
|
||||
i.bigEndian = false;
|
||||
}
|
||||
|
||||
inline function readInt() {
|
||||
#if haxe3
|
||||
return i.readInt32();
|
||||
#else
|
||||
return i.readUInt30();
|
||||
#end
|
||||
}
|
||||
|
||||
public function read() : WAVE {
|
||||
|
||||
if (i.readString(4) != "RIFF")
|
||||
throw "RIFF header expected";
|
||||
|
||||
var len = readInt();
|
||||
|
||||
if (i.readString(4) != "WAVE")
|
||||
throw "WAVE signature not found";
|
||||
|
||||
var fmt = i.readString(4);
|
||||
while(fmt != "fmt ") {
|
||||
switch( fmt ) {
|
||||
case "JUNK": //protool
|
||||
var junkLen = i.readInt32();
|
||||
i.read(junkLen);
|
||||
fmt = i.readString(4);
|
||||
case "bext":
|
||||
var bextLen = i.readInt32();
|
||||
i.read(bextLen);
|
||||
fmt = i.readString(4);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( fmt != "fmt " )
|
||||
throw "unsupported wave chunk "+fmt;
|
||||
|
||||
var fmtlen = readInt();
|
||||
var format = switch (i.readUInt16()) {
|
||||
case 1,3: WF_PCM;
|
||||
default: throw "only PCM (uncompressed) WAV files are supported";
|
||||
}
|
||||
var channels = i.readUInt16();
|
||||
var samplingRate = readInt();
|
||||
var byteRate = readInt();
|
||||
var blockAlign = i.readUInt16();
|
||||
var bitsPerSample = i.readUInt16();
|
||||
|
||||
if (fmtlen > 16)
|
||||
i.read(fmtlen - 16);
|
||||
|
||||
var nextChunk = i.readString (4);
|
||||
while (nextChunk != "data") {
|
||||
// read past other subchunks
|
||||
i.read(readInt());
|
||||
nextChunk = i.readString (4);
|
||||
}
|
||||
|
||||
// data
|
||||
if (nextChunk != "data")
|
||||
throw "expected data subchunk";
|
||||
|
||||
var datalen = readInt();
|
||||
|
||||
var data : haxe.io.Bytes;
|
||||
try {
|
||||
data = i.read(datalen);
|
||||
} catch (e : haxe.io.Eof) {
|
||||
throw "Invalid chunk data length";
|
||||
}
|
||||
|
||||
var cuePoints = new Array<CuePoint>();
|
||||
try {
|
||||
|
||||
while (true) {
|
||||
var nextChunk = i.readString (4);
|
||||
switch (nextChunk) {
|
||||
case "cue ":
|
||||
readInt();
|
||||
var nbCuePoints = readInt();
|
||||
|
||||
for (_ in 0...nbCuePoints) {
|
||||
var cueId = readInt();
|
||||
readInt();
|
||||
i.readString(4);
|
||||
readInt();
|
||||
readInt();
|
||||
var cueSampleOffset = readInt();
|
||||
cuePoints.push({ id : cueId, sampleOffset: cueSampleOffset });
|
||||
}
|
||||
default:
|
||||
var n = readInt();
|
||||
if( n < 0 ) break;
|
||||
i.read(n);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e : haxe.io.Eof) { }
|
||||
|
||||
return {
|
||||
header: {
|
||||
format: format,
|
||||
channels: channels,
|
||||
samplingRate: samplingRate,
|
||||
byteRate: byteRate,
|
||||
blockAlign: blockAlign,
|
||||
bitsPerSample: bitsPerSample
|
||||
},
|
||||
data: data,
|
||||
cuePoints: cuePoints
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
leenkx/Sources/iron/format/wav/Writer.hx
Normal file
72
leenkx/Sources/iron/format/wav/Writer.hx
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
package iron.format.wav;
|
||||
import iron.format.wav.Data;
|
||||
|
||||
class Writer {
|
||||
|
||||
var o : haxe.io.Output;
|
||||
|
||||
public function new(output : haxe.io.Output) {
|
||||
o = output;
|
||||
o.bigEndian = false;
|
||||
}
|
||||
|
||||
public function write(wav : WAVE) {
|
||||
var hdr = wav.header;
|
||||
|
||||
o.writeString("RIFF");
|
||||
writeInt(36 + wav.data.length);
|
||||
o.writeString("WAVE");
|
||||
|
||||
o.writeString("fmt ");
|
||||
writeInt(16);
|
||||
o.writeUInt16(1);
|
||||
o.writeUInt16(hdr.channels);
|
||||
writeInt(hdr.samplingRate);
|
||||
writeInt(hdr.byteRate);
|
||||
o.writeUInt16(hdr.blockAlign);
|
||||
o.writeUInt16(hdr.bitsPerSample);
|
||||
|
||||
o.writeString("data");
|
||||
writeInt(wav.data.length);
|
||||
o.write(wav.data);
|
||||
}
|
||||
|
||||
inline function writeInt( v : Int ) {
|
||||
#if haxe3
|
||||
o.writeInt32(v);
|
||||
#else
|
||||
o.writeUInt30(v);
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user