From 48f24c0cf313b22fdb4dc9fb536d1a563349e4d5 Mon Sep 17 00:00:00 2001 From: James Lissiak Date: Mon, 1 Jun 2015 10:29:52 -0700 Subject: [PATCH] Adding spritesheet import - Updated the import dialog to allow users to specify the number of frames in the image (which defaults to 1 x and 1 y) - Setting the frame count for x and y will draw a dotted line in the preview that shows where the image will be split into individual frames - When imported with a frame count above 1, the source image will be split into the different frames and loaded just as if it were an animated gif - This allows users to import existing spritesheet pngs, including those produced by the piskel export function --- src/css/dialogs-import-image.css | 17 +++- src/css/dialogs.css | 2 +- .../dialogs/ImportImageController.js | 95 +++++++++++++++++-- src/js/utils/CanvasUtils.js | 37 ++++++++ src/templates/dialogs/import-image.html | 11 ++- 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/src/css/dialogs-import-image.css b/src/css/dialogs-import-image.css index d4eef12c..e29265d1 100644 --- a/src/css/dialogs-import-image.css +++ b/src/css/dialogs-import-image.css @@ -3,16 +3,27 @@ /************************************************************************************************/ .import-section-preview { - display : inline-block; - height : 60px; + position: relative; + display: inline-block; + height: 60px; width: 60px; border: 1px dashed #999; border-radius: 3px; } +.import-section-preview.no-border { + border-color: transparent; +} + +.import-section-preview canvas { + position: absolute; + left: 0; + top: 0; +} + .dialog-section-title { display : inline-block; - width: 55px; + width: 70px; } .import-size-field:nth-of-type(2) { diff --git a/src/css/dialogs.css b/src/css/dialogs.css index 85d735b7..3753ca3f 100644 --- a/src/css/dialogs.css +++ b/src/css/dialogs.css @@ -67,7 +67,7 @@ #dialog-container.import-image { width: 500px; - height: 300px; + height: 320px; top : 50%; left : 50%; position : absolute; diff --git a/src/js/controller/dialogs/ImportImageController.js b/src/js/controller/dialogs/ImportImageController.js index 3c88a949..23b5c569 100644 --- a/src/js/controller/dialogs/ImportImageController.js +++ b/src/js/controller/dialogs/ImportImageController.js @@ -1,6 +1,6 @@ (function () { var ns = $.namespace('pskl.controller.dialogs'); - var PREVIEW_HEIGHT = 60; + var PREVIEW_HEIGHT = 60; ns.ImportImageController = function (piskelController) { this.importedImage_ = null; @@ -20,10 +20,15 @@ this.resizeWidth = $('[name=resize-width]'); this.resizeHeight = $('[name=resize-height]'); - this.smoothResize = $('[name=smooth-resize-checkbox]'); + this.smoothResize = $('[name=smooth-resize-checkbox]'); + + this.frameCountX = $('[name=frame-count-x]'); + this.frameCountY = $('[name=frame-count-y]'); this.resizeWidth.keyup(this.onResizeInputKeyUp_.bind(this, 'width')); this.resizeHeight.keyup(this.onResizeInputKeyUp_.bind(this, 'height')); + this.frameCountX.keyup(this.onResizeInputKeyUp_.bind(this, 'frameCountX')); + this.frameCountY.keyup(this.onResizeInputKeyUp_.bind(this, 'frameCountY')); this.importImageForm = $('[name=import-image-form]'); this.importImageForm.submit(this.onImportFormSubmit_.bind(this)); @@ -49,7 +54,24 @@ } var height = this.importedImage_.height; var width = this.importedImage_.width; - if (from === 'width') { + + var frameCountX = parseInt(this.frameCountX.val(), 10); + if (frameCountX <= 0 || isNaN(frameCountX)) { + this.frameCountX.val(1); + frameCountX = 1; + } + + var frameCountY = parseInt(this.frameCountY.val(), 10); + if (frameCountY <= 0 || isNaN(frameCountY)) { + this.frameCountY.val(1); + frameCountY = 1; + } + + if (from === 'frameCountX' || from === 'frameCountY') { + this.resizeWidth.val(Math.round(width / frameCountX)); + this.resizeHeight.val(Math.round(height / frameCountY)); + this.drawFramesGrid_(); + } else if (from === 'width') { this.resizeHeight.val(Math.round(value * height / width)); } else { this.resizeWidth.val(Math.round(value * width / height)); @@ -64,7 +86,7 @@ // FIXME : We remove the onload callback here because JsGif will insert // the image again and we want to avoid retriggering the image onload - this.importedImage_.onload = function () {}; + this.importedImage_.onload = function () { }; var fileName = this.extractFileNameFromPath_(this.file_.name); this.fileNameContainer.html(fileName); @@ -72,6 +94,9 @@ this.resizeWidth.val(w); this.resizeHeight.val(h); + this.frameCountX.val(1); + this.frameCountY.val(1); + this.importPreview.width('auto'); this.importPreview.html(''); this.importPreview.append(this.createImagePreview_()); @@ -101,19 +126,23 @@ if (image) { if (window.confirm('You are about to create a new Piskel, unsaved changes will be lost.')) { var gifLoader = new window.SuperGif({ - gif : image + gif: image }); gifLoader.load({ - success : function () { + success: function () { var images = gifLoader.getFrames().map(function (frame) { return pskl.utils.CanvasUtils.createFromImageData(frame.data); }); this.createPiskelFromImages_(images); this.closeDialog(); }.bind(this), - error : function () { - this.createPiskelFromImages_([image]); + error: function () { + var images = pskl.utils.CanvasUtils.createFramesFromImage( + image, + this.frameCountX.val(), + this.frameCountY.val()); + this.createPiskelFromImages_(images); this.closeDialog(); }.bind(this) }); @@ -143,4 +172,54 @@ pskl.app.piskelController.setPiskel(piskel); pskl.app.previewController.setFPS(Constants.DEFAULT.FPS); }; + + ns.ImportImageController.prototype.drawFramesGrid_ = function () { + var canvasWrapper = this.importPreview.children('canvas'); + var countX = this.frameCountX.val(); + var countY = this.frameCountY.val(); + if (countX > 1 || countY > 1) { + var width = this.importedImage_.width; + var height = this.importedImage_.height; + var frameW = width / countX; + var frameH = height / countY; + + var canvas = canvasWrapper.get(0); + if (!canvasWrapper.length) { + // Create a new canvas for the grid + canvas = pskl.utils.CanvasUtils.createCanvas(width + 1, height + 1); + this.importPreview.append(canvas); + canvasWrapper = $(canvas); + } + + var context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + + // Draw the vertical lines + for (var x = 0.5; x < width + 1; x += frameW) { + context.moveTo(x, 0); + context.lineTo(x, height); + } + + // Draw the horizontal lines + for (var y = 0.5; y < height + 1; y += frameH) { + context.moveTo(0, y); + context.lineTo(width, y); + } + + // Set the line style to dashed + context.lineWidth = 1; + context.setLineDash([2, 1]); + context.strokeStyle = '#000000'; + context.stroke(); + + // Resize the canvas so that it matches the preview height and stretches correctly + canvasWrapper.height(PREVIEW_HEIGHT + 1); + canvasWrapper.show(); + this.importPreview.addClass('no-border'); + } else { + canvasWrapper.hide(); + this.importPreview.removeClass('no-border'); + } + }; })(); diff --git a/src/js/utils/CanvasUtils.js b/src/js/utils/CanvasUtils.js index 67558420..2b8101c1 100644 --- a/src/js/utils/CanvasUtils.js +++ b/src/js/utils/CanvasUtils.js @@ -33,6 +33,43 @@ return canvas; }, + /** + * Splits the specified image into several new canvas elements based on the + * supplied horizontal (x) and vertical (y) counts. + * @param image The source image that will be split + * @param {Number} frameCountX The number of frames in the horizontal axis + * @param {Number} frameCountY The number of frames in the vertical axis + * @returns {Array} An array of canvas elements that contain the split frames + */ + createFramesFromImage : function (image, frameCountX, frameCountY) { + var canvasArray = []; + var frameWidth = image.width / frameCountX; + var frameHeight = image.height / frameCountY; + + // Loop through the frames prioritizing the spritesheet as horizonal strips + for (var y = 0; y < frameCountY; y++) { + for (var x = 0; x < frameCountX; x++) { + var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, frameHeight); + var context = canvas.getContext('2d'); + + // Blit the correct part of the source image into the new canvas + context.drawImage( + image, + x * frameWidth, + y * frameHeight, + frameWidth, + image.height, + 0, + 0, + frameWidth, + image.height); + + canvasArray.push(canvas); + } + } + return canvasArray; + }, + /** * By default, all scaling operations on a Canvas 2D Context are performed using antialiasing. * Resizing a 32x32 image to 320x320 will lead to a blurry output. diff --git a/src/templates/dialogs/import-image.html b/src/templates/dialogs/import-image.html index 95795799..53a0a5fa 100644 --- a/src/templates/dialogs/import-image.html +++ b/src/templates/dialogs/import-image.html @@ -14,12 +14,17 @@
Size : - x - + x + +
+
+ Frames : + x +
Smooth resize : - +