From 0c5cfd646e1596dc38180ab40a19a6794bef783a Mon Sep 17 00:00:00 2001 From: kzhdev Date: Mon, 3 Feb 2014 09:35:28 -0600 Subject: [PATCH] Fix #750 improve image quality in high resolution devices --- src/Canvas.js | 20 +++++++++++++++----- src/Node.js | 24 ++++++++++++++++-------- src/Stage.js | 2 +- test/unit/Node-test.js | 17 +++++++++++++---- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/Canvas.js b/src/Canvas.js index 6559a29f..fcd76754 100644 --- a/src/Canvas.js +++ b/src/Canvas.js @@ -34,9 +34,8 @@ init: function(config) { config = config || {}; - var pixelRatio = config.pixelRatio || Kinetic.pixelRatio || _pixelRatio; + this.pixelRatio = config.pixelRatio || Kinetic.pixelRatio || _pixelRatio; - this.pixelRatio = pixelRatio; this._canvas = document.createElement('canvas'); // set inline styles @@ -67,10 +66,10 @@ return this.pixelRatio; }, /** - * get pixel ratio + * set pixel ratio * @method * @memberof Kinetic.Canvas.prototype - * @param {Number} pixelRatio KineticJS automatically handles pixel ratio adustments in order to render crisp drawings + * @param {Number} pixelRatio KineticJS automatically handles pixel ratio adjustments in order to render crisp drawings * on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios * of 1. Some high end tablets and phones, like iPhones and iPads (not the mini) have a device pixel ratio * of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel @@ -79,8 +78,12 @@ * ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1. */ setPixelRatio: function(pixelRatio) { + // get the original widht and height, take into account pixel ratio + var width = this.getWidth() / this.pixelRatio, + height = this.getHeight() / this.pixelRatio; + this.pixelRatio = pixelRatio; - this.setSize(this.getWidth(), this.getHeight()); + this.setSize(width, height); }, /** * set width @@ -195,7 +198,14 @@ Kinetic.Canvas.call(this, config); this.context = new Kinetic.HitContext(this); this.setSize(width, height); + this.context._context.scale(this.pixelRatio, this.pixelRatio); }; + Kinetic.HitCanvas.prototype = { + setPixelRatio: function(pixelRatio) { + Kinetic.Canvas.prototype.setPixelRatio.call(this, pixelRatio); + this.context._context.scale(pixelRatio, pixelRatio); + } + } Kinetic.Util.extend(Kinetic.HitCanvas, Kinetic.Canvas); })(); diff --git a/src/Node.js b/src/Node.js index 8cd8bff4..2cfffb32 100644 --- a/src/Node.js +++ b/src/Node.js @@ -150,12 +150,10 @@ height = conf.height || this.height(), drawBorder = conf.drawBorder || false, cachedSceneCanvas = new Kinetic.SceneCanvas({ - pixelRatio: 1, width: width, height: height }), cachedFilterCanvas = new Kinetic.SceneCanvas({ - pixelRatio: 1, width: width, height: height }), @@ -204,9 +202,14 @@ return this; }, _drawCachedSceneCanvas: function(context) { + var cachedScenCanvas = this._getCachedSceneCanvas(), + pixelRatio = cachedScenCanvas.getPixelRatio(); context.save(); context._applyTransform(this); - context.drawImage(this._getCachedSceneCanvas()._canvas, 0, 0); + // If canvas pixelRatio not equal to 1, the cached image will be enlarged, + // we need to scale the cached image back to its original size + context.drawImage(cachedScenCanvas._canvas, 0, 0, cachedScenCanvas.getWidth() / pixelRatio, + cachedScenCanvas.getHeight() / pixelRatio); context.restore(); }, _getCachedSceneCanvas: function() { @@ -215,6 +218,7 @@ sceneCanvas = cachedCanvas.scene, filterCanvas = cachedCanvas.filter, filterContext = filterCanvas.getContext(), + pixelRation = sceneCanvas.getPixelRatio(), len, imageData, n, filter; if (filters) { @@ -223,7 +227,8 @@ len = filters.length; filterContext.clear(); // copy cached canvas onto filter context - filterContext.drawImage(sceneCanvas._canvas, 0, 0); + filterContext.drawImage(sceneCanvas._canvas, 0, 0, sceneCanvas.getWidth() / pixelRatio, + sceneCanvas.getHeight() / pixelRatio); imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); // apply filters to filter context @@ -248,12 +253,16 @@ }, _drawCachedHitCanvas: function(context) { var cachedCanvas = this._cache.canvas, - hitCanvas = cachedCanvas.hit; + hitCanvas = cachedCanvas.hit, + pixelRatio = hitCanvas.getPixelRatio(); context.save(); context._applyTransform(this); - context.drawImage(hitCanvas._canvas, 0, 0); - context.restore(); + // If canvas pixelRatio not equal to 1, the cached image will be enlarged, + // we need to scale the cached image back to its original size + context.drawImage(hitCanvas._canvas, 0, 0, hitCanvas.getWidth() / pixelRatio, + hitCanvas.getHeight() / pixelRatio); + context.restore(); }, /** * bind events to the node. KineticJS supports mouseover, mousemove, @@ -1206,7 +1215,6 @@ canvas = new Kinetic.SceneCanvas({ width: config.width || this.getWidth() || (stage ? stage.getWidth() : 0), height: config.height || this.getHeight() || (stage ? stage.getHeight() : 0), - pixelRatio: 1 }), context = canvas.getContext(); diff --git a/src/Stage.js b/src/Stage.js index 557494fe..e07acd86 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -56,6 +56,7 @@ Kinetic.Util.addMethods(Kinetic.Stage, { ___init: function(config) { this.nodeType = STAGE; + Kinetic.pixelRatio = config.pixelRatio || Kinetic.pixelRatio; // call super constructor Kinetic.Container.call(this, config); this._id = Kinetic.idCounter++; @@ -199,7 +200,6 @@ canvas = new Kinetic.SceneCanvas({ width: config.width || this.getWidth(), height: config.height || this.getHeight(), - pixelRatio: 1 }), _context = canvas.getContext()._context, layers = this.children; diff --git a/test/unit/Node-test.js b/test/unit/Node-test.js index 1a44ac7c..3b5daad9 100644 --- a/test/unit/Node-test.js +++ b/test/unit/Node-test.js @@ -1878,8 +1878,11 @@ suite('Node', function() { circle.setOpacity(0.5); layer.add(circle); + layer.getCanvas().setPixelRatio(1); + stage.add(layer); + var sceneTrace = layer.getContext().getTrace(); //console.log(sceneTrace); @@ -2586,6 +2589,7 @@ suite('Node', function() { group.add(circle); layer.add(group); + layer.getCanvas().setPixelRatio(1); stage.add(layer); assert.equal(circle.transformsEnabled(), 'all'); @@ -2608,6 +2612,7 @@ suite('Node', function() { var layer = new Kinetic.Layer({ x: 100 }); + layer.getCanvas().setPixelRatio(1); var group = new Kinetic.Group({ x: 100 }); @@ -2823,6 +2828,7 @@ suite('Node', function() { group.add(circle); layer.add(group); + layer.getCanvas().setPixelRatio(1); stage.add(layer); assert.equal(circle._cache.canvas, undefined); @@ -2851,7 +2857,7 @@ suite('Node', function() { //console.log(layer.getContext().getTrace()); - assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);drawImage([object HTMLCanvasElement],0,0);restore();'); + assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);drawImage([object HTMLCanvasElement],0,0,148,148);restore();'); //console.log(circle._cache.canvas.scene.getContext().getTrace()); @@ -2944,6 +2950,7 @@ suite('Node', function() { test('cache group', function(){ var stage = addStage(); var layer = new Kinetic.Layer(); + layer.getCanvas().setPixelRatio(1); var group = new Kinetic.Group({ x: 100, y: 100, @@ -3008,7 +3015,7 @@ suite('Node', function() { //console.log('---before second draw') layer.draw(); - assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,30);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,170,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,100,170);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,30,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0);restore();'); + assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,30);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,170,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,100,170);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,30,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0,80,80);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0,80,80);restore();'); showHit(layer); }); @@ -3059,6 +3066,7 @@ suite('Node', function() { group.add(top).add(right).add(bottom).add(left); layer.add(group); + layer.getCanvas().setPixelRatio(1); stage.add(layer); //console.log('---before cache') @@ -3085,7 +3093,7 @@ suite('Node', function() { layer.draw(); //console.log(layer.getContext().getTrace()) - assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,147.883,-31.557);beginPath();arc(0,0,50,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,231.557,147.883);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,52.117,231.557);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,-31.557,52.117);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,-24.316,-166.596);drawImage([object HTMLCanvasElement],0,0);restore();clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,-24.316,-166.596);drawImage([object HTMLCanvasElement],0,0);restore();'); + assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,147.883,-31.557);beginPath();arc(0,0,50,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,231.557,147.883);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,52.117,231.557);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1.879,0.684,-0.684,1.879,-31.557,52.117);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,-24.316,-166.596);drawImage([object HTMLCanvasElement],0,0,208,208);restore();clearRect(0,0,578,200);save();transform(1.879,0.684,-0.684,1.879,-24.316,-166.596);drawImage([object HTMLCanvasElement],0,0,208,208);restore();'); showHit(layer); }); @@ -3132,6 +3140,7 @@ suite('Node', function() { group.add(top).add(right).add(bottom).add(left); layer.add(group); + layer.getCanvas().setPixelRatio(1); stage.add(layer); assert.equal(layer._cache.canvas, undefined); @@ -3149,7 +3158,7 @@ suite('Node', function() { layer.draw(); //console.log(layer.getContext().getTrace()); - assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,30);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,170,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,100,170);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,30,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0);restore();'); + assert.equal(layer.getContext().getTrace(), 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,30);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,170,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=red;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,100,170);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=blue;fill();lineWidth=4;strokeStyle=black;stroke();restore();save();transform(1,0,0,1,30,100);beginPath();arc(0,0,30,0,6.283,false);closePath();fillStyle=yellow;fill();lineWidth=4;strokeStyle=black;stroke();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0,80,80);restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);drawImage([object HTMLCanvasElement],0,0,80,80);restore();'); // make sure that the hit graph is also rendered after caching the layer