From 0d1775b24cd125151725147baf879b0ec5b1b493 Mon Sep 17 00:00:00 2001 From: juliandescottes Date: Sun, 16 Sep 2012 18:48:32 +0200 Subject: [PATCH 1/3] Dynamic size for frame --- css/preview-animation-section.css | 2 +- js/Constants.js | 7 + js/Events.js | 2 + js/controller/AnimatedPreviewController.js | 83 ++++-- js/controller/DrawingController.js | 76 ++++- js/controller/PreviewFilmController.js | 331 +++++++++++---------- js/model/FrameSheet.js | 10 +- js/piskel.js | 134 +++------ js/rendering/FrameRenderer.js | 6 - js/service/LocalStorageService.js | 4 +- js/utils/pixelUtils.js | 292 +++++++++--------- 11 files changed, 508 insertions(+), 439 deletions(-) diff --git a/css/preview-animation-section.css b/css/preview-animation-section.css index d970b9c0..a4203aa0 100644 --- a/css/preview-animation-section.css +++ b/css/preview-animation-section.css @@ -2,7 +2,7 @@ .preview-container { position : absolute; bottom : 0; right : 0; - height : 256px; + height : 285px; width : 256px; background : white; border : 0px Solid black; diff --git a/js/Constants.js b/js/Constants.js index 1f676f0e..81dca60e 100644 --- a/js/Constants.js +++ b/js/Constants.js @@ -1,4 +1,11 @@ var Constants = { + DEFAULT_SIZE : { + height : 32, + width : 32 + }, + + MAX_HEIGHT : 128, + MAX_WIDTH : 128, DEFAULT_PEN_COLOR : '#000000', TRANSPARENT_COLOR : 'TRANSPARENT', diff --git a/js/Events.js b/js/Events.js index 7afb8486..3f6d3757 100644 --- a/js/Events.js +++ b/js/Events.js @@ -35,6 +35,8 @@ Events = { */ FRAMESHEET_RESET: "FRAMESHEET_RESET", + FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED", + CURRENT_FRAME_SET: "CURRENT_FRAME_SET", SELECTION_CREATED: "SELECTION_CREATED", diff --git a/js/controller/AnimatedPreviewController.js b/js/controller/AnimatedPreviewController.js index 76a6464e..df8db021 100644 --- a/js/controller/AnimatedPreviewController.js +++ b/js/controller/AnimatedPreviewController.js @@ -1,40 +1,57 @@ (function () { - var ns = $.namespace("pskl.controller"); - ns.AnimatedPreviewController = function (framesheet, container, dpi) { - this.framesheet = framesheet; - this.container = container; + var ns = $.namespace("pskl.controller"); + ns.AnimatedPreviewController = function (framesheet, container, dpi) { + this.framesheet = framesheet; + this.container = container; - this.elapsedTime = 0; - this.currentIndex = 0; + this.elapsedTime = 0; + this.currentIndex = 0; - this.fps = parseInt($("#preview-fps")[0].value, 10); - - var renderingOptions = { - "dpi": dpi - }; - this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions); - }; + this.fps = parseInt($("#preview-fps")[0].value, 10); + + var renderingOptions = { + "dpi": this.calculateDPI_() + }; + this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions); - ns.AnimatedPreviewController.prototype.init = function () { - $("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this)); - }; - - ns.AnimatedPreviewController.prototype.onFPSSliderChange = function(evt) { - this.fps = parseInt($("#preview-fps")[0].value, 10); - $("#display-fps").html(this.fps + " FPS") - }; - - ns.AnimatedPreviewController.prototype.render = function (delta) { - this.elapsedTime += delta; - var index = Math.floor(this.elapsedTime / (1000/this.fps)); - if (index != this.currentIndex) { - this.currentIndex = index; - if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) { - this.currentIndex = 0; - this.elapsedTime = 0; - } - this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex)); - } + $.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this)); }; + ns.AnimatedPreviewController.prototype.init = function () { + $("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this)); + }; + + ns.AnimatedPreviewController.prototype.onFPSSliderChange = function(evt) { + this.fps = parseInt($("#preview-fps")[0].value, 10); + $("#display-fps").html(this.fps + " FPS") + }; + + ns.AnimatedPreviewController.prototype.render = function (delta) { + this.elapsedTime += delta; + var index = Math.floor(this.elapsedTime / (1000/this.fps)); + if (index != this.currentIndex) { + this.currentIndex = index; + if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) { + this.currentIndex = 0; + this.elapsedTime = 0; + } + this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex)); + } + }; + + /** + * Calculate the preview DPI depending on the framesheet size + */ + ns.AnimatedPreviewController.prototype.calculateDPI_ = function () { + var framePixelHeight = this.framesheet.getCurrentFrame().getHeight(), + framePixelWidth = this.framesheet.getCurrentFrame().getWidth(); + // TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?) + + return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth); + }; + + ns.AnimatedPreviewController.prototype.updateDPI_ = function () { + this.dpi = this.calculateDPI_(); + this.renderer.updateDPI(this.dpi); + } })(); \ No newline at end of file diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 7e300f26..7e2303b9 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -1,12 +1,6 @@ (function () { var ns = $.namespace("pskl.controller"); - ns.DrawingController = function (framesheet, container, dpi) { - // TODO(vincz): Store user prefs in a localstorage string ? - var renderingOptions = { - "dpi": dpi, - "hasGrid" : true - }; - + ns.DrawingController = function (framesheet, container) { /** * @public */ @@ -21,6 +15,12 @@ * @private */ this.container = container; + + // TODO(vincz): Store user prefs in a localstorage string ? + var renderingOptions = { + "dpi": this.calculateDPI_(), + "hasGrid" : true + }; this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "drawing-canvas"); this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay"); @@ -51,6 +51,11 @@ this.secondaryColor = color; } }, this)); + + $(window).resize($.proxy(this.startDPIUpdateTimer_, this)); + + $.subscribe(Events.FRAME_SIZE_CHANGED, $.proxy(this.updateDPI_, this)); + $.subscribe(Events.GRID_DISPLAY_STATE_CHANGED, $.proxy(this.forceRendering_, this)); }; ns.DrawingController.prototype.initMouseBehavior = function() { @@ -63,6 +68,13 @@ body.contextmenu(this.onCanvasContextMenu_); }; + + + ns.DrawingController.prototype.startDPIUpdateTimer_ = function () { + if (this.dpiUpdateTimer) window.clearInterval(this.dpiUpdateTimer); + this.dpiUpdateTimer = window.setTimeout($.proxy(this.updateDPI_, this), 200); + }, + /** * @private */ @@ -185,12 +197,6 @@ event.cancelBubble = true; return false; }; - - ns.DrawingController.prototype.updateDPI = function (newDPI) { - this.renderer.updateDPI(newDPI); - this.overlayRenderer.updateDPI(newDPI); - this.forceRendering_(); - }; ns.DrawingController.prototype.render = function () { this.renderFrame(); @@ -199,6 +205,8 @@ ns.DrawingController.prototype.renderFrame = function () { var frame = this.framesheet.getCurrentFrame(); + console.log("render", frame.getHeight()); + if (frame.getHeight() == 32) debugger; var serializedFrame = frame.serialize(); if (this.serializedFrame != serializedFrame) { this.serializedFrame = serializedFrame; @@ -216,5 +224,45 @@ ns.DrawingController.prototype.forceRendering_ = function () { this.serializedFrame = this.serializedOverlay = null; - } + }; + + /** + * @private + */ + ns.DrawingController.prototype.calculateDPI_ = function() { + var userMessageGap = 80; // Reserve some height to show the user message at the bottom + + var availableViewportHeight = $('.main-panel').height() - userMessageGap, + availableViewportWidth = $('.main-panel').width(), + previewHeight = $(".preview-container").height(), + previewWidth = $(".preview-container").width(), + framePixelHeight = this.framesheet.getCurrentFrame().getHeight(), + framePixelWidth = this.framesheet.getCurrentFrame().getWidth(); + var dpi = pskl.PixelUtils.calculateDPI(availableViewportHeight, availableViewportWidth, framePixelHeight, framePixelWidth); + + var drawingCanvasHeight = dpi * framePixelHeight; + var drawingCanvasWidth = dpi * framePixelWidth; + + // Check if preview and drawing canvas overlap + var heightGap = drawingCanvasHeight + previewHeight - availableViewportHeight, + widthGap = drawingCanvasWidth + previewWidth - availableViewportWidth; + if (heightGap > 0 && widthGap > 0) { + // Calculate the DPI change needed to bridge height and width gap + var gapDPI = pskl.PixelUtils.calculateDPI(heightGap, widthGap, framePixelHeight, framePixelWidth); + // substract gap dpi to initial dpi + dpi -= (gapDPI - 1); + } + return dpi; + }; + + /** + * @private + */ + ns.DrawingController.prototype.updateDPI_ = function() { + var dpi = this.calculateDPI_(); + console.log("dpi", dpi); + this.renderer.updateDPI(dpi); + this.overlayRenderer.updateDPI(dpi); + this.forceRendering_(); + }; })(); \ No newline at end of file diff --git a/js/controller/PreviewFilmController.js b/js/controller/PreviewFilmController.js index 626ad8fd..d4b4e4f0 100644 --- a/js/controller/PreviewFilmController.js +++ b/js/controller/PreviewFilmController.js @@ -1,226 +1,243 @@ (function () { - var ns = $.namespace("pskl.controller"); - ns.PreviewFilmController = function (framesheet, container, dpi) { + var ns = $.namespace("pskl.controller"); + ns.PreviewFilmController = function (framesheet, container, dpi) { - this.dpi = dpi; - this.framesheet = framesheet; - this.container = container; + this.framesheet = framesheet; + this.container = container; + this.dpi = this.calculateDPI_(); - this.redrawFlag = true; + this.redrawFlag = true; - $.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this)); - $.subscribe(Events.FRAMESHEET_RESET, this.flagForRedraw_.bind(this)); - }; + $.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this)); + $.subscribe(Events.FRAMESHEET_RESET, this.flagForRedraw_.bind(this)); + $.subscribe(Events.FRAMESHEET_RESET, this.refreshDPI_.bind(this)); + }; - ns.PreviewFilmController.prototype.init = function() { - var addFrameButton = $('#add-frame-button')[0]; - addFrameButton.addEventListener('mousedown', this.addFrame.bind(this)); + ns.PreviewFilmController.prototype.init = function() { + var addFrameButton = $('#add-frame-button')[0]; + addFrameButton.addEventListener('mousedown', this.addFrame.bind(this)); }; ns.PreviewFilmController.prototype.addFrame = function () { - this.framesheet.addEmptyFrame(); + this.framesheet.addEmptyFrame(); this.framesheet.setCurrentFrameIndex(this.framesheet.getFrameCount() - 1); }; ns.PreviewFilmController.prototype.flagForRedraw_ = function () { - this.redrawFlag = true; + this.redrawFlag = true; + }; + + ns.PreviewFilmController.prototype.refreshDPI_ = function () { + this.dpi = this.calculateDPI_(); }; ns.PreviewFilmController.prototype.render = function () { - if (this.redrawFlag) { - // TODO(vincz): Full redraw on any drawing modification, optimize. - this.createPreviews_(); - this.redrawFlag = false; - } + if (this.redrawFlag) { + // TODO(vincz): Full redraw on any drawing modification, optimize. + this.createPreviews_(); + this.redrawFlag = false; + } }; ns.PreviewFilmController.prototype.createPreviews_ = function () { - - this.container.html(""); - // Manually remove tooltips since mouseout events were shortcut by the DOM refresh: - $(".tooltip").remove(); + + this.container.html(""); + // Manually remove tooltips since mouseout events were shortcut by the DOM refresh: + $(".tooltip").remove(); - var frameCount = this.framesheet.getFrameCount(); + var frameCount = this.framesheet.getFrameCount(); - for (var i = 0, l = frameCount; i < l ; i++) { - this.container.append(this.createInterstitialTile_(i)); - this.container.append(this.createPreviewTile_(i)); - } - this.container.append(this.createInterstitialTile_(frameCount)); + for (var i = 0, l = frameCount; i < l ; i++) { + this.container.append(this.createInterstitialTile_(i)); + this.container.append(this.createPreviewTile_(i)); + } + this.container.append(this.createInterstitialTile_(frameCount)); - var needDragndropBehavior = !!(frameCount > 1); - if(needDragndropBehavior) { - this.initDragndropBehavior_(); - } + var needDragndropBehavior = !!(frameCount > 1); + if(needDragndropBehavior) { + this.initDragndropBehavior_(); + } }; /** * @private */ ns.PreviewFilmController.prototype.createInterstitialTile_ = function (tileNumber) { - var interstitialTile = document.createElement("div"); - interstitialTile.className = "interstitial-tile" - interstitialTile.setAttribute("data-tile-type", "interstitial"); - interstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber); + var interstitialTile = document.createElement("div"); + interstitialTile.className = "interstitial-tile" + interstitialTile.setAttribute("data-tile-type", "interstitial"); + interstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber); - return interstitialTile; + return interstitialTile; }; /** * @private */ ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () { - var tiles = $(".preview-tile"); - // Each preview film tile is draggable. - tiles.draggable( { - //containment: '.left-nav', - stack: '.preview-tile', - cursor: 'move', - revert: true, - start: function(event, ui) { - // We only show the fake interstitial tiles when starting the - // drag n drop interaction. We hide them when the DnD is done. - $('#preview-list').addClass("show-interstitial-tiles"); - }, - stop: function() { - $('#preview-list').removeClass("show-interstitial-tiles"); - } - }); + var tiles = $(".preview-tile"); + // Each preview film tile is draggable. + tiles.draggable( { + //containment: '.left-nav', + stack: '.preview-tile', + cursor: 'move', + revert: true, + start: function(event, ui) { + // We only show the fake interstitial tiles when starting the + // drag n drop interaction. We hide them when the DnD is done. + $('#preview-list').addClass("show-interstitial-tiles"); + }, + stop: function() { + $('#preview-list').removeClass("show-interstitial-tiles"); + } + }); - // Each preview film tile is a drop target. This allow us to swap two tiles. - // However, we want to be able to insert a tile between two other tiles. - // For that we created fake interstitial tiles that are used as drop targets as well. - var droppableTiles = $(".interstitial-tile"); - $.merge(droppableTiles, tiles); + // Each preview film tile is a drop target. This allow us to swap two tiles. + // However, we want to be able to insert a tile between two other tiles. + // For that we created fake interstitial tiles that are used as drop targets as well. + var droppableTiles = $(".interstitial-tile"); + $.merge(droppableTiles, tiles); - droppableTiles.droppable( { - accept: ".preview-tile", - tolerance: "pointer", - activeClass: "droppable-active", - hoverClass: "droppable-hover-active", - drop: $.proxy(this.onDrop_, this) - }); + droppableTiles.droppable( { + accept: ".preview-tile", + tolerance: "pointer", + activeClass: "droppable-active", + hoverClass: "droppable-hover-active", + drop: $.proxy(this.onDrop_, this) + }); }; /** * @private */ ns.PreviewFilmController.prototype.onDrop_ = function( event, ui ) { - var activeFrame; - // When we drag from an element, the drag could start from a nested DOM element - // inside the drag target. We normalize that by taking the correct ancestor: - var originTile = $(event.srcElement).closest(".preview-tile"); - var originFrameId = parseInt(originTile.data("tile-number"), 10); + var activeFrame; + // When we drag from an element, the drag could start from a nested DOM element + // inside the drag target. We normalize that by taking the correct ancestor: + var originTile = $(event.srcElement).closest(".preview-tile"); + var originFrameId = parseInt(originTile.data("tile-number"), 10); - var dropTarget = $(event.target); - if(dropTarget.data("tile-type") == "interstitial") { - var targetInsertionId = parseInt(dropTarget.data("inject-drop-tile-at"), 10); - // In case we drop outside of the tile container - if(isNaN(originFrameId) || isNaN(targetInsertionId)) { - return; - } - //console.log("origin-frame: "+originFrameId+" - targetInsertionId: "+ targetInsertionId) - this.framesheet.moveFrame(originFrameId, targetInsertionId); - - activeFrame = targetInsertionId; - // The last fake interstitial tile is outside of the framesheet array bound. - // It allow us to append after the very last element in this fake slot. - // However, when setting back the active frame, we have to make sure the - // frame does exist. - if(activeFrame > (this.framesheet.getFrameCount() - 1)) { - activeFrame = targetInsertionId - 1; - } - } else { - var targetSwapId = parseInt(dropTarget.data("tile-number"), 10); - // In case we drop outside of the tile container - if(isNaN(originFrameId) || isNaN(targetSwapId)) { - return; - } - //console.log("origin-frame: "+originFrameId+" - targetSwapId: "+ targetSwapId) - this.framesheet.swapFrames(originFrameId, targetSwapId); - activeFrame = targetSwapId; - } + var dropTarget = $(event.target); + if(dropTarget.data("tile-type") == "interstitial") { + var targetInsertionId = parseInt(dropTarget.data("inject-drop-tile-at"), 10); + // In case we drop outside of the tile container + if(isNaN(originFrameId) || isNaN(targetInsertionId)) { + return; + } + //console.log("origin-frame: "+originFrameId+" - targetInsertionId: "+ targetInsertionId) + this.framesheet.moveFrame(originFrameId, targetInsertionId); + + activeFrame = targetInsertionId; + // The last fake interstitial tile is outside of the framesheet array bound. + // It allow us to append after the very last element in this fake slot. + // However, when setting back the active frame, we have to make sure the + // frame does exist. + if(activeFrame > (this.framesheet.getFrameCount() - 1)) { + activeFrame = targetInsertionId - 1; + } + } else { + var targetSwapId = parseInt(dropTarget.data("tile-number"), 10); + // In case we drop outside of the tile container + if(isNaN(originFrameId) || isNaN(targetSwapId)) { + return; + } + //console.log("origin-frame: "+originFrameId+" - targetSwapId: "+ targetSwapId) + this.framesheet.swapFrames(originFrameId, targetSwapId); + activeFrame = targetSwapId; + } - - $('#preview-list').removeClass("show-interstitial-tiles"); + + $('#preview-list').removeClass("show-interstitial-tiles"); - this.framesheet.setCurrentFrameIndex(activeFrame); + this.framesheet.setCurrentFrameIndex(activeFrame); - // TODO(vincz): move localstorage request to the model layer? - $.publish(Events.LOCALSTORAGE_REQUEST); - }; + // TODO(vincz): move localstorage request to the model layer? + $.publish(Events.LOCALSTORAGE_REQUEST); + }; - /** + /** * @private * TODO(vincz): clean this giant rendering function & remove listeners. */ ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) { - var currentFrame = this.framesheet.getFrameByIndex(tileNumber); - - var previewTileRoot = document.createElement("li"); - var classname = "preview-tile"; - previewTileRoot.setAttribute("data-tile-number", tileNumber); + var currentFrame = this.framesheet.getFrameByIndex(tileNumber); + + var previewTileRoot = document.createElement("li"); + var classname = "preview-tile"; + previewTileRoot.setAttribute("data-tile-number", tileNumber); - if (this.framesheet.getCurrentFrame() == currentFrame) { - classname += " selected"; - } - previewTileRoot.className = classname; + if (this.framesheet.getCurrentFrame() == currentFrame) { + classname += " selected"; + } + previewTileRoot.className = classname; - var canvasContainer = document.createElement("div"); - canvasContainer.className = "canvas-container"; - - var canvasBackground = document.createElement("div"); - canvasBackground.className = "canvas-background"; - canvasContainer.appendChild(canvasBackground); - - previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber)); + var canvasContainer = document.createElement("div"); + canvasContainer.className = "canvas-container"; + + var canvasBackground = document.createElement("div"); + canvasBackground.className = "canvas-background"; + canvasContainer.appendChild(canvasBackground); + + previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber)); - var canvasPreviewDuplicateAction = document.createElement("button"); - canvasPreviewDuplicateAction.setAttribute('rel', 'tooltip'); - canvasPreviewDuplicateAction.setAttribute('data-placement', 'right'); - canvasPreviewDuplicateAction.setAttribute('title', 'Duplicate this frame'); - canvasPreviewDuplicateAction.className = "tile-action duplicate-frame-action" - - canvasPreviewDuplicateAction.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber)); + var canvasPreviewDuplicateAction = document.createElement("button"); + canvasPreviewDuplicateAction.setAttribute('rel', 'tooltip'); + canvasPreviewDuplicateAction.setAttribute('data-placement', 'right'); + canvasPreviewDuplicateAction.setAttribute('title', 'Duplicate this frame'); + canvasPreviewDuplicateAction.className = "tile-action duplicate-frame-action" + + canvasPreviewDuplicateAction.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber)); - // TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim - // is to make this update function (#createPreviewTile) less aggressive. - var renderingOptions = {"dpi": this.dpi }; - var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view"); - currentFrameRenderer.init(currentFrame); - - previewTileRoot.appendChild(canvasContainer); - previewTileRoot.appendChild(canvasPreviewDuplicateAction); + // TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim + // is to make this update function (#createPreviewTile) less aggressive. + var renderingOptions = {"dpi": this.dpi }; + var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view"); + currentFrameRenderer.init(currentFrame); + + previewTileRoot.appendChild(canvasContainer); + previewTileRoot.appendChild(canvasPreviewDuplicateAction); - if(tileNumber > 0 || this.framesheet.getFrameCount() > 1) { - var canvasPreviewDeleteAction = document.createElement("button"); - canvasPreviewDeleteAction.setAttribute('rel', 'tooltip'); - canvasPreviewDeleteAction.setAttribute('data-placement', 'right'); - canvasPreviewDeleteAction.setAttribute('title', 'Delete this frame'); - canvasPreviewDeleteAction.className = "tile-action delete-frame-action" - canvasPreviewDeleteAction.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber)); - previewTileRoot.appendChild(canvasPreviewDeleteAction); - } + if(tileNumber > 0 || this.framesheet.getFrameCount() > 1) { + var canvasPreviewDeleteAction = document.createElement("button"); + canvasPreviewDeleteAction.setAttribute('rel', 'tooltip'); + canvasPreviewDeleteAction.setAttribute('data-placement', 'right'); + canvasPreviewDeleteAction.setAttribute('title', 'Delete this frame'); + canvasPreviewDeleteAction.className = "tile-action delete-frame-action" + canvasPreviewDeleteAction.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber)); + previewTileRoot.appendChild(canvasPreviewDeleteAction); + } - return previewTileRoot; + return previewTileRoot; }; ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) { - // has not class tile-action: - if(!evt.target.classList.contains('tile-action')) { - this.framesheet.setCurrentFrameIndex(index); - } + // has not class tile-action: + if(!evt.target.classList.contains('tile-action')) { + this.framesheet.setCurrentFrameIndex(index); + } }; ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) { - this.framesheet.removeFrameByIndex(index); - $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + this.framesheet.removeFrameByIndex(index); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model }; ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) { - this.framesheet.duplicateFrameByIndex(index); - $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model - this.framesheet.setCurrentFrameIndex(index + 1); + this.framesheet.duplicateFrameByIndex(index); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + this.framesheet.setCurrentFrameIndex(index + 1); + }; + + /** + * Calculate the preview DPI depending on the framesheet size + */ + ns.PreviewFilmController.prototype.calculateDPI_ = function () { + var previewSize = 128, + framePixelHeight = this.framesheet.getCurrentFrame().getHeight(), + framePixelWidth = this.framesheet.getCurrentFrame().getWidth(); + // TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?) + + return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth); }; })(); \ No newline at end of file diff --git a/js/model/FrameSheet.js b/js/model/FrameSheet.js index 721c63b0..5fb0ca29 100644 --- a/js/model/FrameSheet.js +++ b/js/model/FrameSheet.js @@ -1,6 +1,6 @@ (function () { var ns = $.namespace("pskl.model"); - ns.FrameSheet = function (width, height) { + ns.FrameSheet = function (height, width) { this.width = width; this.height = height; this.frames = []; @@ -68,6 +68,14 @@ var frameCfg = frameConfigurations[i]; this.addFrame(new ns.Frame(frameCfg)); } + + if (this.hasFrameAtIndex(0)) { + this.height = this.getFrameByIndex(0).getHeight(); + this.width = this.getFrameByIndex(0).getWidth(); + this.setCurrentFrameIndex(0); + $.publish(Events.FRAME_SIZE_CHANGED); + } + $.publish(Events.FRAMESHEET_RESET); } catch (e) { throw "Could not load serialized framesheet : " + e.message diff --git a/js/piskel.js b/js/piskel.js index 8b62d505..0ab75452 100644 --- a/js/piskel.js +++ b/js/piskel.js @@ -9,46 +9,21 @@ $.namespace("pskl"); /** * FrameSheetModel instance. */ - var frameSheet, - - // Configuration: - // Canvas size in pixel size (not dpi related) - framePixelWidth = 32, - framePixelHeight = 32, - - // Scaling factors for a given frameSheet rendering: - // Main drawing area dpi is calculated dynamically - // Canvas preview film canvases: - previewTileCanvasDpi = 4, - // Animated canvas preview: - previewAnimationCanvasDpi = 8; - + var frameSheet; /** * Main application controller */ var piskel = { init : function () { - frameSheet = new pskl.model.FrameSheet(framePixelWidth, framePixelHeight); + var frameSize = this.readSizeFromURL_(); + frameSheet = new pskl.model.FrameSheet(frameSize.height, frameSize.width); + frameSheet.addEmptyFrame(); - this.drawingController = new pskl.controller.DrawingController( - frameSheet, - $('#drawing-canvas-container'), - this.calculateDPIsForDrawingCanvas_() - ); - - this.animationController = new pskl.controller.AnimatedPreviewController( - frameSheet, - $('#preview-canvas-container'), - previewAnimationCanvasDpi - ); - - this.previewsController = new pskl.controller.PreviewFilmController( - frameSheet, - $('#preview-list'), - previewTileCanvasDpi - ); + this.drawingController = new pskl.controller.DrawingController(frameSheet, $('#drawing-canvas-container')); + this.animationController = new pskl.controller.AnimatedPreviewController(frameSheet, $('#preview-canvas-container')); + this.previewsController = new pskl.controller.PreviewFilmController(frameSheet, $('#preview-list')); // To catch the current active frame, the selection manager have to be initialized before // the 'frameSheet.setCurrentFrameIndex(0);' line below. @@ -57,8 +32,7 @@ $.namespace("pskl"); // - an event(s) triggering init // All listeners will be hook in a first step, then all event triggering inits will be called // in a second batch. - this.selectionManager = - new pskl.selection.SelectionManager(frameSheet, this.drawingController.overlayFrame); + this.selectionManager = new pskl.selection.SelectionManager(frameSheet, this.drawingController.overlayFrame); // DO NOT MOVE THIS LINE (see comment above) frameSheet.setCurrentFrameIndex(0); @@ -79,7 +53,7 @@ $.namespace("pskl"); this.localStorageService.init(); // TODO: Add comments - var framesheetId = this.getFramesheetIdFromUrl(); + var framesheetId = this.readFramesheetIdFromURL_(); if (framesheetId) { $.publish(Events.SHOW_NOTIFICATION, [{"content": "Loading animation with id : [" + framesheetId + "]"}]); this.loadFramesheetFromService(framesheetId); @@ -96,64 +70,13 @@ $.namespace("pskl"); $('body').tooltip({ selector: '[rel=tooltip]' }); - - this.connectResizeToDpiUpdate_(); }, render : function (delta) { this.drawingController.render(delta); this.animationController.render(delta); this.previewsController.render(delta); - }, - - connectResizeToDpiUpdate_ : function () { - $(window).resize($.proxy(this.startDPIUpdateTimer_, this)); - }, - - startDPIUpdateTimer_ : function () { - if (this.dpiUpdateTimer) window.clearInterval(this.dpiUpdateTimer); - this.dpiUpdateTimer = window.setTimeout($.proxy(this.updateDPIForViewport, this), 200); - }, - - updateDPIForViewport : function () { - var dpi = piskel.calculateDPIsForDrawingCanvas_(); - this.drawingController.updateDPI(dpi); - }, - - /** - * @private - */ - calculateDPIsForDrawingCanvas_ : function() { - - var userMessageGap = 80; // Reserve some height to show the user message at the bottom - - var availableViewportHeight = $('.main-panel').height() - userMessageGap, - availableViewportWidth = $('.main-panel').width(), - previewHeight = $(".preview-container").height(), - previewWidth = $(".preview-container").width(); - - var heightBoundDpi = Math.floor(availableViewportHeight / framePixelHeight), - widthBoundDpi = Math.floor(availableViewportWidth / framePixelWidth); - - var dpi = Math.min(heightBoundDpi, widthBoundDpi); - - var drawingCanvasHeight = dpi * framePixelHeight; - var drawingCanvasWidth = dpi * framePixelWidth; - - // Check if preview and drawing canvas overlap - var heightGap = drawingCanvasHeight + previewHeight - availableViewportHeight, - widthGap = drawingCanvasWidth + previewWidth - availableViewportWidth; - if (heightGap > 0 && widthGap > 0) { - // Calculate the DPI change needed to bridge height and width gap - var heightGapDpi = Math.ceil(heightGap / framePixelHeight), - widthGapDpi = Math.ceil(widthGap / framePixelWidth); - - // substract smallest dpi change to initial dpi - dpi -= Math.min(heightGapDpi, widthGapDpi); - } - - return dpi; - }, + }, finishInit : function () { var toolController = new pskl.controller.ToolController(); @@ -163,12 +86,39 @@ $.namespace("pskl"); paletteController.init(frameSheet); }, - getFramesheetIdFromUrl : function() { - var href = window.location.href; - // TODO: Change frameId to framesheetId on the backend - if (href.indexOf('frameId=') != -1) { - return href.substring(href.indexOf('frameId=')+8); + readSizeFromURL_ : function () { + var sizeParam = this.readUrlParameter_("size"), size; + // parameter expected as size=64x48 => size=widthxheight + var parts = sizeParam.split("x"); + if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { + var width = parseInt(parts[0], 10), + height = parseInt(parts[1], 10); + + size = { + height : Math.min(height, Constants.MAX_HEIGHT), + width : Math.min(width, Constants.MAX_WIDTH), + }; + } else { + size = Constants.DEFAULT_SIZE; } + return size; + }, + + readFramesheetIdFromURL_ : function() { + return this.readUrlParameter_("frameId"); + }, + + readUrlParameter_ : function (paramName) { + var searchString = window.location.search.substring(1), + i, val, params = searchString.split("&"); + + for (i=0;i top left corner - {x1, y1} => bottom right corner - * @private - */ - getOrderedRectangleCoordinates : function (x0, y0, x1, y1) { - return { - x0 : Math.min(x0, x1), y0 : Math.min(y0, y1), - x1 : Math.max(x0, x1), y1 : Math.max(y0, y1), - }; - }, + /** + * Return an object of ordered rectangle coordinate. + * In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner + * @private + */ + getOrderedRectangleCoordinates : function (x0, y0, x1, y1) { + return { + x0 : Math.min(x0, x1), y0 : Math.min(y0, y1), + x1 : Math.max(x0, x1), y1 : Math.max(y0, y1), + }; + }, - /** - * Return the list of pixels that would have been filled by a paintbucket tool applied - * on pixel at coordinate (x,y). - * This function is not altering the Frame object argument. - * - * @param frame pskl.model.Frame The frame target in which we want to paintbucket - * @param col number Column coordinate in the frame - * @param row number Row coordinate in the frame - * - * @return an array of the pixel coordinates paint with the replacement color - */ - getSimilarConnectedPixelsFromFrame: function(frame, col, row) { - // To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm - // in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels - // and are as well connected. - var fakeFrame = frame.clone(); // We just want to - var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color. - var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor); + /** + * Return the list of pixels that would have been filled by a paintbucket tool applied + * on pixel at coordinate (x,y). + * This function is not altering the Frame object argument. + * + * @param frame pskl.model.Frame The frame target in which we want to paintbucket + * @param col number Column coordinate in the frame + * @param row number Row coordinate in the frame + * + * @return an array of the pixel coordinates paint with the replacement color + */ + getSimilarConnectedPixelsFromFrame: function(frame, col, row) { + // To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm + // in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels + // and are as well connected. + var fakeFrame = frame.clone(); // We just want to + var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color. + var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor); - return paintedPixels; - }, + return paintedPixels; + }, - /** - * Apply the paintbucket tool in a frame at the (col, row) initial position - * with the replacement color. - * - * @param frame pskl.model.Frame The frame target in which we want to paintbucket - * @param col number Column coordinate in the frame - * @param row number Row coordinate in the frame - * @param replacementColor string Hexadecimal color used to fill the area - * - * @return an array of the pixel coordinates paint with the replacement color - */ - paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) { - /** - * Queue linear Flood-fill (node, target-color, replacement-color): - * 1. Set Q to the empty queue. - * 2. If the color of node is not equal to target-color, return. - * 3. Add node to Q. - * 4. For each element n of Q: - * 5. If the color of n is equal to target-color: - * 6. Set w and e equal to n. - * 7. Move w to the west until the color of the node to the west of w no longer matches target-color. - * 8. Move e to the east until the color of the node to the east of e no longer matches target-color. - * 9. Set the color of nodes between w and e to replacement-color. - * 10. For each node n between w and e: - * 11. If the color of the node to the north of n is target-color, add that node to Q. - * 12. If the color of the node to the south of n is target-color, add that node to Q. - * 13. Continue looping until Q is exhausted. - * 14. Return. - * - * @private - */ - var paintedPixels = []; - var queue = []; - var dy = [-1, 0, 1, 0]; - var dx = [0, 1, 0, -1]; - try { - var targetColor = frame.getPixel(col, row); - } catch(e) { - // Frame out of bound exception. - } - - if(targetColor == replacementColor) { - return; - } - + /** + * Apply the paintbucket tool in a frame at the (col, row) initial position + * with the replacement color. + * + * @param frame pskl.model.Frame The frame target in which we want to paintbucket + * @param col number Column coordinate in the frame + * @param row number Row coordinate in the frame + * @param replacementColor string Hexadecimal color used to fill the area + * + * @return an array of the pixel coordinates paint with the replacement color + */ + paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) { + /** + * Queue linear Flood-fill (node, target-color, replacement-color): + * 1. Set Q to the empty queue. + * 2. If the color of node is not equal to target-color, return. + * 3. Add node to Q. + * 4. For each element n of Q: + * 5. If the color of n is equal to target-color: + * 6. Set w and e equal to n. + * 7. Move w to the west until the color of the node to the west of w no longer matches target-color. + * 8. Move e to the east until the color of the node to the east of e no longer matches target-color. + * 9. Set the color of nodes between w and e to replacement-color. + * 10. For each node n between w and e: + * 11. If the color of the node to the north of n is target-color, add that node to Q. + * 12. If the color of the node to the south of n is target-color, add that node to Q. + * 13. Continue looping until Q is exhausted. + * 14. Return. + */ + var paintedPixels = []; + var queue = []; + var dy = [-1, 0, 1, 0]; + var dx = [0, 1, 0, -1]; + try { + var targetColor = frame.getPixel(col, row); + } catch(e) { + // Frame out of bound exception. + } + + if(targetColor == replacementColor) { + return; + } + - queue.push({"col": col, "row": row}); - var loopCount = 0; - var cellCount = frame.getWidth() * frame.getHeight(); - while(queue.length > 0) { - loopCount ++; + queue.push({"col": col, "row": row}); + var loopCount = 0; + var cellCount = frame.getWidth() * frame.getHeight(); + while(queue.length > 0) { + loopCount ++; - var currentItem = queue.pop(); - frame.setPixel(currentItem.col, currentItem.row, replacementColor); - paintedPixels.push({"col": currentItem.col, "row": currentItem.row }); + var currentItem = queue.pop(); + frame.setPixel(currentItem.col, currentItem.row, replacementColor); + paintedPixels.push({"col": currentItem.col, "row": currentItem.row }); - for (var i = 0; i < 4; i++) { - var nextCol = currentItem.col + dx[i] - var nextRow = currentItem.row + dy[i] - try { - if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) { - queue.push({"col": nextCol, "row": nextRow }); - } - } catch(e) { - // Frame out of bound exception. - } - } + for (var i = 0; i < 4; i++) { + var nextCol = currentItem.col + dx[i] + var nextRow = currentItem.row + dy[i] + try { + if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) { + queue.push({"col": nextCol, "row": nextRow }); + } + } catch(e) { + // Frame out of bound exception. + } + } - // Security loop breaker: - if(loopCount > 10 * cellCount) { - console.log("loop breaker called") - break; - } - } - return paintedPixels; - } - }; + // Security loop breaker: + if(loopCount > 10 * cellCount) { + console.log("loop breaker called") + break; + } + } + return paintedPixels; + }, + + /** + * Calculate and return the maximal DPI to display a picture in a given container. + * + * @param container jQueryObject Container where the picture should be displayed + * @param number pictureHeight height in pixels of the picture to display + * @param number pictureWidth width in pixels of the picture to display + * @return number maximal dpi + */ + calculateDPIForContainer : function (container, pictureHeight, pictureWidth) { + return this.calculateDPI(container.height(), container.width(), pictureHeight, pictureWidth); + }, + + /** + * Calculate and return the maximal DPI to display a picture for a given height and width. + * + * @param height number Height available to display the picture + * @param width number Width available to display the picture + * @param number pictureHeight height in pixels of the picture to display + * @param number pictureWidth width in pixels of the picture to display + * @return number maximal dpi + */ + calculateDPI : function (height, width, pictureHeight, pictureWidth) { + var heightBoundDpi = Math.floor(height / pictureHeight), + widthBoundDpi = Math.floor(width / pictureWidth); + + return Math.min(heightBoundDpi, widthBoundDpi); + }, + }; })(); \ No newline at end of file From 016649bb4690ede68766bec163742d4d41cc87c5 Mon Sep 17 00:00:00 2001 From: juliandescottes Date: Sun, 16 Sep 2012 23:34:00 +0200 Subject: [PATCH 2/3] Fixed overlap between drawing canvas and preview. --- css/preview-animation-section.css | 3 +-- js/controller/DrawingController.js | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/css/preview-animation-section.css b/css/preview-animation-section.css index a4203aa0..152ce32e 100644 --- a/css/preview-animation-section.css +++ b/css/preview-animation-section.css @@ -2,7 +2,7 @@ .preview-container { position : absolute; bottom : 0; right : 0; - height : 285px; + height : 282px; width : 256px; background : white; border : 0px Solid black; @@ -12,7 +12,6 @@ .preview-container canvas { border : 0px Solid transparent; - border-radius:5px 0px 0px 5px; } #preview-fps { diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 7e2303b9..3509ef2f 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -205,8 +205,6 @@ ns.DrawingController.prototype.renderFrame = function () { var frame = this.framesheet.getCurrentFrame(); - console.log("render", frame.getHeight()); - if (frame.getHeight() == 32) debugger; var serializedFrame = frame.serialize(); if (this.serializedFrame != serializedFrame) { this.serializedFrame = serializedFrame; @@ -250,7 +248,7 @@ // Calculate the DPI change needed to bridge height and width gap var gapDPI = pskl.PixelUtils.calculateDPI(heightGap, widthGap, framePixelHeight, framePixelWidth); // substract gap dpi to initial dpi - dpi -= (gapDPI - 1); + dpi -= (gapDPI + 1); } return dpi; }; From 916ba6ae6fcd5d03f48ca3f20c873e3c1d794a80 Mon Sep 17 00:00:00 2001 From: juliandescottes Date: Sun, 16 Sep 2012 23:47:37 +0200 Subject: [PATCH 3/3] removed extra comma --- js/piskel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/piskel.js b/js/piskel.js index 0ab75452..0b02c976 100644 --- a/js/piskel.js +++ b/js/piskel.js @@ -96,7 +96,7 @@ $.namespace("pskl"); size = { height : Math.min(height, Constants.MAX_HEIGHT), - width : Math.min(width, Constants.MAX_WIDTH), + width : Math.min(width, Constants.MAX_WIDTH) }; } else { size = Constants.DEFAULT_SIZE;