UNPKG

wacomink

Version:
1,165 lines (1,164 loc) 91.5 kB
/** * @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;