forked from LeenkxTeam/LNXSDK
Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9
This commit is contained in:
@ -237,8 +237,24 @@ class App {
|
||||
traitRenders.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
traitRenders2D.push(f);
|
||||
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
|
||||
if (index < 0 || index >= traitRenders2D.length) {
|
||||
traitRenders2D.push(f);
|
||||
} else {
|
||||
traitRenders2D.insert(index, f);
|
||||
}
|
||||
}
|
||||
|
||||
public static function moveRender2D(f: kha.graphics2.Graphics->Void, newIndex: Int) {
|
||||
var oldIndex = traitRenders2D.indexOf(f);
|
||||
if (oldIndex != -1) {
|
||||
traitRenders2D.splice(oldIndex, 1);
|
||||
if (newIndex >= traitRenders2D.length) {
|
||||
traitRenders2D.push(f);
|
||||
} else {
|
||||
traitRenders2D.insert(newIndex, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function removeRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
|
||||
@ -531,13 +531,11 @@ class RenderPath {
|
||||
|
||||
if (!drawn) submitDraw(context);
|
||||
|
||||
#if lnx_debug
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(context);
|
||||
if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length);
|
||||
}
|
||||
#end
|
||||
|
||||
end();
|
||||
}
|
||||
@ -597,7 +595,6 @@ class RenderPath {
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
static var contextEvents: Map<String, Array<Graphics->Int->Int->Void>> = null;
|
||||
public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
|
||||
if (contextEvents == null) contextEvents = new Map();
|
||||
@ -608,7 +605,13 @@ class RenderPath {
|
||||
}
|
||||
ar.push(onContext);
|
||||
}
|
||||
#end
|
||||
|
||||
public static function removeNotifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(name);
|
||||
if (ar != null) ar.remove(onContext);
|
||||
}
|
||||
}
|
||||
|
||||
#if rp_decals
|
||||
public function drawDecals(context: String) {
|
||||
|
||||
@ -14,6 +14,7 @@ import iron.object.SpeakerObject;
|
||||
import iron.object.DecalObject;
|
||||
import iron.object.ProbeObject;
|
||||
import iron.object.Tilesheet;
|
||||
import iron.object.CurveObject;
|
||||
import iron.data.CameraData;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.LightData;
|
||||
@ -64,6 +65,7 @@ class Scene {
|
||||
#end
|
||||
public var empties: Array<Object>;
|
||||
public var animations: Array<Animation>;
|
||||
public var tilesheets: Array<Tilesheet>;
|
||||
#if lnx_skin
|
||||
public var armatures: Array<Armature>;
|
||||
#end
|
||||
@ -110,6 +112,7 @@ class Scene {
|
||||
#end
|
||||
empties = [];
|
||||
animations = [];
|
||||
tilesheets = [];
|
||||
#if lnx_skin
|
||||
armatures = [];
|
||||
#end
|
||||
@ -135,6 +138,14 @@ class Scene {
|
||||
|
||||
// Startup scene
|
||||
active.addScene(format.name, null, function(sceneObject: Object) {
|
||||
|
||||
if (format.properties != null) {
|
||||
sceneObject.properties = new Map();
|
||||
for (p in format.properties) {
|
||||
sceneObject.properties.set(p.name, cleanValue(p.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Create traits bottom-up (children first, then parents)
|
||||
createTraitsBottomUp(sceneObject);
|
||||
|
||||
@ -342,6 +353,7 @@ class Scene {
|
||||
if (terrainStream != null) terrainStream.update(active.camera);
|
||||
#end
|
||||
for (anim in animations) anim.update(Time.delta);
|
||||
for (tilesheet in tilesheets) tilesheet.update();
|
||||
for (e in empties) if (e != null && e.parent != null) e.transform.update();
|
||||
}
|
||||
|
||||
@ -441,6 +453,11 @@ class Scene {
|
||||
return g;
|
||||
}
|
||||
|
||||
public function removeFromGroups(object: Object) {
|
||||
if (groups == null) return;
|
||||
for (name in groups.keys()) getGroup(name).remove(object);
|
||||
}
|
||||
|
||||
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
|
||||
var object = new MeshObject(data, materials);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
@ -450,6 +467,12 @@ class Scene {
|
||||
return object;
|
||||
}
|
||||
|
||||
public function addCurveObject(data: TCurveData, parent: Object = null): CurveObject {
|
||||
var object = new CurveObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
public function addLightObject(data: LightData, parent: Object = null): LightObject {
|
||||
var object = new LightObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
@ -709,6 +732,10 @@ class Scene {
|
||||
else done(ro);
|
||||
});
|
||||
}
|
||||
else if (o.type == "curve_object") {
|
||||
var object = addCurveObject(Data.getCurveRawByName(format.curve_datas, o.data_ref), parent);
|
||||
returnObject(object, o, done);
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
|
||||
@ -889,11 +916,13 @@ class Scene {
|
||||
}
|
||||
else { #end // lnx_skin
|
||||
#if lnx_stream
|
||||
streamMeshObject(
|
||||
if ((o.particle_refs == null || o.particle_refs.length == 0) && o.is_particle == null && parent != null)
|
||||
streamMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
else
|
||||
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#else
|
||||
returnMeshObject(
|
||||
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#end
|
||||
object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#if lnx_skin
|
||||
}
|
||||
#end
|
||||
@ -968,20 +997,24 @@ class Scene {
|
||||
#end
|
||||
if (o.properties != null) {
|
||||
object.properties = new Map();
|
||||
for (p in o.properties) object.properties.set(p.name, p.value);
|
||||
for (p in o.properties) {
|
||||
object.properties.set(p.name, cleanValue(p.value));
|
||||
}
|
||||
}
|
||||
|
||||
if (o.vertex_groups != null) {
|
||||
object.vertex_groups = new Map();
|
||||
for (p in o.vertex_groups){
|
||||
var verts = [];
|
||||
for(i in 0...Std.int(p.value.length/3)){
|
||||
var x = Std.parseFloat(p.value[i*3]);
|
||||
var y = Std.parseFloat(p.value[i*3+1]);
|
||||
var z = Std.parseFloat(p.value[i*3+2]);
|
||||
verts.push(new iron.math.Vec4(x, y, z, 1));
|
||||
cast(object, MeshObject).vertexGroups = new Map();
|
||||
for (p in o.vertex_groups) {
|
||||
var verts:Array<iron.math.Vec4> = [];
|
||||
|
||||
var data:kha.arrays.Float32Array = cast p.value;
|
||||
|
||||
if (data != null) {
|
||||
for (i in 0...Std.int(data.length / 3)) {
|
||||
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
|
||||
}
|
||||
}
|
||||
object.vertex_groups.set(p.name, verts);
|
||||
cast(object, MeshObject).vertexGroups.set(p.name, verts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1145,4 +1178,16 @@ class Scene {
|
||||
public function notifyOnRemove(f: Void->Void) {
|
||||
traitRemoves.push(f);
|
||||
}
|
||||
|
||||
static function cleanValue(val: Dynamic): Dynamic {
|
||||
if (val == null) return null;
|
||||
if (untyped val.buffer != null) {
|
||||
var data: kha.arrays.Float32Array = cast val;
|
||||
return [for (i in 0...data.length) data[i]];
|
||||
}
|
||||
if (Std.isOfType(val, Array)) {
|
||||
return [for (item in (cast val: Array<Dynamic>)) cleanValue(item)];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,10 +125,10 @@ class Trait {
|
||||
/**
|
||||
Add 2D render handler.
|
||||
**/
|
||||
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
|
||||
if (_render2D == null) _render2D = [];
|
||||
_render2D.push(f);
|
||||
App.notifyOnRender2D(f);
|
||||
App.notifyOnRender2D(f, index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -311,7 +311,7 @@ class Data {
|
||||
|
||||
loadingSceneRaws.set(file, [done]);
|
||||
|
||||
// If no extension specified, set to .arm
|
||||
// If no extension specified, set to .lnx
|
||||
var compressed = file.endsWith(".lz4");
|
||||
var isJson = file.endsWith(".json");
|
||||
var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx";
|
||||
@ -405,6 +405,13 @@ class Data {
|
||||
}
|
||||
#end
|
||||
|
||||
public static function getCurveRawByName(datas: Array<TCurveData>, name: String): TCurveData {
|
||||
if (datas == null || datas.length == 0) return null;
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Raw assets
|
||||
public static function getBlob(file: String, done: kha.Blob->Void) {
|
||||
var cached = cachedBlobs.get(file); // Is already cached
|
||||
|
||||
@ -40,6 +40,8 @@ class Geometry {
|
||||
public var instancedVB: VertexBuffer = null;
|
||||
public var instanced = false;
|
||||
public var instanceCount = 0;
|
||||
public var instanceElements: Array<{name: String, data: String}> = [];
|
||||
public var instanceStride: Int = 0;
|
||||
|
||||
public var positions: TVertexArray;
|
||||
public var normals: TVertexArray;
|
||||
@ -130,11 +132,39 @@ class Geometry {
|
||||
structure.add("iscl", kha.graphics4.VertexData.Float3);
|
||||
}
|
||||
|
||||
if (instanceElements != null && instanceElements.length > 0) {
|
||||
for (elem in instanceElements) {
|
||||
if (StringTools.startsWith(elem.name, "i") && elem.name != "ipos" && elem.name != "irot" && elem.name != "iscl") {
|
||||
var vdata = VertexData.Float1;
|
||||
var dataStr: String = Reflect.field(elem, "data");
|
||||
switch (dataStr) {
|
||||
case "float1": vdata = VertexData.Float1;
|
||||
case "float2": vdata = VertexData.Float2;
|
||||
case "float3": vdata = VertexData.Float3;
|
||||
case "float4": vdata = VertexData.Float4;
|
||||
}
|
||||
structure.add(elem.name, vdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.instanceStride = Std.int(structure.byteSize() / 4);
|
||||
instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4));
|
||||
instancedVB = new VertexBuffer(instanceCount, structure, usage, 1);
|
||||
var vertices = instancedVB.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]);
|
||||
instancedVB.unlock();
|
||||
|
||||
}
|
||||
|
||||
public function updateInstanced(data: Float32Array) {
|
||||
if (instancedVB == null) return;
|
||||
|
||||
var vertices = instancedVB.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 4)) {
|
||||
vertices.setFloat32(i * 4, data[i]);
|
||||
}
|
||||
instancedVB.unlock();
|
||||
}
|
||||
|
||||
public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) {
|
||||
|
||||
@ -40,6 +40,8 @@ typedef TSceneFormat = {
|
||||
@:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2
|
||||
@:optional public var terrain_datas: Array<TTerrainData>;
|
||||
@:optional public var terrain_ref: String;
|
||||
@:optional public var properties: Array<TProperty>;
|
||||
@:optional public var curve_datas: Array<TCurveData>;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -432,6 +434,7 @@ typedef TParticleData = {
|
||||
// Velocity
|
||||
public var object_align_factor: Float32Array;
|
||||
public var factor_random: FastFloat;
|
||||
public var normal_factor: FastFloat;
|
||||
// Rotation
|
||||
public var use_rotations: Bool;
|
||||
public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
|
||||
@ -525,7 +528,7 @@ typedef TVertex_groups = {
|
||||
@:structInit class TVertex_groups {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Dynamic;
|
||||
public var value: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -564,6 +567,21 @@ typedef TConstraint = {
|
||||
@:optional public var invert_z: Null<Bool>;
|
||||
@:optional public var use_offset: Null<Bool>;
|
||||
@:optional public var influence: Null<FastFloat>;
|
||||
@:optional public var use_min_x: Null<Bool>;
|
||||
@:optional public var use_max_x: Null<Bool>;
|
||||
@:optional public var use_min_y: Null<Bool>;
|
||||
@:optional public var use_max_y: Null<Bool>;
|
||||
@:optional public var use_min_z: Null<Bool>;
|
||||
@:optional public var use_max_z: Null<Bool>;
|
||||
@:optional public var use_limit_x: Null<Bool>;
|
||||
@:optional public var use_limit_y: Null<Bool>;
|
||||
@:optional public var use_limit_z: Null<Bool>;
|
||||
@:optional public var min_x: Null<FastFloat>;
|
||||
@:optional public var max_x: Null<FastFloat>;
|
||||
@:optional public var min_y: Null<FastFloat>;
|
||||
@:optional public var max_y: Null<FastFloat>;
|
||||
@:optional public var min_z: Null<FastFloat>;
|
||||
@:optional public var max_z: Null<FastFloat>;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -622,3 +640,48 @@ typedef TTrack = {
|
||||
public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values
|
||||
@:optional public var ref_values: Array<Array<String>>; // ref values
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TBezierPoint = {
|
||||
#else
|
||||
@:structInit class TBezierPoint {
|
||||
#end
|
||||
public var co: Float32Array;
|
||||
public var handle_left: Float32Array;
|
||||
public var handle_right: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TSpline = {
|
||||
#else
|
||||
@:structInit class TSpline {
|
||||
#end
|
||||
public var closed: Bool;
|
||||
public var resolution: Int;
|
||||
public var points: Array<TBezierPoint>;
|
||||
public var material_index: Int;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShapeKey = {
|
||||
#else
|
||||
@:structInit class TShapeKey {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Float;
|
||||
public var points: Array<TBezierPoint>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TCurveData = {
|
||||
#else
|
||||
@:structInit class TCurveData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var object: String;
|
||||
public var splines: Array<TSpline>;
|
||||
public var strength: Float;
|
||||
public var color: Float32Array;
|
||||
@:optional public var material_refs: Array<String>;
|
||||
@:optional public var shape_keys: Array<TShapeKey>;
|
||||
}
|
||||
|
||||
@ -79,6 +79,8 @@ class ShaderContext {
|
||||
|
||||
var structure: VertexStructure;
|
||||
var instancingType = 0;
|
||||
var instanceElements: Array<{name: String, data: String}> = [];
|
||||
var instanceStride: Int = 0;
|
||||
|
||||
public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) {
|
||||
this.raw = raw;
|
||||
@ -108,6 +110,11 @@ class ShaderContext {
|
||||
if (instancingType == 3 || instancingType == 4) {
|
||||
instStruct.add("iscl", VertexData.Float3);
|
||||
}
|
||||
|
||||
for (e in instanceElements)
|
||||
instStruct.add(e.name, parseData(e.data));
|
||||
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
||||
|
||||
instStruct.instanced = true;
|
||||
pipeState.inputLayout = [structure, instStruct];
|
||||
}
|
||||
@ -268,10 +275,12 @@ class ShaderContext {
|
||||
if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "irot") { irot = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; }
|
||||
if (Reflect.field(elem, "name").startsWith("i")) { instanceElements.push(elem); continue; }
|
||||
#else
|
||||
if (elem.name == "ipos") { ipos = true; continue; }
|
||||
if (elem.name == "irot") { irot = true; continue; }
|
||||
if (elem.name == "iscl") { iscl = true; continue; }
|
||||
if (elem.name.startsWith("i")) { instanceElements.push(elem); continue; }
|
||||
#end
|
||||
structure.add(elem.name, parseData(elem.data));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,31 +1,119 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
|
||||
class Constraint {
|
||||
var raw: TConstraint;
|
||||
var target: Transform = null;
|
||||
|
||||
public function new(constr: TConstraint) {
|
||||
raw = constr;
|
||||
public function new(constraint: TConstraint) {
|
||||
raw = constraint;
|
||||
}
|
||||
|
||||
public function apply(transform: Transform) {
|
||||
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
|
||||
|
||||
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;
|
||||
|
||||
if (raw.type == "COPY_LOCATION") {
|
||||
if (raw.use_x) {
|
||||
transform.world._30 = target.loc.x;
|
||||
if (raw.use_offset) transform.world._30 += transform.loc.x;
|
||||
if (raw.use_offset) {
|
||||
if (raw.use_x) transform.world._30 += target.world._30;
|
||||
if (raw.use_y) transform.world._31 += target.world._31;
|
||||
if (raw.use_z) transform.world._32 += target.world._32;
|
||||
}
|
||||
if (raw.use_y) {
|
||||
transform.world._31 = target.loc.y;
|
||||
if (raw.use_offset) transform.world._31 += transform.loc.y;
|
||||
else {
|
||||
if (raw.use_x) transform.world._30 = target.world._30;
|
||||
if (raw.use_y) transform.world._31 = target.world._31;
|
||||
if (raw.use_z) transform.world._32 = target.world._32;
|
||||
}
|
||||
if (raw.use_z) {
|
||||
transform.world._32 = target.loc.z;
|
||||
if (raw.use_offset) transform.world._32 += transform.loc.z;
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_ROTATION") {
|
||||
var tq = target.rot;
|
||||
var mq = transform.rot;
|
||||
if (raw.use_offset) {
|
||||
mq.mult(tq);
|
||||
}
|
||||
else {
|
||||
if (raw.use_x) mq.x = tq.x;
|
||||
if (raw.use_y) mq.y = tq.y;
|
||||
if (raw.use_z) mq.z = tq.z;
|
||||
mq.w = tq.w;
|
||||
}
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
var scale = transform.scale;
|
||||
transform.world.compose(loc, mq, scale);
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_SCALE") {
|
||||
var ts = target.scale;
|
||||
if (raw.use_offset) {
|
||||
if (raw.use_x) transform.scale.x *= ts.x;
|
||||
if (raw.use_y) transform.scale.y *= ts.y;
|
||||
if (raw.use_z) transform.scale.z *= ts.z;
|
||||
}
|
||||
else {
|
||||
if (raw.use_x) transform.scale.x = ts.x;
|
||||
if (raw.use_y) transform.scale.y = ts.y;
|
||||
if (raw.use_z) transform.scale.z = ts.z;
|
||||
}
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_TRANSFORMS") {
|
||||
transform.world.setFrom(target.world);
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_LOCATION") {
|
||||
if (raw.use_min_x && transform.world._30 < raw.min_x) transform.world._30 = raw.min_x;
|
||||
if (raw.use_max_x && transform.world._30 > raw.max_x) transform.world._30 = raw.max_x;
|
||||
|
||||
if (raw.use_min_y && transform.world._31 < raw.min_y) transform.world._31 = raw.min_y;
|
||||
if (raw.use_max_y && transform.world._31 > raw.max_y) transform.world._31 = raw.max_y;
|
||||
|
||||
if (raw.use_min_z && transform.world._32 < raw.min_z) transform.world._32 = raw.min_z;
|
||||
if (raw.use_max_z && transform.world._32 > raw.max_z) transform.world._32 = raw.max_z;
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_ROTATION") {
|
||||
var euler = transform.rot.getEuler();
|
||||
var changed = false;
|
||||
|
||||
if (raw.use_limit_x) {
|
||||
if (euler.x < raw.min_x) { euler.x = raw.min_x; changed = true; }
|
||||
if (euler.x > raw.max_x) { euler.x = raw.max_x; changed = true; }
|
||||
}
|
||||
if (raw.use_limit_y) {
|
||||
if (euler.y < raw.min_y) { euler.y = raw.min_y; changed = true; }
|
||||
if (euler.y > raw.max_y) { euler.y = raw.max_y; changed = true; }
|
||||
}
|
||||
if (raw.use_limit_z) {
|
||||
if (euler.z < raw.min_z) { euler.z = raw.min_z; changed = true; }
|
||||
if (euler.z > raw.max_z) { euler.z = raw.max_z; changed = true; }
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
transform.rot.fromEuler(euler.x, euler.y, euler.z);
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_SCALE") {
|
||||
if (raw.use_min_x && transform.scale.x < raw.min_x) transform.scale.x = raw.min_x;
|
||||
if (raw.use_max_x && transform.scale.x > raw.max_x) transform.scale.x = raw.max_x;
|
||||
|
||||
if (raw.use_min_y && transform.scale.y < raw.min_y) transform.scale.y = raw.min_y;
|
||||
if (raw.use_max_y && transform.scale.y > raw.max_y) transform.scale.y = raw.max_y;
|
||||
|
||||
if (raw.use_min_z && transform.scale.z < raw.min_z) transform.scale.z = raw.min_z;
|
||||
if (raw.use_max_z && transform.scale.z > raw.max_z) transform.scale.z = raw.max_z;
|
||||
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1338
leenkx/Sources/iron/object/CurveObject.hx
Normal file
1338
leenkx/Sources/iron/object/CurveObject.hx
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,12 @@ import iron.object.CameraObject;
|
||||
class LightObject extends Object {
|
||||
|
||||
public var data: LightData;
|
||||
public var color: Vec4;
|
||||
public var strength: Float;
|
||||
#if lnx_spot
|
||||
public var size: Float;
|
||||
public var blend: Float;
|
||||
#end
|
||||
|
||||
#if rp_shadowmap
|
||||
#if lnx_shadowmap_atlas
|
||||
@ -79,6 +85,15 @@ class LightObject extends Object {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
this.color = new Vec4(data.raw.color[0], data.raw.color[1], data.raw.color[2]);
|
||||
this.strength = data.raw.strength;
|
||||
|
||||
#if lnx_spot
|
||||
if (data.raw.type == "spot"){
|
||||
this.size = data.raw.spot_size;
|
||||
this.blend = data.raw.spot_blend;
|
||||
}
|
||||
#end
|
||||
|
||||
var type = data.raw.type;
|
||||
var fov = data.raw.fov;
|
||||
@ -374,7 +389,7 @@ class LightObject extends Object {
|
||||
// Centralize discarding conditions when iterating over lights
|
||||
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
|
||||
public inline static function discardLight(light: LightObject) {
|
||||
return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun";
|
||||
return !light.visible || light.strength == 0.0 || light.data.raw.type == "sun";
|
||||
}
|
||||
// Discarding conditions but with culling included
|
||||
public inline static function discardLightCulled(light: LightObject) {
|
||||
@ -457,7 +472,7 @@ class LightObject extends Object {
|
||||
lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
|
||||
lpos.applymat4(camera.V);
|
||||
lpos.z *= -1.0;
|
||||
var radius = getRadius(l.data.raw.strength);
|
||||
var radius = getRadius(l.strength);
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
var minZ = 0;
|
||||
@ -552,10 +567,10 @@ class LightObject extends Object {
|
||||
lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x
|
||||
|
||||
// light color
|
||||
var f = l.data.raw.strength;
|
||||
lightsArray[i * 12 + 4] = l.data.raw.color[0] * f;
|
||||
lightsArray[i * 12 + 5] = l.data.raw.color[1] * f;
|
||||
lightsArray[i * 12 + 6] = l.data.raw.color[2] * f;
|
||||
var f = l.strength;
|
||||
lightsArray[i * 12 + 4] = l.color.x * f;
|
||||
lightsArray[i * 12 + 5] = l.color.y * f;
|
||||
lightsArray[i * 12 + 6] = l.color.z * f;
|
||||
lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y
|
||||
|
||||
// other data
|
||||
@ -566,13 +581,13 @@ class LightObject extends Object {
|
||||
|
||||
#if lnx_spot
|
||||
if (l.data.raw.type == "spot") {
|
||||
lightsArray[i * 12 + 9] = l.data.raw.spot_size;
|
||||
lightsArray[i * 12 + 9] = l.size;
|
||||
|
||||
var dir = l.look().normalize();
|
||||
lightsArraySpot[i * 8 ] = dir.x;
|
||||
lightsArraySpot[i * 8 + 1] = dir.y;
|
||||
lightsArraySpot[i * 8 + 2] = dir.z;
|
||||
lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend;
|
||||
lightsArraySpot[i * 8 + 3] = l.blend;
|
||||
|
||||
// Premultiply scale with z component
|
||||
var scale = l.transform.scale;
|
||||
|
||||
@ -28,13 +28,15 @@ class MeshObject extends Object {
|
||||
public var cameraList: Array<String> = null;
|
||||
public var screenSize = 0.0;
|
||||
public var frustumCulling = true;
|
||||
public var tilesheet: Tilesheet = null;
|
||||
public var activeTilesheet: Tilesheet = null;
|
||||
public var tilesheets: Array<Tilesheet> = null;
|
||||
public var skip_context: String = null; // Do not draw this context
|
||||
public var force_context: String = null; // Draw only this context
|
||||
static var lastPipeline: PipelineState = null;
|
||||
#if lnx_morph_target
|
||||
public var morphTarget: MorphTarget = null;
|
||||
#end
|
||||
public var vertexGroups: Map<String, Array<Vec4>> = null;
|
||||
|
||||
#if lnx_veloc
|
||||
public var prevMatrix = Mat4.identity();
|
||||
@ -50,6 +52,10 @@ class MeshObject extends Object {
|
||||
|
||||
public function setData(data: MeshData) {
|
||||
this.data = data;
|
||||
|
||||
if (this.materials != null && this.materials.length > 0)
|
||||
data.geom.instanceElements = @:privateAccess this.materials[0].shader.contexts[0].instanceElements;
|
||||
|
||||
data.refcount++;
|
||||
|
||||
#if (!lnx_batch)
|
||||
@ -87,7 +93,8 @@ class MeshObject extends Object {
|
||||
particleSystems = null;
|
||||
}
|
||||
#end
|
||||
if (tilesheet != null) tilesheet.remove();
|
||||
if (activeTilesheet != null) activeTilesheet.remove();
|
||||
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
|
||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
||||
#if (rp_renderer == "Deferred")
|
||||
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
|
||||
@ -126,12 +133,34 @@ class MeshObject extends Object {
|
||||
#end
|
||||
|
||||
public function setupTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData) {
|
||||
tilesheet = new Tilesheet(tilesheetData, this);
|
||||
activeTilesheet = new Tilesheet(tilesheetData, this);
|
||||
if (tilesheets == null) tilesheets = new Array<Tilesheet>();
|
||||
tilesheets.push(activeTilesheet);
|
||||
}
|
||||
|
||||
public function setActiveTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData, tilesheetActionRef: String = null) {
|
||||
var set = false;
|
||||
if (tilesheets != null) {
|
||||
for (ts in tilesheets) {
|
||||
if (ts.raw == tilesheetData) {
|
||||
if (activeTilesheet != null) activeTilesheet.pause();
|
||||
activeTilesheet = ts;
|
||||
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
|
||||
set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!set) {
|
||||
if (activeTilesheet != null) activeTilesheet.pause();
|
||||
setupTilesheet(tilesheetData);
|
||||
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
|
||||
}
|
||||
}
|
||||
|
||||
public function setTilesheetAction(actionRef: String) {
|
||||
if (tilesheet != null) {
|
||||
tilesheet.play(actionRef);
|
||||
if (activeTilesheet != null) {
|
||||
activeTilesheet.play(actionRef);
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,6 +193,7 @@ class MeshObject extends Object {
|
||||
}
|
||||
|
||||
function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool {
|
||||
var isShadow = context == "shadowmap";
|
||||
if (camera == null) return false;
|
||||
|
||||
if (camera.data.raw.frustum_culling && frustumCulling) {
|
||||
@ -172,11 +202,12 @@ class MeshObject extends Object {
|
||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||
#if lnx_gpu_particles
|
||||
// particleSystems for update, particleOwner for render
|
||||
if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
|
||||
if (particleSystems != null && particleSystems.length > 0) return setCulled(isShadow, false);
|
||||
#end
|
||||
/*
|
||||
if (context == "voxel") radiusScale *= 100;
|
||||
if (data.geom.instanced) radiusScale *= 100;
|
||||
var isShadow = context == "shadowmap";
|
||||
if (data.geom.instanced) radiusScale *= 100;*/
|
||||
if (data.geom.instanced) return setCulled(isShadow, false);
|
||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||
|
||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
||||
@ -191,8 +222,9 @@ class MeshObject extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
culled = false;
|
||||
return culled;
|
||||
//culled = false;
|
||||
//return culled;
|
||||
return setCulled(isShadow, false);
|
||||
}
|
||||
|
||||
function skipContext(context: String, mat: MaterialData): Bool {
|
||||
@ -227,11 +259,6 @@ class MeshObject extends Object {
|
||||
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
||||
var meshContext = raw != null ? context == "mesh" : false;
|
||||
|
||||
// Update tilesheet
|
||||
if (tilesheet != null && meshContext) {
|
||||
tilesheet.update();
|
||||
}
|
||||
|
||||
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
|
||||
|
||||
#if lnx_gpu_particles
|
||||
|
||||
@ -61,6 +61,25 @@ class MorphTarget {
|
||||
public inline function setMorphValueDirect(index: Int, value: Float) {
|
||||
morphWeights.set(index, value);
|
||||
}
|
||||
|
||||
public function getMorphValue(name: String): Float {
|
||||
var i = morphMap.get(name);
|
||||
return (i != null) ? morphWeights.get(i) : 0.0;
|
||||
}
|
||||
|
||||
public function hasMorph(name: String): Bool {
|
||||
return morphMap.exists(name);
|
||||
}
|
||||
|
||||
public function resetWeights() {
|
||||
for (i in 0...morphWeights.length) {
|
||||
morphWeights.set(i, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMorphNames(): Array<String> {
|
||||
return [for (key in morphMap.keys()) key];
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
@ -27,7 +27,6 @@ class Object {
|
||||
public var culled = false; // Object was culled last frame
|
||||
public var culledMesh = false;
|
||||
public var culledShadow = false;
|
||||
public var vertex_groups: Map<String, Array<Vec4>> = null;
|
||||
public var properties: Map<String, Dynamic> = null;
|
||||
var isEmpty = false;
|
||||
|
||||
@ -95,6 +94,7 @@ class Object {
|
||||
Removes the game object from the scene.
|
||||
**/
|
||||
public function remove() {
|
||||
Scene.active.removeFromGroups(this);
|
||||
if (isEmpty && Scene.active != null) Scene.active.empties.remove(this);
|
||||
if (animation != null) animation.remove();
|
||||
while (children.length > 0) children[0].remove();
|
||||
@ -111,16 +111,12 @@ class Object {
|
||||
@return Object or null
|
||||
**/
|
||||
public function getChild(name: String): Object {
|
||||
if (this.name == name) return this;
|
||||
else if (this.filename != "") {
|
||||
if (this.name == name + "_" + this.filename) return this;
|
||||
}
|
||||
|
||||
for (c in children) {
|
||||
if (c.name == name) return c;
|
||||
if (c.filename != "" && c.name == name + "_" + c.filename) return c;
|
||||
var r = c.getChild(name);
|
||||
if (r != null) return r;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -145,12 +141,10 @@ class Object {
|
||||
}
|
||||
|
||||
public function getChildOfType<T: Object>(type: Class<T>): T {
|
||||
if (Std.isOfType(this, type)) return cast this;
|
||||
else {
|
||||
for (c in children) {
|
||||
var r = c.getChildOfType(type);
|
||||
if (r != null) return r;
|
||||
}
|
||||
for (c in children) {
|
||||
if (Std.isOfType(c, type)) return cast c;
|
||||
var r = c.getChildOfType(type);
|
||||
if (r != null) return r;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import iron.data.SceneFormat;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Vec4;
|
||||
import iron.object.CurveObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Object;
|
||||
import iron.system.Time;
|
||||
@ -19,6 +20,10 @@ import kha.arrays.Uint32Array;
|
||||
class ParticleSystemCPU {
|
||||
public var data: ParticleData;
|
||||
public var speed: FastFloat = 1.0; // Not used yet. Added to go in hand with `ParticleSystemGPU`
|
||||
public var curveGuides: Array<CurveObject> = [];
|
||||
public var curveGuideStrength: FastFloat = 1.0;
|
||||
public var curveGuideSpeed: FastFloat = 1.0;
|
||||
var paused: Bool = false;
|
||||
var r: TParticleData;
|
||||
|
||||
// Format
|
||||
@ -37,6 +42,7 @@ class ParticleSystemCPU {
|
||||
// Velocity
|
||||
var velocity: Vec3 = new Vec3(0.0, 0.0, 1.0); // object_align_factor: Float32Array
|
||||
var velocityRandom: FastFloat = 0.0; // factor_random
|
||||
var normalFactor: FastFloat = 0.0;
|
||||
|
||||
// Rotation
|
||||
var rotation: Bool = false; // use_rotations
|
||||
@ -114,11 +120,12 @@ class ParticleSystemCPU {
|
||||
scale = r.particle_size;
|
||||
scaleRandom = r.size_random;
|
||||
|
||||
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
|
||||
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate);
|
||||
velocityRandom = r.factor_random * (frameRate / baseFrameRate);
|
||||
normalFactor = r.normal_factor * (frameRate / baseFrameRate);
|
||||
|
||||
if (Scene.active.raw.gravity != null) {
|
||||
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
|
||||
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate);
|
||||
}
|
||||
gravityFactor = r.weight_gravity * (frameRate / baseFrameRate);
|
||||
textureFactor = r.weight_texture;
|
||||
@ -139,39 +146,36 @@ class ParticleSystemCPU {
|
||||
scaleElementsCount = getRampElementsLength();
|
||||
scaleRampSizeFactor = getRampSizeFactor();
|
||||
|
||||
switch (type) {
|
||||
case 0: // Emission
|
||||
loopAnim = {
|
||||
tick: function () {
|
||||
spawnTime += Time.delta * Time.scale;
|
||||
var expected: Int = Math.floor(spawnTime / spawnRate);
|
||||
while (spawnedParticles < expected && spawnedParticles < count) {
|
||||
spawnParticle();
|
||||
spawnedParticles++;
|
||||
}
|
||||
updateParticles();
|
||||
},
|
||||
target: null,
|
||||
props: null,
|
||||
duration: loop ? lifetimeSeconds : lifetimeSeconds * 2,
|
||||
done: function () {
|
||||
if (loop) start();
|
||||
}
|
||||
}
|
||||
|
||||
Scene.active.notifyOnInit(function () {
|
||||
if (autoStart) start();
|
||||
});
|
||||
case 1: // Hair
|
||||
Scene.active.notifyOnInit(function () {
|
||||
for (i in 0...count) spawnParticle();
|
||||
});
|
||||
default:
|
||||
}
|
||||
|
||||
Scene.active.notifyOnInit(function () {
|
||||
for (i in 0...count) addToPool();
|
||||
|
||||
switch (type) {
|
||||
case 0: // Emission
|
||||
loopAnim = {
|
||||
tick: function () {
|
||||
if (paused) return;
|
||||
spawnTime += Time.delta;
|
||||
var expected: Int = Math.floor(spawnTime / spawnRate);
|
||||
while (spawnedParticles < expected && spawnedParticles < count) {
|
||||
spawnParticle();
|
||||
spawnedParticles++;
|
||||
}
|
||||
updateParticles();
|
||||
},
|
||||
target: null,
|
||||
props: null,
|
||||
duration: loop ? lifetimeSeconds : lifetimeSeconds * 2,
|
||||
done: function () {
|
||||
if (loop) start();
|
||||
}
|
||||
}
|
||||
if (autoStart) start();
|
||||
case 1: // Hair
|
||||
for (i in 0...count) spawnParticle();
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -182,14 +186,12 @@ class ParticleSystemCPU {
|
||||
Tween.to(loopAnim);
|
||||
}
|
||||
|
||||
// TODO
|
||||
public function pause() {
|
||||
|
||||
paused = true;
|
||||
}
|
||||
|
||||
// TODO
|
||||
public function resume() {
|
||||
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function stop() {
|
||||
@ -246,6 +248,8 @@ class ParticleSystemCPU {
|
||||
var scalePos: FastFloat = owner.data.scalePos;
|
||||
var scalePosParticle: FastFloat = cast(o, MeshObject).data.scalePos;
|
||||
|
||||
var normDir: Vec3 = new Vec3();
|
||||
|
||||
// TODO: add all properties from Blender's UI
|
||||
switch (emitFrom) {
|
||||
case 0: // Vertices
|
||||
@ -253,6 +257,8 @@ class ParticleSystemCPU {
|
||||
var i: Int = Std.int(Math.random() * (pa.values.length / pa.size));
|
||||
var loc: Vec4 = new Vec4(pa.values[i * pa.size] * normFactor, pa.values[i * pa.size + 1] * normFactor, pa.values[i * pa.size + 2] * normFactor, 1);
|
||||
|
||||
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
|
||||
|
||||
if (!localCoords) {
|
||||
loc.applyQuat(objectRot);
|
||||
loc.add(objectPos);
|
||||
@ -274,6 +280,9 @@ class ParticleSystemCPU {
|
||||
var pos: Vec3 = randomPointInTriangle(v0, v1, v2);
|
||||
|
||||
var loc: Vec4 = new Vec4(pos.x, pos.y, pos.z, 1).mult(normFactor);
|
||||
|
||||
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
|
||||
|
||||
if (!localCoords) {
|
||||
loc.applyQuat(objectRot);
|
||||
loc.add(objectPos);
|
||||
@ -285,6 +294,8 @@ class ParticleSystemCPU {
|
||||
scaleFactorVolume.mult(0.5);
|
||||
var loc: Vec4 = new Vec4((Math.random() * 2.0 - 1.0) * scaleFactorVolume.x, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z, 1);
|
||||
|
||||
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
|
||||
|
||||
if (!localCoords) {
|
||||
loc.applyQuat(objectRot);
|
||||
loc.add(objectPos);
|
||||
@ -310,7 +321,9 @@ class ParticleSystemCPU {
|
||||
var randomZ: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
|
||||
var g: Vec3 = new Vec3();
|
||||
|
||||
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX, velocity.y + randomY, velocity.z + randomZ, 1);
|
||||
if (normalFactor != 0.0) normDir = normDir.mult(normalFactor);
|
||||
|
||||
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX + normDir.x, velocity.y + randomY + normDir.y, velocity.z + randomZ + normDir.z, 1);
|
||||
if (!localCoords) rotatedVelocity.applyQuat(objectRot);
|
||||
|
||||
if (rotation) {
|
||||
@ -371,7 +384,7 @@ class ParticleSystemCPU {
|
||||
|
||||
function updateParticles() {
|
||||
for (particle => physics in particlePhysics) {
|
||||
physics.age += Time.delta * Time.scale;
|
||||
physics.age += Time.delta;
|
||||
|
||||
if (physics.age >= physics.lifetime) {
|
||||
particlePhysics.remove(particle);
|
||||
@ -379,14 +392,63 @@ class ParticleSystemCPU {
|
||||
continue;
|
||||
}
|
||||
|
||||
physics.velocity.x += physics.gravity.x * Time.delta * Time.scale;
|
||||
physics.velocity.y += physics.gravity.y * Time.delta * Time.scale;
|
||||
physics.velocity.z += physics.gravity.z * Time.delta * Time.scale;
|
||||
physics.velocity.x += physics.gravity.x * Time.delta;
|
||||
physics.velocity.y += physics.gravity.y * Time.delta;
|
||||
physics.velocity.z += physics.gravity.z * Time.delta;
|
||||
|
||||
if (curveGuides != null && curveGuides.length > 0) {
|
||||
var curveVelX: FastFloat = 0.0;
|
||||
var curveVelY: FastFloat = 0.0;
|
||||
var curveVelZ: FastFloat = 0.0;
|
||||
var validCurves: Int = 0;
|
||||
|
||||
for (curve in curveGuides) {
|
||||
if (curve != null && curve.data != null && curve.data.splines != null && curve.splinesLength > 0) {
|
||||
var t = physics.age / physics.lifetime;
|
||||
|
||||
var tangent = curve.getTangent(t, 0);
|
||||
tangent.w = 0.0;
|
||||
tangent.applymat4(curve.transform.world);
|
||||
tangent.normalize();
|
||||
|
||||
var curveLen = curve.getLength(0);
|
||||
var speed = (curveLen / physics.lifetime) * curveGuideSpeed;
|
||||
|
||||
var tgtX = tangent.x * speed;
|
||||
var tgtY = tangent.y * speed;
|
||||
var tgtZ = tangent.z * speed;
|
||||
|
||||
if (localCoords) {
|
||||
var targetVel = new Vec4(tgtX, tgtY, tgtZ, 0.0);
|
||||
var invOwnerRot = new Quat(-owner.transform.rot.x, -owner.transform.rot.y, -owner.transform.rot.z, owner.transform.rot.w);
|
||||
targetVel.applyQuat(invOwnerRot);
|
||||
tgtX = targetVel.x;
|
||||
tgtY = targetVel.y;
|
||||
tgtZ = targetVel.z;
|
||||
}
|
||||
|
||||
curveVelX += tgtX;
|
||||
curveVelY += tgtY;
|
||||
curveVelZ += tgtZ;
|
||||
validCurves++;
|
||||
}
|
||||
}
|
||||
|
||||
if (validCurves > 0) {
|
||||
curveVelX /= validCurves;
|
||||
curveVelY /= validCurves;
|
||||
curveVelZ /= validCurves;
|
||||
|
||||
physics.velocity.x += (curveVelX - physics.velocity.x) * curveGuideStrength;
|
||||
physics.velocity.y += (curveVelY - physics.velocity.y) * curveGuideStrength;
|
||||
physics.velocity.z += (curveVelZ - physics.velocity.z) * curveGuideStrength;
|
||||
}
|
||||
}
|
||||
|
||||
particle.transform.translate(
|
||||
physics.velocity.x * Time.delta * Time.scale,
|
||||
physics.velocity.y * Time.delta * Time.scale,
|
||||
physics.velocity.z * Time.delta * Time.scale
|
||||
physics.velocity.x * Time.delta,
|
||||
physics.velocity.y * Time.delta,
|
||||
physics.velocity.z * Time.delta
|
||||
);
|
||||
|
||||
if (rotation && dynamicRotation && orientationAxis == 3) setVelocityHair(particle, physics.velocity, randQuat, phaseQuat);
|
||||
|
||||
@ -141,10 +141,10 @@ class ParticleSystemGPU {
|
||||
dimx = object.transform.dim.x;
|
||||
dimy = object.transform.dim.y;
|
||||
|
||||
if (object.tilesheet != null) {
|
||||
tilesx = object.tilesheet.getTilesX();
|
||||
tilesy = object.tilesheet.getTilesY();
|
||||
tilesFramerate = object.tilesheet.action.framerate;
|
||||
if (object.activeTilesheet != null) {
|
||||
tilesx = object.activeTilesheet.getTilesX();
|
||||
tilesy = object.activeTilesheet.getTilesY();
|
||||
tilesFramerate = object.activeTilesheet.action.framerate;
|
||||
}
|
||||
|
||||
// Animate
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.Sound;
|
||||
import kha.audio1.AudioChannel;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
@ -13,9 +14,10 @@ class SpeakerObject extends Object {
|
||||
|
||||
public var data: TSpeakerData;
|
||||
public var paused(default, null) = false;
|
||||
public var sound(default, null): kha.Sound = null;
|
||||
public var sound(default, null): Sound = null;
|
||||
public var channels(default, null): Array<AudioChannel> = [];
|
||||
public var volume(default, null) : FastFloat;
|
||||
public var sampleRate(default, null) : Int;
|
||||
|
||||
public function new(data: TSpeakerData) {
|
||||
super();
|
||||
@ -26,28 +28,32 @@ class SpeakerObject extends Object {
|
||||
|
||||
if (data.sound == "") return;
|
||||
|
||||
Data.getSound(data.sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
Data.getSound(data.sound, function(sound: Sound) {
|
||||
this.sound = cloneSound(sound);
|
||||
App.notifyOnInit(init);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
sampleRate = sound.sampleRate;
|
||||
if (data.pitch != 1.0)
|
||||
sound.sampleRate = Std.int(sampleRate * data.pitch);
|
||||
if (visible && data.play_on_start) play();
|
||||
}
|
||||
|
||||
public function play() {
|
||||
if (sound == null || data.muted) return;
|
||||
public function play(): AudioChannel {
|
||||
if (sound == null || data.muted) return null;
|
||||
if (paused) {
|
||||
for (c in channels) c.play();
|
||||
paused = false;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
var channel = Audio.play(sound, data.loop, data.stream);
|
||||
if (channel != null) {
|
||||
channels.push(channel);
|
||||
if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
@ -65,14 +71,24 @@ class SpeakerObject extends Object {
|
||||
|
||||
data.sound = sound;
|
||||
|
||||
Data.getSound(sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
Data.getSound(sound, function(sound: Sound) {
|
||||
this.sound = cloneSound(sound);
|
||||
});
|
||||
|
||||
sampleRate = this.sound.sampleRate;
|
||||
if (data.pitch != 1.0)
|
||||
this.sound.sampleRate = Std.int(sampleRate * data.pitch);
|
||||
}
|
||||
|
||||
public function setVolume(volume: FastFloat) {
|
||||
data.volume = volume;
|
||||
}
|
||||
|
||||
public function setPosition(position: Float) {
|
||||
for (c in channels)
|
||||
if (position < c.length) c.position = position;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (paused) return;
|
||||
for (c in channels) if (c.finished) channels.remove(c);
|
||||
@ -103,6 +119,17 @@ class SpeakerObject extends Object {
|
||||
super.remove();
|
||||
}
|
||||
|
||||
function cloneSound(sound: Sound): Sound {
|
||||
if (sound == null) return null;
|
||||
var s = Type.createEmptyInstance(Sound);
|
||||
s.compressedData = sound.compressedData;
|
||||
s.uncompressedData = sound.uncompressedData;
|
||||
s.sampleRate = sound.sampleRate;
|
||||
s.length = sound.length;
|
||||
s.channels = sound.channels;
|
||||
return s;
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.App;
|
||||
import iron.Scene;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
import haxe.ds.Map;
|
||||
|
||||
|
||||
@:allow(iron.Scene)
|
||||
class Tilesheet {
|
||||
|
||||
public var tileX: Float = 0.0;
|
||||
@ -14,7 +15,8 @@ class Tilesheet {
|
||||
public var flipY: Bool = false;
|
||||
public var paused: Bool = false;
|
||||
public var frame: Int = 0;
|
||||
public var actions: Array<TTilesheetAction>;
|
||||
public var raw: TTilesheetData = null;
|
||||
public var actions: Array<TTilesheetAction> = null;
|
||||
public var action: TTilesheetAction = null;
|
||||
|
||||
public var ready: Bool = false;
|
||||
@ -31,8 +33,11 @@ class Tilesheet {
|
||||
|
||||
public function new(tilesheetData: TTilesheetData, ownerObject: MeshObject = null) {
|
||||
owner = ownerObject;
|
||||
raw = tilesheetData;
|
||||
actions = tilesheetData.actions;
|
||||
|
||||
Scene.active.tilesheets.push(this);
|
||||
|
||||
pendingAction = tilesheetData.start_action;
|
||||
if ((pendingAction == null || pendingAction == "") && actions.length > 0) {
|
||||
pendingAction = actions[0].name;
|
||||
@ -262,8 +267,10 @@ class Tilesheet {
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
Scene.active.tilesheets.remove(this);
|
||||
ready = false;
|
||||
action = null;
|
||||
raw = null;
|
||||
actions = null;
|
||||
owner = null;
|
||||
currentMesh = null;
|
||||
|
||||
@ -106,6 +106,7 @@ class Transform {
|
||||
Rebuild the matrices, if needed.
|
||||
**/
|
||||
public function update() {
|
||||
if (object.constraints != null && object.constraints.length > 0) dirty = true;
|
||||
if (dirty) buildMatrix();
|
||||
}
|
||||
|
||||
@ -150,12 +151,19 @@ class Transform {
|
||||
if (boneParent != null) local.multmats(boneParent, local);
|
||||
|
||||
if (object.parent != null && !localOnly) {
|
||||
world.multmats3x4(local, object.parent.transform.world);
|
||||
// Swap multiplication order for linked objects to keep local transform intact
|
||||
var swapMult: Bool = object.raw != null && object.parent.raw != null && object.parent.raw.group_ref != null && object.parent.raw.group_ref != "";
|
||||
var a: Mat4 = swapMult ? object.parent.transform.world : local;
|
||||
var b: Mat4 = swapMult ? local : object.parent.transform.world;
|
||||
world.multmats3x4(a, b);
|
||||
}
|
||||
else {
|
||||
world.setFrom(local);
|
||||
}
|
||||
|
||||
// Constraints
|
||||
if (object.constraints != null) for (c in object.constraints) c.apply(this);
|
||||
|
||||
worldUnpack.setFrom(world);
|
||||
if (scaleWorld != 1.0) {
|
||||
worldUnpack._00 *= scaleWorld;
|
||||
@ -172,9 +180,6 @@ class Transform {
|
||||
worldUnpack._23 *= scaleWorld;
|
||||
}
|
||||
|
||||
// Constraints
|
||||
if (object.constraints != null) for (c in object.constraints) c.apply(this);
|
||||
|
||||
computeDim();
|
||||
|
||||
// Update children
|
||||
@ -265,7 +270,7 @@ class Transform {
|
||||
}
|
||||
|
||||
function computeRadius() {
|
||||
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
|
||||
radius = 0.5 * Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
|
||||
}
|
||||
|
||||
function computeDim() {
|
||||
|
||||
@ -428,12 +428,10 @@ class Uniforms {
|
||||
var v: Vec4 = null;
|
||||
helpVec.set(0, 0, 0, 0);
|
||||
switch (c.link) {
|
||||
#if lnx_debug
|
||||
case "_input": {
|
||||
helpVec.set(Input.getMouse().x / iron.App.w(), Input.getMouse().y / iron.App.h(), Input.getMouse().down() ? 1.0 : 0.0, 0.0);
|
||||
v = helpVec;
|
||||
}
|
||||
#end
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -690,7 +688,7 @@ class Uniforms {
|
||||
}
|
||||
case "_hosekSunDirection": {
|
||||
var w = Scene.active.world;
|
||||
if (w != null) {
|
||||
if (w != null && w.raw.sun_direction != null) {
|
||||
// Clamp Z for night cycle
|
||||
helpVec.set(w.raw.sun_direction[0],
|
||||
w.raw.sun_direction[1],
|
||||
@ -956,6 +954,22 @@ class Uniforms {
|
||||
}
|
||||
|
||||
static function setObjectConstant(g: Graphics, object: Object, location: ConstantLocation, c: TShaderConstant) {
|
||||
#if lnx_spot
|
||||
if (c.name == "LWVPSpot") {
|
||||
var light = getSpot(0);
|
||||
if (light != null) {
|
||||
if (object == null) helpMat.setIdentity();
|
||||
else helpMat.setFrom(object.transform.worldUnpack);
|
||||
|
||||
helpMat.multmat(light.VP);
|
||||
helpMat.multmat(biasMat);
|
||||
|
||||
g.setMatrix(location, helpMat.self);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (c.link == null) return;
|
||||
|
||||
var camera = Scene.active.camera;
|
||||
@ -1263,17 +1277,17 @@ class Uniforms {
|
||||
var vy: Null<kha.FastFloat> = null;
|
||||
switch (c.link) {
|
||||
case "_tilesheetOffset": {
|
||||
var ts = cast(object, MeshObject).tilesheet;
|
||||
var ts = cast(object, MeshObject).activeTilesheet;
|
||||
vx = ts.tileX;
|
||||
vy = ts.tileY;
|
||||
}
|
||||
case "_tilesheetFlip": {
|
||||
var ts = cast(object, MeshObject).tilesheet;
|
||||
var ts = cast(object, MeshObject).activeTilesheet;
|
||||
vx = ts.flipX ? 1.0 : 0.0;
|
||||
vy = ts.flipY ? 1.0 : 0.0;
|
||||
}
|
||||
case "_tilesheetTiles": {
|
||||
var ts = cast(object, MeshObject).tilesheet;
|
||||
var ts = cast(object, MeshObject).activeTilesheet;
|
||||
vx = ts.getTilesX();
|
||||
vy = ts.getTilesY();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user