From 9ef46d5ec5b7109da904784a700b97fcec58151e Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sun, 21 Sep 2014 21:39:54 +0200 Subject: [PATCH] added FrameUtils unit tests --- src/js/controller/piskel/PiskelController.js | 2 +- src/js/model/Layer.js | 4 +- src/js/model/Piskel.js | 2 +- src/js/utils/FrameUtils.js | 144 +++++++++--------- src/js/utils/LayerUtils.js | 16 +- src/js/utils/serialization/Deserializer.js | 2 +- test/js/utils/FrameUtilsTest.js | 145 +++++++++++++++++++ 7 files changed, 230 insertions(+), 85 deletions(-) create mode 100644 test/js/utils/FrameUtilsTest.js diff --git a/src/js/controller/piskel/PiskelController.js b/src/js/controller/piskel/PiskelController.js index ed03a303..2aa9f565 100644 --- a/src/js/controller/piskel/PiskelController.js +++ b/src/js/controller/piskel/PiskelController.js @@ -158,7 +158,7 @@ ns.PiskelController.prototype.getFrameCount = function () { var layer = this.piskel.getLayerAt(0); - return layer.length(); + return layer.size(); }; ns.PiskelController.prototype.setCurrentFrameIndex = function (index) { diff --git a/src/js/model/Layer.js b/src/js/model/Layer.js index 13d5977e..206aa565 100644 --- a/src/js/model/Layer.js +++ b/src/js/model/Layer.js @@ -56,7 +56,7 @@ if (this.frames[index]) { this.frames.splice(index, 1); } else { - throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.length() + ')'; + throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.size() + ')'; } }; @@ -93,7 +93,7 @@ } }; - ns.Layer.prototype.length = function () { + ns.Layer.prototype.size = function () { return this.frames.length; }; diff --git a/src/js/model/Piskel.js b/src/js/model/Piskel.js index ab28ec14..d41e8433 100644 --- a/src/js/model/Piskel.js +++ b/src/js/model/Piskel.js @@ -33,7 +33,7 @@ */ ns.Piskel.fromLayers = function (layers, descriptor) { var piskel = null; - if (layers.length > 0 && layers[0].length() > 0) { + if (layers.length > 0 && layers[0].size() > 0) { var sampleFrame = layers[0].getFrameAt(0); piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight(), descriptor); layers.forEach(piskel.addLayer.bind(piskel)); diff --git a/src/js/utils/FrameUtils.js b/src/js/utils/FrameUtils.js index b98c2f43..c550692c 100644 --- a/src/js/utils/FrameUtils.js +++ b/src/js/utils/FrameUtils.js @@ -2,6 +2,14 @@ var ns = $.namespace('pskl.utils'); var colorCache = {}; ns.FrameUtils = { + toImage : function (frame, zoom, bgColor) { + zoom = zoom || 1; + bgColor = bgColor || Constants.TRANSPARENT_COLOR; + var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom); + canvasRenderer.drawTransparentAs(bgColor); + return canvasRenderer.render(); + }, + merge : function (frames) { var merged = null; if (frames.length) { @@ -28,6 +36,66 @@ return pskl.utils.FrameUtils.createFromImage(resizedImage); }, + /* + * Create a pskl.model.Frame from an Image object. + * Transparent pixels will either be converted to completely opaque or completely transparent pixels. + * @param {Image} image source image + * @return {pskl.model.Frame} corresponding frame + */ + createFromImage : function (image) { + var w = image.width, + h = image.height; + var canvas = pskl.CanvasUtils.createCanvas(w, h); + var context = canvas.getContext('2d'); + + context.drawImage(image, 0,0,w,h,0,0,w,h); + var imgData = context.getImageData(0,0,w,h).data; + return pskl.utils.FrameUtils.createFromImageData_(imgData, w, h); + }, + + createFromImageData_ : function (imageData, width, height) { + // Draw the zoomed-up pixels to a different canvas context + var grid = []; + for (var x = 0 ; x < width ; x++){ + grid[x] = []; + for (var y = 0 ; y < height ; y++){ + // Find the starting index in the one-dimensional image data + var i = (y * width + x)*4; + var r = imageData[i ]; + var g = imageData[i+1]; + var b = imageData[i+2]; + var a = imageData[i+3]; + if (a < 125) { + grid[x][y] = Constants.TRANSPARENT_COLOR; + } else { + grid[x][y] = pskl.utils.FrameUtils.rgbToHex_(r,g,b); + } + } + } + return pskl.model.Frame.fromPixelGrid(grid); + }, + + /** + * Convert a rgb(Number, Number, Number) color to hexadecimal representation + * @param {Number} r red value, between 0 and 255 + * @param {Number} g green value, between 0 and 255 + * @param {Number} b blue value, between 0 and 255 + * @return {String} hex representation of the color '#ABCDEF' + */ + rgbToHex_ : function (r, g, b) { + return "#" + this.componentToHex_(r) + this.componentToHex_(g) + this.componentToHex_(b); + }, + + /** + * Convert a color component (as a Number between 0 and 255) to its string hexa representation + * @param {Number} c component value, between 0 and 255 + * @return {String} eg. '0A' + */ + componentToHex_ : function (c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + }, + /** * Alpha compositing using porter duff algorithm : * http://en.wikipedia.org/wiki/Alpha_compositing @@ -36,9 +104,9 @@ * @param {String} strColor2 color under * @return {String} the composite color */ - mergePixels : function (strColor1, strColor2, globalOpacity1) { - var col1 = pskl.utils.FrameUtils.toRgba(strColor1); - var col2 = pskl.utils.FrameUtils.toRgba(strColor2); + mergePixels_ : function (strColor1, strColor2, globalOpacity1) { + var col1 = pskl.utils.FrameUtils.toRgba_(strColor1); + var col2 = pskl.utils.FrameUtils.toRgba_(strColor2); if (typeof globalOpacity1 == 'number') { col1 = JSON.parse(JSON.stringify(col1)); col1.a = globalOpacity1 * col1.a; @@ -58,7 +126,7 @@ * @param {String} c color as a string * @return {Object} {r:Number,g:Number,b:Number,a:Number} */ - toRgba : function (c) { + toRgba_ : function (c) { if (colorCache[c]) { return colorCache[c]; } @@ -97,74 +165,6 @@ } colorCache[c] = color; return color; - }, - - /* - * Create a pskl.model.Frame from an Image object. - * Transparent pixels will either be converted to completely opaque or completely transparent pixels. - * @param {Image} image source image - * @return {pskl.model.Frame} corresponding frame - */ - createFromImage : function (image) { - var w = image.width, - h = image.height; - var canvas = pskl.CanvasUtils.createCanvas(w, h); - var context = canvas.getContext('2d'); - - context.drawImage(image, 0,0,w,h,0,0,w,h); - var imgData = context.getImageData(0,0,w,h).data; - return pskl.utils.FrameUtils.createFromImageData(imgData, w, h); - }, - - createFromImageData : function (imageData, width, height) { - // Draw the zoomed-up pixels to a different canvas context - var grid = []; - for (var x = 0 ; x < width ; x++){ - grid[x] = []; - for (var y = 0 ; y < height ; y++){ - // Find the starting index in the one-dimensional image data - var i = (y * width + x)*4; - var r = imageData[i ]; - var g = imageData[i+1]; - var b = imageData[i+2]; - var a = imageData[i+3]; - if (a < 125) { - grid[x][y] = Constants.TRANSPARENT_COLOR; - } else { - grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); - } - } - } - return pskl.model.Frame.fromPixelGrid(grid); - }, - - /** - * Convert a rgb(Number, Number, Number) color to hexadecimal representation - * @param {Number} r red value, between 0 and 255 - * @param {Number} g green value, between 0 and 255 - * @param {Number} b blue value, between 0 and 255 - * @return {String} hex representation of the color '#ABCDEF' - */ - rgbToHex : function (r, g, b) { - return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b); - }, - - /** - * Convert a color component (as a Number between 0 and 255) to its string hexa representation - * @param {Number} c component value, between 0 and 255 - * @return {String} eg. '0A' - */ - componentToHex : function (c) { - var hex = c.toString(16); - return hex.length == 1 ? "0" + hex : hex; - }, - - toImage : function (frame, zoom, bgColor) { - zoom = zoom || 1; - bgColor = bgColor || Constants.TRANSPARENT_COLOR; - var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom); - canvasRenderer.drawTransparentAs(bgColor); - return canvasRenderer.render(); } }; })(); diff --git a/src/js/utils/LayerUtils.js b/src/js/utils/LayerUtils.js index 38a08e5a..ee709a8d 100644 --- a/src/js/utils/LayerUtils.js +++ b/src/js/utils/LayerUtils.js @@ -8,20 +8,20 @@ * @param {Image} image source image * @return {pskl.model.Frame} corresponding frame */ - createFromImage : function (image, frameCount) { - var w = image.width, - h = image.height, - frameWidth = w / frameCount; + createLayerFromSpritesheet : function (image, frameCount) { + var width = image.width, + height = image.height, + frameWidth = width / frameCount; - var canvas = pskl.CanvasUtils.createCanvas(w, h); + var canvas = pskl.CanvasUtils.createCanvas(frameWidth, height); var context = canvas.getContext('2d'); - context.drawImage(image, 0,0,w,h,0,0,w,h); // Draw the zoomed-up pixels to a different canvas context var frames = []; for (var i = 0 ; i < frameCount ; i++) { - var imgData = context.getImageData(frameWidth*i,0,frameWidth,h).data; - var frame = pskl.utils.FrameUtils.createFromImageData(imgData, frameWidth, h); + context.clearRect(0, 0 , frameWidth, height); + context.drawImage(image, frameWidth * i, 0, frameWidth, height, 0, 0, frameWidth, height); + var frame = pskl.utils.FrameUtils.createFromImage(canvas); frames.push(frame); } return frames; diff --git a/src/js/utils/serialization/Deserializer.js b/src/js/utils/serialization/Deserializer.js index 8c0b43e4..c18808e5 100644 --- a/src/js/utils/serialization/Deserializer.js +++ b/src/js/utils/serialization/Deserializer.js @@ -47,7 +47,7 @@ // 2 - attach the onload callback that will be triggered asynchronously image.onload = function () { // 5 - extract the frames from the loaded image - var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); + var frames = pskl.utils.LayerUtils.createLayerFromSpritesheet(image, layerData.frameCount); // 6 - add each image to the layer this.addFramesToLayer(frames, layer); }.bind(this); diff --git a/test/js/utils/FrameUtilsTest.js b/test/js/utils/FrameUtilsTest.js new file mode 100644 index 00000000..ca9a165c --- /dev/null +++ b/test/js/utils/FrameUtilsTest.js @@ -0,0 +1,145 @@ +describe("FrameUtils suite", function() { + var black = '#000000'; + var red = '#ff0000'; + var transparent = Constants.TRANSPARENT_COLOR; + + it("merges 2 frames", function () { + var frame1 = pskl.model.Frame.fromPixelGrid([ + [black, transparent], + [transparent, black] + ]); + + var frame2 = pskl.model.Frame.fromPixelGrid([ + [transparent, red], + [red, transparent] + ]); + + var mergedFrame = pskl.utils.FrameUtils.merge([frame1, frame2]); + expect(mergedFrame.getPixel(0,0)).toBe(black); + expect(mergedFrame.getPixel(0,1)).toBe(red); + expect(mergedFrame.getPixel(1,0)).toBe(red); + expect(mergedFrame.getPixel(1,1)).toBe(black); + }); + + it("returns same frame when merging single frame", function () { + var frame1 = pskl.model.Frame.fromPixelGrid([ + [black, transparent], + [transparent, black] + ]); + + var mergedFrame = pskl.utils.FrameUtils.merge([frame1]); + expect(mergedFrame.getPixel(0,0)).toBe(black); + expect(mergedFrame.getPixel(0,1)).toBe(transparent); + expect(mergedFrame.getPixel(1,0)).toBe(transparent); + expect(mergedFrame.getPixel(1,1)).toBe(black); + }); + + var checkPixelsColor = function (frame, pixels, color) { + pixels.forEach(function (pixel) { + var pixelColor = frame.getPixel(pixel[0], pixel[1]); + expect(pixelColor).toBe(color); + }); + }; + + it ("converts an image to a frame", function () { + var frame1 = pskl.model.Frame.fromPixelGrid([ + [black, transparent], + [transparent, black] + ]); + + var image = pskl.utils.FrameUtils.toImage(frame1); + expect(image.width).toBe(2); + expect(image.height).toBe(2); + + var biggerImage = pskl.utils.FrameUtils.toImage(frame1, 3); + expect(biggerImage.width).toBe(6); + expect(biggerImage.height).toBe(6); + + var biggerFrame = pskl.utils.FrameUtils.createFromImage(biggerImage); + + checkPixelsColor(biggerFrame, [ + [0,0],[0,1],[0,2], + [1,0],[1,1],[1,2], + [2,0],[2,1],[2,2], + [3,3],[3,4],[3,5], + [4,3],[4,4],[4,5], + [5,3],[5,4],[5,5] + ], black); + + checkPixelsColor(biggerFrame, [ + [0,3],[0,4],[0,5], + [1,3],[1,4],[1,5], + [2,3],[2,4],[2,5], + [3,0],[3,1],[3,2], + [4,0],[4,1],[4,2], + [5,0],[5,1],[5,2] + ], transparent); + }); + + it ("[LayerUtils] creates a layer from a simple spritesheet", function () { + var frame = pskl.model.Frame.fromPixelGrid([ + [black, red], + [red, black], + [black, black], + [red, red] + ]); + var spritesheet = pskl.utils.FrameUtils.toImage(frame); + + var frames = pskl.utils.LayerUtils.createLayerFromSpritesheet(spritesheet, 4); + expect(frames.length).toBe(4); + + expect(frames[0].getPixel(0,0)).toBe(black); + expect(frames[0].getPixel(0,1)).toBe(red); + + expect(frames[1].getPixel(0,0)).toBe(red); + expect(frames[1].getPixel(0,1)).toBe(black); + + expect(frames[2].getPixel(0,0)).toBe(black); + expect(frames[2].getPixel(0,1)).toBe(black); + + expect(frames[3].getPixel(0,0)).toBe(red); + expect(frames[3].getPixel(0,1)).toBe(red); + + }); + + // it("starts at -1", function() { + // historyService = createMockHistoryService(); + // expect(historyService.currentIndex).toBe(-1); + // }); + + // it("is at 0 after init", function() { + // historyService = createMockHistoryService(); + // historyService.init(); + // expect(historyService.currentIndex).toBe(0); + // }); + + // it("stores a piskel snapshot after 5 SAVE", function () { + // // BEFORE + // var SNAPSHOT_PERIOD_BACKUP = pskl.service.HistoryService.SNAPSHOT_PERIOD; + // pskl.service.HistoryService.SNAPSHOT_PERIOD = 5; + + // historyService = createMockHistoryService(); + // historyService.init(); + + // sendSaveEvents(pskl.service.HistoryService.REPLAY).times(5); + + // expect(historyService.currentIndex).toBe(5); + + // expect(getLastState().piskel).toBe(SERIALIZED_PISKEL); + + // sendSaveEvents(pskl.service.HistoryService.REPLAY).times(4); + + // sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); + // expect(getLastState().piskel).toBeUndefined(); + + // sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); + // expect(getLastState().piskel).toBeUndefined(); + + // sendSaveEvents(pskl.service.HistoryService.REPLAY).once(); + // expect(getLastState().piskel).toBe(SERIALIZED_PISKEL); + + // // AFTER + // pskl.service.HistoryService.SNAPSHOT_PERIOD = SNAPSHOT_PERIOD_BACKUP; + + // }) +}); \ No newline at end of file