data-canvas
Advanced tools
Comparing version 0.0.0 to 0.1.0
{ | ||
"name": "data-canvas", | ||
"version": "0.0.0", | ||
"version": "0.1.0", | ||
"description": "Improved event handling and testing for the HTML5 canvas", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -1,2 +0,2 @@ | ||
[![Build Status](https://travis-ci.org/hammerlab/data-canvas.svg?branch=travis-tests)](https://travis-ci.org/hammerlab/data-canvas) [![Coverage Status](https://coveralls.io/repos/hammerlab/data-canvas/badge.svg?branch=master&service=github)](https://coveralls.io/github/hammerlab/data-canvas?branch=master) | ||
[![Build Status](https://travis-ci.org/hammerlab/data-canvas.svg?branch=travis-tests)](https://travis-ci.org/hammerlab/data-canvas) [![Coverage Status](https://coveralls.io/repos/hammerlab/data-canvas/badge.svg?branch=master&service=github)](https://coveralls.io/github/hammerlab/data-canvas?branch=master)[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hammerlab/data-canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
data-canvas | ||
@@ -133,2 +133,10 @@ =========== | ||
Here's what the data stack looks like while the rendering happens: | ||
<!-- | ||
Code for generating this image is here: http://jsfiddle.net/7nkbfbkb/6/ | ||
convert -background white -alpha remove -layers OptimizePlus -delay 75 -dispose Background -loop 0 frame*.png data-canvas-stack.gif | ||
--> | ||
<img src="data-canvas-stack.gif" width=400 height=125> | ||
Testing | ||
@@ -135,0 +143,0 @@ ------- |
@@ -108,9 +108,130 @@ /** | ||
}; | ||
var recordingDrawImage = this.drawImage; // plain recording drawImage() | ||
this.drawImage = function(image) { | ||
// If the drawn image has recorded calls, then they need to be transferred over. | ||
var recorder = RecordingContext.recorderForCanvas(image); | ||
if (!recorder) { | ||
recordingDrawImage.apply(ctx, arguments); | ||
} else { | ||
ctx.drawImage.apply(ctx, arguments); | ||
this.calls = this.calls.concat(transformedCalls(recorder.calls, arguments)); | ||
} | ||
} | ||
} | ||
// Transform the calls to a new coordinate system. | ||
// The arguments are those to drawImage(). | ||
function transformedCalls(calls, args) { | ||
var image = args[0], | ||
sx = 0, | ||
sy = 0, | ||
sWidth = image.width, | ||
sHeight = image.height, | ||
dx, | ||
dy, | ||
dWidth = image.width, | ||
dHeight = image.height; | ||
if (args.length == 3) { | ||
// void ctx.drawImage(image, dx, dy); | ||
dx = args[1]; | ||
dy = args[2]; | ||
} else if (args.length == 5) { | ||
// void ctx.drawImage(image, dx, dy, dWidth, dHeight); | ||
dx = args[1]; | ||
dy = args[2]; | ||
dWidth = args[3]; | ||
dHeight = args[4]; | ||
} else if (args.length == 9) { | ||
// void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); | ||
sx = args[1]; | ||
sy = args[2]; | ||
sWidth = args[3]; | ||
sHeight = args[4]; | ||
dx = args[5]; | ||
dy = args[6]; | ||
dWidth = args[7]; | ||
dHeight = args[8]; | ||
} | ||
// Other arities will make the browser throw an error on ctx.drawImage.apply | ||
var xScaling = getScaleFactor(sx, sx + sWidth, dx, dx + dWidth), | ||
xScale = makeScale( sx, sx + sWidth, dx, dx + dWidth), | ||
yScaling = getScaleFactor(sy, sy + sHeight, dy, dy + dHeight), | ||
yScale = makeScale( sy, sy + sHeight, dy, dy + dHeight); | ||
// These calls are more complex: | ||
// arc | ||
// arcTo | ||
// ellipse | ||
// TODO: clip calls outside of the source rectangle. | ||
var transformCall = function(originalCall) { | ||
var call = originalCall.slice(), // make a copy | ||
type = call[0]; | ||
if (type in CALLS_XY) { | ||
var xys = CALLS_XY[type]; | ||
if (typeof(xys) == 'number') xys = [xys]; | ||
xys.forEach(function(pos) { | ||
call[1 + pos] = xScale(call[1 + pos]); | ||
call[2 + pos] = yScale(call[2 + pos]); | ||
}); | ||
} | ||
if (type in CALLS_WH) { | ||
var whs = CALLS_WH[type]; | ||
if (typeof(whs) == 'number') whs = [whs]; | ||
whs.forEach(function(pos) { | ||
call[1 + pos] *= xScaling; | ||
call[2 + pos] *= yScaling; | ||
}); | ||
} | ||
return call; | ||
}; | ||
return calls.map(transformCall); | ||
} | ||
// Helpers for transformedCalls | ||
// Map (x1, x2) --> (y1, y2) | ||
function getScaleFactor(x1, x2, y1, y2) { | ||
return (y2 - y1) / (x2 - x1); | ||
}; | ||
function makeScale(x1, x2, y1, y2) { | ||
var scale = getScaleFactor(x1, x2, y1, y2); | ||
return function(x) { | ||
return y1 + scale * (x - x1); | ||
}; | ||
}; | ||
// These calls all have (x, y) as args at the specified positions. | ||
var CALLS_XY = { | ||
clearRect: 0, | ||
fillRect: 0, | ||
strokeRect: 0, | ||
fillText: 1, | ||
strokeText: 1, | ||
moveTo: 0, | ||
lineTo: 0, | ||
bezierCurveTo: [0, 2, 4], | ||
quadraticCurveTo: [0, 2], | ||
rect: 0 | ||
}; | ||
// These calls have (width, height) as args at the specified positions. | ||
var CALLS_WH = { | ||
clearRect: 2, | ||
fillRect: 2, | ||
strokeRect: 2, | ||
// fillText has an optional `max_width` param | ||
rect: 2, | ||
}; | ||
/** | ||
* Get a list of objects which have been pushed to the data canvas that match | ||
* the particular predicate. | ||
* If no predicate is specified, all objects are returned. | ||
*/ | ||
RecordingContext.prototype.drawnObjectsWith = function(predicate) { | ||
if (!predicate) predicate = function() { return true; }; | ||
return this.callsOf('pushObject') | ||
@@ -120,2 +241,4 @@ .filter(function(x) { return predicate(x[1]) }) | ||
}; | ||
// This version reads better if there's no predicate. | ||
RecordingContext.prototype.drawnObjects = RecordingContext.prototype.drawnObjectsWith; | ||
@@ -195,3 +318,5 @@ /** | ||
if (!div) { | ||
if (RecordingContext.recorders.length == 0) { | ||
if (!RecordingContext.recorders) { | ||
throw 'You must call RecordingContext.recordAll() before using other RecordingContext static methods'; | ||
} else if (RecordingContext.recorders.length == 0) { | ||
throw 'Called a RecordingContext method, but no canvases are being recorded.'; | ||
@@ -198,0 +323,0 @@ } else if (RecordingContext.recorders.length > 1) { |
@@ -53,3 +53,3 @@ (function() { | ||
expect(dtx2).to.equal(dtx2); | ||
expect(dtx2).to.equal(dtx); | ||
}); | ||
@@ -331,2 +331,3 @@ | ||
}).to.throw(/forgot.*reset/); | ||
RecordingContext.reset(); | ||
}); | ||
@@ -337,5 +338,114 @@ | ||
RecordingContext.drawnObjects(); | ||
}).to.throw(/no canvases.*recorded/); | ||
}).to.throw(/You must call .*recordAll/); | ||
}); | ||
it('should throw on access with nothing recorded', function() { | ||
expect(function() { | ||
RecordingContext.recordAll(); | ||
RecordingContext.drawnObjects(); | ||
}).to.throw(/no canvases are being recorded/); | ||
RecordingContext.reset(); | ||
}); | ||
}); | ||
describe('drawImage', function() { | ||
beforeEach(function() { | ||
RecordingContext.recordAll(); | ||
}); | ||
afterEach(function() { | ||
RecordingContext.reset(); | ||
}); | ||
function makeOffscreenImage() { | ||
var image = document.createElement('canvas'); | ||
image.width = 100; | ||
image.height = 100; | ||
var dtx = dataCanvas.getDataContext(image); | ||
dtx.pushObject('A'); | ||
dtx.fillRect(0, 0, 50, 50); | ||
dtx.popObject(); | ||
return image; | ||
} | ||
it('should transfer recorded calls', function() { | ||
var image = makeOffscreenImage(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
dtx.drawImage(image, 0, 0); | ||
expect(dtx.calls).to.have.length(3); | ||
expect(dtx.drawnObjects()).to.deep.equal(['A']); | ||
expect(dtx.callsOf('fillRect')).to.deep.equal([['fillRect', 0, 0, 50, 50]]); | ||
// The drawImage call is elided. | ||
// This could be changed -- either way would be reasonable. | ||
expect(dtx.callsOf('drawImage')).to.deep.equal([]); | ||
}); | ||
it('should translate recorded calls', function() { | ||
var image = makeOffscreenImage(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
dtx.drawImage(image, 50, 0); // dx=50 | ||
expect(dtx.calls).to.have.length(3); | ||
expect(dtx.callsOf('fillRect')).to.deep.equal([['fillRect', 50, 0, 50, 50]]); | ||
}); | ||
it('should transform recorded calls', function() { | ||
var image = makeOffscreenImage(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
dtx.drawImage(image, 50, 0, 75, 50); // dx=50, dWidth=75, dHeight=50 | ||
expect(dtx.calls).to.have.length(3); | ||
expect(dtx.callsOf('fillRect')).to.deep.equal([['fillRect', 50, 0, 37.5, 25]]); | ||
}); | ||
it('should support a source rectangle', function() { | ||
var image = makeOffscreenImage(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
// This copies x=75-100 and y=50-100 from source to dest | ||
dtx.drawImage(image, 25, 50, 75, 50, 0, 0, 75, 50); | ||
expect(dtx.calls).to.have.length(3); | ||
expect(dtx.callsOf('fillRect')).to.deep.equal([['fillRect', -25, -50, 50, 50]]); | ||
}); | ||
it('should reject invalid drawImage calls', function() { | ||
var image = makeOffscreenImage(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
expect(function() { | ||
dtx.drawImage(image, 50, 0, 75); // four params, should be 3, 5 or 9 | ||
}).to.throw(); // exact error depends on browser | ||
}); | ||
it('should transform paths', function() { | ||
var image = makeOffscreenImage(); | ||
var ctx = dataCanvas.getDataContext(image); | ||
ctx.beginPath(); | ||
ctx.moveTo(20, 10); | ||
ctx.lineTo(30, 20); | ||
ctx.quadraticCurveTo(50, 20, 40, 30); | ||
ctx.closePath(); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
dtx.drawImage(image, 0, 10, 50, 25); // dx=0, dy=10, dWidth=50, dHeight=25 | ||
expect(dtx.callsOf('moveTo')).to.deep.equal([['moveTo', 10, 12.5]]); | ||
expect(dtx.callsOf('lineTo')).to.deep.equal([['lineTo', 15, 15]]); | ||
expect(dtx.callsOf('quadraticCurveTo')).to.deep.equal( | ||
[['quadraticCurveTo', 25, 15, 20, 17.5]]); | ||
}); | ||
it('should not transfer calls from unrecorded canvases', function() { | ||
var image = document.createElement('canvas'); | ||
image.width = 100; | ||
image.height = 100; | ||
image.getContext('2d').fillRect(0, 0, 100, 100); | ||
var dtx = dataCanvas.getDataContext(canvas); | ||
dtx.drawImage(image, 0, 0); | ||
// The fillRect call should not be transferred over. | ||
expect(dtx.callsOf('drawImage')).to.deep.equal( | ||
[['drawImage', image, 0, 0]]); | ||
expect(dtx.callsOf('fillRect')).to.deep.equal([]); | ||
}); | ||
}); | ||
}); | ||
@@ -342,0 +452,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59261
13
798
272