diff --git a/src/css/dialogs-import-image.css b/src/css/dialogs-import-image.css index e29265d1..0de7313b 100644 --- a/src/css/dialogs-import-image.css +++ b/src/css/dialogs-import-image.css @@ -2,28 +2,51 @@ /* Import dialog */ /************************************************************************************************/ +.import-subsection { + margin-left: 25px; +} + +.import-section:not(.import-subsection) > .dialog-section-title { + width: 50px; +} + +.import-section-preview-title { + position: absolute; + margin-left: 50%; + margin-top: -28px; +} + .import-section-preview { - position: relative; + position: absolute; display: inline-block; - height: 60px; - width: 60px; border: 1px dashed #999; border-radius: 3px; + margin-left: 50%; +} + +.import-section-preview img { + max-width: 220px; + max-height: 220px; } .import-section-preview.no-border { - border-color: transparent; + border-color: transparent; } .import-section-preview canvas { - position: absolute; - left: 0; - top: 0; + position: absolute; + left: 0; + top: 0; } .dialog-section-title { display : inline-block; - width: 70px; + width: 80px; +} + +.dialog-section-radio { + margin-top: 15px; + vertical-align: sub; } .import-size-field:nth-of-type(2) { @@ -34,12 +57,14 @@ display: inline-block; overflow: hidden; - height: 2rem; word-break : break-all; vertical-align: middle; font-style: italic; font-weight: normal; text-shadow: none; + width: 200px; + white-space: nowrap; + text-overflow: ellipsis; } [name=smooth-resize-checkbox] { @@ -48,11 +73,12 @@ .dialog-import-body { padding:10px 20px; - font-size:1.5em + font-size:1.3em } .import-button { font-size: 1em; height: auto; padding: 5px 10px; + margin-top: 15px; } \ No newline at end of file diff --git a/src/css/dialogs.css b/src/css/dialogs.css index 3753ca3f..e146309a 100644 --- a/src/css/dialogs.css +++ b/src/css/dialogs.css @@ -66,8 +66,8 @@ } #dialog-container.import-image { - width: 500px; - height: 320px; + width: 550px; + height: 350px; top : 50%; left : 50%; position : absolute; diff --git a/src/js/controller/dialogs/ImportImageController.js b/src/js/controller/dialogs/ImportImageController.js index ae40c140..8ea5c5fe 100644 --- a/src/js/controller/dialogs/ImportImageController.js +++ b/src/js/controller/dialogs/ImportImageController.js @@ -1,6 +1,5 @@ (function () { var ns = $.namespace('pskl.controller.dialogs'); - var PREVIEW_HEIGHT = 60; ns.ImportImageController = function (piskelController) { this.importedImage_ = null; @@ -18,17 +17,25 @@ this.fileNameContainer = $('.import-image-file-name'); + this.importType = $('[name=import-type]'); + this.resizeWidth = $('[name=resize-width]'); this.resizeHeight = $('[name=resize-height]'); this.smoothResize = $('[name=smooth-resize-checkbox]'); - this.frameCountX = $('[name=frame-count-x]'); - this.frameCountY = $('[name=frame-count-y]'); + this.frameSizeX = $('[name=frame-size-x]'); + this.frameSizeY = $('[name=frame-size-y]'); + this.frameOffsetX = $('[name=frame-offset-x]'); + this.frameOffsetY = $('[name=frame-offset-y]'); + + this.importType.change(this.onImportTypeChange_.bind(this)); 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.frameSizeX.keyup(this.onFrameInputKeyUp_.bind(this, 'frameSizeX')); + this.frameSizeY.keyup(this.onFrameInputKeyUp_.bind(this, 'frameSizeY')); + this.frameOffsetX.keyup(this.onFrameInputKeyUp_.bind(this, 'frameOffsetX')); + this.frameOffsetY.keyup(this.onFrameInputKeyUp_.bind(this, 'frameOffsetY')); this.importImageForm = $('[name=import-image-form]'); this.importImageForm.submit(this.onImportFormSubmit_.bind(this)); @@ -36,6 +43,20 @@ pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this)); }; + ns.ImportImageController.prototype.onImportTypeChange_ = function (evt) { + if (this.getImportType_() === 'single') { + // Using single image, so remove the frame grid + this.hideFrameGrid_(); + } else { + // Using spritesheet import, so draw the frame grid in the preview + var x = this.sanitizeInputValue_(this.frameOffsetX, 0); + var y = this.sanitizeInputValue_(this.frameOffsetY, 0); + var w = this.sanitizeInputValue_(this.frameSizeX, 1); + var h = this.sanitizeInputValue_(this.frameSizeY, 1); + this.drawFrameGrid_(x, y, w, h); + } + }; + ns.ImportImageController.prototype.onImportFormSubmit_ = function (evt) { evt.originalEvent.preventDefault(); this.importImageToPiskel_(); @@ -47,6 +68,12 @@ } }; + ns.ImportImageController.prototype.onFrameInputKeyUp_ = function (from, evt) { + if (this.importedImage_) { + this.synchronizeFrameFields_(evt.target.value, from); + } + }; + ns.ImportImageController.prototype.synchronizeResizeFields_ = function (value, from) { value = parseInt(value, 10); if (isNaN(value)) { @@ -55,31 +82,48 @@ var height = this.importedImage_.height; var width = this.importedImage_.width; - // Parse the X frame count - var frameCountX = parseInt(this.frameCountX.val(), 10); - if (frameCountX <= 0 || isNaN(frameCountX)) { - this.frameCountX.val(1); - frameCountX = 1; - } + // Select single image import type since the user changed a value here + this.importType.filter('[value="single"]').attr('checked', 'checked'); - // Parse the Y frame count - 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') { + if (from === 'width') { this.resizeHeight.val(Math.round(value * height / width)); } else { this.resizeWidth.val(Math.round(value * width / height)); } }; + ns.ImportImageController.prototype.synchronizeFrameFields_ = function (value, from) { + value = parseInt(value, 10); + if (isNaN(value)) { + value = 0; + } + + // Parse the frame input values + var frameSizeX = this.sanitizeInputValue_(this.frameSizeX, 1); + var frameSizeY = this.sanitizeInputValue_(this.frameSizeY, 1); + var frameOffsetX = this.sanitizeInputValue_(this.frameOffsetX, 0); + var frameOffsetY = this.sanitizeInputValue_(this.frameOffsetY, 0); + + // Select spritesheet import type since the user changed a value here + this.importType.filter('[value="sheet"]').attr('checked', 'checked'); + + // Draw the grid + this.drawFrameGrid_(frameOffsetX, frameOffsetY, frameSizeX, frameSizeY); + }; + + ns.ImportImageController.prototype.sanitizeInputValue_ = function(input, minValue) { + var value = parseInt(input.val(), 10); + if (value <= minValue || isNaN(value)) { + input.val(minValue); + value = minValue; + } + return value; + }; + + ns.ImportImageController.prototype.getImportType_ = function () { + return this.importType.filter(':checked').val(); + }; + ns.ImportImageController.prototype.onImageLoaded_ = function (image) { this.importedImage_ = image; @@ -96,10 +140,13 @@ this.resizeWidth.val(w); this.resizeHeight.val(h); - this.frameCountX.val(1); - this.frameCountY.val(1); + this.frameSizeX.val(w); + this.frameSizeY.val(h); + this.frameOffsetX.val(0); + this.frameOffsetY.val(0); this.importPreview.width('auto'); + this.importPreview.height('auto'); this.importPreview.html(''); this.importPreview.append(this.createImagePreview_()); }; @@ -107,7 +154,6 @@ ns.ImportImageController.prototype.createImagePreview_ = function () { var image = document.createElement('IMG'); image.src = this.importedImage_.src; - image.setAttribute('height', PREVIEW_HEIGHT); return image; }; @@ -131,20 +177,37 @@ gif : image }); + var resizeW = this.resizeWidth.val(); + var resizeH = this.resizeHeight.val(); + gifLoader.load({ success : function () { var images = gifLoader.getFrames().map(function (frame) { return pskl.utils.CanvasUtils.createFromImageData(frame.data); }); - this.createPiskelFromImages_(images); + this.createPiskelFromImages_(images, resizeW, resizeH); this.closeDialog(); }.bind(this), - error : function () { - var images = pskl.utils.CanvasUtils.createFramesFromImage( - image, - this.frameCountX.val(), - this.frameCountY.val()); - this.createPiskelFromImages_(images); + error: function () { + if (this.getImportType_() === 'single') { + // Single image + this.createPiskelFromImages_([image], resizeW, resizeH); + } else { + // Spritesheet + var x = this.sanitizeInputValue_(this.frameOffsetX, 0); + var y = this.sanitizeInputValue_(this.frameOffsetY, 0); + var w = this.sanitizeInputValue_(this.frameSizeX, 1); + var h = this.sanitizeInputValue_(this.frameSizeY, 1); + var images = pskl.utils.CanvasUtils.createFramesFromImage( + image, + x, + y, + w, + h, + /*useHorizonalStrips=*/ true, + /*ignoreEmptyFrames=*/ true); + this.createPiskelFromImages_(images, w, h); + } this.closeDialog(); }.bind(this) }); @@ -153,9 +216,7 @@ } }; - ns.ImportImageController.prototype.createFramesFromImages_ = function (images) { - var w = this.resizeWidth.val(); - var h = this.resizeHeight.val(); + ns.ImportImageController.prototype.createFramesFromImages_ = function (images, w, h) { var smoothing = !!this.smoothResize.prop('checked'); var frames = images.map(function (image) { @@ -165,8 +226,8 @@ return frames; }; - ns.ImportImageController.prototype.createPiskelFromImages_ = function (images) { - var frames = this.createFramesFromImages_(images); + ns.ImportImageController.prototype.createPiskelFromImages_ = function (images, w, h) { + var frames = this.createFramesFromImages_(images, w, h); var layer = pskl.model.Layer.fromFrames('Layer 1', frames); var descriptor = new pskl.model.piskel.Descriptor('Imported piskel', ''); var piskel = pskl.model.Piskel.fromLayers([layer], descriptor); @@ -175,38 +236,52 @@ pskl.app.previewController.setFPS(Constants.DEFAULT.FPS); }; - ns.ImportImageController.prototype.drawFramesGrid_ = function () { + ns.ImportImageController.prototype.drawFrameGrid_ = function (frameX, frameY, frameW, frameH) { + if (!this.importedImage_) { + return; + } + + // Grab the sizes of the source and preview images + var width = this.importedImage_.width; + var height = this.importedImage_.height; + var previewWidth = this.importPreview.width(); + var previewHeight = this.importPreview.height(); + var canvasWrapper = this.importPreview.children('canvas'); - var countX = this.frameCountX.val(); - var countY = this.frameCountY.val(); - if (countX > 1 || countY > 1) { - var width = this.importPreview.width(); - var height = this.importPreview.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( + previewWidth + 1, + previewHeight + 1); + this.importPreview.append(canvas); + canvasWrapper = $(canvas); + } - 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(); - var context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - context.beginPath(); + // Calculate the number of whole frames + var countX = Math.floor((width - frameX) / frameW); + var countY = Math.floor((height - frameY) / frameH); + + if (countX > 0 && countY > 0) { + var scaleX = previewWidth / width; + var scaleY = previewHeight / height; + var maxWidth = countX * frameW + frameX; + var maxHeight = countY * frameH + frameY; // Draw the vertical lines - for (var x = 0.5; x < width + 1; x += frameW) { - context.moveTo(x, 0); - context.lineTo(x, height); + for (var x = frameX + 0.5; x < maxWidth + 1 && x < width + 1; x += frameW) { + context.moveTo(x * scaleX, frameY * scaleY); + context.lineTo(x * scaleX, maxHeight * scaleY); } // Draw the horizontal lines - for (var y = 0.5; y < height + 1; y += frameH) { - context.moveTo(0, y); - context.lineTo(width, y); + for (var y = frameY + 0.5; y < maxHeight + 1 && y < height + 1; y += frameH) { + context.moveTo(frameX * scaleX, y * scaleY); + context.lineTo(maxWidth * scaleX, y * scaleY); } // Set the line style to dashed @@ -215,13 +290,17 @@ context.strokeStyle = '#000000'; context.stroke(); - // Resize the canvas so that it matches the preview height and stretches correctly - canvasWrapper.height(PREVIEW_HEIGHT + 1); + // Show the canvas canvasWrapper.show(); this.importPreview.addClass('no-border'); } else { - canvasWrapper.hide(); - this.importPreview.removeClass('no-border'); + this.hideFrameGrid_(); } }; + + ns.ImportImageController.prototype.hideFrameGrid_ = function() { + this.importPreview.children('canvas').hide(); + this.importPreview.removeClass('no-border'); + }; + })(); diff --git a/src/js/utils/CanvasUtils.js b/src/js/utils/CanvasUtils.js index 2b8101c1..4f81c198 100644 --- a/src/js/utils/CanvasUtils.js +++ b/src/js/utils/CanvasUtils.js @@ -35,38 +35,61 @@ /** * Splits the specified image into several new canvas elements based on the - * supplied horizontal (x) and vertical (y) counts. + * supplied offset and frame sizes * @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 + * @param {Number} offsetX The padding from the left side of the source image + * @param {Number} offsetY The padding from the top side of the source image + * @param {Number} width The width of an individual frame + * @param {Number} height The height of an individual frame + * @param {Boolean} useHorizonalStrips True if the frames should be layed out from left to + * right, False if it should use top to bottom + * @param {Boolean} ignoreEmptyFrames True to ignore empty frames, false to keep them * @returns {Array} An array of canvas elements that contain the split frames */ - createFramesFromImage : function (image, frameCountX, frameCountY) { + createFramesFromImage : function (image, offsetX, offsetY, width, height, useHorizonalStrips, ignoreEmptyFrames) { var canvasArray = []; - var frameWidth = image.width / frameCountX; - var frameHeight = image.height / frameCountY; + var x = offsetX; + var y = offsetY; + var blankData = pskl.utils.CanvasUtils.createCanvas(width, height).toDataURL(); - // 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'); + while (x < image.width && y < image.height) { + // Create a new canvas element + var canvas = pskl.utils.CanvasUtils.createCanvas(width, height); + 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); + // Blit the correct part of the source image into the new canvas + context.drawImage( + image, + x, + y, + width, + height, + 0, + 0, + width, + height); + if (!ignoreEmptyFrames || canvas.toDataURL() !== blankData) { canvasArray.push(canvas); } + + if (useHorizonalStrips) { + // Move from left to right + x += width; + if (x + width > image.width) { + x = offsetX; + y += height; + } + } else { + // Move from top to bottom + y += height; + if (y + height > image.height) { + x += width; + y = offsetY; + } + } } + return canvasArray; }, diff --git a/src/templates/dialogs/import-image.html b/src/templates/dialogs/import-image.html index e4a1b71e..e7dcad1d 100644 --- a/src/templates/dialogs/import-image.html +++ b/src/templates/dialogs/import-image.html @@ -9,24 +9,37 @@ Name :