mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Merge branch 'master' into gh-pages
This commit is contained in:
commit
10a7fd4890
@ -2,7 +2,7 @@
|
|||||||
.preview-container {
|
.preview-container {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
bottom : 0; right : 0;
|
bottom : 0; right : 0;
|
||||||
height : 256px;
|
height : 282px;
|
||||||
width : 256px;
|
width : 256px;
|
||||||
background : white;
|
background : white;
|
||||||
border : 0px Solid black;
|
border : 0px Solid black;
|
||||||
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
.preview-container canvas {
|
.preview-container canvas {
|
||||||
border : 0px Solid transparent;
|
border : 0px Solid transparent;
|
||||||
border-radius:5px 0px 0px 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview-fps {
|
#preview-fps {
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
var Constants = {
|
var Constants = {
|
||||||
|
DEFAULT_SIZE : {
|
||||||
|
height : 32,
|
||||||
|
width : 32
|
||||||
|
},
|
||||||
|
|
||||||
|
MAX_HEIGHT : 128,
|
||||||
|
MAX_WIDTH : 128,
|
||||||
|
|
||||||
DEFAULT_PEN_COLOR : '#000000',
|
DEFAULT_PEN_COLOR : '#000000',
|
||||||
TRANSPARENT_COLOR : 'TRANSPARENT',
|
TRANSPARENT_COLOR : 'TRANSPARENT',
|
||||||
|
@ -35,6 +35,8 @@ Events = {
|
|||||||
*/
|
*/
|
||||||
FRAMESHEET_RESET: "FRAMESHEET_RESET",
|
FRAMESHEET_RESET: "FRAMESHEET_RESET",
|
||||||
|
|
||||||
|
FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED",
|
||||||
|
|
||||||
CURRENT_FRAME_SET: "CURRENT_FRAME_SET",
|
CURRENT_FRAME_SET: "CURRENT_FRAME_SET",
|
||||||
|
|
||||||
SELECTION_CREATED: "SELECTION_CREATED",
|
SELECTION_CREATED: "SELECTION_CREATED",
|
||||||
|
@ -1,40 +1,57 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace("pskl.controller");
|
var ns = $.namespace("pskl.controller");
|
||||||
ns.AnimatedPreviewController = function (framesheet, container, dpi) {
|
ns.AnimatedPreviewController = function (framesheet, container, dpi) {
|
||||||
this.framesheet = framesheet;
|
this.framesheet = framesheet;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
this.elapsedTime = 0;
|
this.elapsedTime = 0;
|
||||||
this.currentIndex = 0;
|
this.currentIndex = 0;
|
||||||
|
|
||||||
this.fps = parseInt($("#preview-fps")[0].value, 10);
|
this.fps = parseInt($("#preview-fps")[0].value, 10);
|
||||||
|
|
||||||
var renderingOptions = {
|
var renderingOptions = {
|
||||||
"dpi": dpi
|
"dpi": this.calculateDPI_()
|
||||||
};
|
};
|
||||||
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);
|
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);
|
||||||
};
|
|
||||||
|
|
||||||
ns.AnimatedPreviewController.prototype.init = function () {
|
$.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this));
|
||||||
$("#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));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
})();
|
})();
|
@ -1,12 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace("pskl.controller");
|
var ns = $.namespace("pskl.controller");
|
||||||
ns.DrawingController = function (framesheet, container, dpi) {
|
ns.DrawingController = function (framesheet, container) {
|
||||||
// TODO(vincz): Store user prefs in a localstorage string ?
|
|
||||||
var renderingOptions = {
|
|
||||||
"dpi": dpi,
|
|
||||||
"hasGrid" : true
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -22,6 +16,12 @@
|
|||||||
*/
|
*/
|
||||||
this.container = container;
|
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.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "drawing-canvas");
|
||||||
this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay");
|
this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay");
|
||||||
|
|
||||||
@ -51,6 +51,11 @@
|
|||||||
this.secondaryColor = color;
|
this.secondaryColor = color;
|
||||||
}
|
}
|
||||||
}, this));
|
}, 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() {
|
ns.DrawingController.prototype.initMouseBehavior = function() {
|
||||||
@ -63,6 +68,13 @@
|
|||||||
body.contextmenu(this.onCanvasContextMenu_);
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -186,12 +198,6 @@
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.DrawingController.prototype.updateDPI = function (newDPI) {
|
|
||||||
this.renderer.updateDPI(newDPI);
|
|
||||||
this.overlayRenderer.updateDPI(newDPI);
|
|
||||||
this.forceRendering_();
|
|
||||||
};
|
|
||||||
|
|
||||||
ns.DrawingController.prototype.render = function () {
|
ns.DrawingController.prototype.render = function () {
|
||||||
this.renderFrame();
|
this.renderFrame();
|
||||||
this.renderOverlay();
|
this.renderOverlay();
|
||||||
@ -216,5 +222,45 @@
|
|||||||
|
|
||||||
ns.DrawingController.prototype.forceRendering_ = function () {
|
ns.DrawingController.prototype.forceRendering_ = function () {
|
||||||
this.serializedFrame = this.serializedOverlay = null;
|
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_();
|
||||||
|
};
|
||||||
})();
|
})();
|
@ -1,226 +1,243 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace("pskl.controller");
|
var ns = $.namespace("pskl.controller");
|
||||||
ns.PreviewFilmController = function (framesheet, container, dpi) {
|
ns.PreviewFilmController = function (framesheet, container, dpi) {
|
||||||
|
|
||||||
this.dpi = dpi;
|
this.framesheet = framesheet;
|
||||||
this.framesheet = framesheet;
|
this.container = container;
|
||||||
this.container = container;
|
this.dpi = this.calculateDPI_();
|
||||||
|
|
||||||
this.redrawFlag = true;
|
this.redrawFlag = true;
|
||||||
|
|
||||||
$.subscribe(Events.TOOL_RELEASED, 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.flagForRedraw_.bind(this));
|
||||||
};
|
$.subscribe(Events.FRAMESHEET_RESET, this.refreshDPI_.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.init = function() {
|
ns.PreviewFilmController.prototype.init = function() {
|
||||||
var addFrameButton = $('#add-frame-button')[0];
|
var addFrameButton = $('#add-frame-button')[0];
|
||||||
addFrameButton.addEventListener('mousedown', this.addFrame.bind(this));
|
addFrameButton.addEventListener('mousedown', this.addFrame.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.addFrame = function () {
|
ns.PreviewFilmController.prototype.addFrame = function () {
|
||||||
this.framesheet.addEmptyFrame();
|
this.framesheet.addEmptyFrame();
|
||||||
this.framesheet.setCurrentFrameIndex(this.framesheet.getFrameCount() - 1);
|
this.framesheet.setCurrentFrameIndex(this.framesheet.getFrameCount() - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.flagForRedraw_ = function () {
|
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 () {
|
ns.PreviewFilmController.prototype.render = function () {
|
||||||
if (this.redrawFlag) {
|
if (this.redrawFlag) {
|
||||||
// TODO(vincz): Full redraw on any drawing modification, optimize.
|
// TODO(vincz): Full redraw on any drawing modification, optimize.
|
||||||
this.createPreviews_();
|
this.createPreviews_();
|
||||||
this.redrawFlag = false;
|
this.redrawFlag = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.createPreviews_ = function () {
|
ns.PreviewFilmController.prototype.createPreviews_ = function () {
|
||||||
|
|
||||||
this.container.html("");
|
this.container.html("");
|
||||||
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
|
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
|
||||||
$(".tooltip").remove();
|
$(".tooltip").remove();
|
||||||
|
|
||||||
var frameCount = this.framesheet.getFrameCount();
|
var frameCount = this.framesheet.getFrameCount();
|
||||||
|
|
||||||
for (var i = 0, l = frameCount; i < l ; i++) {
|
for (var i = 0, l = frameCount; i < l ; i++) {
|
||||||
this.container.append(this.createInterstitialTile_(i));
|
this.container.append(this.createInterstitialTile_(i));
|
||||||
this.container.append(this.createPreviewTile_(i));
|
this.container.append(this.createPreviewTile_(i));
|
||||||
}
|
}
|
||||||
this.container.append(this.createInterstitialTile_(frameCount));
|
this.container.append(this.createInterstitialTile_(frameCount));
|
||||||
|
|
||||||
var needDragndropBehavior = !!(frameCount > 1);
|
var needDragndropBehavior = !!(frameCount > 1);
|
||||||
if(needDragndropBehavior) {
|
if(needDragndropBehavior) {
|
||||||
this.initDragndropBehavior_();
|
this.initDragndropBehavior_();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ns.PreviewFilmController.prototype.createInterstitialTile_ = function (tileNumber) {
|
ns.PreviewFilmController.prototype.createInterstitialTile_ = function (tileNumber) {
|
||||||
var interstitialTile = document.createElement("div");
|
var interstitialTile = document.createElement("div");
|
||||||
interstitialTile.className = "interstitial-tile"
|
interstitialTile.className = "interstitial-tile"
|
||||||
interstitialTile.setAttribute("data-tile-type", "interstitial");
|
interstitialTile.setAttribute("data-tile-type", "interstitial");
|
||||||
interstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber);
|
interstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber);
|
||||||
|
|
||||||
return interstitialTile;
|
return interstitialTile;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () {
|
ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () {
|
||||||
var tiles = $(".preview-tile");
|
var tiles = $(".preview-tile");
|
||||||
// Each preview film tile is draggable.
|
// Each preview film tile is draggable.
|
||||||
tiles.draggable( {
|
tiles.draggable( {
|
||||||
//containment: '.left-nav',
|
//containment: '.left-nav',
|
||||||
stack: '.preview-tile',
|
stack: '.preview-tile',
|
||||||
cursor: 'move',
|
cursor: 'move',
|
||||||
revert: true,
|
revert: true,
|
||||||
start: function(event, ui) {
|
start: function(event, ui) {
|
||||||
// We only show the fake interstitial tiles when starting the
|
// We only show the fake interstitial tiles when starting the
|
||||||
// drag n drop interaction. We hide them when the DnD is done.
|
// drag n drop interaction. We hide them when the DnD is done.
|
||||||
$('#preview-list').addClass("show-interstitial-tiles");
|
$('#preview-list').addClass("show-interstitial-tiles");
|
||||||
},
|
},
|
||||||
stop: function() {
|
stop: function() {
|
||||||
$('#preview-list').removeClass("show-interstitial-tiles");
|
$('#preview-list').removeClass("show-interstitial-tiles");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Each preview film tile is a drop target. This allow us to swap two 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.
|
// 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.
|
// For that we created fake interstitial tiles that are used as drop targets as well.
|
||||||
var droppableTiles = $(".interstitial-tile");
|
var droppableTiles = $(".interstitial-tile");
|
||||||
$.merge(droppableTiles, tiles);
|
$.merge(droppableTiles, tiles);
|
||||||
|
|
||||||
droppableTiles.droppable( {
|
droppableTiles.droppable( {
|
||||||
accept: ".preview-tile",
|
accept: ".preview-tile",
|
||||||
tolerance: "pointer",
|
tolerance: "pointer",
|
||||||
activeClass: "droppable-active",
|
activeClass: "droppable-active",
|
||||||
hoverClass: "droppable-hover-active",
|
hoverClass: "droppable-hover-active",
|
||||||
drop: $.proxy(this.onDrop_, this)
|
drop: $.proxy(this.onDrop_, this)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ns.PreviewFilmController.prototype.onDrop_ = function( event, ui ) {
|
ns.PreviewFilmController.prototype.onDrop_ = function( event, ui ) {
|
||||||
var activeFrame;
|
var activeFrame;
|
||||||
// When we drag from an element, the drag could start from a nested DOM element
|
// 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:
|
// inside the drag target. We normalize that by taking the correct ancestor:
|
||||||
var originTile = $(event.srcElement).closest(".preview-tile");
|
var originTile = $(event.srcElement).closest(".preview-tile");
|
||||||
var originFrameId = parseInt(originTile.data("tile-number"), 10);
|
var originFrameId = parseInt(originTile.data("tile-number"), 10);
|
||||||
|
|
||||||
var dropTarget = $(event.target);
|
var dropTarget = $(event.target);
|
||||||
if(dropTarget.data("tile-type") == "interstitial") {
|
if(dropTarget.data("tile-type") == "interstitial") {
|
||||||
var targetInsertionId = parseInt(dropTarget.data("inject-drop-tile-at"), 10);
|
var targetInsertionId = parseInt(dropTarget.data("inject-drop-tile-at"), 10);
|
||||||
// In case we drop outside of the tile container
|
// In case we drop outside of the tile container
|
||||||
if(isNaN(originFrameId) || isNaN(targetInsertionId)) {
|
if(isNaN(originFrameId) || isNaN(targetInsertionId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//console.log("origin-frame: "+originFrameId+" - targetInsertionId: "+ targetInsertionId)
|
//console.log("origin-frame: "+originFrameId+" - targetInsertionId: "+ targetInsertionId)
|
||||||
this.framesheet.moveFrame(originFrameId, targetInsertionId);
|
this.framesheet.moveFrame(originFrameId, targetInsertionId);
|
||||||
|
|
||||||
activeFrame = targetInsertionId;
|
activeFrame = targetInsertionId;
|
||||||
// The last fake interstitial tile is outside of the framesheet array bound.
|
// 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.
|
// 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
|
// However, when setting back the active frame, we have to make sure the
|
||||||
// frame does exist.
|
// frame does exist.
|
||||||
if(activeFrame > (this.framesheet.getFrameCount() - 1)) {
|
if(activeFrame > (this.framesheet.getFrameCount() - 1)) {
|
||||||
activeFrame = targetInsertionId - 1;
|
activeFrame = targetInsertionId - 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var targetSwapId = parseInt(dropTarget.data("tile-number"), 10);
|
var targetSwapId = parseInt(dropTarget.data("tile-number"), 10);
|
||||||
// In case we drop outside of the tile container
|
// In case we drop outside of the tile container
|
||||||
if(isNaN(originFrameId) || isNaN(targetSwapId)) {
|
if(isNaN(originFrameId) || isNaN(targetSwapId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//console.log("origin-frame: "+originFrameId+" - targetSwapId: "+ targetSwapId)
|
//console.log("origin-frame: "+originFrameId+" - targetSwapId: "+ targetSwapId)
|
||||||
this.framesheet.swapFrames(originFrameId, targetSwapId);
|
this.framesheet.swapFrames(originFrameId, targetSwapId);
|
||||||
activeFrame = 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?
|
// TODO(vincz): move localstorage request to the model layer?
|
||||||
$.publish(Events.LOCALSTORAGE_REQUEST);
|
$.publish(Events.LOCALSTORAGE_REQUEST);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* TODO(vincz): clean this giant rendering function & remove listeners.
|
* TODO(vincz): clean this giant rendering function & remove listeners.
|
||||||
*/
|
*/
|
||||||
ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) {
|
ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) {
|
||||||
var currentFrame = this.framesheet.getFrameByIndex(tileNumber);
|
var currentFrame = this.framesheet.getFrameByIndex(tileNumber);
|
||||||
|
|
||||||
var previewTileRoot = document.createElement("li");
|
var previewTileRoot = document.createElement("li");
|
||||||
var classname = "preview-tile";
|
var classname = "preview-tile";
|
||||||
previewTileRoot.setAttribute("data-tile-number", tileNumber);
|
previewTileRoot.setAttribute("data-tile-number", tileNumber);
|
||||||
|
|
||||||
if (this.framesheet.getCurrentFrame() == currentFrame) {
|
if (this.framesheet.getCurrentFrame() == currentFrame) {
|
||||||
classname += " selected";
|
classname += " selected";
|
||||||
}
|
}
|
||||||
previewTileRoot.className = classname;
|
previewTileRoot.className = classname;
|
||||||
|
|
||||||
var canvasContainer = document.createElement("div");
|
var canvasContainer = document.createElement("div");
|
||||||
canvasContainer.className = "canvas-container";
|
canvasContainer.className = "canvas-container";
|
||||||
|
|
||||||
var canvasBackground = document.createElement("div");
|
var canvasBackground = document.createElement("div");
|
||||||
canvasBackground.className = "canvas-background";
|
canvasBackground.className = "canvas-background";
|
||||||
canvasContainer.appendChild(canvasBackground);
|
canvasContainer.appendChild(canvasBackground);
|
||||||
|
|
||||||
previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber));
|
previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber));
|
||||||
|
|
||||||
var canvasPreviewDuplicateAction = document.createElement("button");
|
var canvasPreviewDuplicateAction = document.createElement("button");
|
||||||
canvasPreviewDuplicateAction.setAttribute('rel', 'tooltip');
|
canvasPreviewDuplicateAction.setAttribute('rel', 'tooltip');
|
||||||
canvasPreviewDuplicateAction.setAttribute('data-placement', 'right');
|
canvasPreviewDuplicateAction.setAttribute('data-placement', 'right');
|
||||||
canvasPreviewDuplicateAction.setAttribute('title', 'Duplicate this frame');
|
canvasPreviewDuplicateAction.setAttribute('title', 'Duplicate this frame');
|
||||||
canvasPreviewDuplicateAction.className = "tile-action duplicate-frame-action"
|
canvasPreviewDuplicateAction.className = "tile-action duplicate-frame-action"
|
||||||
|
|
||||||
canvasPreviewDuplicateAction.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber));
|
canvasPreviewDuplicateAction.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber));
|
||||||
|
|
||||||
// TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim
|
// 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.
|
// is to make this update function (#createPreviewTile) less aggressive.
|
||||||
var renderingOptions = {"dpi": this.dpi };
|
var renderingOptions = {"dpi": this.dpi };
|
||||||
var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view");
|
var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view");
|
||||||
currentFrameRenderer.init(currentFrame);
|
currentFrameRenderer.init(currentFrame);
|
||||||
|
|
||||||
previewTileRoot.appendChild(canvasContainer);
|
previewTileRoot.appendChild(canvasContainer);
|
||||||
previewTileRoot.appendChild(canvasPreviewDuplicateAction);
|
previewTileRoot.appendChild(canvasPreviewDuplicateAction);
|
||||||
|
|
||||||
if(tileNumber > 0 || this.framesheet.getFrameCount() > 1) {
|
if(tileNumber > 0 || this.framesheet.getFrameCount() > 1) {
|
||||||
var canvasPreviewDeleteAction = document.createElement("button");
|
var canvasPreviewDeleteAction = document.createElement("button");
|
||||||
canvasPreviewDeleteAction.setAttribute('rel', 'tooltip');
|
canvasPreviewDeleteAction.setAttribute('rel', 'tooltip');
|
||||||
canvasPreviewDeleteAction.setAttribute('data-placement', 'right');
|
canvasPreviewDeleteAction.setAttribute('data-placement', 'right');
|
||||||
canvasPreviewDeleteAction.setAttribute('title', 'Delete this frame');
|
canvasPreviewDeleteAction.setAttribute('title', 'Delete this frame');
|
||||||
canvasPreviewDeleteAction.className = "tile-action delete-frame-action"
|
canvasPreviewDeleteAction.className = "tile-action delete-frame-action"
|
||||||
canvasPreviewDeleteAction.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber));
|
canvasPreviewDeleteAction.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber));
|
||||||
previewTileRoot.appendChild(canvasPreviewDeleteAction);
|
previewTileRoot.appendChild(canvasPreviewDeleteAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return previewTileRoot;
|
return previewTileRoot;
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) {
|
ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) {
|
||||||
// has not class tile-action:
|
// has not class tile-action:
|
||||||
if(!evt.target.classList.contains('tile-action')) {
|
if(!evt.target.classList.contains('tile-action')) {
|
||||||
this.framesheet.setCurrentFrameIndex(index);
|
this.framesheet.setCurrentFrameIndex(index);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) {
|
ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) {
|
||||||
this.framesheet.removeFrameByIndex(index);
|
this.framesheet.removeFrameByIndex(index);
|
||||||
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) {
|
ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) {
|
||||||
this.framesheet.duplicateFrameByIndex(index);
|
this.framesheet.duplicateFrameByIndex(index);
|
||||||
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
||||||
this.framesheet.setCurrentFrameIndex(index + 1);
|
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);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
@ -1,6 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace("pskl.model");
|
var ns = $.namespace("pskl.model");
|
||||||
ns.FrameSheet = function (width, height) {
|
ns.FrameSheet = function (height, width) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.frames = [];
|
this.frames = [];
|
||||||
@ -68,6 +68,14 @@
|
|||||||
var frameCfg = frameConfigurations[i];
|
var frameCfg = frameConfigurations[i];
|
||||||
this.addFrame(new ns.Frame(frameCfg));
|
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);
|
$.publish(Events.FRAMESHEET_RESET);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw "Could not load serialized framesheet : " + e.message
|
throw "Could not load serialized framesheet : " + e.message
|
||||||
|
132
js/piskel.js
132
js/piskel.js
@ -9,46 +9,21 @@ $.namespace("pskl");
|
|||||||
/**
|
/**
|
||||||
* FrameSheetModel instance.
|
* FrameSheetModel instance.
|
||||||
*/
|
*/
|
||||||
var frameSheet,
|
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application controller
|
* Main application controller
|
||||||
*/
|
*/
|
||||||
var piskel = {
|
var piskel = {
|
||||||
|
|
||||||
init : function () {
|
init : function () {
|
||||||
frameSheet = new pskl.model.FrameSheet(framePixelWidth, framePixelHeight);
|
var frameSize = this.readSizeFromURL_();
|
||||||
|
frameSheet = new pskl.model.FrameSheet(frameSize.height, frameSize.width);
|
||||||
|
|
||||||
frameSheet.addEmptyFrame();
|
frameSheet.addEmptyFrame();
|
||||||
|
|
||||||
this.drawingController = new pskl.controller.DrawingController(
|
this.drawingController = new pskl.controller.DrawingController(frameSheet, $('#drawing-canvas-container'));
|
||||||
frameSheet,
|
this.animationController = new pskl.controller.AnimatedPreviewController(frameSheet, $('#preview-canvas-container'));
|
||||||
$('#drawing-canvas-container'),
|
this.previewsController = new pskl.controller.PreviewFilmController(frameSheet, $('#preview-list'));
|
||||||
this.calculateDPIsForDrawingCanvas_()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.animationController = new pskl.controller.AnimatedPreviewController(
|
|
||||||
frameSheet,
|
|
||||||
$('#preview-canvas-container'),
|
|
||||||
previewAnimationCanvasDpi
|
|
||||||
);
|
|
||||||
|
|
||||||
this.previewsController = new pskl.controller.PreviewFilmController(
|
|
||||||
frameSheet,
|
|
||||||
$('#preview-list'),
|
|
||||||
previewTileCanvasDpi
|
|
||||||
);
|
|
||||||
|
|
||||||
// To catch the current active frame, the selection manager have to be initialized before
|
// To catch the current active frame, the selection manager have to be initialized before
|
||||||
// the 'frameSheet.setCurrentFrameIndex(0);' line below.
|
// the 'frameSheet.setCurrentFrameIndex(0);' line below.
|
||||||
@ -57,8 +32,7 @@ $.namespace("pskl");
|
|||||||
// - an event(s) triggering init
|
// - an event(s) triggering init
|
||||||
// All listeners will be hook in a first step, then all event triggering inits will be called
|
// All listeners will be hook in a first step, then all event triggering inits will be called
|
||||||
// in a second batch.
|
// in a second batch.
|
||||||
this.selectionManager =
|
this.selectionManager = new pskl.selection.SelectionManager(frameSheet, this.drawingController.overlayFrame);
|
||||||
new pskl.selection.SelectionManager(frameSheet, this.drawingController.overlayFrame);
|
|
||||||
|
|
||||||
// DO NOT MOVE THIS LINE (see comment above)
|
// DO NOT MOVE THIS LINE (see comment above)
|
||||||
frameSheet.setCurrentFrameIndex(0);
|
frameSheet.setCurrentFrameIndex(0);
|
||||||
@ -79,7 +53,7 @@ $.namespace("pskl");
|
|||||||
this.localStorageService.init();
|
this.localStorageService.init();
|
||||||
|
|
||||||
// TODO: Add comments
|
// TODO: Add comments
|
||||||
var framesheetId = this.getFramesheetIdFromUrl();
|
var framesheetId = this.readFramesheetIdFromURL_();
|
||||||
if (framesheetId) {
|
if (framesheetId) {
|
||||||
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Loading animation with id : [" + framesheetId + "]"}]);
|
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Loading animation with id : [" + framesheetId + "]"}]);
|
||||||
this.loadFramesheetFromService(framesheetId);
|
this.loadFramesheetFromService(framesheetId);
|
||||||
@ -96,8 +70,6 @@ $.namespace("pskl");
|
|||||||
$('body').tooltip({
|
$('body').tooltip({
|
||||||
selector: '[rel=tooltip]'
|
selector: '[rel=tooltip]'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connectResizeToDpiUpdate_();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function (delta) {
|
render : function (delta) {
|
||||||
@ -106,55 +78,6 @@ $.namespace("pskl");
|
|||||||
this.previewsController.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 () {
|
finishInit : function () {
|
||||||
var toolController = new pskl.controller.ToolController();
|
var toolController = new pskl.controller.ToolController();
|
||||||
toolController.init();
|
toolController.init();
|
||||||
@ -163,12 +86,39 @@ $.namespace("pskl");
|
|||||||
paletteController.init(frameSheet);
|
paletteController.init(frameSheet);
|
||||||
},
|
},
|
||||||
|
|
||||||
getFramesheetIdFromUrl : function() {
|
readSizeFromURL_ : function () {
|
||||||
var href = window.location.href;
|
var sizeParam = this.readUrlParameter_("size"), size;
|
||||||
// TODO: Change frameId to framesheetId on the backend
|
// parameter expected as size=64x48 => size=widthxheight
|
||||||
if (href.indexOf('frameId=') != -1) {
|
var parts = sizeParam.split("x");
|
||||||
return href.substring(href.indexOf('frameId=')+8);
|
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<params.length;i++) {
|
||||||
|
val = params[i].split("=");
|
||||||
|
if (val[0] == paramName) {
|
||||||
|
return unescape(val[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
},
|
},
|
||||||
|
|
||||||
loadFramesheetFromService : function (frameId) {
|
loadFramesheetFromService : function (frameId) {
|
||||||
|
@ -23,8 +23,6 @@
|
|||||||
this.hasGrid = renderingOptions.hasGrid;
|
this.hasGrid = renderingOptions.hasGrid;
|
||||||
this.gridStrokeWidth = 0;
|
this.gridStrokeWidth = 0;
|
||||||
|
|
||||||
this.lastRenderedFrame = null;
|
|
||||||
|
|
||||||
// Flag to know if the config was altered
|
// Flag to know if the config was altered
|
||||||
this.canvasConfigDirty = true;
|
this.canvasConfigDirty = true;
|
||||||
|
|
||||||
@ -51,10 +49,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.canvasConfigDirty = true;
|
this.canvasConfigDirty = true;
|
||||||
|
|
||||||
if(this.lastRenderedFrame) {
|
|
||||||
this.render(this.lastRenderedFrame);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.FrameRenderer.prototype.render = function (frame) {
|
ns.FrameRenderer.prototype.render = function (frame) {
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
*/
|
*/
|
||||||
ns.LocalStorageService.prototype.persistToLocalStorage_ = function() {
|
ns.LocalStorageService.prototype.persistToLocalStorage_ = function() {
|
||||||
|
|
||||||
console.log('[LocalStorage service]: Snapshot stored');
|
// console.log('[LocalStorage service]: Snapshot stored');
|
||||||
window.localStorage.snapShot = this.framesheet.serialize();
|
// window.localStorage.snapShot = this.framesheet.serialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,148 +1,174 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace("pskl");
|
var ns = $.namespace("pskl");
|
||||||
|
|
||||||
ns.PixelUtils = {
|
ns.PixelUtils = {
|
||||||
|
|
||||||
getRectanglePixels : function (x0, y0, x1, y1) {
|
getRectanglePixels : function (x0, y0, x1, y1) {
|
||||||
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
||||||
var pixels = [];
|
var pixels = [];
|
||||||
|
|
||||||
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
||||||
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
||||||
pixels.push({"col": x, "row": y});
|
pixels.push({"col": x, "row": y});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pixels;
|
return pixels;
|
||||||
},
|
},
|
||||||
|
|
||||||
getBoundRectanglePixels : function (x0, y0, x1, y1) {
|
getBoundRectanglePixels : function (x0, y0, x1, y1) {
|
||||||
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
||||||
var pixels = [];
|
var pixels = [];
|
||||||
// Creating horizontal sides of the rectangle:
|
// Creating horizontal sides of the rectangle:
|
||||||
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
||||||
pixels.push({"col": x, "row": rectangle.y0});
|
pixels.push({"col": x, "row": rectangle.y0});
|
||||||
pixels.push({"col": x, "row": rectangle.y1});
|
pixels.push({"col": x, "row": rectangle.y1});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating vertical sides of the rectangle:
|
// Creating vertical sides of the rectangle:
|
||||||
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
||||||
pixels.push({"col": rectangle.x0, "row": y});
|
pixels.push({"col": rectangle.x0, "row": y});
|
||||||
pixels.push({"col": rectangle.x1, "row": y});
|
pixels.push({"col": rectangle.x1, "row": y});
|
||||||
}
|
}
|
||||||
|
|
||||||
return pixels;
|
return pixels;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object of ordered rectangle coordinate.
|
* Return an object of ordered rectangle coordinate.
|
||||||
* In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner
|
* In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
getOrderedRectangleCoordinates : function (x0, y0, x1, y1) {
|
getOrderedRectangleCoordinates : function (x0, y0, x1, y1) {
|
||||||
return {
|
return {
|
||||||
x0 : Math.min(x0, x1), y0 : Math.min(y0, y1),
|
x0 : Math.min(x0, x1), y0 : Math.min(y0, y1),
|
||||||
x1 : Math.max(x0, x1), y1 : Math.max(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
|
* Return the list of pixels that would have been filled by a paintbucket tool applied
|
||||||
* on pixel at coordinate (x,y).
|
* on pixel at coordinate (x,y).
|
||||||
* This function is not altering the Frame object argument.
|
* This function is not altering the Frame object argument.
|
||||||
*
|
*
|
||||||
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
||||||
* @param col number Column coordinate in the frame
|
* @param col number Column coordinate in the frame
|
||||||
* @param row number Row coordinate in the frame
|
* @param row number Row coordinate in the frame
|
||||||
*
|
*
|
||||||
* @return an array of the pixel coordinates paint with the replacement color
|
* @return an array of the pixel coordinates paint with the replacement color
|
||||||
*/
|
*/
|
||||||
getSimilarConnectedPixelsFromFrame: function(frame, col, row) {
|
getSimilarConnectedPixelsFromFrame: function(frame, col, row) {
|
||||||
// To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm
|
// 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
|
// in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels
|
||||||
// and are as well connected.
|
// and are as well connected.
|
||||||
var fakeFrame = frame.clone(); // We just want to
|
var fakeFrame = frame.clone(); // We just want to
|
||||||
var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color.
|
var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color.
|
||||||
var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor);
|
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
|
* Apply the paintbucket tool in a frame at the (col, row) initial position
|
||||||
* with the replacement color.
|
* with the replacement color.
|
||||||
*
|
*
|
||||||
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
||||||
* @param col number Column coordinate in the frame
|
* @param col number Column coordinate in the frame
|
||||||
* @param row number Row coordinate in the frame
|
* @param row number Row coordinate in the frame
|
||||||
* @param replacementColor string Hexadecimal color used to fill the area
|
* @param replacementColor string Hexadecimal color used to fill the area
|
||||||
*
|
*
|
||||||
* @return an array of the pixel coordinates paint with the replacement color
|
* @return an array of the pixel coordinates paint with the replacement color
|
||||||
*/
|
*/
|
||||||
paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) {
|
paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) {
|
||||||
/**
|
/**
|
||||||
* Queue linear Flood-fill (node, target-color, replacement-color):
|
* Queue linear Flood-fill (node, target-color, replacement-color):
|
||||||
* 1. Set Q to the empty queue.
|
* 1. Set Q to the empty queue.
|
||||||
* 2. If the color of node is not equal to target-color, return.
|
* 2. If the color of node is not equal to target-color, return.
|
||||||
* 3. Add node to Q.
|
* 3. Add node to Q.
|
||||||
* 4. For each element n of Q:
|
* 4. For each element n of Q:
|
||||||
* 5. If the color of n is equal to target-color:
|
* 5. If the color of n is equal to target-color:
|
||||||
* 6. Set w and e equal to n.
|
* 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.
|
* 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.
|
* 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.
|
* 9. Set the color of nodes between w and e to replacement-color.
|
||||||
* 10. For each node n between w and e:
|
* 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.
|
* 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.
|
* 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.
|
* 13. Continue looping until Q is exhausted.
|
||||||
* 14. Return.
|
* 14. Return.
|
||||||
*
|
*/
|
||||||
* @private
|
var paintedPixels = [];
|
||||||
*/
|
var queue = [];
|
||||||
var paintedPixels = [];
|
var dy = [-1, 0, 1, 0];
|
||||||
var queue = [];
|
var dx = [0, 1, 0, -1];
|
||||||
var dy = [-1, 0, 1, 0];
|
try {
|
||||||
var dx = [0, 1, 0, -1];
|
var targetColor = frame.getPixel(col, row);
|
||||||
try {
|
} catch(e) {
|
||||||
var targetColor = frame.getPixel(col, row);
|
// Frame out of bound exception.
|
||||||
} catch(e) {
|
}
|
||||||
// Frame out of bound exception.
|
|
||||||
}
|
|
||||||
|
|
||||||
if(targetColor == replacementColor) {
|
if(targetColor == replacementColor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
queue.push({"col": col, "row": row});
|
queue.push({"col": col, "row": row});
|
||||||
var loopCount = 0;
|
var loopCount = 0;
|
||||||
var cellCount = frame.getWidth() * frame.getHeight();
|
var cellCount = frame.getWidth() * frame.getHeight();
|
||||||
while(queue.length > 0) {
|
while(queue.length > 0) {
|
||||||
loopCount ++;
|
loopCount ++;
|
||||||
|
|
||||||
var currentItem = queue.pop();
|
var currentItem = queue.pop();
|
||||||
frame.setPixel(currentItem.col, currentItem.row, replacementColor);
|
frame.setPixel(currentItem.col, currentItem.row, replacementColor);
|
||||||
paintedPixels.push({"col": currentItem.col, "row": currentItem.row });
|
paintedPixels.push({"col": currentItem.col, "row": currentItem.row });
|
||||||
|
|
||||||
for (var i = 0; i < 4; i++) {
|
for (var i = 0; i < 4; i++) {
|
||||||
var nextCol = currentItem.col + dx[i]
|
var nextCol = currentItem.col + dx[i]
|
||||||
var nextRow = currentItem.row + dy[i]
|
var nextRow = currentItem.row + dy[i]
|
||||||
try {
|
try {
|
||||||
if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
|
if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
|
||||||
queue.push({"col": nextCol, "row": nextRow });
|
queue.push({"col": nextCol, "row": nextRow });
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// Frame out of bound exception.
|
// Frame out of bound exception.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Security loop breaker:
|
// Security loop breaker:
|
||||||
if(loopCount > 10 * cellCount) {
|
if(loopCount > 10 * cellCount) {
|
||||||
console.log("loop breaker called")
|
console.log("loop breaker called")
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paintedPixels;
|
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);
|
||||||
|
},
|
||||||
|
};
|
||||||
})();
|
})();
|
Loading…
x
Reference in New Issue
Block a user