From b339e73ebbae0a93279bd002d046b069578cf282 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 16:26:25 +0100 Subject: [PATCH] Add a CanvasLayer class to handle onscreen/offscreen canvases --- lib/canvas-layer.js | 72 +++++++++++++++++++++++++++ spec/canvas-layer-spec.js | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 lib/canvas-layer.js create mode 100644 spec/canvas-layer-spec.js diff --git a/lib/canvas-layer.js b/lib/canvas-layer.js new file mode 100644 index 00000000..f2db3f56 --- /dev/null +++ b/lib/canvas-layer.js @@ -0,0 +1,72 @@ +'use babel' + +export default class CanvasLayer { + constructor () { + /** + * The onscreen canvas. + * @type {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas') + /** + * The onscreen canvas context. + * @type {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d') + this.canvas.webkitImageSmoothingEnabled = false + this.context.imageSmoothingEnabled = false + + /** + * The offscreen canvas. + * @type {HTMLCanvasElement} + * @access private + */ + this.offscreenCanvas = document.createElement('canvas') + /** + * The offscreen canvas context. + * @type {CanvasRenderingContext2D} + * @access private + */ + this.offscreenContext = this.offscreenCanvas.getContext('2d') + this.offscreenCanvas.webkitImageSmoothingEnabled = false + this.offscreenContext.imageSmoothingEnabled = false + } + + attach (parent) { + if (this.canvas.parentNode) { return } + + parent.appendChild(this.canvas) + } + + setSize (width = 0, height = 0) { + this.canvas.width = width + this.canvas.height = height + this.context.imageSmoothingEnabled = false + this.resetOffscreenSize() + } + + resetOffscreenSize () { + this.offscreenCanvas.width = this.canvas.width + this.offscreenCanvas.height = this.canvas.height + this.offscreenContext.imageSmoothingEnabled = false + } + + copyToOffscreen () { + this.offscreenContext.drawImage(this.canvas, 0, 0) + } + + copyFromOffscreen () { + this.context.drawImage(this.offscreenCanvas, 0, 0) + } + + copyPartFromOffscreen (srcY, destY, height) { + this.context.drawImage( + this.offscreenCanvas, + 0, srcY, this.offscreenCanvas.width, height, + 0, destY, this.offscreenCanvas.width, height + ) + } + + clearCanvas () { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height) + } +} diff --git a/spec/canvas-layer-spec.js b/spec/canvas-layer-spec.js new file mode 100644 index 00000000..415a6a49 --- /dev/null +++ b/spec/canvas-layer-spec.js @@ -0,0 +1,100 @@ +'use babel' + +import CanvasLayer from '../lib/canvas-layer' + +describe('CanvasLayer', () => { + let [layer] = [] + + beforeEach(() => { + layer = new CanvasLayer() + + layer.setSize(100, 300) + }) + + it('has two canvas', () => { + expect(layer.canvas).toBeDefined() + expect(layer.offscreenCanvas).toBeDefined() + }) + + it('has a context for each canvas', () => { + expect(layer.context).toBeDefined() + expect(layer.offscreenContext).toBeDefined() + }) + + it('disables the smoothing for the canvas', () => { + expect(layer.canvas.webkitImageSmoothingEnabled).toBeFalsy() + expect(layer.offscreenCanvas.webkitImageSmoothingEnabled).toBeFalsy() + + expect(layer.context.imageSmoothingEnabled).toBeFalsy() + expect(layer.offscreenContext.imageSmoothingEnabled).toBeFalsy() + }) + + describe('.prototype.attach', () => { + it('attaches the onscreen canvas to the provided element', () => { + let jasmineContent = document.body.querySelector('#jasmine-content') + + layer.attach(jasmineContent) + + expect(jasmineContent.querySelector('canvas')).toExist() + }) + }) + + describe('.prototype.resetOffscreenSize', () => { + it('sets the width of the offscreen canvas to the ', () => { + layer.canvas.width = 500 + layer.canvas.height = 400 + + expect(layer.offscreenCanvas.width).toEqual(100) + expect(layer.offscreenCanvas.height).toEqual(300) + + layer.resetOffscreenSize() + + expect(layer.offscreenCanvas.width).toEqual(500) + expect(layer.offscreenCanvas.height).toEqual(400) + }) + }) + + describe('.prototype.copyToOffscreen', () => { + it('copies the onscreen bitmap onto the offscreen canvas', () => { + spyOn(layer.offscreenContext, 'drawImage') + + layer.copyToOffscreen() + + expect(layer.offscreenContext.drawImage).toHaveBeenCalledWith(layer.canvas, 0, 0) + }) + }) + + describe('.prototype.copyFromOffscreen', () => { + it('copies the offscreen bitmap onto the onscreen canvas', () => { + spyOn(layer.context, 'drawImage') + + layer.copyFromOffscreen() + + expect(layer.context.drawImage).toHaveBeenCalledWith(layer.offscreenCanvas, 0, 0) + }) + }) + + describe('.prototype.copyPartFromOffscren', () => { + it('copies to the onscreen canvas the region that were specified', () => { + spyOn(layer.context, 'drawImage') + + layer.copyPartFromOffscreen(50, 100, 150) + + expect(layer.context.drawImage).toHaveBeenCalledWith( + layer.offscreenCanvas, + 0, 50, 100, 150, + 0, 100, 100, 150 + ) + }) + }) + + describe('.prototype.clearCanvas', () => { + it('clears the whole canvas region', () => { + spyOn(layer.context, 'clearRect') + + layer.clearCanvas() + + expect(layer.context.clearRect).toHaveBeenCalledWith(0, 0, layer.canvas.width, layer.canvas.height) + }) + }) +})