wacomink
Version:
1,165 lines (1,164 loc) • 91.5 kB
JavaScript
/**
* @namespace Module
*
* @description connect to canvas
*/
/*
For local usagse:
Firefox: "security.fileuri.strict_origin_policy" should be false, parameter can be found in about:config
Chrome: start with parameter "--allow-access-from-files"
*/
var Module = {
preRun: [],
postRun: [],
addPostScript: function (callback) {
this.postRun.unshift(callback);
},
print: function (text) {
if (text && text != "")
console.log(text);
},
printErr: function (text) {
text = Array.prototype.slice.call(arguments).join(" ");
if (0)
dump(text + "\n"); // fast, straight to the real console
else
console.log(text);
},
canvas: null,
canvasID: "canvas",
// setStatus: function(text) {},
totalDependencies: 0,
monitorRunDependencies: function (left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
// Module.setStatus(left?"Preparing... (" + (this.totalDependencies-left) + "/" + this.totalDependencies + ")":"All downloads complete.");
}
};
Module.getScriptPrefixURL = function (name) {
var result = null;
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.contains(name)) {
var src = scripts[i].getAttribute("src");
if (src.contains("/"))
src = src.substring(0, src.lastIndexOf("/"));
else
src = "";
result = src;
break;
}
}
return result;
};
if (ENVIRONMENT == EnvironmentType.WEB) {
/**
* Prefix path to the engine mem file.
* Auto initialized with prefix path of Module.js.
* Should be configured manually when used in workers or for custom mem file location.
*
* @memberof Module
* @member {String} memoryInitializerPrefixURL
*/
Module.memoryInitializerPrefixURL = Module.getScriptPrefixURL("Module.js") + "/";
}
Module.addPostScript(function () {
Object.defineProperty(Module.VectorInterval.prototype, "length", { get: function () { return this.size(); } });
Object.defineProperty(Module.VectorInt64Ptr.prototype, "length", { get: function () { return this.size(); } });
Object.defineProperty(Module.PathBuilder.prototype, "stride", { get: function () { return this.calculateStride(); } });
Object.extend(Module.VectorInterval.prototype, {
push: Module.VectorInterval.prototype.push_back,
forEach: function (callback, context) {
for (var i = 0; i < this.size(); i++)
callback.call(context || {}, this.get(i), i, this);
},
toArray: function () {
var result = new Array();
for (var i = 0; i < this.size(); i++)
result.push(this.get(i));
return result;
}
});
Module.VectorInt64Ptr.prototype.forEach = Module.VectorInterval.prototype.forEach;
Object.extend(Module.PathBuilder, {
createPath: function (points, stride) {
if (Array.isArray(points))
points = points.toFloat32Array();
return { points: points, stride: stride };
}
});
Object.extend(Module.PathBuilder.prototype, {
createPath: function (points) {
this.super.createPath.apply(this.super, arguments);
return Module.PathBuilder.createPath(points, this.stride);
},
addPathPart: function (pathPart) {
this.super.addPathPart.apply(this.super, arguments);
var pathContext;
Module.writeBytes(pathPart.points, function (points) {
pathContext = this.nativeAddPathPart(points);
}, this);
pathContext.stride = this.stride;
return pathContext;
},
finishPreliminaryPath: function (pathEnding) {
this.super.finishPreliminaryPath.apply(this.super, arguments);
var path;
Module.writeBytes(pathEnding.points, function (points) {
path = this.nativeFinishPreliminaryPath(points);
}, this);
return path;
}
});
Object.extend(Module.MultiChannelSmoothener.prototype, {
smooth: function (pathPart, finish) {
this.super.smooth.apply(this.super, arguments);
var path;
Module.writeBytes(pathPart.points, function (points) {
path = Module.PathBuilder.createPath(this.nativeSmooth(points, finish), pathPart.stride);
}, this);
return path;
}
});
Object.extend(Module.ParticleBrush.prototype, {
configureShape: function (src, callback, context) {
if (!this.shapeTexture) {
this.shapeTexture = Module.GLTools.createTexture(GLctx.CLAMP_TO_EDGE, GLctx.LINEAR);
}
Module.GLTools.prepareTexture(this.shapeTexture, src, callback, context);
},
configureFill: function (src, callback, context) {
if (!this.fillTexture) {
this.fillTexture = Module.GLTools.createTexture(GLctx.REPEAT, GLctx.NEAREST);
}
Module.GLTools.prepareTexture(this.fillTexture, src, function (texture) {
this.setFillTextureSize(texture.image.width, texture.image.height);
if (callback)
callback.call(context || {});
}, this);
}
});
Object.extend(Module.Intersector.prototype, {
setTargetAsStroke: function (path, width) {
this.super.setTargetAsStroke.apply(this.super, arguments);
Module.writeBytes(path.points, function (points) {
this.nativeSetTargetAsStroke(points, path.stride, width);
}, this);
},
setTargetAsClosedPath: function (path) {
this.super.setTargetAsClosedPath.apply(this.super, arguments);
Module.writeBytes(path.points, function (points) {
this.nativeSetTargetAsClosedPath(points, path.stride);
}, this);
},
isIntersectingTarget: function (stroke) {
this.super.isIntersectingTarget.apply(this.super, arguments);
var result;
Module.writeBytes(stroke.path.points, function (points) {
Module.writeBytes(stroke.path.segmentsBounds(), function (segments) {
result = this.nativeIsIntersectingTarget(points, stroke.path.stride, stroke.width, stroke.ts, stroke.tf, stroke.path.bounds, segments);
}, this);
}, this);
return result;
},
intersectWithTarget: function (stroke) {
this.super.intersectWithTarget.apply(this.super, arguments);
var intervals;
Module.writeBytes(stroke.path.points, function (points) {
Module.writeBytes(stroke.path.segmentsBounds(), function (segments) {
intervals = this.nativeIntersectWithTarget(points, stroke.path.stride, stroke.width, stroke.ts, stroke.tf, stroke.path.bounds, segments);
}, this);
}, this);
return intervals;
}
});
Object.extend(Module.InkEncoder, {
encode: function (strokes) {
var bytes;
var encoder = new Module.InkEncoder();
strokes.forEach(function (stroke) {
encoder.encode(stroke);
}, this);
bytes = Module.readBytes(encoder.getBytes());
encoder.delete();
return bytes;
}
});
Object.extend(Module.InkEncoder.prototype, {
encode: function (stroke, paint) {
this.super.encode(stroke);
Module.writeBytes(stroke.path.points, function (points) {
var ink = {
precision: stroke.encodePrecision || 2,
path: Module.PathBuilder.createPath(points, stroke.path.stride),
width: stroke.width,
color: stroke.color,
ts: stroke.ts,
tf: stroke.tf,
randomSeed: Module.getUnsignedInt(stroke.randomSeed),
blendMode: stroke.blendMode,
paint: Module.getUnsignedInt((paint && !isNaN(paint)) ? paint : stroke.brush.id),
id: Module.getUnsignedInt(stroke.id)
};
this.nativeEncode(ink);
}, this);
}
});
Object.extend(Module.InkDecoder, {
decode: function (bytes) {
var strokes = new Array();
var stroke;
var dirtyArea;
Module.writeBytes(bytes, function (int64Ptr) {
var decoder = new Module.InkDecoder(int64Ptr);
while (decoder.hasNext()) {
stroke = decoder.decode();
dirtyArea = Module.RectTools.union(dirtyArea, stroke.bounds);
strokes.push(stroke);
}
decoder.delete();
}, this);
strokes.bounds = dirtyArea;
return strokes;
},
getStrokeBrush: function (paint) {
throw new Error("Module.InkDecoder.getStrokeBrush(paint, [user]) should be implemented");
}
});
Object.extend(Module.InkDecoder.prototype, {
decode: function () {
this.super.decode.apply(this.super, arguments);
var ink = this.nativeDecode();
var brush = Module.InkDecoder.getStrokeBrush(this.paint);
var stroke = new Module.Stroke(brush, ink.path, ink.width, ink.color, ink.ts, ink.tf, ink.randomSeed.null ? NaN : ink.randomSeed.value, ink.blendMode);
this.paint = ink.paint.null ? null : ink.paint.value;
return stroke;
}
});
Object.extend(Module.BrushEncoder.prototype, {
encode: function (brush) {
if (brush instanceof Module.ParticleBrush) {
var shapes = new Module.VectorInt64Ptr();
var fills = new Module.VectorInt64Ptr();
this.encodeImages(brush.shapeTexture, shapes);
this.encodeImages(brush.fillTexture, fills);
this.encodeParticleBrush(brush, shapes, fills, Module.getUnsignedInt(brush.id));
for (var i = 0; i < shapes.size(); i++)
Module._free(shapes.get(i).ptr);
for (var i = 0; i < fills.size(); i++)
Module._free(fills.get(i).ptr);
shapes.delete();
fills.delete();
}
},
encodeImages: function (textureID, int64Ptrs) {
var images = GL.textures[textureID].image ? [GL.textures[textureID].image] : GL.textures[textureID].mipmap;
if (!images)
throw new Error("Texture images not found");
images.forEach(function (image) {
// do not encode auto generated images
if (image.src.startsWith("data:"))
return;
var bytes = image.getBytes();
var ptr = Module._malloc(bytes.length);
var int64Ptr = { ptr: ptr, length: bytes.length };
Module.HEAPU8.set(bytes, ptr);
int64Ptrs.push_back(int64Ptr);
});
}
});
Object.extend(Module.BrushDecoder.prototype, {
decode: function () {
this.brushes = new Array();
this.loader = 0;
while (this.hasNext()) {
var id = this.getBrushID();
var brushID = id.null ? null : id.value;
var brush = Module.InkDecoder.getStrokeBrush(brushID);
if (!brush) {
brush = this.getParticleBrush();
brush.id = brushID;
this.decodeImages(brush.shapeTexture, this.getShapes());
this.decodeImages(brush.fillTexture, this.getFills());
}
this.brushes.push(brush);
}
if (this.loader == 0)
this.onComplete(this.brushes);
},
decodeImages: function (textureID, int64Ptrs) {
var texture = GL.textures[textureID];
var self = this;
this.loader += int64Ptrs.size();
if (int64Ptrs.size() > 1) {
var mipmap = new Array();
var cnt = int64Ptrs.size();
for (var i = 0; i < int64Ptrs.size(); i++) {
var image = Image.fromBytes(Module.readBytes(int64Ptrs.get(i)), function () {
cnt--;
self.loader--;
if (cnt == 0) {
Module.GLTools.completeMipMap(mipmap, function () {
texture.mipmap = mipmap;
Module.GLTools.initTexture(texture);
if (self.loader == 0)
self.onComplete(self.brushes);
});
}
});
mipmap.push(image);
}
}
else {
var image = Image.fromBytes(Module.readBytes(int64Ptrs.get(0)), function () {
self.loader--;
texture.image = this;
Module.GLTools.initTexture(texture);
if (self.loader == 0)
self.onComplete(self.brushes);
});
}
},
onComplete: function (brushes) { }
});
Object.extend(Module.PathOperationEncoder.prototype, {
encodeComposeStyle: function (style) {
this.super.encodeComposeStyle.apply(this.super, arguments);
this.nativeComposeStyle(style.width, style.color, style.blendMode, Module.getUnsignedInt(style.brush.id), Module.getUnsignedInt(style.randomSeed));
},
encodeComposePathPart: function (path, color, variableWidth, variableColor, endStroke) {
this.super.encodeComposePathPart.apply(this.super, arguments);
if (path.points.length > 0) {
Module.writeBytes(path.points, function (points) {
this.nativeComposePathPart(points, color, variableWidth, variableColor, endStroke);
}, this);
}
},
encodeAdd: function (strokes) {
this.super.encodeAdd.apply(this.super, arguments);
var addEncoder = this.nativeAdd();
strokes.forEach(function (stroke) {
Module.writeBytes(stroke.path.points, function (points) {
var ink = {
precision: stroke.encodePrecision || 2,
path: Module.PathBuilder.createPath(points, stroke.path.stride),
width: stroke.width,
color: stroke.color,
ts: stroke.ts,
tf: stroke.tf,
randomSeed: Module.getUnsignedInt(stroke.randomSeed),
blendMode: stroke.blendMode,
paint: Module.getUnsignedInt(stroke.brush.id),
id: Module.getUnsignedInt(stroke.id)
};
addEncoder.encodeStroke(ink);
}, this);
}, this);
this.flush();
},
encodeRemove: function (group) {
this.super.encodeRemove.apply(this.super, arguments);
Module.writeBytes(group, function (group) {
this.nativeEncodeRemove(group);
}, this);
},
encodeUpdateColor: function (group, color) {
this.super.encodeUpdateColor.apply(this.super, arguments);
Module.writeBytes(group, function (group) {
this.nativeEncodeUpdateColor(group, color);
}, this);
},
encodeUpdateBlendMode: function (group, blendMode) {
this.super.encodeUpdateBlendMode.apply(this.super, arguments);
Module.writeBytes(group, function (group) {
this.nativeEncodeUpdateBlendMode(group, blendMode);
}, this);
},
encodeSplit: function (splits) {
this.super.encodeSplit.apply(this.super, arguments);
var splitEncoder = this.nativeSplit();
var affectedArea;
splits.forEach(function (split) {
var intervals = new Module.VectorInterval();
var intervalIDs = new Array();
affectedArea = Module.RectTools.union(affectedArea, split.bounds);
for (var i = 0; i < split.strokes.length; i++) {
var strokeID = split.strokes[i].id;
var interval = split.intervals[i];
intervals.push(interval);
if (strokeID)
intervalIDs.push(strokeID);
}
Module.writeBytes(intervalIDs.toUint32Array(), function (intervalIDs) {
splitEncoder.encodeStroke(split.id, affectedArea, intervals, intervalIDs);
}, this);
intervals.delete();
}, this);
this.flush();
},
encodeTransform: function (group, mat) {
this.super.encodeTransform.apply(this.super, arguments);
Module.writeBytes(group, function (group) {
this.nativeEncodeTransform(group, mat);
}, this);
}
});
Object.extend(Module.PathOperationDecoder, {
getPathOperationDecoderCallbacksHandler: function (implementation) {
var implementationFunctions = ["onComposeStyle", "onComposePathPart", "onComposeAbort", "onAdd", "onRemove", "onUpdateColor", "onUpdateBlendMode", "onSplit", "onTransform"];
implementationFunctions.forEach(function (name) {
if (!implementation[name] || typeof implementation[name] != "function")
throw new Error("Implementation of \"" + name + "\" missing. Please provide implementation.");
});
implementation = Object.clone(implementation);
Object.extend(implementation, {
composeStyle: function (user, style, paint) {
style.brush = Module.InkDecoder.getStrokeBrush(paint, user);
style.randomSeed = style.randomSeed.null ? NaN : style.randomSeed.value;
return style;
},
onAddStroke: function (user, strokes, ink) {
if (!strokes)
strokes = [];
var brush = Module.InkDecoder.getStrokeBrush(ink.paint.null ? null : ink.paint.value, user);
var stroke = new Module.Stroke(brush, ink.path, ink.width, ink.color, ink.ts, ink.tf, ink.randomSeed.null ? NaN : ink.randomSeed.value, ink.blendMode);
if (!ink.id.null)
stroke.id = ink.id.value;
strokes.push(stroke);
return strokes;
},
onSplitStroke: function (splits, id, affectedArea, intervals, intervalIDs) {
if (!splits)
splits = new Array();
splits.affectedArea = Module.RectTools.union(splits.affectedArea, affectedArea);
var split = { id: id, intervals: new Array() };
splits.push(split);
intervals.forEach(function (interval, i) {
var splitInterval = { fromIndex: interval.fromIndex, toIndex: interval.toIndex, fromTValue: interval.fromTValue, toTValue: interval.toTValue };
if (intervalIDs.length > 0)
splitInterval.id = intervalIDs[i];
split.intervals.push(splitInterval);
});
return splits;
}
});
return Module.PathOperationDecoderCallbacksHandlerInterface.implement(implementation);
}
});
Object.extend(Module.MatTools, {
create: function (values) {
var mat = this.super.create();
if (values) {
for (var name in values) {
if (name in mat)
mat[name] = values[name];
else
console.warn("Key '" + name + "' is not applicable key for the Matrix2D type");
}
}
return mat;
},
makeTranslate: function (tx, ty) {
if (arguments.length == 1) {
var point = tx;
tx = point.x;
ty = point.y;
}
return this.create({ tx: tx, ty: ty });
},
makeScale: function (sx, sy) {
if (arguments.length == 1)
sy = sx;
return this.create({ a: sx, d: sy });
},
makeRotate: function (alpha) {
return this.create({
a: Math.cos(alpha),
b: Math.sin(alpha),
c: -Math.sin(alpha),
d: Math.cos(alpha)
});
},
makeScaleAtPoint: function (scale, point) {
return this.makeTransformAroundPoint(0, scale, point);
},
makeRotationAroundPoint: function (alpha, point) {
return this.makeTransformAroundPoint(alpha, 1, point);
},
makeTransformAroundPoint: function (alpha, scale, point) {
var sx = Math.cos(alpha) * (isNaN(scale) ? scale.x : scale);
var sy = Math.cos(alpha) * (isNaN(scale) ? scale.y : scale);
var sin = Math.sin(alpha);
return this.create({
a: sx, b: sin, c: -sin, d: sy,
tx: point.x - point.x * sx + point.y * sin,
ty: point.y - point.x * sin - point.y * sy
});
},
fromCSS: function (element) {
var result = this.create();
var transform = element.getStyle("transform");
if (transform != "none") {
transform = transform.substring(transform.indexOf("(") + 1, transform.indexOf(")")).split(/,\s*/g);
result.a = parseFloat(transform[0]);
result.b = parseFloat(transform[1]);
result.c = parseFloat(transform[2]);
result.d = parseFloat(transform[3]);
result.tx = parseFloat(transform[4]);
result.ty = parseFloat(transform[5]);
}
return result;
},
toCSS: function (mat) {
return "matrix(" + this.toArray(mat).join(", ") + ")";
},
toArray: function (mat) {
return [mat.a, mat.b, mat.c, mat.d, mat.tx, mat.ty];
},
transformRect: function (rect, mat, discardCeil) {
return discardCeil ? this.super.transformRect(rect, mat) : Module.RectTools.ceil(this.super.transformRect(rect, mat));
}
});
var enums = [Module.BlendMode, Module.RotationMode, Module.PropertyName, Module.PropertyFunction, Module.InputPhase];
enums.forEach(function (enm) {
for (var name in enm) {
if (typeof enm[name] == "object") {
enm[name]["name"] = name;
enm[name]["type"] = enm[name].constructor.name.split("_")[0];
enm[enm[name].value] = enm[name];
}
}
});
});
Module.getUnsignedInt = Module.getUnsignedLong = function (value) {
if (value && !isNaN(value) && value >= 0)
return { value: value, null: false };
else
return { value: 0, null: true };
return result;
};
Module.isInt64Ptr = function (int64Ptr) {
return int64Ptr && "ptr" in int64Ptr && "length" in int64Ptr && "byteLength" in int64Ptr;
};
/**
* Read bytes from HEAP
*
* @param {Module.Int64Ptr} int64Ptr
* @return {Uint8Array} bytes
*/
Module.readBytes = function (int64Ptr) {
var data = Module.HEAPU8.subarray(int64Ptr.ptr, int64Ptr.ptr + int64Ptr.byteLength);
var bytes = new Uint8Array(data, data.byteOffset, int64Ptr.length);
return bytes;
};
/**
* Read ints from HEAP
*
* @param {Module.Int64Ptr} int64Ptr
* @return {Uint32Array} ints
*/
Module.readInts = function (int64Ptr) {
var bytes = Module.readBytes(int64Ptr);
var ints = new Uint32Array(bytes.buffer);
return ints;
};
/**
* Read floats from HEAP
*
* @param {Module.Int64Ptr} int64Ptr
* @return {Float32Array} floats
*/
Module.readFloats = function (int64Ptr) {
var bytes = Module.readBytes(int64Ptr);
var floats = new Float32Array(bytes.buffer);
return floats;
};
/**
* Handler for extracted data
*
* @callback WriteBytesCallback
* @param {Module.Int64Ptr} int64Ptr
* @see Module.writeBytes
*/
/**
* Write bytes to HEAP
*
* @param {TypedArray} data
* @param {WriteBytesCallback} callback
* @param {Object} [context={}] callback context
*/
Module.writeBytes = function (data, callback, context) {
if (Module.isInt64Ptr(data)) {
callback.call(context || {}, data);
return;
}
else if (!ArrayBuffer.isTypedArray(data))
throw new Error("data is not TypedArray object");
var ptr = Module._malloc(data.byteLength);
var int64Ptr = { ptr: ptr, length: data.length, byteLength: data.byteLength };
var bytes = (data instanceof Uint8Array) ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
try {
Module.HEAPU8.set(bytes, ptr);
callback.call(context || {}, int64Ptr);
}
finally {
Module._free(ptr);
}
};
/**
* Storage for a bitmap data that can be manipulated or presented on the screen.
* It corresponds to an GL texture or a render buffer.
* It defines the ration between actual pixels and the abstract layer dimensions.
*
* @class Module.GenericLayer
* @since version 1.3
* @abstract
*/
Module.GenericLayer = function () {
throw new BindingError("GenericLayer has no accessible constructor");
};
/**
* Layer width
*
* @memberof Module.GenericLayer.prototype
* @member {int} width
*/
Object.defineProperty(Module.GenericLayer.prototype, "width", {
get: function () {
return this.layer.width;
}
});
/**
* Layer height
*
* @memberof Module.GenericLayer.prototype
* @member {int} height
*/
Object.defineProperty(Module.GenericLayer.prototype, "height", {
get: function () {
return this.layer.height;
}
});
/**
* The bounds of the layer. Equals to (0, 0, width, height).
*
* @memberof Module.GenericLayer.prototype
* @member {Module.Rectangle} bounds
*/
Object.defineProperty(Module.GenericLayer.prototype, "bounds", {
get: function () {
return Module.RectTools.create(this.layer.bounds.left, this.layer.bounds.bottom, this.layer.bounds.width, this.layer.bounds.height);
}
});
Object.extend(Module.GenericLayer.prototype, {
/**
* Clears layer area with a color
*
* @method Module.GenericLayer.prototype.clear
* @param {(Module.Rectangle | Module.Color)} [rect=bounds] area to clear
* @param {Module.Color} [color=Module.Color.TRANSPERENT]
*/
clear: function (rect, color) {
if (!color) {
if (Module.Color.is(rect)) {
color = rect;
rect = null;
}
else
color = Module.Color.TRANSPERENT;
}
else if (Module.Color.is(rect))
throw new Error("`clear` first argument should be Module.Rectangle");
var wrect = rect ? makeRectFromFlippedRect(rect) : null;
this.renderingContext.setTarget(this.layer, wrect);
this.renderingContext.clearColor(color);
if (wrect) {
this.renderingContext.disableTargetClipRect();
}
},
/**
* Draws points with a brush over the layer
*
* @method Module.GenericLayer.prototype.draw
* @param {Module.Stroke} stroke model instance
* @return {Module.Rectangle} affected area
*/
draw: function (stroke) {
if (!Module.Stroke.validatePath(stroke.path))
return null;
var drawContext = null;
if (stroke.brush instanceof Module.ParticleBrush && !isNaN(stroke.randomSeed)) {
drawContext = new Module.StrokeDrawContext();
drawContext.randomSeed = stroke.randomSeed;
}
var dirtyArea = this.drawStroke(stroke.brush, stroke.path, stroke.width, stroke.color, true, true, stroke.ts, stroke.tf, drawContext);
if (drawContext)
drawContext.delete();
return dirtyArea;
},
/**
* Draws points with a brush over the layer
*
* @method Module.GenericLayer.prototype.drawStroke
* @param {Module.StrokeBrush} brush instance of the Module.StrokeBrush class that determines how the stroke is going to be rendered
* @param {Module.Path} path points to draw
* @param {float} width The width of the stroke. If the stroke has a variable thickness which is included in the control points array, this parameter must be NaN.
* @param {Module.Color} color stroke color
* If the stroke has a variable alpha which is included in the control points array, alpha component must be NaN.
* @param {boolean} roundCapBeginning Cap the stroke with a circle at the start
* @param {boolean} roundCapEnding Cap the stroke with a circle at the end
* @param {float} ts The starting value for the Catmull-Roll spline parameter
* @param {float} tf The final value for the Catmull-Rom spline parameter
* @param {Module.StrokeDrawContext} drawContext Module.StrokeDrawContext instance (contains previous path point), applicable only when brush is Module.ParticleBrush
*
* @return {Module.Rectangle} affected area
*/
drawStroke: function (brush, path, width, color, roundCapBeggining, roundCapEnding, ts, tf, drawContext) {
if (!Module.Stroke.validatePath(path))
return null;
var dirtyArea;
Module.writeBytes(path.points, function (points) {
if (path.transform && !Module.MatTools.isIdentity(path.transform)) {
Module.MatTools.transformPath(Module.PathBuilder.createPath(points, path.stride), path.transform);
if (!isNaN(width))
width *= Math.sqrt(path.transform.a * path.transform.a + path.transform.c * path.transform.c);
}
points = Module.readFloats(points);
this.renderingContext.setTarget(this.layer);
dirtyArea = this.renderingContext.drawStroke(brush, points, path.stride, width, color, roundCapBeggining, roundCapEnding, ts, tf, drawContext, true);
if (dirtyArea)
dirtyArea = Module.RectTools.create(dirtyArea.left, dirtyArea.bottom, dirtyArea.width, dirtyArea.height);
}, this);
if (path.transform && !Module.MatTools.isIdentity(path.transform) && Module.isInt64Ptr(path.points))
Module.MatTools.transformPath(path, Module.MatTools.invert(path.transform));
return dirtyArea;
},
/**
* Fills path with color
*
* @method Module.GenericLayer.prototype.fillPath
* @param {Module.Path} path control points to work with it
* @param {Module.Color} color fill color
* @param {boolean} antiAliasing is anti aliasing is enabled
*/
fillPath: function (path, color, antiAliasing) {
if (!Module.Stroke.validatePath(path))
return;
Module.writeBytes(path.points, function (points) {
this.renderingContext.setTarget(this.layer);
this.renderingContext.fillPath(points, path.stride, color, !!antiAliasing);
}, this);
},
/**
* Draws the content of the 'source'. The 'source' must use a texture storage.
*
* @method Module.GenericLayer.prototype.blend
* @param {Module.GenericLayer} source layer to be drawn
* @param {Module.BlendOptions} options blending configuration
*/
blend: function (source, options) {
if (!options)
options = {};
if (!options.mode)
options.mode = Module.BlendMode.NORMAL;
if (options.rect) {
options.sourceRect = options.rect;
options.destinationRect = options.rect;
}
if (options.transform && (options.sourceRect || options.destinationRect))
throw new Error("`sourceRect` and `destinationRect` are not applicable with `transform`");
if (options.sourceRect && !options.destinationRect)
throw new Error("With `sourceRect`, `destinationRect` is required");
if (options.destinationRect && !options.sourceRect)
throw new Error("With `destinationRect`, `sourceRect`is required");
if (options.transform) {
this.renderingContext.setTarget(this.layer);
this.renderingContext.drawLayerWithTransform(source.layer, makeMat4FromMatrix2D(options.transform), options.mode);
}
else if (options.sourceRect && options.destinationRect) {
this.renderingContext.setTarget(this.layer);
this.renderingContext.drawLayer(source.layer, makeQuadFromFlippedRect(options.sourceRect), makeQuadFromFlippedRect(options.destinationRect), options.mode);
}
else {
this.renderingContext.setTarget(this.layer);
this.renderingContext.drawLayerWithTransform(source.layer, null, options.mode);
}
},
/**
* Read pixels data
*
* @method Module.GenericLayer.prototype.readPixels
* @param {Module.Rectangle} [rect=bounds] area to read
*/
readPixels: function (rect) {
this.renderingContext.setTarget(this.layer);
return this.renderingContext.readPixels(rect ? makeRectFromFlippedRect(rect) : null);
},
/**
* Write pixels data
*
* @method Module.GenericLayer.prototype.writePixels
* @param {Uint8Array} bytes pixels data
* @param {Module.Rectangle} [rect=bounds] area to write
*/
writePixels: function (bytes, rect) {
if (!bytes)
throw new Error("GenericLayer$writePixels 'bytes' parameter is required");
if (!(bytes instanceof Uint8Array))
throw new Error("GenericLayer$writePixels 'bytes' parameter is not instance of Uint8Array");
this.renderingContext.setTarget(this.layer);
this.renderingContext.writePixels(rect ? makeRectFromFlippedRect(rect) : null, bytes);
}
});
/**
* Creates ink canvas connected with html canvas.
* This layer is the target of screen rendering.
*
* @class Module.InkCanvas
* @extends Module.GenericLayer
* @since version 1.0
* @param {HTMLCanvasElement} canvas DOM element related with ink canvas
* @param {int} width initial width
* @param {int} height initial height
* @param {Module.WebGLContextAttributes} [webGLContextAttributes] WebGL context configuration
*/
Module.InkCanvas = function (canvas, width, height, webGLContextAttributes) {
if (!(canvas instanceof HTMLCanvasElement))
throw new Error("canvas is required");
if (!(width > 0 && height > 0))
throw new Error("width and height are required and should be positive whole numbers");
/**
* Drawing surface
*
* @readonly
* @memberof Module.InkCanvas.prototype
* @member {HTMLCanvasElement} surface
*/
Object.defineProperty(this, "surface", { value: canvas });
canvas.width = width;
canvas.height = height;
var renderingContext = new RenderingContext(canvas, webGLContextAttributes || {});
Object.defineProperty(this, "renderingContext", { value: renderingContext });
this.activate();
this.screenHeight = this.screenWidth = Math.max(screen.width, screen.height);
var layer = this.renderingContext.createLayer(width, height, 1);
layer.init3(null, null, null, false);
layer.flipY = true;
Object.defineProperty(this, "layer", {
get: function () {
this.activate();
return layer;
}
});
/**
* Drawing surface context
*
* @readonly
* @memberof Module.InkCanvas.prototype
* @member {WebGLRenderingContext} ctx
*/
Object.defineProperty(this, "ctx", {
get: function () {
return this.willGLContext.gl;
}
});
if (webGLContextAttributes && webGLContextAttributes.preserveDrawingBuffer) {
Object.defineProperty(this, "frameID", { value: NaN });
this.requestAnimationFrame = function (callback) {
throw new Error("This usage is not applicable when preserve drawing buffer");
// callback(Date.now());
};
}
else if (Module.ownBackbuffer) {
var self = this;
this.requestAnimationFrame = function (callback) {
self.frameID = requestAnimationFrame(callback);
};
this.frameID = -1;
}
else {
var blend = this.super.bind("blend");
Module.InkCanvas.implementBackbuffer(this, blend);
}
};
Module.InkCanvas.extends(Module.GenericLayer);
Module.InkCanvas.implementBackbuffer = function (self, blend) {
if (arguments.callee.caller != Module.InkCanvas.prototype.constructor)
throw new Error("Backbuffer implementation is part from InkCanvas implementation");
var backbuffer = self.createLayer();
var currentFrameUpdates = new Array();
Object.defineProperty(self, "backbuffer", {
get: function () {
self.activate();
return backbuffer;
}
});
/**
* Adds callback to queue, executed on first animation frame.
* This method is not applicable when `preserveDrawingBuffer` WebGL context attribute is setted.
*
* @method Module.InkCanvas.prototype.requestAnimationFrame
* @param {Function} callback A parameter specifying a function to call when it's time to update your animation for the next repaint.
* The callback has one single argument, a DOMHighResTimeStamp,
* which indicates the current time (the time returned from Performance.now()) for when requestAnimationFrame starts to fire callbacks.
* @param {boolean} present is callback affects canvas
* @return {int} animation frame ID
*/
self.requestAnimationFrame = function (callback, present) {
if (present)
self.present = true;
currentFrameUpdates.add(callback);
return self.frameID;
};
var present = function (timestamp) {
while (currentFrameUpdates.length > 0) {
var callback = currentFrameUpdates.shift();
callback(timestamp);
}
if (self.present) {
delete self.present;
blend(self.backbuffer, { mode: Module.BlendMode.NONE });
}
self.frameID = requestAnimationFrame(present);
};
self.frameID = requestAnimationFrame(present);
Object.extend(Module.InkCanvas.prototype, {
clear: function (rect, color) {
this.backbuffer.clear(rect, color);
this.present = true;
},
draw: function (stroke) {
this.backbuffer.draw(stroke);
this.present = true;
},
drawStroke: function (brush, path, width, color, roundCapBeggining, roundCapEnding, ts, tf, drawContext) {
var dirtyArea = this.backbuffer.drawStroke(brush, path, width, color, roundCapBeggining, roundCapEnding, ts, tf, drawContext);
this.present = true;
return dirtyArea;
},
fillPath: function (path, color, antiAliasing) {
this.backbuffer.fillPath(path, color, antiAliasing);
this.present = true;
},
blend: function (source, options) {
this.backbuffer.blend(source, options);
this.present = true;
},
readPixels: function (rect) {
return this.backbuffer.readPixels(rect);
},
writePixels: function (bytes, rect) {
this.backbuffer.writePixels(bytes, rect);
this.present = true;
}
}, true);
};
Object.extend(Module.InkCanvas.prototype, {
/**
* Creates OffscreenLayer instance
*
* @method Module.InkCanvas.prototype.createLayer
* @param {Module.OffscreenLayerOptions} options layer configuration
* @return {Module.OffscreenLayer} layer for offscreen rendering
*/
createLayer: function (options) {
if (!options)
options = {};
var layer;
if (options.renderbuffer) {
if (!options.framebuffer)
throw new Error("`framebuffer` is required when `renderbuffer` available");
if (!(options.framebuffer instanceof WebGLFramebuffer))
throw new Error("`framebuffer` is not instance of WebGLFramebuffer");
if (!(options.renderbuffer instanceof WebGLRenderbuffer))
throw new Error("`renderbuffer` is not instance of WebGLRenderbuffer");
layer = this.renderingContext.createLayerFromGLBuffers(options.framebuffer, options.renderbuffer, !!options.ownGlResources);
}
else if (options.framebuffer) {
if (!(options.framebuffer instanceof WebGLFramebuffer))
throw new Error("`framebuffer` is not instance of WebGLFramebuffer");
layer = this.renderingContext.createLayerFromGLFramebuffer(options.framebuffer, !!options.ownGlResources);
}
else if (options.texture) {
if (!(options.texture instanceof WebGLTexture))
throw new Error("`texture` is not instance of WebGLTexture");
if (options.width > 0 && options.height > 0) {
layer = this.renderingContext.createLayer(options.width, options.height, 1);
layer.init3(null, null, options.texture, !!options.ownGlResources);
}
else if (options.texture.image) {
layer = this.renderingContext.createLayer(options.texture.image.width, options.texture.image.height, 1);
layer.init3(null, null, options.texture, !!options.ownGlResources);
}
else
throw new Error("`width` and `height` are required when `texture` is available");
}
else if (options.width > 0 && options.height > 0) {
if (options.scaleFactor > 0) {
layer = this.renderingContext.createLayer(options.width, options.height, options.scaleFactor);
layer.init1(true);
}
else {
layer = this.renderingContext.createLayer(options.width, options.height, 1);
layer.init1(true);
}
}
else {
layer = this.renderingContext.createLayer(this.screenWidth, this.screenHeight, 1);
layer.init1(true);
}
var OffscreenLayer = Function.create("OffscreenLayer", function (layer, renderingContext) {
this.layer = layer;
this.renderingContext = renderingContext;
// init_ClassHandle in WacomInkEngine.js
// this.isAliasOf = function(other) {return this.nativeLayer.isAliasOf(other.nativeLayer);};
// this.clone = function() {return this.nativeLayer.clone();};
this.delete = function () {
layer.delete();
};
this.isDeleted = function () {
return layer.isDeleted();
};
this.deleteLater = function () {
layer.deleteLater();
return this;
};
});
OffscreenLayer.extends(Module.GenericLayer);
Object.defineProperty(OffscreenLayer.prototype, "renderbuffer", {
get: function () {
return this.layer.renderbuffer;
}
});
Object.defineProperty(OffscreenLayer.prototype, "framebuffer", {
get: function () {
return this.layer.framebuffer;
}
});
Object.defineProperty(OffscreenLayer.prototype, "texture", {
get: function () {
return this.layer.colorTexture;
}
});
return new OffscreenLayer(layer, this.renderingContext);
},
/**
* Resize canvas
*
* @method Module.InkCanvas.prototype.resize
* @param {int} width
* @param {int} height
*/
resize: function (width, height) {
this.surface.width = width;
this.surface.height = height;
this.layer.resize(width, height);
},
activate: function () {
Module.canvas = this.surface;
window.GLctx = this.renderingContext.willGLContext.gl;
},
delete: function () {
cancelAnimationFrame(this.frameID);
this.layer.delete();
this.backbuffer.delete();
},
deleteLater: function () {
this.layer.deleteLater();
this.backbuffer.deleteLater();
return this;
},
isDeleted: function () {
return this.layer.isDeleted();
}
}, true);
/**
* Stroke model
*
* @class Module.Stroke
* @since version 1.4
*
* @param {Module.StrokeBrush} brush
* @param {Module.Path} path
* @param {float} width
* @param {Module.Color} color
* @param {float} ts
* @param {float} tf
* @param {int} [randomSeed=NaN]
* @param {Module.BlendMode} [blendMode=Module.BlendMode.NORMAL]
*/
Module.Stroke = function (brush, path, width, color, ts, tf, randomSeed, blendMode) {
this.brush = brush;
this.brush.mutable = true;
this.path = path;
this.width = width;
this.color = Object.clone(color);
this.ts = ts;
this.tf = tf;
this.randomSeed = (randomSeed && !isNaN(randomSeed)) ? randomSeed : NaN;
this.blendMode = blendMode || Module.BlendMode.NORMAL;
this.initPath();
};
/**
* Stroke length
*
* @memberof Module.Stroke.prototype
* @member {int} length
*/
Object.defineProperty(Module.Stroke.prototype, "length", { enumerable: true, get: function () { return this.path.length; } });
/**
* Stroke bounds
*
* @memberof Module.Stroke.prototype
* @member {Module.Rectangle} bounds
*/
Object.defineProperty(Module.Stroke.prototype, "bounds", { enumerable: true, get: function () { return this.path.bounds; } });
Object.defineProperty(Module.Stroke.prototype, "data", { enumerable: true, get: function () { return { path: { points: this.path.points, stride: this.path.stride }, width: this.width, color: this.color, ts: this.ts, tf: this.tf, randomSeed: this.randomSeed, blendMode: this.blendMode }; } });
Object.extend(Module.Stroke, {
/**
* Creates stroke point object
*
* @method Module.Stroke.createPoint
* @param {int} [x=Infinity]
* @param {int} [y=Infinity]
* @param {float} [width=Infinity]
* @param {float} [alpha=Infinity]
* @return {Module.Point2D} new point, with args as properties
*/
createPoint: function (x, y, width, alpha) {
return {
x: (typeof x == "undefined" || x == null || isNaN(x)) ? Infinity : x,
y: (typeof y == "undefined" || y == null || isNaN(y)) ? Infinity : y,
width: (typeof width == "undefined" || width == null || isNaN(width)) ? Infinity : width,
alpha: (typeof alpha == "undefined" || alpha == null || isNaN(alpha)) ? Infinity : alpha
};
},
fromJSON: function (brush, data) {
if (!data.width)
data.width = NaN;
if (!data.color.alpha)
data.color.alpha = NaN;
if (!data.path)
data.path = Module.PathBuilder.createPath(data.points, data.stride);
return new Module.Stroke(brush, data.path, data.width, data.color, data.ts, data.tf, data.randomSeed, Module.BlendMode[data.blendMode]);
},
validatePath: function (path) {
if (!path)
return false;
if (Module.isInt64Ptr(path.points))
return true;
if (path.points.length < 4 * path.stride) {
if (path.points.length > 0)
Module.printErr("WARNING: Less than needed minimum of points passed (At least 4 points are needed to define a path)!");
return false;
}
if (path.points.length % path.stride != 0) {
Module.printErr("WARNING: The points array size (" + path.points.length + ") is should be a multiple of the stride property (" + path.stride + ")!");
return false;
}
return true;
},
normalizeStrokeData: function (data) {
if (!data.path)
throw new Error("StrokeData path is required");
if (!("width" in data))
data.width = NaN;