Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upper canvas retina scaling #5938

Merged
merged 13 commits into from
Dec 28, 2019
17 changes: 11 additions & 6 deletions src/brushes/base_brush.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,18 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
return;
}

var ctx = this.canvas.contextTop,
zoom = this.canvas.getZoom();
var canvas = this.canvas,
shadow = this.shadow,
ctx = canvas.contextTop,
zoom = canvas.getZoom();
if (canvas && canvas._isRetinaScaling()) {
zoom *= fabric.devicePixelRatio;
}

ctx.shadowColor = this.shadow.color;
ctx.shadowBlur = this.shadow.blur * zoom;
ctx.shadowOffsetX = this.shadow.offsetX * zoom;
ctx.shadowOffsetY = this.shadow.offsetY * zoom;
ctx.shadowColor = shadow.color;
ctx.shadowBlur = shadow.blur * zoom;
ctx.shadowOffsetX = shadow.offsetX * zoom;
ctx.shadowOffsetY = shadow.offsetY * zoom;
},

needsFullRender: function() {
Expand Down
26 changes: 17 additions & 9 deletions src/canvas.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,12 @@
pointer = this.restorePointerVpt(pointer);
}

var retinaScaling = this.getRetinaScaling();
if (retinaScaling !== 1) {
pointer.x /= retinaScaling;
pointer.y /= retinaScaling;
}

if (boundsWidth === 0 || boundsHeight === 0) {
// If bounds are not available (i.e. not visible), do not apply scale.
cssScale = { width: 1, height: 1 };
Expand All @@ -1349,22 +1355,24 @@
* @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
*/
_createUpperCanvas: function () {
var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''),
lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl;

// there is no need to create a new upperCanvas element if we have already one.
if (this.upperCanvasEl) {
this.upperCanvasEl.className = '';
if (upperCanvasEl) {
upperCanvasEl.className = '';
}
else {
this.upperCanvasEl = this._createCanvasElement();
upperCanvasEl = this._createCanvasElement();
this.upperCanvasEl = upperCanvasEl;
}
fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
fabric.util.addClass(upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);

this.wrapperEl.appendChild(this.upperCanvasEl);
this.wrapperEl.appendChild(upperCanvasEl);

this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
this._applyCanvasStyle(this.upperCanvasEl);
this.contextTop = this.upperCanvasEl.getContext('2d');
this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl);
this._applyCanvasStyle(upperCanvasEl);
this.contextTop = upperCanvasEl.getContext('2d');
},

/**
Expand Down
14 changes: 11 additions & 3 deletions src/static_canvas.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,20 @@
if (!this._isRetinaScaling()) {
return;
}
this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
var scaleRatio = fabric.devicePixelRatio;
this.__initRetinaScaling(scaleRatio, this.lowerCanvasEl, this.contextContainer);
if (this.upperCanvasEl) {
this.__initRetinaScaling(scaleRatio, this.upperCanvasEl, this.contextTop);
}
},

this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
__initRetinaScaling: function(scaleRatio, canvas, context) {
canvas.setAttribute('width', this.width * scaleRatio);
canvas.setAttribute('height', this.height * scaleRatio);
context.scale(scaleRatio, scaleRatio);
},


/**
* Calculates canvas element offset relative to the document
* This method is also attached as "resize" event handler of window
Expand Down
151 changes: 80 additions & 71 deletions test/unit/brushes.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,88 @@
(function() {
var canvas = new fabric.Canvas();
QUnit.module('fabric.BaseBrush', {
afterEach: function() {
QUnit.module('fabric.BaseBrush', function(hooks) {
hooks.afterEach(function() {
canvas.cancelRequestedRender();
}
});
QUnit.test('fabric brush constructor', function(assert) {
assert.ok(fabric.BaseBrush);
});

var brush = new fabric.BaseBrush();
QUnit.test('fabric brush constructor', function(assert) {
assert.ok(fabric.BaseBrush);

assert.ok(brush instanceof fabric.BaseBrush, 'should inherit from fabric.BaseBrush');
assert.equal(brush.color, 'rgb(0, 0, 0)', 'default color is black');
assert.equal(brush.width, 1, 'default width is 1');
assert.ok(typeof brush.setShadow === 'function', 'setShadow is a method');
});
QUnit.test('fabric pencil brush constructor', function(assert) {
assert.ok(fabric.PencilBrush);
var brush = new fabric.PencilBrush(canvas);
assert.equal(brush.canvas, canvas, 'assigns canvas');
assert.deepEqual(brush._points, [], 'points is an empty array');
});
QUnit.test('fabric pencil brush draw point', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
brush.onMouseDown(pointer, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
});
QUnit.test('fabric pencil brush multiple points', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
assert.equal(brush._points.length, 2, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15});
var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001', 'path data create a complex path');
assert.equal(brush._points.length, 6, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {
var fired = false;
var added = null;
canvas.on('path:created', function(opt) {
fired = true;
added = opt.path;
var brush = new fabric.BaseBrush();

assert.ok(brush instanceof fabric.BaseBrush, 'should inherit from fabric.BaseBrush');
assert.equal(brush.color, 'rgb(0, 0, 0)', 'default color is black');
assert.equal(brush.width, 1, 'default width is 1');
assert.ok(typeof brush.setShadow === 'function', 'setShadow is a method');
});
QUnit.test('fabric pencil brush constructor', function(assert) {
assert.ok(fabric.PencilBrush);
var brush = new fabric.PencilBrush(canvas);
assert.equal(brush.canvas, canvas, 'assigns canvas');
assert.deepEqual(brush._points, [], 'points is an empty array');
});

[true, false].forEach(function(val) {
QUnit.module('fabric.BaseBrush with canvas.enableRetinaScaling = ' + val, function(hooks) {
hooks.beforeEach(function() {
canvas.enableRetinaScaling = val;
});
QUnit.test('fabric pencil brush draw point', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
brush.onMouseDown(pointer, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
});
QUnit.test('fabric pencil brush multiple points', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
assert.equal(brush._points.length, 2, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15});
var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
var pathData = brush.convertPointsToSVGPath(brush._points).join('');
assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001', 'path data create a complex path');
assert.equal(brush._points.length, 6, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {
var fired = false;
var added = null;
canvas.on('path:created', function(opt) {
fired = true;
added = opt.path;
});
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15});
var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseUp({ e: {} });
assert.equal(fired, true, 'event is fired');
assert.ok(added instanceof fabric.Path, 'a path is added');
assert.ok(added.path.length, 6, 'path has 6 steps');
canvas.off();
});
});
});
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15});
var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20});
brush.onMouseDown(pointer, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseUp({ e: {} });
assert.equal(fired, true, 'event is fired');
assert.ok(added instanceof fabric.Path, 'a path is added');
assert.ok(added.path.length, 6, 'path has 6 steps');
canvas.off();
});
})();
Loading