2011-11-14 14:18:08 -05:00
|
|
|
/*
|
2013-01-28 21:03:27 -05:00
|
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
2011-11-14 14:18:08 -05:00
|
|
|
* http://paperjs.org/
|
|
|
|
*
|
2015-12-27 12:09:25 -05:00
|
|
|
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
|
2014-01-03 19:47:16 -05:00
|
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
2011-11-14 14:18:08 -05:00
|
|
|
*
|
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @name CanvasView
|
2012-11-06 16:34:46 -05:00
|
|
|
* @class
|
2011-11-14 14:18:08 -05:00
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var CanvasView = View.extend(/** @lends CanvasView# */{
|
2014-08-16 13:24:54 -04:00
|
|
|
_class: 'CanvasView',
|
2013-06-23 23:18:32 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Creates a view object that wraps a canvas element.
|
|
|
|
*
|
|
|
|
* @name CanvasView#initialize
|
|
|
|
* @param {HTMLCanvasElement} canvas the canvas object that this view should
|
|
|
|
* wrap
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Creates a view object that wraps a newly created canvas element.
|
|
|
|
*
|
|
|
|
* @name CanvasView#initialize
|
|
|
|
* @param {Size} size the size of the canvas to be created
|
|
|
|
*/
|
2016-01-12 20:13:30 -05:00
|
|
|
initialize: function CanvasView(project, canvas) {
|
2014-08-16 13:24:54 -04:00
|
|
|
// Handle canvas argument
|
|
|
|
if (!(canvas instanceof HTMLCanvasElement)) {
|
|
|
|
// See if the arguments describe the view size:
|
2015-02-27 12:00:36 -05:00
|
|
|
var size = Size.read(arguments, 1);
|
2014-08-16 13:24:54 -04:00
|
|
|
if (size.isZero())
|
|
|
|
throw new Error(
|
|
|
|
'Cannot create CanvasView with the provided argument: '
|
|
|
|
+ [].slice.call(arguments, 1));
|
|
|
|
canvas = CanvasProvider.getCanvas(size);
|
|
|
|
}
|
|
|
|
this._context = canvas.getContext('2d');
|
|
|
|
this._pixelRatio = 1;
|
2014-09-26 12:03:37 -04:00
|
|
|
if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {
|
2014-08-16 13:24:54 -04:00
|
|
|
// Hi-DPI Canvas support based on:
|
|
|
|
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
|
|
|
var deviceRatio = window.devicePixelRatio || 1,
|
|
|
|
backingStoreRatio = DomElement.getPrefixed(this._context,
|
|
|
|
'backingStorePixelRatio') || 1;
|
|
|
|
this._pixelRatio = deviceRatio / backingStoreRatio;
|
|
|
|
}
|
|
|
|
View.call(this, project, canvas);
|
|
|
|
},
|
2013-11-06 05:54:05 -05:00
|
|
|
|
2016-01-26 05:41:49 -05:00
|
|
|
_setViewSize: function _setViewSize(width, height) {
|
|
|
|
var pixelRatio = this._pixelRatio;
|
2014-12-29 18:16:51 -05:00
|
|
|
// Upscale the canvas if the pixel ratio is more than 1.
|
2016-01-26 05:41:49 -05:00
|
|
|
_setViewSize.base.call(this, width * pixelRatio, height * pixelRatio);
|
2014-12-29 18:16:51 -05:00
|
|
|
if (pixelRatio !== 1) {
|
2016-01-26 05:41:49 -05:00
|
|
|
var element = this._element;
|
2015-01-02 08:38:06 -05:00
|
|
|
// We need to set the correct size on non-resizable canvases through
|
|
|
|
// their style when HiDPI is active, as otherwise they would appear
|
|
|
|
// too big.
|
|
|
|
if (!PaperScope.hasAttribute(element, 'resize')) {
|
|
|
|
var style = element.style;
|
|
|
|
style.width = width + 'px';
|
|
|
|
style.height = height + 'px';
|
|
|
|
}
|
2014-12-29 18:16:51 -05:00
|
|
|
// Scale the context to counter the fact that we've manually scaled
|
|
|
|
// our canvas element.
|
|
|
|
this._context.scale(pixelRatio, pixelRatio);
|
|
|
|
}
|
2014-08-16 13:24:54 -04:00
|
|
|
},
|
2011-11-14 14:18:08 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Converts the provide size in any of the units allowed in the browser to
|
2015-08-19 06:57:22 -04:00
|
|
|
* pixels.
|
2014-08-16 13:24:54 -04:00
|
|
|
*/
|
|
|
|
getPixelSize: function(size) {
|
2016-01-26 05:41:49 -05:00
|
|
|
var agent = paper.agent,
|
2015-08-19 06:57:22 -04:00
|
|
|
pixels;
|
2016-01-26 05:41:49 -05:00
|
|
|
if (agent && agent.firefox) {
|
2015-12-28 11:54:07 -05:00
|
|
|
// Firefox doesn't appear to convert context.font sizes to pixels,
|
2015-08-19 06:57:22 -04:00
|
|
|
// while other browsers do. Workaround:
|
|
|
|
var parent = this._element.parentNode,
|
|
|
|
temp = document.createElement('div');
|
|
|
|
temp.style.fontSize = size;
|
|
|
|
parent.appendChild(temp);
|
|
|
|
pixels = parseFloat(DomElement.getStyles(temp).fontSize);
|
|
|
|
parent.removeChild(temp);
|
|
|
|
} else {
|
|
|
|
var ctx = this._context,
|
|
|
|
prevFont = ctx.font;
|
|
|
|
ctx.font = size + ' serif';
|
|
|
|
pixels = parseFloat(ctx.font);
|
|
|
|
ctx.font = prevFont;
|
|
|
|
}
|
|
|
|
return pixels;
|
2014-08-16 13:24:54 -04:00
|
|
|
},
|
2014-03-17 11:41:57 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
getTextWidth: function(font, lines) {
|
|
|
|
var ctx = this._context,
|
|
|
|
prevFont = ctx.font,
|
|
|
|
width = 0;
|
|
|
|
ctx.font = font;
|
|
|
|
// Measure the real width of the text. Unfortunately, there is no sane
|
|
|
|
// way to measure text height with canvas.
|
|
|
|
for (var i = 0, l = lines.length; i < l; i++)
|
|
|
|
width = Math.max(width, ctx.measureText(lines[i]).width);
|
|
|
|
ctx.font = prevFont;
|
|
|
|
return width;
|
|
|
|
},
|
2014-03-17 11:41:57 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
2015-04-12 09:23:24 -04:00
|
|
|
* Updates the view if there are changes. Note that when using built-in
|
|
|
|
* event hanlders for interaction, animation and load events, this method is
|
|
|
|
* invoked for you automatically at the end.
|
2014-08-16 13:24:54 -04:00
|
|
|
*
|
2015-04-12 09:23:24 -04:00
|
|
|
* @param {Boolean} [force=false] {@true if the view should be updated even
|
|
|
|
* if no change has happened}
|
|
|
|
* @return {Boolean} {@true if the view was updated}
|
2014-08-16 13:24:54 -04:00
|
|
|
*/
|
2015-04-12 09:23:24 -04:00
|
|
|
update: function(force) {
|
2014-08-16 13:24:54 -04:00
|
|
|
var project = this._project;
|
2015-05-11 13:15:52 -04:00
|
|
|
if (!project || !force && !project._needsUpdate)
|
2014-08-16 13:24:54 -04:00
|
|
|
return false;
|
|
|
|
var ctx = this._context,
|
|
|
|
size = this._viewSize;
|
|
|
|
ctx.clearRect(0, 0, size.width + 1, size.height + 1);
|
|
|
|
project.draw(ctx, this._matrix, this._pixelRatio);
|
|
|
|
project._needsUpdate = false;
|
|
|
|
return true;
|
|
|
|
}
|
2011-11-14 14:18:08 -05:00
|
|
|
});
|
|
|
|
|
2013-11-29 14:26:38 -05:00
|
|
|
/*#*/ if (__options.environment == 'node') {
|
2013-05-28 03:01:55 -04:00
|
|
|
// Node.js based image exporting code.
|
2011-11-14 14:18:08 -05:00
|
|
|
CanvasView.inject(new function() {
|
2014-08-16 13:24:54 -04:00
|
|
|
// Utility function that converts a number to a string with
|
|
|
|
// x amount of padded 0 digits:
|
|
|
|
function toPaddedString(number, length) {
|
|
|
|
var str = number.toString(10);
|
|
|
|
for (var i = 0, l = length - str.length; i < l; i++) {
|
|
|
|
str = '0' + str;
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
2013-06-27 16:49:04 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
var fs = require('fs');
|
2013-06-27 16:49:04 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
return {
|
|
|
|
// DOCS: CanvasView#exportFrames(param);
|
|
|
|
exportFrames: function(param) {
|
|
|
|
param = new Base({
|
|
|
|
fps: 30,
|
|
|
|
prefix: 'frame-',
|
|
|
|
amount: 1
|
|
|
|
}, param);
|
|
|
|
if (!param.directory) {
|
|
|
|
throw new Error('Missing param.directory');
|
|
|
|
}
|
|
|
|
var view = this,
|
|
|
|
count = 0,
|
|
|
|
frameDuration = 1 / param.fps,
|
|
|
|
startTime = Date.now(),
|
|
|
|
lastTime = startTime;
|
2011-11-14 14:18:08 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
// Start exporting frames by exporting the first frame:
|
|
|
|
exportFrame(param);
|
2011-11-14 14:18:08 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
function exportFrame(param) {
|
|
|
|
var filename = param.prefix + toPaddedString(count, 6) + '.png',
|
|
|
|
path = param.directory + '/' + filename;
|
|
|
|
var out = view.exportImage(path, function() {
|
|
|
|
// When the file has been closed, export the next fame:
|
|
|
|
var then = Date.now();
|
|
|
|
if (param.onProgress) {
|
|
|
|
param.onProgress({
|
|
|
|
count: count,
|
|
|
|
amount: param.amount,
|
|
|
|
percentage: Math.round(count / param.amount
|
|
|
|
* 10000) / 100,
|
|
|
|
time: then - startTime,
|
|
|
|
delta: then - lastTime
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lastTime = then;
|
|
|
|
if (count < param.amount) {
|
|
|
|
exportFrame(param);
|
|
|
|
} else {
|
|
|
|
// Call onComplete handler when finished:
|
|
|
|
if (param.onComplete) {
|
|
|
|
param.onComplete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Use new Base() to convert into a Base object, for #toString()
|
2014-10-08 08:57:56 -04:00
|
|
|
view.emit('frame', new Base({
|
2014-08-16 13:24:54 -04:00
|
|
|
delta: frameDuration,
|
|
|
|
time: frameDuration * count,
|
|
|
|
count: count
|
|
|
|
}));
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
},
|
2013-05-08 21:17:23 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
// DOCS: CanvasView#exportImage(path, callback);
|
|
|
|
exportImage: function(path, callback) {
|
|
|
|
this.draw();
|
|
|
|
var out = fs.createWriteStream(path),
|
|
|
|
stream = this._element.createPNGStream();
|
|
|
|
// Pipe the png stream to the write stream:
|
|
|
|
stream.pipe(out);
|
|
|
|
if (callback) {
|
|
|
|
out.on('close', callback);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
};
|
2011-11-14 14:18:08 -05:00
|
|
|
});
|
2013-11-29 14:26:38 -05:00
|
|
|
/*#*/ } // __options.environment == 'node'
|