391 lines
13 KiB
Haxe
391 lines
13 KiB
Haxe
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|