mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
deploy dev version
This commit is contained in:
22
dev/js/rendering/AbstractRenderer.js
Normal file
22
dev/js/rendering/AbstractRenderer.js
Normal file
@@ -0,0 +1,22 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
ns.AbstractRenderer = function () {};
|
||||
|
||||
ns.AbstractRenderer.prototype.clear = Constants.ABSTRACT_FUNCTION;
|
||||
ns.AbstractRenderer.prototype.render = Constants.ABSTRACT_FUNCTION;
|
||||
|
||||
ns.AbstractRenderer.prototype.getCoordinates = Constants.ABSTRACT_FUNCTION;
|
||||
|
||||
ns.AbstractRenderer.prototype.setGridWidth = Constants.ABSTRACT_FUNCTION;
|
||||
ns.AbstractRenderer.prototype.getGridWidth = Constants.ABSTRACT_FUNCTION;
|
||||
|
||||
ns.AbstractRenderer.prototype.setZoom = Constants.ABSTRACT_FUNCTION;
|
||||
ns.AbstractRenderer.prototype.getZoom = Constants.ABSTRACT_FUNCTION;
|
||||
|
||||
ns.AbstractRenderer.prototype.setOffset = Constants.ABSTRACT_FUNCTION;
|
||||
ns.AbstractRenderer.prototype.getOffset = Constants.ABSTRACT_FUNCTION;
|
||||
|
||||
ns.AbstractRenderer.prototype.setDisplaySize = Constants.ABSTRACT_FUNCTION;
|
||||
ns.AbstractRenderer.prototype.getDisplaySize = Constants.ABSTRACT_FUNCTION;
|
||||
})();
|
||||
45
dev/js/rendering/CanvasRenderer.js
Normal file
45
dev/js/rendering/CanvasRenderer.js
Normal file
@@ -0,0 +1,45 @@
|
||||
(function () {
|
||||
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
ns.CanvasRenderer = function (frame, zoom) {
|
||||
this.frame = frame;
|
||||
this.zoom = zoom;
|
||||
this.opacity_ = 1;
|
||||
this.transparentColor_ = 'white';
|
||||
};
|
||||
|
||||
/**
|
||||
* Decide which color should be used to represent transparent pixels
|
||||
* Default : white
|
||||
* @param {String} color the color to use either as '#ABCDEF' or 'red' or 'rgb(x,y,z)' or 'rgba(x,y,z,a)'
|
||||
*/
|
||||
ns.CanvasRenderer.prototype.drawTransparentAs = function (color) {
|
||||
this.transparentColor_ = color;
|
||||
};
|
||||
|
||||
ns.CanvasRenderer.prototype.setOpacity = function (opacity) {
|
||||
this.opacity_ = opacity;
|
||||
};
|
||||
|
||||
ns.CanvasRenderer.prototype.render = function () {
|
||||
var canvas = this.createCanvas_();
|
||||
|
||||
// Draw in canvas
|
||||
pskl.utils.FrameUtils.drawToCanvas(this.frame, canvas, this.transparentColor_, this.opacity_);
|
||||
|
||||
var scaledCanvas = this.createCanvas_(this.zoom);
|
||||
var scaledContext = scaledCanvas.getContext('2d');
|
||||
pskl.utils.CanvasUtils.disableImageSmoothing(scaledCanvas);
|
||||
scaledContext.scale(this.zoom, this.zoom);
|
||||
scaledContext.drawImage(canvas, 0, 0);
|
||||
|
||||
return scaledCanvas;
|
||||
};
|
||||
|
||||
ns.CanvasRenderer.prototype.createCanvas_ = function (zoom) {
|
||||
zoom = zoom || 1;
|
||||
var width = this.frame.getWidth() * zoom;
|
||||
var height = this.frame.getHeight() * zoom;
|
||||
return pskl.utils.CanvasUtils.createCanvas(width, height);
|
||||
};
|
||||
})();
|
||||
68
dev/js/rendering/CompositeRenderer.js
Normal file
68
dev/js/rendering/CompositeRenderer.js
Normal file
@@ -0,0 +1,68 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
ns.CompositeRenderer = function () {
|
||||
this.renderers = [];
|
||||
};
|
||||
|
||||
pskl.utils.inherit(pskl.rendering.CompositeRenderer, pskl.rendering.AbstractRenderer);
|
||||
|
||||
ns.CompositeRenderer.prototype.add = function (renderer) {
|
||||
this.renderers.push(renderer);
|
||||
return this;
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.clear = function () {
|
||||
this.renderers.forEach(function (renderer) {
|
||||
renderer.clear();
|
||||
});
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.setZoom = function (zoom) {
|
||||
this.renderers.forEach(function (renderer) {
|
||||
renderer.setZoom(zoom);
|
||||
});
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.getZoom = function () {
|
||||
return this.getSampleRenderer_().getZoom();
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.setDisplaySize = function (w, h) {
|
||||
this.renderers.forEach(function (renderer) {
|
||||
renderer.setDisplaySize(w, h);
|
||||
});
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.getDisplaySize = function () {
|
||||
return this.getSampleRenderer_().getDisplaySize();
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.setOffset = function (x, y) {
|
||||
this.renderers.forEach(function (renderer) {
|
||||
renderer.setOffset(x, y);
|
||||
});
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.getOffset = function () {
|
||||
return this.getSampleRenderer_().getOffset();
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.setGridWidth = function (b) {
|
||||
this.renderers.forEach(function (renderer) {
|
||||
renderer.setGridWidth(b);
|
||||
});
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.getGridWidth = function () {
|
||||
return this.getSampleRenderer_().getGridWidth();
|
||||
};
|
||||
|
||||
ns.CompositeRenderer.prototype.getSampleRenderer_ = function () {
|
||||
if (this.renderers.length > 0) {
|
||||
return this.renderers[0];
|
||||
} else {
|
||||
throw 'Renderer manager is empty';
|
||||
}
|
||||
};
|
||||
})();
|
||||
63
dev/js/rendering/DrawingLoop.js
Normal file
63
dev/js/rendering/DrawingLoop.js
Normal file
@@ -0,0 +1,63 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
ns.DrawingLoop = function () {
|
||||
this.requestAnimationFrame = this.getRequestAnimationFrameShim_();
|
||||
this.isRunning = false;
|
||||
this.previousTime = 0;
|
||||
this.callbacks = [];
|
||||
this.loop_ = this.loop_.bind(this);
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.addCallback = function (callback, scope, args) {
|
||||
var callbackObj = {
|
||||
fn : callback,
|
||||
scope : scope,
|
||||
args : args
|
||||
};
|
||||
|
||||
this.callbacks.push(callbackObj);
|
||||
return callbackObj;
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.removeCallback = function (callbackObj) {
|
||||
var index = this.callbacks.indexOf(callbackObj);
|
||||
if (index != -1) {
|
||||
this.callbacks.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.start = function () {
|
||||
this.isRunning = true;
|
||||
this.loop_();
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.loop_ = function () {
|
||||
var currentTime = Date.now();
|
||||
var delta = currentTime - this.previousTime;
|
||||
this.executeCallbacks_(delta);
|
||||
this.previousTime = currentTime;
|
||||
this.requestAnimationFrame.call(window, this.loop_);
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.executeCallbacks_ = function (deltaTime) {
|
||||
for (var i = 0 ; i < this.callbacks.length ; i++) {
|
||||
var cb = this.callbacks[i];
|
||||
cb.fn.call(cb.scope, deltaTime, cb.args);
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.stop = function () {
|
||||
this.isRunning = false;
|
||||
};
|
||||
|
||||
ns.DrawingLoop.prototype.getRequestAnimationFrameShim_ = function () {
|
||||
var requestAnimationFrame = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function (callback) { window.setTimeout(callback, 1000 / 60); };
|
||||
|
||||
return requestAnimationFrame;
|
||||
};
|
||||
})();
|
||||
46
dev/js/rendering/FramesheetRenderer.js
Normal file
46
dev/js/rendering/FramesheetRenderer.js
Normal file
@@ -0,0 +1,46 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
/**
|
||||
* Render an array of frames
|
||||
* @param {Array.<pskl.model.Frame>} frames
|
||||
*/
|
||||
ns.FramesheetRenderer = function (frames) {
|
||||
if (frames.length > 0) {
|
||||
this.frames = frames;
|
||||
} else {
|
||||
throw 'FramesheetRenderer : Invalid argument : frames is empty';
|
||||
}
|
||||
};
|
||||
|
||||
ns.FramesheetRenderer.prototype.renderAsCanvas = function (columns) {
|
||||
columns = columns || this.frames.length;
|
||||
var rows = Math.ceil(this.frames.length / columns);
|
||||
|
||||
var canvas = this.createCanvas_(columns, rows);
|
||||
|
||||
for (var i = 0 ; i < this.frames.length ; i++) {
|
||||
var frame = this.frames[i];
|
||||
var posX = (i % columns) * frame.getWidth();
|
||||
var posY = Math.floor(i / columns) * frame.getHeight();
|
||||
this.drawFrameInCanvas_(frame, canvas, posX, posY);
|
||||
}
|
||||
return canvas;
|
||||
};
|
||||
|
||||
ns.FramesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) {
|
||||
var context = canvas.getContext('2d');
|
||||
var imageData = context.createImageData(frame.getWidth(), frame.getHeight());
|
||||
var pixels = frame.getPixels();
|
||||
var data = new Uint8ClampedArray(pixels.buffer);
|
||||
imageData.data.set(data);
|
||||
context.putImageData(imageData, offsetWidth, offsetHeight);
|
||||
};
|
||||
|
||||
ns.FramesheetRenderer.prototype.createCanvas_ = function (columns, rows) {
|
||||
var sampleFrame = this.frames[0];
|
||||
var width = columns * sampleFrame.getWidth();
|
||||
var height = rows * sampleFrame.getHeight();
|
||||
return pskl.utils.CanvasUtils.createCanvas(width, height);
|
||||
};
|
||||
})();
|
||||
90
dev/js/rendering/OnionSkinRenderer.js
Normal file
90
dev/js/rendering/OnionSkinRenderer.js
Normal file
@@ -0,0 +1,90 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
ns.OnionSkinRenderer = function (renderer, piskelController) {
|
||||
pskl.rendering.CompositeRenderer.call(this);
|
||||
|
||||
this.piskelController = piskelController;
|
||||
this.renderer = renderer;
|
||||
this.add(this.renderer);
|
||||
|
||||
this.hash = '';
|
||||
};
|
||||
|
||||
ns.OnionSkinRenderer.createInContainer = function (container, renderingOptions, piskelController) {
|
||||
// Do not use CachedFrameRenderers here, caching is performed in the render method
|
||||
var renderer = new pskl.rendering.frame.FrameRenderer(container, renderingOptions, ['onion-skin-canvas']);
|
||||
return new ns.OnionSkinRenderer(renderer, piskelController);
|
||||
};
|
||||
|
||||
pskl.utils.inherit(pskl.rendering.OnionSkinRenderer, pskl.rendering.CompositeRenderer);
|
||||
|
||||
ns.OnionSkinRenderer.prototype.render = function () {
|
||||
var frames = this.getOnionFrames_();
|
||||
var hash = this.computeHash_(frames);
|
||||
if (this.hash != hash) {
|
||||
this.hash = hash;
|
||||
this.clear();
|
||||
if (frames.length > 0) {
|
||||
var mergedFrame = pskl.utils.FrameUtils.merge(frames);
|
||||
this.renderer.render(mergedFrame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ns.OnionSkinRenderer.prototype.getOnionFrames_ = function () {
|
||||
var frames = [];
|
||||
|
||||
var currentFrameIndex = this.piskelController.getCurrentFrameIndex();
|
||||
var layer = this.piskelController.getCurrentLayer();
|
||||
|
||||
var previousIndex = currentFrameIndex - 1;
|
||||
var previousFrame = layer.getFrameAt(previousIndex);
|
||||
if (previousFrame) {
|
||||
frames.push(previousFrame);
|
||||
}
|
||||
|
||||
var nextIndex = currentFrameIndex + 1;
|
||||
var nextFrame = layer.getFrameAt(nextIndex);
|
||||
if (nextFrame) {
|
||||
frames.push(nextFrame);
|
||||
}
|
||||
|
||||
return frames;
|
||||
};
|
||||
|
||||
ns.OnionSkinRenderer.prototype.computeHash_ = function (frames) {
|
||||
var offset = this.getOffset();
|
||||
var size = this.getDisplaySize();
|
||||
var layers = this.piskelController.getLayers();
|
||||
return [
|
||||
this.getZoom(),
|
||||
this.getGridWidth(),
|
||||
offset.x,
|
||||
offset.y,
|
||||
size.width,
|
||||
size.height,
|
||||
frames.map(function (f) {
|
||||
return f.getHash();
|
||||
}).join('-'),
|
||||
layers.length
|
||||
].join('-');
|
||||
};
|
||||
|
||||
/**
|
||||
* See @pskl.rendering.frame.CachedFrameRenderer
|
||||
* Same issue : FrameRenderer setDisplaySize destroys the canvas
|
||||
* @param {Number} width
|
||||
* @param {Number} height
|
||||
*/
|
||||
ns.OnionSkinRenderer.prototype.setDisplaySize = function (width, height) {
|
||||
var size = this.getDisplaySize();
|
||||
if (size.width !== width || size.height !== height) {
|
||||
this.superclass.setDisplaySize.call(this, width, height);
|
||||
}
|
||||
};
|
||||
|
||||
ns.OnionSkinRenderer.prototype.flush = function () {
|
||||
this.hash = '';
|
||||
};
|
||||
})();
|
||||
39
dev/js/rendering/PiskelRenderer.js
Normal file
39
dev/js/rendering/PiskelRenderer.js
Normal file
@@ -0,0 +1,39 @@
|
||||
(function () {
|
||||
|
||||
var ns = $.namespace('pskl.rendering');
|
||||
|
||||
ns.PiskelRenderer = function (piskelController) {
|
||||
var frames = [];
|
||||
for (var i = 0 ; i < piskelController.getFrameCount() ; i++) {
|
||||
frames.push(piskelController.renderFrameAt(i, true));
|
||||
}
|
||||
this.piskelController = piskelController;
|
||||
this.frames = frames;
|
||||
};
|
||||
|
||||
ns.PiskelRenderer.prototype.renderAsCanvas = function (columns) {
|
||||
columns = columns || this.frames.length;
|
||||
var rows = Math.ceil(this.frames.length / columns);
|
||||
|
||||
var canvas = this.createCanvas_(columns, rows);
|
||||
|
||||
for (var i = 0 ; i < this.frames.length ; i++) {
|
||||
var frame = this.frames[i];
|
||||
var posX = (i % columns) * this.piskelController.getWidth();
|
||||
var posY = Math.floor(i / columns) * this.piskelController.getHeight();
|
||||
this.drawFrameInCanvas_(frame, canvas, posX, posY);
|
||||
}
|
||||
return canvas;
|
||||
};
|
||||
|
||||
ns.PiskelRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) {
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(frame, offsetWidth, offsetHeight, frame.width, frame.height);
|
||||
};
|
||||
|
||||
ns.PiskelRenderer.prototype.createCanvas_ = function (columns, rows) {
|
||||
var width = columns * this.piskelController.getWidth();
|
||||
var height = rows * this.piskelController.getHeight();
|
||||
return pskl.utils.CanvasUtils.createCanvas(width, height);
|
||||
};
|
||||
})();
|
||||
56
dev/js/rendering/frame/BackgroundImageFrameRenderer.js
Normal file
56
dev/js/rendering/frame/BackgroundImageFrameRenderer.js
Normal file
@@ -0,0 +1,56 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering.frame');
|
||||
|
||||
ns.BackgroundImageFrameRenderer = function (container, zoom) {
|
||||
this.container = container;
|
||||
this.setZoom(zoom);
|
||||
|
||||
var containerEl = container.get(0);
|
||||
var containerDocument = containerEl.ownerDocument;
|
||||
this.frameContainer = containerDocument.createElement('div');
|
||||
this.frameContainer.classList.add('background-image-frame-container');
|
||||
container.get(0).appendChild(this.frameContainer);
|
||||
|
||||
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
|
||||
this.cachedFrameProcessor.setFrameProcessor(this.frameToDataUrl_.bind(this));
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.frameToDataUrl_ = function (frame) {
|
||||
var canvas;
|
||||
if (frame instanceof pskl.model.frame.RenderedFrame) {
|
||||
canvas = pskl.utils.ImageResizer.scale(frame.getRenderedFrame(), this.zoom);
|
||||
} else {
|
||||
canvas = pskl.utils.FrameUtils.toImage(frame, this.zoom);
|
||||
}
|
||||
return canvas.toDataURL('image/png');
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.render = function (frame) {
|
||||
var imageSrc = this.cachedFrameProcessor.get(frame, this.zoom);
|
||||
this.frameContainer.style.backgroundImage = 'url(' + imageSrc + ')';
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.show = function () {
|
||||
if (this.frameContainer) {
|
||||
this.frameContainer.style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.setZoom = function (zoom) {
|
||||
this.zoom = zoom;
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.getZoom = function () {
|
||||
return this.zoom;
|
||||
};
|
||||
|
||||
ns.BackgroundImageFrameRenderer.prototype.setRepeated = function (repeat) {
|
||||
var repeatValue;
|
||||
if (repeat) {
|
||||
repeatValue = 'repeat';
|
||||
} else {
|
||||
repeatValue = 'no-repeat';
|
||||
}
|
||||
this.frameContainer.style.backgroundRepeat = repeatValue;
|
||||
};
|
||||
})();
|
||||
49
dev/js/rendering/frame/CachedFrameRenderer.js
Normal file
49
dev/js/rendering/frame/CachedFrameRenderer.js
Normal file
@@ -0,0 +1,49 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering.frame');
|
||||
|
||||
/**
|
||||
* FrameRenderer implementation that prevents unnecessary redraws.
|
||||
* @param {HtmlElement} container HtmlElement to use as parentNode of the Frame
|
||||
* @param {Object} renderingOptions
|
||||
* @param {Array} classList array of strings to use for css classes
|
||||
*/
|
||||
ns.CachedFrameRenderer = function (container, renderingOptions, classList) {
|
||||
pskl.rendering.frame.FrameRenderer.call(this, container, renderingOptions, classList);
|
||||
this.serializedFrame = '';
|
||||
};
|
||||
|
||||
pskl.utils.inherit(pskl.rendering.frame.CachedFrameRenderer, pskl.rendering.frame.FrameRenderer);
|
||||
|
||||
/**
|
||||
* Only call display size if provided values are different from current values.
|
||||
* FrameRenderer:setDisplaySize destroys the underlying canvas
|
||||
* If the canvas is destroyed, a rendering is mandatory.
|
||||
* (Alternatively we could find a way to force the rendering of the CachedFrameRenderer from the outside)
|
||||
* @param {Number} width
|
||||
* @param {Number} height
|
||||
*/
|
||||
ns.CachedFrameRenderer.prototype.setDisplaySize = function (width, height) {
|
||||
if (this.displayWidth !== width || this.displayHeight !== height) {
|
||||
this.superclass.setDisplaySize.call(this, width, height);
|
||||
}
|
||||
};
|
||||
|
||||
ns.CachedFrameRenderer.prototype.render = function (frame) {
|
||||
var offset = this.getOffset();
|
||||
var size = this.getDisplaySize();
|
||||
var serializedFrame = [
|
||||
this.getZoom(),
|
||||
this.getGridWidth(),
|
||||
pskl.UserSettings.get('SEAMLESS_MODE'),
|
||||
pskl.UserSettings.get('SEAMLESS_OPACITY'),
|
||||
offset.x, offset.y,
|
||||
size.width, size.height,
|
||||
frame.getHash()
|
||||
].join('-');
|
||||
|
||||
if (this.serializedFrame != serializedFrame) {
|
||||
this.serializedFrame = serializedFrame;
|
||||
this.superclass.render.call(this, frame);
|
||||
}
|
||||
};
|
||||
})();
|
||||
320
dev/js/rendering/frame/FrameRenderer.js
Normal file
320
dev/js/rendering/frame/FrameRenderer.js
Normal file
@@ -0,0 +1,320 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering.frame');
|
||||
|
||||
/**
|
||||
* FrameRenderer will display a given frame inside a canvas element.
|
||||
* @param {HtmlElement} container HtmlElement to use as parentNode of the Frame
|
||||
* @param {Object} renderingOptions
|
||||
* @param {Array} classList array of strings to use for css classList
|
||||
*/
|
||||
ns.FrameRenderer = function (container, renderingOptions, classList) {
|
||||
this.defaultRenderingOptions = {
|
||||
'supportGridRendering' : false,
|
||||
'zoom' : 1
|
||||
};
|
||||
|
||||
renderingOptions = $.extend(true, {}, this.defaultRenderingOptions, renderingOptions);
|
||||
|
||||
if (container === undefined) {
|
||||
throw 'Bad FrameRenderer initialization. <container> undefined.';
|
||||
}
|
||||
|
||||
if (isNaN(renderingOptions.zoom)) {
|
||||
throw 'Bad FrameRenderer initialization. <zoom> not well defined.';
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
|
||||
this.zoom = renderingOptions.zoom;
|
||||
|
||||
this.offset = {
|
||||
x : 0,
|
||||
y : 0
|
||||
};
|
||||
|
||||
this.margin = {
|
||||
x : 0,
|
||||
y : 0
|
||||
};
|
||||
|
||||
this.supportGridRendering = renderingOptions.supportGridRendering;
|
||||
|
||||
this.classList = classList || [];
|
||||
this.classList.push('canvas');
|
||||
|
||||
/**
|
||||
* Off dom canvas, will be used to draw the frame at 1:1 ratio
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
this.canvas = null;
|
||||
|
||||
/**
|
||||
* Displayed canvas, scaled-up from the offdom canvas
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
this.displayCanvas = null;
|
||||
this.setDisplaySize(renderingOptions.width, renderingOptions.height);
|
||||
|
||||
this.setGridWidth(pskl.UserSettings.get(pskl.UserSettings.GRID_WIDTH));
|
||||
|
||||
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
|
||||
};
|
||||
|
||||
pskl.utils.inherit(pskl.rendering.frame.FrameRenderer, pskl.rendering.AbstractRenderer);
|
||||
|
||||
ns.FrameRenderer.prototype.render = function (frame) {
|
||||
if (frame) {
|
||||
this.clear();
|
||||
this.renderFrame_(frame);
|
||||
}
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.clear = function () {
|
||||
pskl.utils.CanvasUtils.clear(this.canvas);
|
||||
pskl.utils.CanvasUtils.clear(this.displayCanvas);
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.setZoom = function (zoom) {
|
||||
// Minimum zoom is one to ensure one sprite pixel occupies at least one pixel on screen.
|
||||
var minimumZoom = 1;
|
||||
// Maximum zoom is relative to the display dimensions to ensure at least 10 pixels can
|
||||
// be drawn on screen.
|
||||
var maximumZoom = Math.min(this.displayWidth, this.displayHeight) / 10;
|
||||
zoom = pskl.utils.Math.minmax(zoom, minimumZoom, maximumZoom);
|
||||
|
||||
if (zoom == this.zoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
// back up center coordinates
|
||||
var centerX = this.offset.x + (this.displayWidth / (2 * this.zoom));
|
||||
var centerY = this.offset.y + (this.displayHeight / (2 * this.zoom));
|
||||
|
||||
this.zoom = zoom;
|
||||
// recenter
|
||||
this.setOffset(
|
||||
centerX - (this.displayWidth / (2 * this.zoom)),
|
||||
centerY - (this.displayHeight / (2 * this.zoom))
|
||||
);
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.getZoom = function () {
|
||||
return this.zoom;
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.setDisplaySize = function (width, height) {
|
||||
this.displayWidth = width;
|
||||
this.displayHeight = height;
|
||||
if (this.displayCanvas) {
|
||||
$(this.displayCanvas).remove();
|
||||
this.displayCanvas = null;
|
||||
}
|
||||
this.createDisplayCanvas_();
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.getDisplaySize = function () {
|
||||
return {
|
||||
height : this.displayHeight,
|
||||
width : this.displayWidth
|
||||
};
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.getOffset = function () {
|
||||
return {
|
||||
x : this.offset.x,
|
||||
y : this.offset.y
|
||||
};
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.setOffset = function (x, y) {
|
||||
var width = pskl.app.piskelController.getWidth();
|
||||
var height = pskl.app.piskelController.getHeight();
|
||||
var maxX = width - (this.displayWidth / this.zoom);
|
||||
x = pskl.utils.Math.minmax(x, 0, maxX);
|
||||
var maxY = height - (this.displayHeight / this.zoom);
|
||||
y = pskl.utils.Math.minmax(y, 0, maxY);
|
||||
|
||||
this.offset.x = x;
|
||||
this.offset.y = y;
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.setGridWidth = function (value) {
|
||||
this.gridWidth_ = value;
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.getGridWidth = function () {
|
||||
if (!this.supportGridRendering) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.gridWidth_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute a grid width value best suited to the current display context,
|
||||
* particularly for the current zoom level
|
||||
*/
|
||||
ns.FrameRenderer.prototype.computeGridWidthForDisplay_ = function () {
|
||||
var gridWidth = this.getGridWidth();
|
||||
while (this.zoom < 6 * gridWidth) {
|
||||
gridWidth--;
|
||||
}
|
||||
return gridWidth;
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.updateMargins_ = function (frame) {
|
||||
var deltaX = this.displayWidth - (this.zoom * frame.getWidth());
|
||||
this.margin.x = Math.max(0, deltaX) / 2;
|
||||
|
||||
var deltaY = this.displayHeight - (this.zoom * frame.getHeight());
|
||||
this.margin.y = Math.max(0, deltaY) / 2;
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.createDisplayCanvas_ = function () {
|
||||
var height = this.displayHeight;
|
||||
var width = this.displayWidth;
|
||||
|
||||
this.displayCanvas = pskl.utils.CanvasUtils.createCanvas(width, height, this.classList);
|
||||
pskl.utils.CanvasUtils.disableImageSmoothing(this.displayCanvas);
|
||||
this.container.append(this.displayCanvas);
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
|
||||
if (settingName == pskl.UserSettings.GRID_WIDTH) {
|
||||
this.setGridWidth(settingValue);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform a screen pixel-based coordinate (relative to the top-left corner of the rendered
|
||||
* frame) into a sprite coordinate in column and row.
|
||||
* @public
|
||||
*/
|
||||
ns.FrameRenderer.prototype.getCoordinates = function(x, y) {
|
||||
var containerOffset = this.container.offset();
|
||||
x = x - containerOffset.left;
|
||||
y = y - containerOffset.top;
|
||||
|
||||
// apply margins
|
||||
x = x - this.margin.x;
|
||||
y = y - this.margin.y;
|
||||
|
||||
var cellSize = this.zoom;
|
||||
// apply frame offset
|
||||
x = x + this.offset.x * cellSize;
|
||||
y = y + this.offset.y * cellSize;
|
||||
|
||||
return {
|
||||
x : Math.floor(x / cellSize),
|
||||
y : Math.floor(y / cellSize)
|
||||
};
|
||||
};
|
||||
|
||||
ns.FrameRenderer.prototype.reverseCoordinates = function(x, y) {
|
||||
var cellSize = this.zoom;
|
||||
|
||||
x = x * cellSize;
|
||||
y = y * cellSize;
|
||||
|
||||
x = x - this.offset.x * cellSize;
|
||||
y = y - this.offset.y * cellSize;
|
||||
|
||||
x = x + this.margin.x;
|
||||
y = y + this.margin.y;
|
||||
|
||||
var containerOffset = this.container.offset();
|
||||
x = x + containerOffset.left;
|
||||
y = y + containerOffset.top;
|
||||
|
||||
return {
|
||||
x : x + (cellSize / 2),
|
||||
y : y + (cellSize / 2)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ns.FrameRenderer.prototype.renderFrame_ = function (frame) {
|
||||
if (!this.canvas || frame.getWidth() != this.canvas.width || frame.getHeight() != this.canvas.height) {
|
||||
this.canvas = pskl.utils.CanvasUtils.createCanvas(frame.getWidth(), frame.getHeight());
|
||||
}
|
||||
|
||||
var w = this.canvas.width;
|
||||
var h = this.canvas.height;
|
||||
var z = this.zoom;
|
||||
|
||||
// Draw in canvas
|
||||
pskl.utils.FrameUtils.drawToCanvas(frame, this.canvas);
|
||||
|
||||
this.updateMargins_(frame);
|
||||
|
||||
var displayContext = this.displayCanvas.getContext('2d');
|
||||
displayContext.save();
|
||||
|
||||
var translateX = this.margin.x - this.offset.x * z;
|
||||
var translateY = this.margin.y - this.offset.y * z;
|
||||
|
||||
var isZoomedOut = translateX > 0 || translateY > 0;
|
||||
// Draw the background / zoomed-out color only if needed. Otherwise the clearRect
|
||||
// happening after that will clear "out of bounds" and seems to be doing nothing
|
||||
// on some chromebooks (cf https://github.com/piskelapp/piskel/issues/651)
|
||||
if (isZoomedOut) {
|
||||
// Draw background
|
||||
displayContext.fillStyle = Constants.ZOOMED_OUT_BACKGROUND_COLOR;
|
||||
// The -1 on the width and height here is a workaround for a Chrome-only bug
|
||||
// that was potentially fixed, but is very hardware dependant. Seems to be
|
||||
// triggered when doing clear rect or fill rect using the full width & height
|
||||
// of a canvas. (https://bugs.chromium.org/p/chromium/issues/detail?id=469906)
|
||||
displayContext.fillRect(0, 0, this.displayCanvas.width - 1, this.displayCanvas.height - 1);
|
||||
}
|
||||
|
||||
displayContext.translate(translateX, translateY);
|
||||
|
||||
// Scale up to draw the canvas content
|
||||
displayContext.scale(z, z);
|
||||
|
||||
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
|
||||
displayContext.clearRect(-1 * w, -1 * h, 3 * w, 3 * h);
|
||||
} else {
|
||||
displayContext.clearRect(0, 0, w, h);
|
||||
}
|
||||
|
||||
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
|
||||
this.drawTiledFrames_(displayContext, this.canvas, w, h, 1);
|
||||
}
|
||||
displayContext.drawImage(this.canvas, 0, 0);
|
||||
|
||||
// Draw grid.
|
||||
var gridWidth = this.computeGridWidthForDisplay_();
|
||||
if (gridWidth > 0) {
|
||||
// Scale out before drawing the grid.
|
||||
displayContext.scale(1 / z, 1 / z);
|
||||
// Clear vertical lines.
|
||||
for (var i = 1 ; i < frame.getWidth() ; i++) {
|
||||
displayContext.clearRect((i * z) - (gridWidth / 2), 0, gridWidth, h * z);
|
||||
}
|
||||
// Clear horizontal lines.
|
||||
for (var j = 1 ; j < frame.getHeight() ; j++) {
|
||||
displayContext.clearRect(0, (j * z) - (gridWidth / 2), w * z, gridWidth);
|
||||
}
|
||||
}
|
||||
|
||||
displayContext.restore();
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw repeatedly the provided image around the main drawing area. Used for seamless
|
||||
* drawing mode, to easily create seamless textures. A colored overlay is applied to
|
||||
* differentiate those additional frames from the main frame.
|
||||
*/
|
||||
ns.FrameRenderer.prototype.drawTiledFrames_ = function (context, image, w, h, z) {
|
||||
var opacity = pskl.UserSettings.get('SEAMLESS_OPACITY');
|
||||
opacity = pskl.utils.Math.minmax(opacity, 0, 1);
|
||||
context.fillStyle = 'rgba(255, 255, 255, ' + opacity + ')';
|
||||
[[0, -1], [0, 1], [-1, -1], [-1, 0], [-1, 1], [1, -1], [1, 0], [1, 1]].forEach(function (d) {
|
||||
context.drawImage(image, d[0] * w * z, d[1] * h * z);
|
||||
context.fillRect(d[0] * w * z, d[1] * h * z, w * z, h * z);
|
||||
});
|
||||
};
|
||||
})();
|
||||
96
dev/js/rendering/layer/LayersRenderer.js
Normal file
96
dev/js/rendering/layer/LayersRenderer.js
Normal file
@@ -0,0 +1,96 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.rendering.layer');
|
||||
|
||||
ns.LayersRenderer = function (container, renderingOptions, piskelController) {
|
||||
pskl.rendering.CompositeRenderer.call(this);
|
||||
|
||||
this.piskelController = piskelController;
|
||||
|
||||
// Do not use CachedFrameRenderers here, since the caching will be performed in the render method of LayersRenderer
|
||||
this.belowRenderer = new pskl.rendering.frame.FrameRenderer(container, renderingOptions,
|
||||
['layers-canvas', 'layers-below-canvas']);
|
||||
|
||||
this.aboveRenderer = new pskl.rendering.frame.FrameRenderer(container, renderingOptions,
|
||||
['layers-canvas', 'layers-above-canvas']);
|
||||
|
||||
this.add(this.belowRenderer);
|
||||
this.add(this.aboveRenderer);
|
||||
|
||||
this.serializedRendering = '';
|
||||
|
||||
this.stylesheet_ = document.createElement('style');
|
||||
document.head.appendChild(this.stylesheet_);
|
||||
this.updateLayersCanvasOpacity_(pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY));
|
||||
|
||||
$.subscribe(Events.PISKEL_RESET, this.flush.bind(this));
|
||||
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
|
||||
};
|
||||
|
||||
pskl.utils.inherit(pskl.rendering.layer.LayersRenderer, pskl.rendering.CompositeRenderer);
|
||||
|
||||
ns.LayersRenderer.prototype.render = function () {
|
||||
var offset = this.getOffset();
|
||||
var size = this.getDisplaySize();
|
||||
var layers = this.piskelController.getLayers();
|
||||
var frameIndex = this.piskelController.getCurrentFrameIndex();
|
||||
var layerIndex = this.piskelController.getCurrentLayerIndex();
|
||||
|
||||
var belowLayers = layers.slice(0, layerIndex);
|
||||
var aboveLayers = layers.slice(layerIndex + 1, layers.length);
|
||||
|
||||
var serializedRendering = [
|
||||
this.getZoom(),
|
||||
this.getGridWidth(),
|
||||
offset.x,
|
||||
offset.y,
|
||||
size.width,
|
||||
size.height,
|
||||
pskl.utils.LayerUtils.getFrameHashAt(belowLayers, frameIndex),
|
||||
pskl.utils.LayerUtils.getFrameHashAt(aboveLayers, frameIndex),
|
||||
layers.length
|
||||
].join('-');
|
||||
|
||||
if (this.serializedRendering != serializedRendering) {
|
||||
this.serializedRendering = serializedRendering;
|
||||
|
||||
this.clear();
|
||||
|
||||
if (belowLayers.length > 0) {
|
||||
var belowFrame = pskl.utils.LayerUtils.mergeFrameAt(belowLayers, frameIndex);
|
||||
this.belowRenderer.render(belowFrame);
|
||||
}
|
||||
|
||||
if (aboveLayers.length > 0) {
|
||||
var aboveFrame = pskl.utils.LayerUtils.mergeFrameAt(aboveLayers, frameIndex);
|
||||
this.aboveRenderer.render(aboveFrame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* See @pskl.rendering.frame.CachedFrameRenderer
|
||||
* Same issue : FrameRenderer setDisplaySize destroys the canvas
|
||||
* @param {Number} width
|
||||
* @param {Number} height
|
||||
*/
|
||||
ns.LayersRenderer.prototype.setDisplaySize = function (width, height) {
|
||||
var size = this.getDisplaySize();
|
||||
if (size.width !== width || size.height !== height) {
|
||||
this.superclass.setDisplaySize.call(this, width, height);
|
||||
}
|
||||
};
|
||||
|
||||
ns.LayersRenderer.prototype.onUserSettingsChange_ = function (evt, settingsName, settingsValue) {
|
||||
if (settingsName == pskl.UserSettings.LAYER_OPACITY) {
|
||||
this.updateLayersCanvasOpacity_(settingsValue);
|
||||
}
|
||||
};
|
||||
|
||||
ns.LayersRenderer.prototype.updateLayersCanvasOpacity_ = function (opacity) {
|
||||
this.stylesheet_.innerHTML = '.layers-canvas { opacity : ' + opacity + '}';
|
||||
};
|
||||
|
||||
ns.LayersRenderer.prototype.flush = function () {
|
||||
this.serializedRendering = '';
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user