+
diff --git a/src/js/Constants.js b/src/js/Constants.js
index f017ca5e..0ce59ab4 100644
--- a/src/js/Constants.js
+++ b/src/js/Constants.js
@@ -27,7 +27,6 @@ var Constants = {
NO_PALETTE_ID : '__no-palette',
CURRENT_COLORS_PALETTE_ID : '__current-colors',
- MANAGE_PALETTE_ID : '__manage-palettes',
// Used for Spectrum input
PREFERRED_COLOR_FORMAT : 'rgb',
diff --git a/src/js/Events.js b/src/js/Events.js
index 5b9d1106..fb2203a3 100644
--- a/src/js/Events.js
+++ b/src/js/Events.js
@@ -47,6 +47,10 @@ var Events = {
SHOW_NOTIFICATION: "SHOW_NOTIFICATION",
HIDE_NOTIFICATION: "HIDE_NOTIFICATION",
+ SHOW_PROGRESS: "SHOW_PROGRESS",
+ UPDATE_PROGRESS: "UPDATE_PROGRESS",
+ HIDE_PROGRESS: "HIDE_PROGRESS",
+
ZOOM_CHANGED : "ZOOM_CHANGED",
CURRENT_COLORS_UPDATED : "CURRENT_COLORS_UPDATED",
diff --git a/src/js/app.js b/src/js/app.js
index 90477e90..fe8eb764 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -11,7 +11,6 @@
init : function () {
/**
- * True when piskel is running in static mode (no back end needed).
* When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl
*/
this.isAppEngineVersion = !!pskl.appEngineToken_;
@@ -39,6 +38,10 @@
this.piskelController = new pskl.controller.piskel.PublicPiskelController(this.corePiskelController);
this.piskelController.init();
+ this.paletteImportService = new pskl.service.palette.PaletteImportService();
+ this.paletteService = new pskl.service.palette.PaletteService();
+ this.paletteService.addDynamicPalette(new pskl.service.palette.CurrentColorsPalette());
+
this.paletteController = new pskl.controller.PaletteController();
this.paletteController.init();
@@ -84,6 +87,9 @@
this.notificationController = new pskl.controller.NotificationController();
this.notificationController.init();
+ this.progressBarController = new pskl.controller.ProgressBarController();
+ this.progressBarController.init();
+
this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController();
this.canvasBackgroundController.init();
diff --git a/src/js/controller/LayersListController.js b/src/js/controller/LayersListController.js
index 48b6e3e7..44bf10d3 100644
--- a/src/js/controller/LayersListController.js
+++ b/src/js/controller/LayersListController.js
@@ -28,6 +28,31 @@
this.layersListEl.innerHTML = '';
var layers = this.piskelController.getLayers();
layers.forEach(this.addLayerItem.bind(this));
+ this.updateButtonStatus_();
+ };
+
+ ns.LayersListController.prototype.updateButtonStatus_ = function () {
+ var layers = this.piskelController.getLayers();
+ var currentLayer = this.piskelController.getCurrentLayer();
+ var index = this.piskelController.getCurrentLayerIndex();
+
+ var isLast = index === 0;
+ var isOnly = layers.length === 1;
+ var isFirst = index === layers.length - 1;
+
+ this.toggleButtonDisabledState_('up', isFirst);
+ this.toggleButtonDisabledState_('down', isLast);
+ this.toggleButtonDisabledState_('merge', isLast);
+ this.toggleButtonDisabledState_('delete', isOnly);
+ };
+
+ ns.LayersListController.prototype.toggleButtonDisabledState_ = function (buttonAction, isDisabled) {
+ var button = document.querySelector('.layers-button[data-action="'+buttonAction+'"]');
+ if (isDisabled) {
+ button.setAttribute('disabled', 'disabled');
+ } else {
+ button.removeAttribute('disabled');
+ }
};
ns.LayersListController.prototype.updateToggleLayerPreview_ = function () {
@@ -64,21 +89,25 @@
} else if (el.classList.contains('layer-item')) {
index = el.dataset.layerIndex;
this.piskelController.setCurrentLayerIndex(parseInt(index, 10));
- } else if (el.classList.contains('edit-icon')) {
- index = el.parentNode.dataset.layerIndex;
- this.renameLayerAt_(index);
}
};
- ns.LayersListController.prototype.renameLayerAt_ = function (index) {
- var layer = this.piskelController.getLayerAt(index);
+ ns.LayersListController.prototype.renameCurrentLayer_ = function () {
+ var layer = this.piskelController.getCurrentLayer();
var name = window.prompt("Please enter the layer name", layer.getName());
if (name) {
+ var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.renameLayerAt(index, name);
this.renderLayerList_();
}
};
+ ns.LayersListController.prototype.mergeDownCurrentLayer_ = function () {
+ var index = this.piskelController.getCurrentLayerIndex();
+ this.piskelController.mergeDownLayerAt(index);
+ this.renderLayerList_();
+ };
+
ns.LayersListController.prototype.onButtonClick_ = function (button) {
var action = button.getAttribute('data-action');
if (action == 'up') {
@@ -89,6 +118,10 @@
this.piskelController.createLayer();
} else if (action == 'delete') {
this.piskelController.removeCurrentLayer();
+ } else if (action == 'merge') {
+ this.mergeDownCurrentLayer_();
+ } else if (action == 'edit') {
+ this.renameCurrentLayer_();
}
};
diff --git a/src/js/controller/PalettesListController.js b/src/js/controller/PalettesListController.js
index 0901d3df..1f9e33ef 100644
--- a/src/js/controller/PalettesListController.js
+++ b/src/js/controller/PalettesListController.js
@@ -13,70 +13,82 @@
ns.PalettesListController = function (paletteController, usedColorService) {
this.usedColorService = usedColorService;
+ this.paletteService = pskl.app.paletteService;
this.paletteController = paletteController;
};
ns.PalettesListController.prototype.init = function () {
this.paletteColorTemplate_ = pskl.utils.Template.get('palette-color-template');
+
this.colorListContainer_ = document.querySelector('.palettes-list-colors');
this.colorPaletteSelect_ = document.querySelector('.palettes-list-select');
- this.paletteListOptGroup_ = document.querySelector('.palettes-list-select-group');
+
+ var createPaletteButton_ = document.querySelector('.create-palette-button');
+ var editPaletteButton_ = document.querySelector('.edit-palette-button');
this.colorPaletteSelect_.addEventListener('change', this.onPaletteSelected_.bind(this));
this.colorListContainer_.addEventListener('mouseup', this.onColorContainerMouseup.bind(this));
this.colorListContainer_.addEventListener('contextmenu', this.onColorContainerContextMenu.bind(this));
+ createPaletteButton_.addEventListener('click', this.onCreatePaletteClick_.bind(this));
+ editPaletteButton_.addEventListener('click', this.onEditPaletteClick_.bind(this));
+
$.subscribe(Events.PALETTE_LIST_UPDATED, this.onPaletteListUpdated.bind(this));
$.subscribe(Events.CURRENT_COLORS_UPDATED, this.fillColorListContainer.bind(this));
$.subscribe(Events.PRIMARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
+ $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
+
+
+ pskl.app.shortcutService.addShortcuts(['>', 'shift+>'], this.selectNextColor_.bind(this));
+ pskl.app.shortcutService.addShortcut('<', this.selectPreviousColor_.bind(this));
this.fillPaletteList();
- this.selectPaletteFromUserSettings();
+ this.updateFromUserSettings();
this.fillColorListContainer();
};
ns.PalettesListController.prototype.fillPaletteList = function () {
- var palettes = [{
- id : Constants.NO_PALETTE_ID,
- name : 'No palette'
- }];
- palettes = palettes.concat(this.retrievePalettes());
+ var palettes = this.paletteService.getPalettes();
var html = palettes.map(function (palette) {
return pskl.utils.Template.replace('', palette);
}).join('');
- this.paletteListOptGroup_.innerHTML = html;
+ this.colorPaletteSelect_.innerHTML = html;
};
ns.PalettesListController.prototype.fillColorListContainer = function () {
+
var colors = this.getSelectedPaletteColors_();
- var html = colors.map(function (color) {
- return pskl.utils.Template.replace(this.paletteColorTemplate_, {color : color});
- }.bind(this)).join('');
- this.colorListContainer_.innerHTML = html;
+ if (colors.length > 0) {
+ var html = colors.map(function (color, index) {
+ return pskl.utils.Template.replace(this.paletteColorTemplate_, {color : color, index : index});
+ }.bind(this)).join('');
+ this.colorListContainer_.innerHTML = html;
- this.highlightSelectedColors();
+ this.highlightSelectedColors();
- var hasScrollbar = colors.length > NO_SCROLL_MAX_COLORS;
- if (hasScrollbar && !pskl.utils.UserAgent.isChrome) {
- this.colorListContainer_.classList.add(HAS_SCROLL_CLASSNAME);
+ var hasScrollbar = colors.length > NO_SCROLL_MAX_COLORS;
+ if (hasScrollbar && !pskl.utils.UserAgent.isChrome) {
+ this.colorListContainer_.classList.add(HAS_SCROLL_CLASSNAME);
+ } else {
+ this.colorListContainer_.classList.remove(HAS_SCROLL_CLASSNAME);
+ }
} else {
- this.colorListContainer_.classList.remove(HAS_SCROLL_CLASSNAME);
+ this.colorListContainer_.innerHTML = pskl.utils.Template.get('palettes-list-no-colors-partial');
}
};
+ ns.PalettesListController.prototype.selectPalette = function (paletteId) {
+ pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, paletteId);
+ };
+
ns.PalettesListController.prototype.getSelectedPaletteColors_ = function () {
var colors = [];
- var paletteId = this.colorPaletteSelect_.value;
- if (paletteId === Constants.CURRENT_COLORS_PALETTE_ID) {
- colors = this.usedColorService.getCurrentColors();
- } else {
- var palette = this.getPaletteById(paletteId, this.retrievePalettes());
- if (palette) {
- colors = palette.colors;
- }
+ var palette = this.getSelectedPalette_();
+ if (palette) {
+ colors = palette.getColors();
}
if (colors.length > Constants.MAX_CURRENT_COLORS_DISPLAYED) {
@@ -86,27 +98,65 @@
return colors;
};
- ns.PalettesListController.prototype.selectPalette = function (paletteId) {
- this.colorPaletteSelect_.value = paletteId;
+ ns.PalettesListController.prototype.getSelectedPalette_ = function () {
+ var paletteId = pskl.UserSettings.get(pskl.UserSettings.SELECTED_PALETTE);
+ return this.paletteService.getPaletteById(paletteId);
};
- ns.PalettesListController.prototype.selectPaletteFromUserSettings = function () {
- this.selectPalette(pskl.UserSettings.get(pskl.UserSettings.SELECTED_PALETTE));
+ ns.PalettesListController.prototype.selectNextColor_ = function () {
+ this.selectColor_(this.getCurrentColorIndex_() + 1);
+ };
+
+ ns.PalettesListController.prototype.selectPreviousColor_ = function () {
+ this.selectColor_(this.getCurrentColorIndex_() - 1);
+ };
+
+ ns.PalettesListController.prototype.getCurrentColorIndex_ = function () {
+ var currentIndex = 0;
+ var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME);
+ if (selectedColor) {
+ currentIndex = parseInt(selectedColor.dataset.colorIndex, 10);
+ }
+ return currentIndex;
+ };
+
+ ns.PalettesListController.prototype.selectColor_ = function (index) {
+ var colors = this.getSelectedPaletteColors_();
+ var color = colors[index];
+ if (color) {
+ $.publish(Events.SELECT_PRIMARY_COLOR, [color]);
+ }
+ };
+
+ ns.PalettesListController.prototype.onUserSettingsChange_ = function (evt, name, value) {
+ if (name == pskl.UserSettings.SELECTED_PALETTE) {
+ this.updateFromUserSettings();
+ }
+ };
+
+ ns.PalettesListController.prototype.updateFromUserSettings = function () {
+ var paletteId = pskl.UserSettings.get(pskl.UserSettings.SELECTED_PALETTE);
+ this.fillColorListContainer();
+ this.colorPaletteSelect_.value = paletteId;
};
ns.PalettesListController.prototype.onPaletteSelected_ = function (evt) {
var paletteId = this.colorPaletteSelect_.value;
- if (paletteId === Constants.MANAGE_PALETTE_ID) {
- $.publish(Events.DIALOG_DISPLAY, 'manage-palettes');
- this.selectPaletteFromUserSettings();
- } else {
- pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, paletteId);
- }
-
- this.fillColorListContainer();
+ this.selectPalette(paletteId);
+ this.colorPaletteSelect_.blur();
};
+ ns.PalettesListController.prototype.onCreatePaletteClick_ = function (evt) {
+ $.publish(Events.DIALOG_DISPLAY, 'create-palette');
+ };
+ ns.PalettesListController.prototype.onEditPaletteClick_ = function (evt) {
+ var paletteId = this.colorPaletteSelect_.value;
+ $.publish(Events.DIALOG_DISPLAY, {
+ dialogId : 'create-palette',
+ initArgs : paletteId
+ });
+ };
ns.PalettesListController.prototype.onColorContainerContextMenu = function (event) {
event.preventDefault();
@@ -155,24 +205,6 @@
ns.PalettesListController.prototype.onPaletteListUpdated = function () {
this.fillPaletteList();
- this.selectPaletteFromUserSettings();
- this.fillColorListContainer();
- };
-
- ns.PalettesListController.prototype.getPaletteById = function (paletteId, palettes) {
- var match = null;
-
- palettes.forEach(function (palette) {
- if (palette.id === paletteId) {
- match = palette;
- }
- });
-
- return match;
- };
-
- ns.PalettesListController.prototype.retrievePalettes = function () {
- var palettesString = window.localStorage.getItem('piskel.palettes');
- return JSON.parse(palettesString) || [];
+ this.updateFromUserSettings();
};
})();
\ No newline at end of file
diff --git a/src/js/controller/PreviewFilmController.js b/src/js/controller/PreviewFilmController.js
index 867c1859..0571c02e 100644
--- a/src/js/controller/PreviewFilmController.js
+++ b/src/js/controller/PreviewFilmController.js
@@ -218,7 +218,7 @@
};
ns.PreviewFilmController.prototype.clonePreviewCanvas_ = function (canvas) {
- var clone = pskl.CanvasUtils.clone(canvas);
+ var clone = pskl.utils.CanvasUtils.clone(canvas);
clone.classList.add('tile-view', 'canvas');
return clone;
};
diff --git a/src/js/controller/ProgressBarController.js b/src/js/controller/ProgressBarController.js
new file mode 100644
index 00000000..2681982b
--- /dev/null
+++ b/src/js/controller/ProgressBarController.js
@@ -0,0 +1,61 @@
+(function () {
+ var ns = $.namespace('pskl.controller');
+
+ ns.ProgressBarController = function () {
+ this.template = pskl.utils.Template.get('progress-bar-template');
+ this.progressBar = null;
+ this.progressBarStatus = null;
+
+ this.showProgressTimer_ = 0;
+ };
+
+ ns.ProgressBarController.prototype.init = function () {
+ $.subscribe(Events.SHOW_PROGRESS, $.proxy(this.showProgress_, this));
+ $.subscribe(Events.UPDATE_PROGRESS, $.proxy(this.updateProgress_, this));
+ $.subscribe(Events.HIDE_PROGRESS, $.proxy(this.hideProgress_, this));
+ };
+
+ ns.ProgressBarController.prototype.showProgress_ = function (event, progressInfo) {
+ this.removeProgressBar_();
+ this.showProgressTimer_ = window.setTimeout(this.onTimerExpired_.bind(this, progressInfo), 300);
+ };
+
+ ns.ProgressBarController.prototype.onTimerExpired_ = function (progressInfo) {
+ var progressBarHtml = pskl.utils.Template.replace(this.template, {
+ name : progressInfo.name,
+ status : 0
+ });
+
+ var progressBarEl = pskl.utils.Template.createFromHTML(progressBarHtml);
+ document.body.appendChild(progressBarEl);
+
+ this.progressBar = document.querySelector('.progress-bar');
+ this.progressBarStatus = document.querySelector('.progress-bar-status');
+ };
+
+ ns.ProgressBarController.prototype.updateProgress_ = function (event, progressInfo) {
+ if (this.progressBar && this.progressBarStatus) {
+ var progress = progressInfo.progress;
+ var width = this.progressBar.offsetWidth;
+ var progressWidth = width - ((progress * width) / 100);
+ this.progressBar.style.backgroundPosition = (-progressWidth) + 'px 0';
+ this.progressBarStatus.innerHTML = progress + '%';
+ }
+ };
+
+ ns.ProgressBarController.prototype.hideProgress_ = function (event, progressInfo) {
+ if (this.showProgressTimer_) {
+ window.clearTimeout(this.showProgressTimer_);
+ }
+ this.removeProgressBar_();
+ };
+
+ ns.ProgressBarController.prototype.removeProgressBar_ = function () {
+ var progressBarContainer = document.querySelector('.progress-bar-container');
+ if (progressBarContainer) {
+ progressBarContainer.parentNode.removeChild(progressBarContainer);
+ this.progressBar = null;
+ this.progressBarStatus = null;
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/controller/dialogs/AbstractDialogController.js b/src/js/controller/dialogs/AbstractDialogController.js
index 1c8c7715..296b78e9 100644
--- a/src/js/controller/dialogs/AbstractDialogController.js
+++ b/src/js/controller/dialogs/AbstractDialogController.js
@@ -12,7 +12,15 @@
ns.AbstractDialogController.prototype.destroy = function () {};
ns.AbstractDialogController.prototype.closeDialog = function () {
+ this.destroy();
$.publish(Events.DIALOG_HIDE);
};
+ ns.AbstractDialogController.prototype.setTitle = function (title) {
+ var dialogTitle = document.querySelector('.dialog-title');
+ if (dialogTitle) {
+ dialogTitle.innerText = title;
+ }
+ };
+
})();
\ No newline at end of file
diff --git a/src/js/controller/dialogs/CreatePaletteController.js b/src/js/controller/dialogs/CreatePaletteController.js
new file mode 100644
index 00000000..fa777fcc
--- /dev/null
+++ b/src/js/controller/dialogs/CreatePaletteController.js
@@ -0,0 +1,127 @@
+(function () {
+ var ns = $.namespace('pskl.controller.dialogs');
+
+ ns.CreatePaletteController = function (piskelController) {
+ this.paletteService = pskl.app.paletteService;
+ this.paletteImportService = pskl.app.paletteImportService;
+ };
+
+ pskl.utils.inherit(ns.CreatePaletteController, ns.AbstractDialogController);
+
+ ns.CreatePaletteController.prototype.init = function (paletteId) {
+ this.superclass.init.call(this);
+
+ this.hiddenFileInput = document.querySelector('.create-palette-import-input');
+ this.nameInput = document.querySelector('input[name="palette-name"]');
+
+ var buttonsContainer = document.querySelector('.create-palette-actions');
+ var deleteButton = document.querySelector('.create-palette-delete');
+ var downloadButton = document.querySelector('.create-palette-download-button');
+ var importFileButton = document.querySelector('.create-palette-import-button');
+
+ this.nameInput.addEventListener('input', this.onNameInputChange_.bind(this));
+ this.hiddenFileInput.addEventListener('change', this.onFileInputChange_.bind(this));
+
+ buttonsContainer.addEventListener('click', this.onButtonClick_.bind(this));
+ downloadButton.addEventListener('click', this.onDownloadButtonClick_.bind(this));
+ importFileButton.addEventListener('click', this.onImportFileButtonClick_.bind(this));
+
+ var colorsListContainer = document.querySelector('.colors-container');
+ this.colorsListWidget = new pskl.controller.widgets.ColorsList(colorsListContainer);
+
+ var palette;
+ var isCurrentColorsPalette = paletteId == Constants.CURRENT_COLORS_PALETTE_ID;
+ if (paletteId && !isCurrentColorsPalette) {
+ importFileButton.style.display = 'none';
+ this.setTitle('Edit Palette');
+
+ var paletteObject = this.paletteService.getPaletteById(paletteId);
+ palette = pskl.model.Palette.fromObject(paletteObject);
+ } else {
+ downloadButton.style.display = 'none';
+ deleteButton.style.display = 'none';
+ this.setTitle('Create Palette');
+
+ var uuid = pskl.utils.Uuid.generate();
+ if (isCurrentColorsPalette) {
+ palette = new pskl.model.Palette(uuid, 'Current colors clone', this.getCurrentColors_());
+ } else {
+ palette = new pskl.model.Palette(uuid, 'New palette', []);
+ }
+ }
+
+ this.setPalette_(palette);
+ };
+
+ ns.CreatePaletteController.prototype.getCurrentColors_ = function () {
+ var palette = this.paletteService.getPaletteById(Constants.CURRENT_COLORS_PALETTE_ID);
+ return palette.getColors();
+ };
+
+ ns.CreatePaletteController.prototype.setPalette_ = function (palette) {
+ this.palette = palette;
+ this.nameInput.value = pskl.utils.unescapeHtml(palette.name);
+ this.colorsListWidget.setColors(palette.getColors());
+ };
+
+ ns.CreatePaletteController.prototype.destroy = function () {
+ this.colorsListWidget.destroy();
+ this.nameInput = null;
+ };
+
+ ns.CreatePaletteController.prototype.onButtonClick_ = function (evt) {
+ var target = evt.target;
+ if (target.dataset.action === 'submit') {
+ this.saveAndSelectPalette_();
+ } else if (target.dataset.action === 'cancel') {
+ this.closeDialog();
+ } else if (target.dataset.action === 'delete') {
+ this.deletePalette_();
+ }
+ };
+
+ ns.CreatePaletteController.prototype.saveAndSelectPalette_ = function () {
+ this.palette.setColors(this.colorsListWidget.getColors());
+ this.paletteService.savePalette(this.palette);
+ pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, this.palette.id);
+ this.closeDialog();
+ };
+
+ ns.CreatePaletteController.prototype.deletePalette_ = function () {
+ if (window.confirm('Are you sure you want to delete palette ' + this.palette.name)) {
+ this.paletteService.deletePaletteById(this.palette.id);
+ pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, Constants.CURRENT_COLORS_PALETTE_ID);
+ this.closeDialog();
+ }
+ };
+
+ ns.CreatePaletteController.prototype.onDownloadButtonClick_ = function () {
+ var paletteWriter = new pskl.service.palette.PaletteGplWriter(this.palette);
+ var paletteAsString = paletteWriter.write();
+
+ pskl.utils.BlobUtils.stringToBlob(paletteAsString, function(blob) {
+ pskl.utils.FileUtils.downloadAsFile(blob, this.palette.name + '.gpl');
+ }.bind(this), "application/json");
+ };
+
+ ns.CreatePaletteController.prototype.onImportFileButtonClick_ = function () {
+ this.hiddenFileInput.click();
+ };
+
+ ns.CreatePaletteController.prototype.onFileInputChange_ = function (evt) {
+ var files = this.hiddenFileInput.files;
+ if (files.length == 1) {
+ this.paletteImportService.read(files[0], this.setPalette_.bind(this), this.displayErrorMessage_.bind(this));
+ }
+ };
+
+ ns.CreatePaletteController.prototype.displayErrorMessage_ = function (message) {
+ message = "Could not import palette : " + message;
+ $.publish(Events.SHOW_NOTIFICATION, [{"content": message}]);
+ window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000);
+ };
+
+ ns.CreatePaletteController.prototype.onNameInputChange_ = function (evt) {
+ this.palette.name = pskl.utils.escapeHtml(this.nameInput.value);
+ };
+})();
\ No newline at end of file
diff --git a/src/js/controller/dialogs/DialogsController.js b/src/js/controller/dialogs/DialogsController.js
index 2f52a021..eac69e4d 100644
--- a/src/js/controller/dialogs/DialogsController.js
+++ b/src/js/controller/dialogs/DialogsController.js
@@ -2,9 +2,9 @@
var ns = $.namespace('pskl.controller.dialogs');
var dialogs = {
- 'manage-palettes' : {
- template : 'templates/dialogs/manage-palettes.html',
- controller : ns.PaletteManagerController
+ 'create-palette' : {
+ template : 'templates/dialogs/create-palette.html',
+ controller : ns.CreatePaletteController
},
'browse-local' : {
template : 'templates/dialogs/browse-local.html',
@@ -27,7 +27,8 @@
$.subscribe(Events.DIALOG_DISPLAY, this.onDialogDisplayEvent_.bind(this));
$.subscribe(Events.DIALOG_HIDE, this.onDialogHideEvent_.bind(this));
- pskl.app.shortcutService.addShortcut('alt+P', this.onDialogDisplayEvent_.bind(this, null, 'manage-palettes'));
+ pskl.app.shortcutService.addShortcut('alt+P', this.onDialogDisplayEvent_.bind(this, null, 'create-palette'));
+
this.dialogWrapper_.classList.add('animated');
};
@@ -42,8 +43,8 @@
if (!this.isDisplayed()) {
var config = dialogs[dialogId];
if (config) {
- this.dialogContainer_.innerHTML = pskl.utils.Template.get(config.template);
this.dialogContainer_.classList.add(dialogId);
+ this.dialogContainer_.innerHTML = pskl.utils.Template.get(config.template);
var controller = new config.controller(this.piskelController);
controller.init(initArgs);
diff --git a/src/js/controller/dialogs/ImportImageController.js b/src/js/controller/dialogs/ImportImageController.js
index 08f6d417..564fa116 100644
--- a/src/js/controller/dialogs/ImportImageController.js
+++ b/src/js/controller/dialogs/ImportImageController.js
@@ -28,7 +28,7 @@
this.importImageForm = $('[name=import-image-form]');
this.importImageForm.submit(this.onImportFormSubmit_.bind(this));
- pskl.utils.FileUtils.readFile(this.file_, this.processImageSource_.bind(this));
+ pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this));
};
ns.ImportImageController.prototype.onImportFormSubmit_ = function (evt) {
@@ -55,18 +55,9 @@
}
};
- /**
- * Create an image from the given source (url or data-url), and onload forward to onImageLoaded
- * TODO : should be a generic utility method, should take a callback
- * @param {String} imageSource url or data-url, will be used as src for the image
- */
- ns.ImportImageController.prototype.processImageSource_ = function (imageSource) {
- this.importedImage_ = new Image();
- this.importedImage_.onload = this.onImageLoaded_.bind(this);
- this.importedImage_.src = imageSource;
- };
+ ns.ImportImageController.prototype.onImageLoaded_ = function (image) {
+ this.importedImage_ = image;
- ns.ImportImageController.prototype.onImageLoaded_ = function (evt) {
var w = this.importedImage_.width,
h = this.importedImage_.height;
@@ -115,7 +106,7 @@
gifLoader.load({
success : function(){
var images = gifLoader.getFrames().map(function (frame) {
- return pskl.CanvasUtils.createFromImageData(frame.data);
+ return pskl.utils.CanvasUtils.createFromImageData(frame.data);
});
this.createPiskelFromImages_(images);
this.closeDialog();
diff --git a/src/js/controller/dialogs/PaletteManagerController.js b/src/js/controller/dialogs/PaletteManagerController.js
deleted file mode 100644
index dc16ba4d..00000000
--- a/src/js/controller/dialogs/PaletteManagerController.js
+++ /dev/null
@@ -1,382 +0,0 @@
-(function () {
- var ns = $.namespace('pskl.controller.dialogs');
-
- var tinycolor = window.tinycolor;
-
- var SELECTED_CLASSNAME = 'selected';
- var NEW_COLOR_CLASS = 'palette-manager-new-color';
- var CLOSE_ICON_CLASS = 'palette-manager-delete-card';
- var EDIT_NAME_CLASS = 'edit-icon';
-
- ns.PaletteManagerController = function (piskelController) {
- this.piskelController = piskelController;
- this.palettes = this.retrieveUserPalettes();
- this.originalPalettes = this.retrieveUserPalettes();
- this.selectedPaletteId = null;
-
- // Keep track of all spectrum instances created, to dispose them when closing the popup
- this.spectrumContainers = [];
- };
-
- pskl.utils.inherit(ns.PaletteManagerController, ns.AbstractDialogController);
-
- ns.PaletteManagerController.prototype.init = function () {
- this.superclass.init.call(this);
-
- this.palettesList = document.querySelector('.palette-manager-list');
- this.paletteBody = document.querySelector('.palette-manager-details-body');
- this.paletteHead = document.querySelector('.palette-manager-details-head');
- this.createButton = document.querySelector('.palette-manager-actions-button[data-action="create"]');
- this.saveAllButton = document.querySelector('.palette-manager-actions-button[data-action="save-all"]');
-
- this.colorCardTemplate = pskl.utils.Template.get('palette-color-card-template');
- this.newColorTemplate = pskl.utils.Template.get('palette-new-color-template');
- this.paletteHeadTemplate = pskl.utils.Template.get('palette-details-head-template');
-
- // Events
- this.palettesList.addEventListener('click', this.onPaletteListClick.bind(this));
- // Delegated event listener for events repeated on all cards
- this.paletteBody.addEventListener('click', this.delegatedPaletteBodyClick.bind(this));
- this.paletteHead.addEventListener('click', this.delegatedPaletteHeadClick.bind(this));
- this.createButton.addEventListener('click', this.onCreateClick_.bind(this));
- this.saveAllButton.addEventListener('click', this.saveAll.bind(this));
-
- // Init markup
- this.createPaletteListMarkup();
- if (this.palettes.length > 0) {
- this.selectPalette(this.palettes[0].id);
- } else {
- this.createPalette('New palette');
- }
- };
-
- ns.PaletteManagerController.prototype.destroy = function () {
- this.destroySpectrumPickers();
- };
-
- ns.PaletteManagerController.prototype.onCreateClick_ = function (evt) {
- this.createPalette();
- };
-
- ns.PaletteManagerController.prototype.createPalette = function (name) {
- if (!name) {
- name = window.prompt('Please enter a name for your palette', 'New palette');
- }
- if (name) {
- var palette = this.createPaletteObject(name);
- this.palettes.push(palette);
- this.createPaletteListMarkup();
- this.selectPalette(palette.id);
- }
- };
-
- ns.PaletteManagerController.prototype.createPaletteObject = function (name) {
- return {
- id : 'palette-' + Date.now() + '-' + Math.floor(Math.random()*1000),
- name : name,
- colors : []
- };
- };
-
- ns.PaletteManagerController.prototype.redraw = function () {
- this.createPaletteListMarkup();
- this.selectPalette(this.selectedPaletteId);
- };
-
- ns.PaletteManagerController.prototype.selectPalette = function (paletteId) {
- this.deselectCurrentPalette();
- var paletteListItem = this.palettesList.querySelector('[data-palette-id='+paletteId+']');
- if (paletteListItem) {
- this.selectedPaletteId = paletteId;
- paletteListItem.classList.add(SELECTED_CLASSNAME);
- this.refreshPaletteDetails();
- }
- };
-
- ns.PaletteManagerController.prototype.refreshPaletteDetails = function () {
- this.createPaletteHeadMarkup();
- this.createPaletteBodyMarkup();
- this.initPaletteDetailsEvents();
- this.initPaletteCardsSpectrum();
- };
-
- ns.PaletteManagerController.prototype.createPaletteListMarkup = function () {
- var html = this.palettes.map(function (palette) {
- var paletteCopy = {
- id : palette.id,
- name : this.isPaletteModified(palette) ? palette.name + " *" : palette.name
- };
- return pskl.utils.Template.replace('
{{name}}
', paletteCopy);
- }.bind(this)).join('');
- this.palettesList.innerHTML = html;
- };
-
- /**
- * Fill the palette body container with color cards for the selected palette
- */
- ns.PaletteManagerController.prototype.createPaletteHeadMarkup = function () {
- var palette = this.getSelectedPalette();
- var dict = {
- 'name' : palette.name,
- 'save:disabled' : !this.isPaletteModified(palette),
- 'revert:disabled' : !this.isPaletteModified(palette),
- 'delete:disabled' : this.palettes.length < 2
- };
- var html = pskl.utils.Template.replace(this.paletteHeadTemplate, dict);
-
- this.paletteHead.innerHTML = html;
- };
-
- ns.PaletteManagerController.prototype.isPaletteModified = function (palette) {
- var isModified = false;
- var originalPalette = this.getPaletteById(palette.id, this.originalPalettes);
- if (originalPalette) {
- var differentName = originalPalette.name !== palette.name;
- var differentColors = palette.colors.join('') !== originalPalette.colors.join('');
- isModified = differentName || differentColors;
- } else {
- isModified = true;
- }
- return isModified;
- };
-
- /**
- * Fill the palette body container with color cards for the selected palette
- */
- ns.PaletteManagerController.prototype.createPaletteBodyMarkup = function () {
- var palette = this.getSelectedPalette();
-
- var html = this.getColorCardsMarkup(palette.colors);
- html += pskl.utils.Template.replace(this.newColorTemplate, {classname : NEW_COLOR_CLASS});
-
- this.paletteBody.innerHTML = html;
- };
-
- ns.PaletteManagerController.prototype.initPaletteDetailsEvents = function () {
- // New Card click event
- var newCard = this.paletteBody.querySelector('.' + NEW_COLOR_CLASS);
- newCard.addEventListener('click', this.onNewCardClick.bind(this));
-
- if (this.palettes.length < 2) {
- var deleteButton = this.paletteHead.querySelector('.palette-manager-palette-button[data-action="delete"]');
- deleteButton.setAttribute("disabled", "disabled");
- }
- };
-
- ns.PaletteManagerController.prototype.onNewCardClick = function () {
- var color;
- var palette = this.getSelectedPalette();
- if (palette && palette.colors.length > 0) {
- color = palette.colors[palette.colors.length-1];
- } else {
- color = '#FFFFFF';
- }
- this.addColorInSelectedPalette(color);
- };
-
- ns.PaletteManagerController.prototype.delegatedPaletteBodyClick = function (event) {
- var target = event.target;
- if (target.classList.contains(CLOSE_ICON_CLASS)) {
- var colorId = parseInt(target.parentNode.dataset.colorId, 10);
- this.removeColorInSelectedPalette(colorId);
- }
- };
-
- ns.PaletteManagerController.prototype.delegatedPaletteHeadClick = function (event) {
- var target = event.target;
- if (target.classList.contains(EDIT_NAME_CLASS)) {
- this.renameSelectedPalette();
- } else if (target.classList.contains('palette-manager-palette-button')) {
- var action = target.dataset.action;
- if (action === 'save') {
- this.savePalette(this.getSelectedPalette().id);
- this.redraw();
- } else if (action === 'revert') {
- this.revertChanges();
- } else if (action === 'delete') {
- this.deleteSelectedPalette();
- }
- }
- };
-
- ns.PaletteManagerController.prototype.getSpectrumSelector_ = function () {
- return ':not(.' + NEW_COLOR_CLASS + ')>.palette-manager-color-square';
- };
-
- ns.PaletteManagerController.prototype.initPaletteCardsSpectrum = function () {
- var oSelf = this;
- var container = $(this.getSpectrumSelector_());
- container.spectrum({
- clickoutFiresChange : true,
- showInput: true,
- showButtons: false,
- change : function (color) {
- var target = this;
- var colorId = parseInt(target.parentNode.dataset.colorId, 10);
- oSelf.updateColorInSelectedPalette(colorId, color);
- },
- beforeShow : function() {
- var target = this;
- var colorId = parseInt(target.parentNode.dataset.colorId, 10);
- var palette = oSelf.getSelectedPalette();
- var color = palette.colors[colorId];
- container.spectrum("set", color);
- }
- });
-
- this.spectrumContainers.push(container);
- };
-
- /**
- * Destroy all spectrum instances generated by the palette manager
- */
- ns.PaletteManagerController.prototype.destroySpectrumPickers = function () {
- this.spectrumContainers.forEach(function (container) {
- container.spectrum("destroy");
- });
- this.spectrumContainers = [];
- };
-
- ns.PaletteManagerController.prototype.updateColorInSelectedPalette = function (colorId, color) {
- var palette = this.getSelectedPalette();
- var hexColor = '#' + (color.toHex().toUpperCase());
- palette.colors.splice(colorId, 1, hexColor);
-
- this.redraw();
- };
-
- ns.PaletteManagerController.prototype.addColorInSelectedPalette = function (color) {
- var selectedPalette = this.getSelectedPalette();
- selectedPalette.colors.push(color);
-
- this.redraw();
- };
-
- ns.PaletteManagerController.prototype.removeColorInSelectedPalette = function (colorId) {
- var palette = this.getSelectedPalette();
- palette.colors.splice(colorId, 1);
-
- this.redraw();
- };
-
- ns.PaletteManagerController.prototype.renameSelectedPalette = function () {
- var palette = this.getSelectedPalette();
- var name = window.prompt('Please enter a new name for palette "' + palette.name + '"', palette.name);
- if (name) {
- palette.name = name;
- this.redraw();
- }
- };
-
- ns.PaletteManagerController.prototype.getSelectedPalette = function () {
- return this.getPaletteById(this.selectedPaletteId, this.palettes);
- };
-
- ns.PaletteManagerController.prototype.getColorCardsMarkup = function (colors) {
- var html = colors.map(function (color, index) {
- var dict = {
- colorId : index,
- hex : color,
- rgb : tinycolor(color).toRgbString(),
- hsl : tinycolor(color).toHslString()
- };
- return pskl.utils.Template.replace(this.colorCardTemplate, dict);
- }.bind(this)).join('');
- return html;
- };
-
- ns.PaletteManagerController.prototype.getPaletteById = function (paletteId, palettes) {
- var match = null;
-
- palettes.forEach(function (palette) {
- if (palette.id === paletteId) {
- match = palette;
- }
- });
-
- return match;
- };
-
- ns.PaletteManagerController.prototype.removePaletteById = function (paletteId, palettes) {
- var palette = this.getPaletteById(paletteId, palettes);
- if (palette) {
- var index = palettes.indexOf(palette);
- palettes.splice(index, 1);
- }
- };
-
- ns.PaletteManagerController.prototype.deselectCurrentPalette = function () {
- var selectedItem = this.palettesList.querySelector('.' + SELECTED_CLASSNAME);
- if (selectedItem) {
- this.selectedPaletteId = null;
- selectedItem.classList.remove(SELECTED_CLASSNAME);
- }
- };
-
- ns.PaletteManagerController.prototype.revertChanges = function () {
- var palette = this.getSelectedPalette();
- var originalPalette = this.getPaletteById(palette.id, this.originalPalettes);
- palette.name = originalPalette.name;
- palette.colors = originalPalette.colors.slice(0);
-
- this.redraw();
- };
-
- ns.PaletteManagerController.prototype.deleteSelectedPalette = function () {
- var palette = this.getSelectedPalette();
- if (this.palettes.length > 1) {
- if (window.confirm('Are you sure you want to delete "' + palette.name + '" ?')) {
- this.removePaletteById(palette.id, this.palettes);
- this.removePaletteById(palette.id, this.originalPalettes);
-
- this.persistToLocalStorage();
-
- this.createPaletteListMarkup();
- this.selectPalette(this.palettes[0].id);
- }
- }
- };
-
- ns.PaletteManagerController.prototype.onPaletteListClick = function (event) {
- var target = event.target;
- if (target.dataset.paletteId) {
- this.selectPalette(target.dataset.paletteId);
- }
- };
-
- ns.PaletteManagerController.prototype.saveAll = function () {
- this.palettes.forEach(function (palette) {
- this.savePalette(palette.id);
- }.bind(this));
-
- this.redraw();
- };
-
- ns.PaletteManagerController.prototype.savePalette = function (paletteId) {
- var palette = this.getPaletteById(paletteId, this.palettes);
- var originalPalette = this.getPaletteById(paletteId, this.originalPalettes);
- if (originalPalette) {
- originalPalette.name = palette.name;
- originalPalette.colors = palette.colors;
- } else {
- this.originalPalettes.push(palette);
- }
-
- this.persistToLocalStorage();
-
- $.publish(Events.SHOW_NOTIFICATION, [{"content": "Palette " + palette.name + " successfully saved !"}]);
- window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000);
- };
-
- ns.PaletteManagerController.prototype.persistToLocalStorage = function () {
- window.localStorage.setItem('piskel.palettes', JSON.stringify(this.originalPalettes));
- this.originalPalettes = this.retrieveUserPalettes();
- $.publish(Events.PALETTE_LIST_UPDATED);
- };
-
- ns.PaletteManagerController.prototype.retrieveUserPalettes = function () {
- var palettesString = window.localStorage.getItem('piskel.palettes');
- return JSON.parse(palettesString) || [];
- };
-
-})();
\ No newline at end of file
diff --git a/src/js/controller/piskel/PiskelController.js b/src/js/controller/piskel/PiskelController.js
index ed03a303..d4eb301e 100644
--- a/src/js/controller/piskel/PiskelController.js
+++ b/src/js/controller/piskel/PiskelController.js
@@ -158,7 +158,7 @@
ns.PiskelController.prototype.getFrameCount = function () {
var layer = this.piskel.getLayerAt(0);
- return layer.length();
+ return layer.size();
};
ns.PiskelController.prototype.setCurrentFrameIndex = function (index) {
@@ -205,6 +205,18 @@
}
};
+ ns.PiskelController.prototype.mergeDownLayerAt = function (index) {
+ var layer = this.getLayerByIndex(index);
+ var downLayer = this.getLayerByIndex(index-1);
+ if (layer && downLayer) {
+ var mergedLayer = pskl.utils.LayerUtils.mergeLayers(layer, downLayer);
+ this.removeLayerAt(index);
+ this.piskel.addLayerAt(mergedLayer, index);
+ this.removeLayerAt(index-1);
+ this.selectLayer(mergedLayer);
+ }
+ };
+
ns.PiskelController.prototype.generateLayerName_ = function () {
var name = "Layer " + this.layerIdCounter;
while (this.hasLayerForName_(name)) {
diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js
index 39264cef..e5da46bf 100644
--- a/src/js/controller/piskel/PublicPiskelController.js
+++ b/src/js/controller/piskel/PublicPiskelController.js
@@ -99,6 +99,12 @@
$.publish(Events.PISKEL_RESET);
};
+ ns.PublicPiskelController.prototype.mergeDownLayerAt = function (index) {
+ this.raiseSaveStateEvent_(this.piskelController.mergeDownLayerAt, [index]);
+ this.piskelController.mergeDownLayerAt(index);
+ $.publish(Events.PISKEL_RESET);
+ };
+
ns.PublicPiskelController.prototype.moveLayerUp = function () {
this.raiseSaveStateEvent_(this.piskelController.moveLayerUp, []);
this.piskelController.moveLayerUp();
diff --git a/src/js/controller/settings/GifExportController.js b/src/js/controller/settings/GifExportController.js
index c0b68eaf..02618498 100644
--- a/src/js/controller/settings/GifExportController.js
+++ b/src/js/controller/settings/GifExportController.js
@@ -36,9 +36,6 @@
this.downloadButton = $(".gif-download-button");
this.downloadButton.click(this.onDownloadButtonClick_.bind(this));
- this.exportProgressStatusEl = document.querySelector('.gif-export-progress-status');
- this.exportProgressBarEl = document.querySelector('.gif-export-progress-bar');
-
this.createOptionElements_();
};
@@ -123,29 +120,19 @@
});
}
+ $.publish(Events.SHOW_PROGRESS, [{"name": 'Building animated GIF ...'}]);
gif.on('progress', function(percentage) {
- this.updateProgressStatus_((percentage*100).toFixed(2));
+ $.publish(Events.UPDATE_PROGRESS, [{"progress": (percentage*100).toFixed(1)}]);
}.bind(this));
gif.on('finished', function(blob) {
- this.hideProgressStatus_();
+ $.publish(Events.HIDE_PROGRESS);
pskl.utils.FileUtils.readFile(blob, cb);
}.bind(this));
gif.render();
};
- ns.GifExportController.prototype.updateProgressStatus_ = function (percentage) {
- this.exportProgressStatusEl.innerHTML = percentage + '%';
- this.exportProgressBarEl.style.width = percentage + "%";
-
- };
-
- ns.GifExportController.prototype.hideProgressStatus_ = function () {
- this.exportProgressStatusEl.innerHTML = '';
- this.exportProgressBarEl.style.width = "0";
- };
-
// FIXME : HORRIBLE COPY/PASTA
ns.GifExportController.prototype.updateStatus_ = function (imageUrl, error) {
diff --git a/src/js/controller/settings/PngExportController.js b/src/js/controller/settings/PngExportController.js
index d4aab120..db74da66 100644
--- a/src/js/controller/settings/PngExportController.js
+++ b/src/js/controller/settings/PngExportController.js
@@ -34,7 +34,7 @@
var canvas = this.getFrameAsCanvas_(frame);
var basename = this.pngFilePrefixInput.value;
var filename = basename + (i+1) + ".png";
- zip.file(filename, pskl.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true});
+ zip.file(filename, pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true});
}
var fileName = this.getPiskelName_() + '.zip';
diff --git a/src/js/controller/widgets/ColorsList.js b/src/js/controller/widgets/ColorsList.js
new file mode 100644
index 00000000..1c137b5c
--- /dev/null
+++ b/src/js/controller/widgets/ColorsList.js
@@ -0,0 +1,145 @@
+(function () {
+ var ns = $.namespace('pskl.controller.widgets');
+
+ var DEFAULT_COLOR = '#000000';
+
+ ns.ColorsList = function (container) {
+ this.selectedIndex = -1;
+ this.palette = new pskl.model.Palette('tmp', 'tmp', []);
+ this.container = container;
+
+ this.colorsList = this.container.querySelector('.colors-list');
+ this.colorPreviewEl = this.container.querySelector('.color-preview');
+
+ $(container).sortable({
+ placeholder: 'colors-list-drop-proxy',
+ update: this.onColorDrop_.bind(this),
+ items: '.create-palette-color'
+ });
+
+ this.colorsList.addEventListener('click', this.onColorContainerClick_.bind(this));
+
+ var colorPickerContainer = container.querySelector('.color-picker-container');
+ this.hslRgbColorPicker = new pskl.controller.widgets.HslRgbColorPicker(colorPickerContainer, this.onColorUpdated_.bind(this));
+ this.hslRgbColorPicker.init();
+ };
+
+ ns.ColorsList.prototype.setColors = function (colors) {
+ if (colors.length === 0) {
+ colors.push(DEFAULT_COLOR);
+ }
+
+ this.palette.setColors(colors);
+
+ this.selectColor_(0);
+ this.refresh_();
+ };
+
+ ns.ColorsList.prototype.getColors = function () {
+ return this.palette.getColors();
+ };
+
+ ns.ColorsList.prototype.destroy = function () {
+ this.hslRgbColorPicker.destroy();
+ this.container = null;
+ this.colorsList = null;
+ this.colorPreviewEl = null;
+ };
+
+ /**
+ * Lightweight refresh only changing the color of one element of the palette color list
+ */
+ ns.ColorsList.prototype.refreshColorElement_ = function (index) {
+ var color = this.palette.get(this.selectedIndex);
+ var element = document.querySelector('[data-palette-index="'+index+'"]');
+ if (element) {
+ element.style.background = color;
+ element.classList.toggle('light-color', this.isLight_(color));
+ }
+ };
+
+ ns.ColorsList.prototype.onColorContainerClick_ = function (evt) {
+ var target = evt.target;
+ if (target.classList.contains('create-palette-color')) {
+ this.onPaletteColorClick_(evt, target);
+ } else if (target.classList.contains('create-palette-new-color')) {
+ this.onNewColorClick_(evt, target);
+ } else if (target.classList.contains('create-palette-remove-color')) {
+ this.onRemoveColorClick_(evt, target);
+ }
+ this.refresh_();
+ };
+
+ ns.ColorsList.prototype.onColorUpdated_ = function (color) {
+ var rgbColor = color.toRgbString();
+ this.colorPreviewEl.style.background = rgbColor;
+ if (this.palette) {
+ this.palette.set(this.selectedIndex, rgbColor);
+ this.refreshColorElement_(this.selectedIndex);
+ }
+ };
+
+ ns.ColorsList.prototype.onPaletteColorClick_ = function (evt, target) {
+ var index = parseInt(target.dataset.paletteIndex,10);
+ this.selectColor_(index);
+ };
+
+ ns.ColorsList.prototype.onRemoveColorClick_ = function (evt, target) {
+ var colorElement = target.parentNode;
+ var index = parseInt(colorElement.dataset.paletteIndex,10);
+ this.removeColor_(index);
+ };
+
+ ns.ColorsList.prototype.onNewColorClick_ = function (evt, target) {
+ var newColor = this.palette.get(this.selectedIndex) || '#000000';
+ this.palette.add(newColor);
+ this.selectColor_(this.palette.size()-1);
+ };
+
+ ns.ColorsList.prototype.refresh_ = function () {
+ var html = "";
+ var tpl = pskl.utils.Template.get('create-palette-color-template');
+ var colors = this.palette.getColors();
+
+ colors.forEach(function (color, index) {
+ var isSelected = (index === this.selectedIndex);
+
+ html += pskl.utils.Template.replace(tpl, {
+ 'color':color, index:index,
+ ':selected':isSelected,
+ ':light-color':this.isLight_(color)
+ });
+ }.bind(this));
+
+ html += '
+
';
+
+ this.colorsList.innerHTML = html;
+ };
+
+ ns.ColorsList.prototype.selectColor_ = function (index) {
+ this.selectedIndex = index;
+ this.hslRgbColorPicker.setColor(this.palette.get(index));
+ };
+
+ ns.ColorsList.prototype.removeColor_ = function (index) {
+ this.palette.removeAt(index);
+ this.refresh_();
+ };
+
+ ns.ColorsList.prototype.isLight_ = function (color) {
+ var rgb = window.tinycolor(color).toRgb();
+ return rgb.r+rgb.b+rgb.g > 128*3;
+ };
+
+ ns.ColorsList.prototype.onColorDrop_ = function (evt, drop) {
+ var colorElement = drop.item.get(0);
+
+ var oldIndex = parseInt(colorElement.dataset.paletteIndex, 10);
+ var newIndex = $('.create-palette-color').index(drop.item);
+ this.palette.move(oldIndex, newIndex);
+
+ this.selectedIndex = newIndex;
+
+ this.refresh_();
+ };
+})();
\ No newline at end of file
diff --git a/src/js/controller/widgets/HslRgbColorPicker.js b/src/js/controller/widgets/HslRgbColorPicker.js
new file mode 100644
index 00000000..f7e539d5
--- /dev/null
+++ b/src/js/controller/widgets/HslRgbColorPicker.js
@@ -0,0 +1,194 @@
+(function () {
+ var ns = $.namespace('pskl.controller.widgets');
+
+ ns.HslRgbColorPicker = function (container, colorUpdatedCallback) {
+ this.container = container;
+ this.colorUpdatedCallback = colorUpdatedCallback;
+ this.lastInputTimestamp_ = 0;
+ };
+
+ ns.HslRgbColorPicker.prototype.init = function () {
+ var isChromeOrFirefox = pskl.utils.UserAgent.isChrome || pskl.utils.UserAgent.isFirefox;
+ var changeEvent = isChromeOrFirefox ? 'input' : 'change';
+ this.container.addEventListener(changeEvent, this.onPickerChange_.bind(this));
+ this.container.addEventListener('keydown', this.onKeydown_.bind(this));
+
+ this.spectrumEl = this.container.querySelector('.color-picker-spectrum');
+
+ $(this.spectrumEl).spectrum({
+ flat: true,
+ showInput: true,
+ showButtons: false,
+ move : this.setColor.bind(this),
+ change : this.setColor.bind(this),
+ preferredFormat: 'hex'
+ });
+
+ this.setColor("#000000");
+ };
+
+ ns.HslRgbColorPicker.prototype.destroy = function () {
+ this.container = null;
+ this.spectrumEl = null;
+ };
+
+ ns.HslRgbColorPicker.prototype.onPickerChange_ = function (evt) {
+ var target = evt.target;
+
+ var model = target.dataset.model;
+ var dimension = target.dataset.dimension;
+
+ var value = parseInt(target.value, 10);
+ if (dimension === 'v' || dimension === 's') {
+ value = value/100;
+ }
+
+ var color;
+ if (model === 'rgb') {
+ color = this.tinyColor.toRgb();
+ } else if (model === 'hsv') {
+ color = this.hsvColor;
+ }
+
+ if (isNaN(value)) {
+ value = color[dimension];
+ } else {
+ color[dimension] = value;
+ }
+
+ this.setColor(color);
+ };
+
+ ns.HslRgbColorPicker.prototype.onKeydown_ = function (evt) {
+ var target = evt.target;
+
+ if (target.getAttribute('type').toLowerCase() === 'text') {
+ var value = parseInt(target.value, 10);
+ var dimension = target.dataset.dimension;
+
+ var key = pskl.service.keyboard.KeycodeTranslator.toChar(evt.keyCode);
+ if (key === 'up') {
+ value = value + 1;
+ } else if (key === 'down') {
+ value = value - 1;
+ }
+
+ value = this.normalizeDimension_(value, dimension);
+
+ target.value = value;
+ this.onPickerChange_(evt);
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.setColor = function (inputColor) {
+ if (!this.unplugged) {
+ this.unplugged = true;
+
+ this.hsvColor = this.toHsvColor_(inputColor);
+ this.tinyColor = this.toTinyColor_(inputColor);
+
+ this.updateInputs();
+ $(".color-picker-spectrum").spectrum("set", this.tinyColor);
+
+ this.colorUpdatedCallback(this.tinyColor);
+
+ this.unplugged = false;
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.updateInputs = function () {
+ var inputs = this.container.querySelectorAll('input');
+ var rgb = this.tinyColor.toRgb();
+
+
+ for (var i = 0 ; i < inputs.length ; i++) {
+ var input = inputs[i];
+ var dimension = input.dataset.dimension;
+ var model = input.dataset.model;
+
+ if (model === 'rgb') {
+ input.value = rgb[dimension];
+ } else if (model === 'hsv') {
+ var value = this.hsvColor[dimension];
+ if (dimension === 'v' || dimension === 's') {
+ value = 100 * value;
+ }
+ input.value = Math.round(value);
+ }
+
+ if (input.getAttribute('type') === 'range') {
+ this.updateSliderBackground(input);
+ }
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.updateSliderBackground = function (slider) {
+ var dimension = slider.dataset.dimension;
+ var model = slider.dataset.model;
+
+ var start, end;
+ var isHueSlider = dimension === 'h';
+ if (!isHueSlider) {
+ var colors = this.getSliderBackgroundColors_(model, dimension);
+ slider.style.backgroundImage = "linear-gradient(to right, " + colors.start + " 0, " + colors.end + " 100%)";
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.getSliderBackgroundColors_ = function (model, dimension) {
+ var start, end;
+ if (model === 'hsv') {
+ start = JSON.parse(JSON.stringify(this.hsvColor));
+ start[dimension] = 0;
+
+ end = JSON.parse(JSON.stringify(this.hsvColor));
+ end[dimension] = 1;
+ } else {
+ start = this.tinyColor.toRgb();
+ start[dimension] = 0;
+
+ end = this.tinyColor.toRgb();
+ end[dimension] = 255;
+ }
+
+ return {
+ start : window.tinycolor(start).toRgbString(),
+ end : window.tinycolor(end).toRgbString()
+ };
+ };
+
+ ns.HslRgbColorPicker.prototype.toTinyColor_ = function (color) {
+ if (typeof color == "object" && color.hasOwnProperty("_tc_id")) {
+ return color;
+ } else {
+ return window.tinycolor(JSON.parse(JSON.stringify(color)));
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.toHsvColor_ = function (color) {
+ var isHsvColor = ['h','s','v'].every(color.hasOwnProperty.bind(color));
+ if (isHsvColor) {
+ return {
+ h : Math.max(0, Math.min(359, color.h)),
+ s : Math.max(0, Math.min(1, color.s)),
+ v : Math.max(0, Math.min(1, color.v))
+ };
+ } else {
+ return this.toTinyColor_(color).toHsv();
+ }
+ };
+
+ ns.HslRgbColorPicker.prototype.normalizeDimension_ = function (value, dimension) {
+ var ranges = {
+ 'h' : [0, 359],
+ 's' : [0, 100],
+ 'v' : [0, 100],
+ 'r' : [0, 255],
+ 'g' : [0, 255],
+ 'b' : [0, 255]
+ };
+ var range = ranges[dimension];
+ return Math.max(range[0], Math.min(range[1], value));
+ } ;
+
+
+})();
\ No newline at end of file
diff --git a/src/js/devtools/DrawingTestPlayer.js b/src/js/devtools/DrawingTestPlayer.js
index 82dfbf3c..6c77d4d0 100644
--- a/src/js/devtools/DrawingTestPlayer.js
+++ b/src/js/devtools/DrawingTestPlayer.js
@@ -47,7 +47,7 @@
var then = function () {};
image.onload = function () {
- this.referencePng = pskl.CanvasUtils.createFromImage(image).toDataURL();
+ this.referencePng = pskl.utils.CanvasUtils.createFromImage(image).toDataURL();
then();
}.bind(this);
image.src = this.referencePng;
@@ -101,6 +101,10 @@
var screenCoordinates = pskl.app.drawingController.getScreenCoordinates(recordEvent.coords.x, recordEvent.coords.y);
event.clientX = screenCoordinates.x;
event.clientY = screenCoordinates.y;
+ if (pskl.utils.UserAgent.isMac && event.ctrlKey) {
+ event.metaKey = true;
+ }
+
if (event.type == 'mousedown') {
pskl.app.drawingController.onMousedown_(event);
} else if (event.type == 'mouseup') {
diff --git a/src/js/lib/gif/libgif.js b/src/js/lib/gif/libgif.js
index ef1d6ab4..157f4dee 100644
--- a/src/js/lib/gif/libgif.js
+++ b/src/js/lib/gif/libgif.js
@@ -624,6 +624,7 @@ var SuperGif = function ( opts ) {
};
var load_callback = false;
+ var step_callback = false;
var error_callback = false;
var tmpCanvas = document.createElement('canvas');
@@ -632,6 +633,7 @@ var SuperGif = function ( opts ) {
load: function (callback) {
load_callback = callback.success;
+ step_callback = callback.step;
error_callback = callback.error;
loading = true;
diff --git a/src/js/lib/spectrum/spectrum.js b/src/js/lib/spectrum/spectrum.js
index 731b0ce1..b60d3894 100644
--- a/src/js/lib/spectrum/spectrum.js
+++ b/src/js/lib/spectrum/spectrum.js
@@ -504,8 +504,10 @@
$(doc).bind("mousedown.spectrum", onMousedown);
- // Piskel-specific : change the color as soon as the user does a mouseup
- $(doc).bind("mouseup.spectrum", updateColor);
+ if (!flat) {
+ // Piskel-specific : change the color as soon as the user does a mouseup
+ $(doc).bind("mouseup.spectrum", updateColor);
+ }
$(window).bind("resize.spectrum", resize);
replacer.addClass("sp-active");
@@ -667,10 +669,9 @@
}
}
-
// Update the text entry input as it changes happen
if (opts.showInput) {
- textInput.val(realColor.toString(Constants.PREFERRED_COLOR_FORMAT || format));
+ textInput.val(realColor.toString(format));
}
if (opts.showPalette) {
diff --git a/src/js/model/Layer.js b/src/js/model/Layer.js
index 13d5977e..206aa565 100644
--- a/src/js/model/Layer.js
+++ b/src/js/model/Layer.js
@@ -56,7 +56,7 @@
if (this.frames[index]) {
this.frames.splice(index, 1);
} else {
- throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.length() + ')';
+ throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.size() + ')';
}
};
@@ -93,7 +93,7 @@
}
};
- ns.Layer.prototype.length = function () {
+ ns.Layer.prototype.size = function () {
return this.frames.length;
};
diff --git a/src/js/model/Palette.js b/src/js/model/Palette.js
new file mode 100644
index 00000000..eaadedae
--- /dev/null
+++ b/src/js/model/Palette.js
@@ -0,0 +1,46 @@
+(function () {
+ var ns = $.namespace('pskl.model');
+
+ ns.Palette = function (id, name, colors) {
+ this.id = id;
+ this.name = name;
+ this.colors = colors;
+ };
+
+ ns.Palette.fromObject = function (paletteObj) {
+ var colors = paletteObj.colors.slice(0 , paletteObj.colors.length);
+ return new ns.Palette(paletteObj.id, paletteObj.name, colors);
+ };
+
+ ns.Palette.prototype.getColors = function () {
+ return this.colors;
+ };
+
+ ns.Palette.prototype.setColors = function (colors) {
+ this.colors = colors;
+ };
+
+ ns.Palette.prototype.get = function (index) {
+ return this.colors[index];
+ };
+
+ ns.Palette.prototype.set = function (index, color) {
+ this.colors[index] = color;
+ };
+
+ ns.Palette.prototype.add = function (color) {
+ this.colors.push(color);
+ };
+
+ ns.Palette.prototype.size = function () {
+ return this.colors.length;
+ };
+
+ ns.Palette.prototype.removeAt = function (index) {
+ this.colors.splice(index, 1);
+ };
+
+ ns.Palette.prototype.move = function (oldIndex, newIndex) {
+ this.colors.splice(newIndex, 0, this.colors.splice(oldIndex, 1)[0]);
+ };
+})();
\ No newline at end of file
diff --git a/src/js/model/Piskel.js b/src/js/model/Piskel.js
index ab28ec14..fcc1ebdd 100644
--- a/src/js/model/Piskel.js
+++ b/src/js/model/Piskel.js
@@ -33,7 +33,7 @@
*/
ns.Piskel.fromLayers = function (layers, descriptor) {
var piskel = null;
- if (layers.length > 0 && layers[0].length() > 0) {
+ if (layers.length > 0 && layers[0].size() > 0) {
var sampleFrame = layers[0].getFrameAt(0);
piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight(), descriptor);
layers.forEach(piskel.addLayer.bind(piskel));
@@ -73,6 +73,10 @@
this.layers.push(layer);
};
+ ns.Piskel.prototype.addLayerAt = function (layer, index) {
+ this.layers.splice(index, 0, layer);
+ };
+
ns.Piskel.prototype.moveLayerUp = function (layer) {
var index = this.layers.indexOf(layer);
if (index > -1 && index < this.layers.length-1) {
diff --git a/src/js/rendering/CanvasRenderer.js b/src/js/rendering/CanvasRenderer.js
index ffdea8af..c124c73b 100644
--- a/src/js/rendering/CanvasRenderer.js
+++ b/src/js/rendering/CanvasRenderer.js
@@ -25,7 +25,7 @@
var scaledCanvas = this.createCanvas_(this.zoom);
var scaledContext = scaledCanvas.getContext('2d');
- pskl.CanvasUtils.disableImageSmoothing(scaledCanvas);
+ pskl.utils.CanvasUtils.disableImageSmoothing(scaledCanvas);
scaledContext.scale(this.zoom, this.zoom);
scaledContext.drawImage(canvas, 0, 0);
@@ -44,6 +44,6 @@
zoom = zoom || 1;
var width = this.frame.getWidth() * zoom;
var height = this.frame.getHeight() * zoom;
- return pskl.CanvasUtils.createCanvas(width, height);
+ return pskl.utils.CanvasUtils.createCanvas(width, height);
};
})();
\ No newline at end of file
diff --git a/src/js/rendering/FramesheetRenderer.js b/src/js/rendering/FramesheetRenderer.js
index 06eb4d99..3f30b0b3 100644
--- a/src/js/rendering/FramesheetRenderer.js
+++ b/src/js/rendering/FramesheetRenderer.js
@@ -37,7 +37,7 @@
var count = this.frames.length;
var width = count * sampleFrame.getWidth();
var height = sampleFrame.getHeight();
- return pskl.CanvasUtils.createCanvas(width, height);
+ return pskl.utils.CanvasUtils.createCanvas(width, height);
};
})();
\ No newline at end of file
diff --git a/src/js/rendering/frame/FrameRenderer.js b/src/js/rendering/frame/FrameRenderer.js
index 94d5a440..03dc0c8e 100644
--- a/src/js/rendering/frame/FrameRenderer.js
+++ b/src/js/rendering/frame/FrameRenderer.js
@@ -70,8 +70,8 @@
};
ns.FrameRenderer.prototype.clear = function () {
- pskl.CanvasUtils.clear(this.canvas);
- pskl.CanvasUtils.clear(this.displayCanvas);
+ pskl.utils.CanvasUtils.clear(this.canvas);
+ pskl.utils.CanvasUtils.clear(this.displayCanvas);
};
ns.FrameRenderer.prototype.setZoom = function (zoom) {
@@ -153,8 +153,8 @@
var height = this.displayHeight;
var width = this.displayWidth;
- this.displayCanvas = pskl.CanvasUtils.createCanvas(width, height, this.classes);
- pskl.CanvasUtils.disableImageSmoothing(this.displayCanvas);
+ this.displayCanvas = pskl.utils.CanvasUtils.createCanvas(width, height, this.classes);
+ pskl.utils.CanvasUtils.disableImageSmoothing(this.displayCanvas);
this.container.append(this.displayCanvas);
};
@@ -223,7 +223,7 @@
*/
ns.FrameRenderer.prototype.renderFrame_ = function (frame) {
if (!this.canvas || frame.getWidth() != this.canvas.width || frame.getHeight() != this.canvas.height) {
- this.canvas = pskl.CanvasUtils.createCanvas(frame.getWidth(), frame.getHeight());
+ this.canvas = pskl.utils.CanvasUtils.createCanvas(frame.getWidth(), frame.getHeight());
}
var context = this.canvas.getContext('2d');
diff --git a/src/js/service/CurrentColorsService.js b/src/js/service/CurrentColorsService.js
index 4aed52e7..11a02536 100644
--- a/src/js/service/CurrentColorsService.js
+++ b/src/js/service/CurrentColorsService.js
@@ -5,37 +5,59 @@
this.piskelController = piskelController;
this.currentColors = [];
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
- this.cachedFrameProcessor.setFrameProcessor(this.frameToColors_.bind(this));
+ this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this));
- this.framesColorsCache_ = {};
+ this.colorSorter = new pskl.service.color.ColorSorter();
+ this.paletteService = pskl.app.paletteService;
};
ns.CurrentColorsService.prototype.init = function () {
$.subscribe(Events.PISKEL_RESET, this.onPiskelUpdated_.bind(this));
$.subscribe(Events.TOOL_RELEASED, this.onPiskelUpdated_.bind(this));
+ $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
};
ns.CurrentColorsService.prototype.getCurrentColors = function () {
return this.currentColors;
};
- ns.CurrentColorsService.prototype.frameToColors_ = function (frame) {
- var frameColors = {};
- frame.forEachPixel(function (color, x, y) {
- frameColors[color] = (frameColors[color] || 0) + 1;
- });
- return frameColors;
+ ns.CurrentColorsService.prototype.setCurrentColors = function (colors) {
+ if (colors.join('') !== this.currentColors.join('')) {
+ this.currentColors = colors;
+ $.publish(Events.CURRENT_COLORS_UPDATED);
+ }
};
+ ns.CurrentColorsService.prototype.onUserSettingsChange_ = function (evt, name, value) {
+ if (name == pskl.UserSettings.SELECTED_PALETTE) {
+ if (this.isCurrentColorsPaletteSelected_()) {
+ this.updateCurrentColors_();
+ }
+ }
+ };
ns.CurrentColorsService.prototype.onPiskelUpdated_ = function (evt) {
+ if (this.isCurrentColorsPaletteSelected_()) {
+ this.updateCurrentColors_();
+ }
+ };
+
+ ns.CurrentColorsService.prototype.isCurrentColorsPaletteSelected_ = function () {
+ var paletteId = pskl.UserSettings.get(pskl.UserSettings.SELECTED_PALETTE);
+ var palette = this.paletteService.getPaletteById(paletteId);
+
+ return palette.id === Constants.CURRENT_COLORS_PALETTE_ID;
+ };
+
+ ns.CurrentColorsService.prototype.updateCurrentColors_ = function () {
var layers = this.piskelController.getLayers();
var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);});
var colors = {};
+
frames.forEach(function (f) {
var frameColors = this.cachedFrameProcessor.get(f);
Object.keys(frameColors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED).forEach(function (color) {
- colors[color] = (colors[color] || 0) + frameColors[color];
+ colors[color] = true;
});
}.bind(this));
@@ -43,14 +65,36 @@
delete colors[Constants.TRANSPARENT_COLOR];
// limit the array to the max colors to display
- this.currentColors = Object.keys(colors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED);
+ var colorsArray = Object.keys(colors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED);
+ var currentColors = this.colorSorter.sort(colorsArray);
- // sort by most frequent color
- this.currentColors = this.currentColors.sort(function (c1, c2) {
- return colors[c2] - colors[c1];
- });
+ this.setCurrentColors(currentColors);
+ };
- // TODO : only fire if there was a change
- $.publish(Events.CURRENT_COLORS_UPDATED, colors);
+ ns.CurrentColorsService.prototype.getFrameColors_ = function (frame) {
+ var frameColors = {};
+ frame.forEachPixel(function (color, x, y) {
+ var hexColor = this.toHexString_(color);
+ frameColors[hexColor] = true;
+ }.bind(this));
+ return frameColors;
+ };
+
+ ns.CurrentColorsService.prototype.toHexString_ = function (color) {
+ if (color === Constants.TRANSPARENT_COLOR) {
+ return color;
+ } else {
+ color = color.replace(/\s/g, '');
+ var hexRe = (/^#([a-f0-9]{3}){1,2}$/i);
+ var rgbRe = (/^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/i);
+ if (hexRe.test(color)) {
+ return color.toUpperCase();
+ } else if (rgbRe.test(color)) {
+ var exec = rgbRe.exec(color);
+ return pskl.utils.rgbToHex(exec[1] * 1, exec[2] * 1, exec[3] * 1);
+ } else {
+ console.error('Could not convert color to hex : ', color);
+ }
+ }
};
})();
\ No newline at end of file
diff --git a/src/js/service/FileDropperService.js b/src/js/service/FileDropperService.js
index bf9104ad..5bc2a3c3 100644
--- a/src/js/service/FileDropperService.js
+++ b/src/js/service/FileDropperService.js
@@ -31,14 +31,27 @@
for (var i = 0; i < files.length ; i++) {
var file = files[i];
var isImage = file.type.indexOf('image') === 0;
+ var isPiskel = /\.piskel$/i.test(file.name);
+ var isPalette = /\.(gpl|txt)$/i.test(file.name);
if (isImage) {
this.readImageFile_(file);
- } else if (/\.piskel$/i.test(file.name)) {
+ } else if (isPiskel) {
pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_);
+ } else if (isPalette) {
+ pskl.app.paletteImportService.read(file, this.onPaletteLoaded_.bind(this));
}
}
};
+ ns.FileDropperService.prototype.readImageFile_ = function (imageFile) {
+ pskl.utils.FileUtils.readFile(imageFile, this.processImageSource_.bind(this));
+ };
+
+ ns.FileDropperService.prototype.onPaletteLoaded_ = function (palette) {
+ pskl.app.paletteService.savePalette(palette);
+ pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, palette.id);
+ };
+
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel, descriptor, fps) {
if (window.confirm('This will replace your current animation')) {
piskel.setDescriptor(descriptor);
@@ -47,10 +60,6 @@
}
};
- ns.FileDropperService.prototype.readImageFile_ = function (imageFile) {
- pskl.utils.FileUtils.readFile(imageFile, this.processImageSource_.bind(this));
- };
-
ns.FileDropperService.prototype.processImageSource_ = function (imageSource) {
this.importedImage_ = new Image();
this.importedImage_.onload = this.onImageLoaded_.bind(this);
diff --git a/src/js/service/color/ColorSorter.js b/src/js/service/color/ColorSorter.js
new file mode 100644
index 00000000..0f8e80c1
--- /dev/null
+++ b/src/js/service/color/ColorSorter.js
@@ -0,0 +1,105 @@
+(function () {
+ var ns = $.namespace('pskl.service.color');
+
+ var LOW_SAT = 0.1;
+ var LOW_LUM = 0.1;
+ var HI_LUM = 0.9;
+
+
+ var HUE_STEP = 36;
+ var HUE_BAGS = 10;
+ var HUE_BOUNDS = [];
+ for (var i = 0 ; i < HUE_BAGS ; i++) {
+ HUE_BOUNDS.push(i * HUE_STEP);
+ }
+
+ ns.ColorSorter = function () {
+ this.colorsHslMap_ = {};
+ };
+
+ ns.ColorSorter.prototype.sort = function (colors) {
+ this.colorsHslMap_ = {};
+
+ colors.forEach(function (color) {
+ this.colorsHslMap_[color] = window.tinycolor(color).toHsl();
+ }.bind(this));
+
+ // sort by most frequent color
+ var darkColors = colors.filter(function (c) {
+ var hsl = this.colorsHslMap_[c];
+ return hsl.l <= LOW_LUM;
+ }.bind(this));
+
+ var brightColors = colors.filter(function (c) {
+ var hsl = this.colorsHslMap_[c];
+ return hsl.l >= HI_LUM;
+ }.bind(this));
+
+ var desaturatedColors = colors.filter(function (c) {
+ return brightColors.indexOf(c) === -1 && darkColors.indexOf(c) === -1;
+ }).filter(function (c) {
+ var hsl = this.colorsHslMap_[c];
+ return hsl.s <= LOW_SAT;
+ }.bind(this));
+
+ darkColors = this.sortOnHslProperty_(darkColors, 'l');
+ brightColors = this.sortOnHslProperty_(brightColors, 'l');
+ desaturatedColors = this.sortOnHslProperty_(desaturatedColors, 'h');
+
+ var sortedColors = darkColors.concat(brightColors, desaturatedColors);
+
+ var regularColors = colors.filter(function (c) {
+ return sortedColors.indexOf(c) === -1;
+ });
+
+ var regularColorsBags = HUE_BOUNDS.map(function (hue) {
+ var bagColors = regularColors.filter(function (color) {
+ var hsl = this.colorsHslMap_[color];
+ return (hsl.h >= hue && hsl.h < hue + HUE_STEP);
+ }.bind(this));
+
+ return this.sortRegularColors_(bagColors);
+ }.bind(this));
+
+ return Array.prototype.concat.apply(sortedColors, regularColorsBags);
+ };
+
+ ns.ColorSorter.prototype.sortRegularColors_ = function (colors) {
+ var sortedColors = colors.sort(function (c1, c2) {
+ var hsl1 = this.colorsHslMap_[c1];
+ var hsl2 = this.colorsHslMap_[c2];
+ var hDiff = Math.abs(hsl1.h - hsl2.h);
+ var sDiff = Math.abs(hsl1.s - hsl2.s);
+ var lDiff = Math.abs(hsl1.l - hsl2.l);
+ if (hDiff < 10) {
+ if (sDiff > lDiff) {
+ return this.compareValues_(hsl1.s, hsl2.s);
+ } else {
+ return this.compareValues_(hsl1.l, hsl2.l);
+ }
+ } else {
+ return this.compareValues_(hsl1.h, hsl2.h);
+ }
+ }.bind(this));
+
+ return sortedColors;
+ };
+
+ ns.ColorSorter.prototype.sortOnHslProperty_ = function (colors, property) {
+ return colors.sort(function (c1, c2) {
+ var hsl1 = this.colorsHslMap_[c1];
+ var hsl2 = this.colorsHslMap_[c2];
+ return this.compareValues_(hsl1[property], hsl2[property]);
+ }.bind(this));
+ };
+
+ ns.ColorSorter.prototype.compareValues_ = function (v1, v2) {
+ if (v1 > v2) {
+ return 1;
+ } else if (v1 < v2) {
+ return -1;
+ }
+ return 0;
+ };
+
+})();
\ No newline at end of file
diff --git a/src/js/service/keyboard/CheatsheetService.js b/src/js/service/keyboard/CheatsheetService.js
index 03c085bc..d319784c 100644
--- a/src/js/service/keyboard/CheatsheetService.js
+++ b/src/js/service/keyboard/CheatsheetService.js
@@ -11,12 +11,12 @@
throw 'cheatsheetEl_ DOM element could not be retrieved';
}
this.initMarkup_();
- pskl.app.shortcutService.addShortcut('shift+?', this.toggleCheatsheet_.bind(this));
- pskl.app.shortcutService.addShortcut('?', this.toggleCheatsheet_.bind(this));
+ pskl.app.shortcutService.addShortcuts(['?', 'shift+?'], this.toggleCheatsheet_.bind(this));
var link = $('.cheatsheet-link');
link.click(this.toggleCheatsheet_.bind(this));
+
$.subscribe(Events.TOGGLE_HELP, this.toggleCheatsheet_.bind(this));
$.subscribe(Events.ESCAPE, this.onEscape_.bind(this));
};
@@ -106,7 +106,8 @@
this.toDescriptor_('N', 'Create new frame'),
this.toDescriptor_('shift + N', 'Duplicate selected frame'),
this.toDescriptor_('shift + ?', 'Open/Close this popup'),
- this.toDescriptor_('alt + P', 'Open the Palette Manager'),
+ this.toDescriptor_('alt + P', 'Create a Palette'),
+ this.toDescriptor_('</>', 'Select previous/next palette color'),
this.toDescriptor_('alt + O', 'Toggle Onion Skin'),
this.toDescriptor_('alt + L', 'Toggle Layer Preview')
];
diff --git a/src/js/service/keyboard/KeycodeTranslator.js b/src/js/service/keyboard/KeycodeTranslator.js
index 23269a20..a9e352ad 100644
--- a/src/js/service/keyboard/KeycodeTranslator.js
+++ b/src/js/service/keyboard/KeycodeTranslator.js
@@ -7,7 +7,9 @@
40 : "down",
46 : "del",
189 : "-",
- 187 : "+"
+ 187 : "+",
+ 188 : "<",
+ 190 : ">"
};
var ns = $.namespace('pskl.service.keyboard');
diff --git a/src/js/service/keyboard/ShortcutService.js b/src/js/service/keyboard/ShortcutService.js
index bc0f77b3..960deb18 100644
--- a/src/js/service/keyboard/ShortcutService.js
+++ b/src/js/service/keyboard/ShortcutService.js
@@ -35,6 +35,12 @@
}
};
+ ns.ShortcutService.prototype.addShortcuts = function (keys, callback) {
+ keys.forEach(function (key) {
+ this.addShortcut(key, callback);
+ }.bind(this));
+ };
+
ns.ShortcutService.prototype.removeShortcut = function (rawKey) {
var parsedKey = this.parseKey_(rawKey.toLowerCase());
diff --git a/src/js/service/palette/CurrentColorsPalette.js b/src/js/service/palette/CurrentColorsPalette.js
new file mode 100644
index 00000000..2120872b
--- /dev/null
+++ b/src/js/service/palette/CurrentColorsPalette.js
@@ -0,0 +1,12 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ ns.CurrentColorsPalette = function () {
+ this.name = 'Current colors';
+ this.id = Constants.CURRENT_COLORS_PALETTE_ID;
+ };
+
+ ns.CurrentColorsPalette.prototype.getColors = function () {
+ return pskl.app.currentColorsService.getCurrentColors();
+ };
+})();
\ No newline at end of file
diff --git a/src/js/service/palette/PaletteGplReader.js b/src/js/service/palette/PaletteGplReader.js
new file mode 100644
index 00000000..76173859
--- /dev/null
+++ b/src/js/service/palette/PaletteGplReader.js
@@ -0,0 +1,50 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ var RE_COLOR_LINE = /^(\s*\d{1,3})(\s*\d{1,3})(\s*\d{1,3})/;
+ var RE_EXTRACT_NAME = /^name\s*\:\s*(.*)$/i;
+
+ ns.PaletteGplReader = function (file, onSuccess, onError) {
+ this.file = file;
+ this.onSuccess = onSuccess;
+ this.onError = onError;
+ };
+
+ ns.PaletteGplReader.prototype.read = function () {
+ pskl.utils.FileUtils.readFile(this.file, this.onFileLoaded_.bind(this));
+ };
+
+ ns.PaletteGplReader.prototype.onFileLoaded_ = function (content) {
+ var text = pskl.utils.Base64.toText(content);
+ var lines = text.match(/[^\r\n]+/g);
+
+ var name = lines.map(function (l) {
+ var matches = l.match(RE_EXTRACT_NAME);
+ return matches ? matches[1] : '';
+ }).join('');
+
+ var colorLines = lines.filter(function (l) {
+ return RE_COLOR_LINE.test(l);
+ });
+
+ var colors = colorLines.map(function (l) {
+ var matches = l.match(RE_COLOR_LINE);
+ var color = window.tinycolor({
+ r : parseInt(matches[1], 10),
+ g : parseInt(matches[2], 10),
+ b : parseInt(matches[3], 10)
+ });
+
+ return color.toRgbString();
+ });
+
+ if (name && colors.length) {
+ var uuid = pskl.utils.Uuid.generate();
+ var palette = new pskl.model.Palette(uuid, name, colors);
+ this.onSuccess(palette);
+ } else {
+ this.onError();
+ }
+
+ };
+})();
\ No newline at end of file
diff --git a/src/js/service/palette/PaletteGplWriter.js b/src/js/service/palette/PaletteGplWriter.js
new file mode 100644
index 00000000..4d8ae0ae
--- /dev/null
+++ b/src/js/service/palette/PaletteGplWriter.js
@@ -0,0 +1,40 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ ns.PaletteGplWriter = function (palette) {
+ this.palette = palette;
+ };
+
+ ns.PaletteGplWriter.prototype.write = function () {
+ var lines = [];
+ lines.push('GIMP Palette');
+ lines.push('Name: ' + this.palette.name);
+ lines.push('Columns: 0');
+ lines.push('#');
+ this.palette.getColors().forEach(function (color) {
+ lines.push(this.writeColorLine(color));
+ }.bind(this));
+ lines.push('\r\n');
+
+ return lines.join('\r\n');
+ };
+
+ ns.PaletteGplWriter.prototype.writeColorLine = function (color) {
+ var tinycolor = window.tinycolor(color);
+ var rgb = tinycolor.toRgb();
+ var strBuffer = [];
+ strBuffer.push(this.padString(rgb.r, 3));
+ strBuffer.push(this.padString(rgb.g, 3));
+ strBuffer.push(this.padString(rgb.b, 3));
+ strBuffer.push('Untitled');
+
+ return strBuffer.join(' ');
+ };
+
+ ns.PaletteGplWriter.prototype.padString = function (str, size) {
+ str = str.toString();
+ var pad = (new Array(1+size-str.length)).join(' ');
+ return pad + str;
+ };
+
+})();
diff --git a/src/js/service/palette/PaletteImageReader.js b/src/js/service/palette/PaletteImageReader.js
new file mode 100644
index 00000000..d96b66ea
--- /dev/null
+++ b/src/js/service/palette/PaletteImageReader.js
@@ -0,0 +1,55 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ ns.PaletteImageReader = function (file, onSuccess, onError) {
+ this.file = file;
+ this.onSuccess = onSuccess;
+ this.onError = onError;
+
+ this.colorSorter_ = new pskl.service.color.ColorSorter();
+ };
+
+ ns.PaletteImageReader.prototype.read = function () {
+ pskl.utils.FileUtils.readImageFile(this.file, this.onImageLoaded_.bind(this));
+ };
+
+ ns.PaletteImageReader.prototype.onImageLoaded_ = function (image) {
+ var imageProcessor = new pskl.worker.ImageProcessor(image,
+ this.onWorkerSuccess_.bind(this),
+ this.onWorkerStep_.bind(this),
+ this.onWorkerError_.bind(this));
+
+
+ $.publish(Events.SHOW_PROGRESS, [{"name": 'Processing image colors ...'}]);
+
+ imageProcessor.process();
+ };
+
+ ns.PaletteImageReader.prototype.onWorkerSuccess_ = function (event) {
+ var data = event.data;
+ var colorsMap = data.colorsMap;
+
+ var colors = Object.keys(colorsMap);
+
+ if (colors.length > 200) {
+ this.onError('Too many colors : ' + colors.length);
+ } else {
+ var uuid = pskl.utils.Uuid.generate();
+ var sortedColors = this.colorSorter_.sort(colors);
+ var palette = new pskl.model.Palette(uuid, this.file.name + ' palette', sortedColors);
+
+ this.onSuccess(palette);
+ }
+ $.publish(Events.HIDE_PROGRESS);
+ };
+
+ ns.PaletteImageReader.prototype.onWorkerStep_ = function (event) {
+ var progress = event.data.progress;
+ $.publish(Events.UPDATE_PROGRESS, [{"progress": progress}]);
+ };
+
+ ns.PaletteImageReader.prototype.onWorkerError_ = function (event) {
+ $.publish(Events.HIDE_PROGRESS);
+ this.onError('Unable to process the image : ' + event.data.message);
+ };
+})();
\ No newline at end of file
diff --git a/src/js/service/palette/PaletteImportService.js b/src/js/service/palette/PaletteImportService.js
new file mode 100644
index 00000000..fe618dac
--- /dev/null
+++ b/src/js/service/palette/PaletteImportService.js
@@ -0,0 +1,49 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ var fileReaders = {
+ 'gpl' : ns.PaletteGplReader,
+ 'txt' : ns.PaletteTxtReader
+ };
+
+ ns.PaletteImportService = function () {};
+
+ ns.PaletteImportService.prototype.read = function (file, onSuccess, onError) {
+ var reader = this.getReader_(file, onSuccess, onError);
+ if (reader) {
+ reader.read();
+ } else {
+ throw 'Could not find reader for file : ' + file.name;
+ }
+ };
+
+ ns.PaletteImportService.prototype.isImage_ = function (file) {
+ return file.type.indexOf('image') === 0;
+ };
+
+ ns.PaletteImportService.prototype.getReader_ = function (file, onSuccess, onError) {
+ var readerClass = this.getReaderClass_(file);
+ if (readerClass) {
+ return new readerClass(file, onSuccess, onError);
+ } else {
+ return null;
+ }
+ };
+
+ ns.PaletteImportService.prototype.getReaderClass_ = function (file) {
+ var readerClass;
+ if (this.isImage_(file)) {
+ readerClass = ns.PaletteImageReader;
+ } else {
+ var extension = this.getExtension_(file);
+ readerClass = fileReaders[extension];
+ }
+ return readerClass;
+ };
+
+ ns.PaletteImportService.prototype.getExtension_ = function (file) {
+ var parts = file.name.split('.');
+ var extension = parts[parts.length-1];
+ return extension.toLowerCase();
+ };
+})();
\ No newline at end of file
diff --git a/src/js/service/palette/PaletteService.js b/src/js/service/palette/PaletteService.js
new file mode 100644
index 00000000..d02e7708
--- /dev/null
+++ b/src/js/service/palette/PaletteService.js
@@ -0,0 +1,72 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ ns.PaletteService = function () {
+ this.dynamicPalettes = [];
+ this.localStorageService = window.localStorage;
+ };
+
+ ns.PaletteService.prototype.getPalettes = function () {
+ var palettesString = this.localStorageService.getItem('piskel.palettes');
+ var palettes = JSON.parse(palettesString) || [];
+ palettes = palettes.map(function (palette) {
+ return pskl.model.Palette.fromObject(palette);
+ });
+
+ return this.dynamicPalettes.concat(palettes);
+ };
+
+ ns.PaletteService.prototype.getPaletteById = function (paletteId) {
+ var palettes = this.getPalettes();
+ return this.findPaletteInArray_(paletteId, palettes);
+ };
+
+ ns.PaletteService.prototype.savePalette = function (palette) {
+ var palettes = this.getPalettes();
+ var existingPalette = this.findPaletteInArray_(palette.id, palettes);
+ if (existingPalette) {
+ var currentIndex = palettes.indexOf(existingPalette);
+ palettes.splice(currentIndex, 1, palette);
+ } else {
+ palettes.push(palette);
+ }
+
+ this.savePalettes_(palettes);
+
+ $.publish(Events.SHOW_NOTIFICATION, [{"content": "Palette " + palette.name + " successfully saved !"}]);
+ window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000);
+ };
+
+ ns.PaletteService.prototype.addDynamicPalette = function (palette) {
+ this.dynamicPalettes.push(palette);
+ };
+
+ ns.PaletteService.prototype.deletePaletteById = function (id) {
+ var palettes = this.getPalettes();
+ var filteredPalettes = palettes.filter(function (palette) {
+ return palette.id !== id;
+ });
+
+ this.savePalettes_(filteredPalettes);
+ };
+
+ ns.PaletteService.prototype.savePalettes_ = function (palettes) {
+ palettes = palettes.filter(function (palette) {
+ return this.dynamicPalettes.indexOf(palette) === -1;
+ }.bind(this));
+ this.localStorageService.setItem('piskel.palettes', JSON.stringify(palettes));
+ $.publish(Events.PALETTE_LIST_UPDATED);
+ };
+
+ ns.PaletteService.prototype.findPaletteInArray_ = function (paletteId, palettes) {
+ var match = null;
+
+ palettes.forEach(function (palette) {
+ if (palette.id === paletteId) {
+ match = palette;
+ }
+ });
+
+ return match;
+ };
+})();
\ No newline at end of file
diff --git a/src/js/service/palette/PaletteTxtReader.js b/src/js/service/palette/PaletteTxtReader.js
new file mode 100644
index 00000000..ee7dcf66
--- /dev/null
+++ b/src/js/service/palette/PaletteTxtReader.js
@@ -0,0 +1,38 @@
+(function () {
+ var ns = $.namespace('pskl.service.palette');
+
+ var RE_COLOR_LINE = /^[A-F0-9]{2}([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})/;
+
+ ns.PaletteTxtReader = function (file, onSuccess, onError) {
+ this.file = file;
+ this.onSuccess = onSuccess;
+ this.onError = onError;
+ };
+
+ ns.PaletteTxtReader.prototype.read = function () {
+ pskl.utils.FileUtils.readFile(this.file, this.onFileLoaded_.bind(this));
+ };
+
+ ns.PaletteTxtReader.prototype.onFileLoaded_ = function (content) {
+ var text = pskl.utils.Base64.toText(content);
+ var lines = text.match(/[^\r\n]+/g);
+
+ var colorLines = lines.filter(function (l) {
+ return RE_COLOR_LINE.test(l);
+ });
+
+ var colors = colorLines.map(function (l) {
+ var matches = l.match(RE_COLOR_LINE);
+ var color = "#" + matches[1] + matches[2] + matches[3];
+ return color;
+ });
+
+ if (colors.length) {
+ var uuid = pskl.utils.Uuid.generate();
+ var palette = new pskl.model.Palette(uuid, 'Imported palette', colors);
+ this.onSuccess(palette);
+ } else {
+ this.onError();
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/utils/Base64.js b/src/js/utils/Base64.js
index 8e8273f8..6652f635 100644
--- a/src/js/utils/Base64.js
+++ b/src/js/utils/Base64.js
@@ -13,6 +13,10 @@
}
ns.Base64 = {
+ toText : function (base64) {
+ return window.atob(base64.replace(/data\:.*?\;base64\,/,''));
+ },
+
decode : function(base64) {
var outptr = 0;
var last = [0, 0];
diff --git a/src/js/utils/BlobUtils.js b/src/js/utils/BlobUtils.js
index 8ef595bf..169c2f96 100644
--- a/src/js/utils/BlobUtils.js
+++ b/src/js/utils/BlobUtils.js
@@ -1,6 +1,8 @@
(function () {
var ns = $.namespace('pskl.utils');
+
+
var BASE64_REGEX = /\s*;\s*base64\s*(?:;|$)/i;
ns.BlobUtils = {
diff --git a/src/js/utils/CanvasUtils.js b/src/js/utils/CanvasUtils.js
index 3edbe7c7..da3c1927 100644
--- a/src/js/utils/CanvasUtils.js
+++ b/src/js/utils/CanvasUtils.js
@@ -1,13 +1,13 @@
(function () {
- var ns = $.namespace("pskl");
+ var ns = $.namespace('pskl.utils');
ns.CanvasUtils = {
createCanvas : function (width, height, classList) {
- var canvas = document.createElement("canvas");
- canvas.setAttribute("width", width);
- canvas.setAttribute("height", height);
+ var canvas = document.createElement('canvas');
+ canvas.setAttribute('width', width);
+ canvas.setAttribute('height', height);
- if (typeof classList == "string") {
+ if (typeof classList == 'string') {
classList = [classList];
}
if (Array.isArray(classList)) {
@@ -20,14 +20,14 @@
},
createFromImageData : function (imageData) {
- var canvas = pskl.CanvasUtils.createCanvas(imageData.width, imageData.height);
+ var canvas = pskl.utils.CanvasUtils.createCanvas(imageData.width, imageData.height);
var context = canvas.getContext('2d');
context.putImageData(imageData, 0, 0);
return canvas;
},
createFromImage : function (image) {
- var canvas = pskl.CanvasUtils.createCanvas(image.width, image.height);
+ var canvas = pskl.utils.CanvasUtils.createCanvas(image.width, image.height);
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
return canvas;
@@ -51,12 +51,12 @@
clear : function (canvas) {
if (canvas) {
- canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
+ canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
}
},
clone : function (canvas) {
- var clone = pskl.CanvasUtils.createCanvas(canvas.width, canvas.height);
+ var clone = pskl.utils.CanvasUtils.createCanvas(canvas.width, canvas.height);
//apply the old canvas to the new one
clone.getContext('2d').drawImage(canvas, 0, 0);
@@ -71,8 +71,8 @@
},
getBase64FromCanvas : function (canvas, format) {
- format = format || "png";
- var data = canvas.toDataURL("image/" + format);
+ format = format || 'png';
+ var data = canvas.toDataURL('image/' + format);
return data.substr(data.indexOf(',')+1);
}
};
diff --git a/src/js/utils/FileUtils.js b/src/js/utils/FileUtils.js
index 645ddc2d..4189e325 100644
--- a/src/js/utils/FileUtils.js
+++ b/src/js/utils/FileUtils.js
@@ -14,6 +14,14 @@
reader.readAsDataURL(file);
},
+ readImageFile : function (file, callback) {
+ ns.FileUtils.readFile(file, function (content) {
+ var image = new Image();
+ image.onload = callback.bind(null, image);
+ image.src = content;
+ });
+ },
+
downloadAsFile : function (content, filename) {
var saveAs = window.saveAs || (navigator.msSaveBlob && navigator.msSaveBlob.bind(navigator));
if (saveAs) {
diff --git a/src/js/utils/FrameUtils.js b/src/js/utils/FrameUtils.js
index b98c2f43..a2094d43 100644
--- a/src/js/utils/FrameUtils.js
+++ b/src/js/utils/FrameUtils.js
@@ -2,6 +2,14 @@
var ns = $.namespace('pskl.utils');
var colorCache = {};
ns.FrameUtils = {
+ toImage : function (frame, zoom, bgColor) {
+ zoom = zoom || 1;
+ bgColor = bgColor || Constants.TRANSPARENT_COLOR;
+ var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom);
+ canvasRenderer.drawTransparentAs(bgColor);
+ return canvasRenderer.render();
+ },
+
merge : function (frames) {
var merged = null;
if (frames.length) {
@@ -28,6 +36,45 @@
return pskl.utils.FrameUtils.createFromImage(resizedImage);
},
+ /*
+ * Create a pskl.model.Frame from an Image object.
+ * Transparent pixels will either be converted to completely opaque or completely transparent pixels.
+ * @param {Image} image source image
+ * @return {pskl.model.Frame} corresponding frame
+ */
+ createFromImage : function (image) {
+ var w = image.width,
+ h = image.height;
+ var canvas = pskl.utils.CanvasUtils.createCanvas(w, h);
+ var context = canvas.getContext('2d');
+
+ context.drawImage(image, 0,0,w,h,0,0,w,h);
+ var imgData = context.getImageData(0,0,w,h).data;
+ return pskl.utils.FrameUtils.createFromImageData_(imgData, w, h);
+ },
+
+ createFromImageData_ : function (imageData, width, height) {
+ // Draw the zoomed-up pixels to a different canvas context
+ var grid = [];
+ for (var x = 0 ; x < width ; x++){
+ grid[x] = [];
+ for (var y = 0 ; y < height ; y++){
+ // Find the starting index in the one-dimensional image data
+ var i = (y * width + x)*4;
+ var r = imageData[i ];
+ var g = imageData[i+1];
+ var b = imageData[i+2];
+ var a = imageData[i+3];
+ if (a < 125) {
+ grid[x][y] = Constants.TRANSPARENT_COLOR;
+ } else {
+ grid[x][y] = pskl.utils.rgbToHex(r,g,b);
+ }
+ }
+ }
+ return pskl.model.Frame.fromPixelGrid(grid);
+ },
+
/**
* Alpha compositing using porter duff algorithm :
* http://en.wikipedia.org/wiki/Alpha_compositing
@@ -36,9 +83,9 @@
* @param {String} strColor2 color under
* @return {String} the composite color
*/
- mergePixels : function (strColor1, strColor2, globalOpacity1) {
- var col1 = pskl.utils.FrameUtils.toRgba(strColor1);
- var col2 = pskl.utils.FrameUtils.toRgba(strColor2);
+ mergePixels__ : function (strColor1, strColor2, globalOpacity1) {
+ var col1 = pskl.utils.FrameUtils.toRgba__(strColor1);
+ var col2 = pskl.utils.FrameUtils.toRgba__(strColor2);
if (typeof globalOpacity1 == 'number') {
col1 = JSON.parse(JSON.stringify(col1));
col1.a = globalOpacity1 * col1.a;
@@ -58,7 +105,7 @@
* @param {String} c color as a string
* @return {Object} {r:Number,g:Number,b:Number,a:Number}
*/
- toRgba : function (c) {
+ toRgba__ : function (c) {
if (colorCache[c]) {
return colorCache[c];
}
@@ -97,74 +144,6 @@
}
colorCache[c] = color;
return color;
- },
-
- /*
- * Create a pskl.model.Frame from an Image object.
- * Transparent pixels will either be converted to completely opaque or completely transparent pixels.
- * @param {Image} image source image
- * @return {pskl.model.Frame} corresponding frame
- */
- createFromImage : function (image) {
- var w = image.width,
- h = image.height;
- var canvas = pskl.CanvasUtils.createCanvas(w, h);
- var context = canvas.getContext('2d');
-
- context.drawImage(image, 0,0,w,h,0,0,w,h);
- var imgData = context.getImageData(0,0,w,h).data;
- return pskl.utils.FrameUtils.createFromImageData(imgData, w, h);
- },
-
- createFromImageData : function (imageData, width, height) {
- // Draw the zoomed-up pixels to a different canvas context
- var grid = [];
- for (var x = 0 ; x < width ; x++){
- grid[x] = [];
- for (var y = 0 ; y < height ; y++){
- // Find the starting index in the one-dimensional image data
- var i = (y * width + x)*4;
- var r = imageData[i ];
- var g = imageData[i+1];
- var b = imageData[i+2];
- var a = imageData[i+3];
- if (a < 125) {
- grid[x][y] = Constants.TRANSPARENT_COLOR;
- } else {
- grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b);
- }
- }
- }
- return pskl.model.Frame.fromPixelGrid(grid);
- },
-
- /**
- * Convert a rgb(Number, Number, Number) color to hexadecimal representation
- * @param {Number} r red value, between 0 and 255
- * @param {Number} g green value, between 0 and 255
- * @param {Number} b blue value, between 0 and 255
- * @return {String} hex representation of the color '#ABCDEF'
- */
- rgbToHex : function (r, g, b) {
- return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);
- },
-
- /**
- * Convert a color component (as a Number between 0 and 255) to its string hexa representation
- * @param {Number} c component value, between 0 and 255
- * @return {String} eg. '0A'
- */
- componentToHex : function (c) {
- var hex = c.toString(16);
- return hex.length == 1 ? "0" + hex : hex;
- },
-
- toImage : function (frame, zoom, bgColor) {
- zoom = zoom || 1;
- bgColor = bgColor || Constants.TRANSPARENT_COLOR;
- var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom);
- canvasRenderer.drawTransparentAs(bgColor);
- return canvasRenderer.render();
}
};
})();
diff --git a/src/js/utils/ImageResizer.js b/src/js/utils/ImageResizer.js
index 0e9d4749..dea39e69 100644
--- a/src/js/utils/ImageResizer.js
+++ b/src/js/utils/ImageResizer.js
@@ -3,12 +3,12 @@
ns.ImageResizer = {
resize : function (image, targetWidth, targetHeight, smoothingEnabled) {
- var canvas = pskl.CanvasUtils.createCanvas(targetWidth, targetHeight);
+ var canvas = pskl.utils.CanvasUtils.createCanvas(targetWidth, targetHeight);
var context = canvas.getContext('2d');
context.save();
if (!smoothingEnabled) {
- pskl.CanvasUtils.disableImageSmoothing(canvas);
+ pskl.utils.CanvasUtils.disableImageSmoothing(canvas);
}
context.translate(canvas.width / 2, canvas.height / 2);
@@ -34,10 +34,10 @@
*/
resizeNearestNeighbour : function (source, zoom, margin, marginColor) {
margin = margin || 0;
- var canvas = pskl.CanvasUtils.createCanvas(zoom*source.width, zoom*source.height);
+ var canvas = pskl.utils.CanvasUtils.createCanvas(zoom*source.width, zoom*source.height);
var context = canvas.getContext('2d');
- var imgData = pskl.CanvasUtils.getImageDataFromCanvas(source);
+ var imgData = pskl.utils.CanvasUtils.getImageDataFromCanvas(source);
var yRanges = {},
xOffset = 0,
diff --git a/src/js/utils/LayerUtils.js b/src/js/utils/LayerUtils.js
index 38a08e5a..91bab7e0 100644
--- a/src/js/utils/LayerUtils.js
+++ b/src/js/utils/LayerUtils.js
@@ -8,23 +8,35 @@
* @param {Image} image source image
* @return {pskl.model.Frame} corresponding frame
*/
- createFromImage : function (image, frameCount) {
- var w = image.width,
- h = image.height,
- frameWidth = w / frameCount;
+ createLayerFromSpritesheet : function (image, frameCount) {
+ var width = image.width,
+ height = image.height,
+ frameWidth = width / frameCount;
- var canvas = pskl.CanvasUtils.createCanvas(w, h);
+ var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, height);
var context = canvas.getContext('2d');
- context.drawImage(image, 0,0,w,h,0,0,w,h);
// Draw the zoomed-up pixels to a different canvas context
var frames = [];
for (var i = 0 ; i < frameCount ; i++) {
- var imgData = context.getImageData(frameWidth*i,0,frameWidth,h).data;
- var frame = pskl.utils.FrameUtils.createFromImageData(imgData, frameWidth, h);
+ context.clearRect(0, 0 , frameWidth, height);
+ context.drawImage(image, frameWidth * i, 0, frameWidth, height, 0, 0, frameWidth, height);
+ var frame = pskl.utils.FrameUtils.createFromImage(canvas);
frames.push(frame);
}
return frames;
+ },
+
+ mergeLayers : function (layerA, layerB) {
+ var framesA = layerA.getFrames();
+ var framesB = layerB.getFrames();
+ var mergedFrames = [];
+ framesA.forEach(function (frame, index) {
+ var otherFrame = framesB[index];
+ mergedFrames.push(pskl.utils.FrameUtils.merge([otherFrame, frame]));
+ });
+ var mergedLayer = pskl.model.Layer.fromFrames(layerA.getName(), mergedFrames);
+ return mergedLayer;
}
};
diff --git a/src/js/utils/PiskelFileUtils.js b/src/js/utils/PiskelFileUtils.js
index 901aa4b6..59d64aed 100644
--- a/src/js/utils/PiskelFileUtils.js
+++ b/src/js/utils/PiskelFileUtils.js
@@ -12,7 +12,7 @@
*/
loadFromFile : function (file, onSuccess, onError) {
pskl.utils.FileUtils.readFile(file, function (content) {
- var rawPiskel = window.atob(content.replace(/data\:.*?\;base64\,/,''));
+ var rawPiskel = pskl.utils.Base64.toText(content);
var serializedPiskel = JSON.parse(rawPiskel);
var fps = serializedPiskel.piskel.fps;
var descriptor = new pskl.model.piskel.Descriptor(serializedPiskel.piskel.name, serializedPiskel.piskel.description, true);
diff --git a/src/js/utils/Uuid.js b/src/js/utils/Uuid.js
new file mode 100644
index 00000000..685d315c
--- /dev/null
+++ b/src/js/utils/Uuid.js
@@ -0,0 +1,17 @@
+(function(){
+ var ns = $.namespace('pskl.utils');
+
+ var s4 = function () {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ };
+
+ ns.Uuid = {
+ generate : function () {
+ return 'ss-s-s-s-sss'.replace(/s/g, function () {
+ return s4();
+ });
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/utils/WorkerUtils.js b/src/js/utils/WorkerUtils.js
new file mode 100644
index 00000000..0cd421a5
--- /dev/null
+++ b/src/js/utils/WorkerUtils.js
@@ -0,0 +1,17 @@
+(function () {
+ var ns = $.namespace('pskl.utils');
+
+ var workers = {};
+
+ ns.WorkerUtils = {
+ createWorker : function (worker, workerId) {
+ if (!workers[workerId]) {
+ var typedArray = [(worker+"").replace(/function \(\)\s?\{/,"").replace(/\}[^}]*$/, "")];
+ var blob = new Blob(typedArray, {type: "application/javascript"}); // pass a useful mime type here
+ workers[workerId] = window.URL.createObjectURL(blob);
+ }
+
+ return new Worker(workers[workerId]);
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/utils/core.js b/src/js/utils/core.js
index cd4226d5..2fd92c14 100644
--- a/src/js/utils/core.js
+++ b/src/js/utils/core.js
@@ -46,12 +46,25 @@ if (!Function.prototype.bind) {
var ns = $.namespace("pskl.utils");
- ns.rgbToHex = function(r, g, b) {
- if (r > 255 || g > 255 || b > 255) {
- throw "Invalid color component";
- }
+ /**
+ * Convert a rgb(Number, Number, Number) color to hexadecimal representation
+ * @param {Number} r red value, between 0 and 255
+ * @param {Number} g green value, between 0 and 255
+ * @param {Number} b blue value, between 0 and 255
+ * @return {String} hex representation of the color '#ABCDEF'
+ */
+ ns.rgbToHex = function (r, g, b) {
+ return "#" + pskl.utils.componentToHex(r) + pskl.utils.componentToHex(g) + pskl.utils.componentToHex(b);
+ };
- return ((r << 16) | (g << 8) | b).toString(16);
+ /**
+ * Convert a color component (as a Number between 0 and 255) to its string hexa representation
+ * @param {Number} c component value, between 0 and 255
+ * @return {String} eg. '0A'
+ */
+ ns.componentToHex = function (c) {
+ var hex = c.toString(16);
+ return hex.length == 1 ? "0" + hex : hex;
};
ns.normalize = function (value, def) {
@@ -76,5 +89,29 @@ if (!Function.prototype.bind) {
}
};
+ var entityMap = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': '"',
+ "'": ''',
+ "/": '/'
+ };
+
+ ns.escapeHtml= function (string) {
+ return String(string).replace(/[&<>"'\/]/g, function (s) {
+ return entityMap[s];
+ });
+ };
+
+ var reEntityMap = {};
+ ns.unescapeHtml= function (string) {
+ Object.keys(entityMap).forEach(function(key) {
+ reEntityMap[key] = reEntityMap[key] || new RegExp(entityMap[key], "g");
+ string = string.replace(reEntityMap[key], key);
+ });
+ return string;
+ };
+
})();
diff --git a/src/js/utils/serialization/Deserializer.js b/src/js/utils/serialization/Deserializer.js
index 8c0b43e4..c18808e5 100644
--- a/src/js/utils/serialization/Deserializer.js
+++ b/src/js/utils/serialization/Deserializer.js
@@ -47,7 +47,7 @@
// 2 - attach the onload callback that will be triggered asynchronously
image.onload = function () {
// 5 - extract the frames from the loaded image
- var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount);
+ var frames = pskl.utils.LayerUtils.createLayerFromSpritesheet(image, layerData.frameCount);
// 6 - add each image to the layer
this.addFramesToLayer(frames, layer);
}.bind(this);
diff --git a/src/js/worker/ImageProcessor.js b/src/js/worker/ImageProcessor.js
new file mode 100644
index 00000000..82b17695
--- /dev/null
+++ b/src/js/worker/ImageProcessor.js
@@ -0,0 +1,177 @@
+(function () {
+ var ns = $.namespace('pskl.worker');
+
+ var imageProcessorWorker = function () {
+ var currentStep, currentProgress, currentTotal;
+
+ var initStepCounter_ = function (total) {
+ currentStep = 0;
+ currentProgress = 0;
+ currentTotal = total;
+ };
+
+ var postStep_ = function () {
+ currentStep = currentStep + 1;
+ var progress = ((currentStep / currentTotal) *100).toFixed(1);
+ if (progress != currentProgress) {
+ currentProgress = progress;
+ this.postMessage({
+ type : 'STEP',
+ progress : currentProgress,
+ currentStep : currentStep,
+ total : currentTotal
+ });
+ }
+ };
+
+ var rgbToHex = function (r, g, b) {
+ return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
+ };
+
+ var componentToHex = function (c) {
+ var hex = c.toString(16);
+ return hex.length == 1 ? "0" + hex : hex;
+ };
+
+ var imageDataToGrid = function (imageData, width, height, transparent) {
+ // Draw the zoomed-up pixels to a different canvas context
+ var grid = [];
+ for (var x = 0 ; x < width ; x++){
+ grid[x] = [];
+ postStep_();
+ for (var y = 0 ; y < height ; y++){
+ // Find the starting index in the one-dimensional image data
+ var i = (y * width + x)*4;
+ var r = imageData[i ];
+ var g = imageData[i+1];
+ var b = imageData[i+2];
+ var a = imageData[i+3];
+ if (a < 125) {
+ grid[x][y] = transparent;
+ } else {
+ grid[x][y] = rgbToHex(r,g,b);
+ }
+ }
+ }
+ return grid;
+ };
+
+ var getColorsMapFromImageData = function (imageData, width, height) {
+ var grid = imageDataToGrid(imageData, width, height, 'transparent');
+
+ var colorsMap = {};
+ for (var i = 0 ; i < grid.length ; i++) {
+ postStep_();
+ for (var j = 0 ; j < grid[i].length ; j++) {
+ var color = grid[i][j];
+ if (color != 'transparent') {
+ colorsMap[color] = true;
+ }
+ }
+ }
+ return colorsMap;
+ };
+
+ this.onmessage = function(event) {
+ try {
+ var data = event.data;
+
+ initStepCounter_(data.width * 2);
+
+ var colorsMap = getColorsMapFromImageData(data.imageData, data.width, data.height);
+
+ this.postMessage({
+ type : 'SUCCESS',
+ colorsMap : colorsMap
+ });
+ } catch(e) {
+ this.postMessage({
+ type : 'ERROR',
+ message : e.message
+ });
+ }
+ };
+ };
+
+ ns.ImageProcessor = function (image, onSuccess, onStep, onError) {
+ this.image = image;
+
+ this.onStep = onStep;
+ this.onSuccess = onSuccess;
+ this.onError = onError;
+
+ // var worker = pskl.utils.WorkerUtils.addPartialWorker(imageProcessorWorker, 'step-counter');
+ this.worker = pskl.utils.WorkerUtils.createWorker(imageProcessorWorker, 'image-colors-processor');
+ this.worker.onmessage = this.onWorkerMessage.bind(this);
+ };
+
+ ns.ImageProcessor.prototype.process = function () {
+ var canvas = pskl.utils.CanvasUtils.createFromImage(this.image);
+ var imageData = pskl.utils.CanvasUtils.getImageDataFromCanvas(canvas);
+ this.worker.postMessage({
+ imageData : imageData,
+ width : this.image.width,
+ height : this.image.height
+ });
+ };
+
+ ns.ImageProcessor.prototype.createNamespace = function (name) {
+ var createNamespace = (function () {
+ var parts = name.split('.');
+ if (parts.length > 0) {
+ var node = this;
+ for (var i = 0 ; i < parts.length ; i++) {
+ if (!node[parts[i]]) {
+ node[parts[i]] = {};
+ }
+ node = node[parts[i]];
+ }
+ }
+ });
+ var script = createNamespace + "";
+ script = script.replace(/function \(\) \{/,"").replace(/\}[^}]*$/, "");
+ script = "var name = '" + name + "';" + script;
+
+ this.runScript(script);
+ };
+
+ ns.ImageProcessor.prototype.onWorkerMessage = function (event) {
+ if (event.data.type === 'STEP') {
+ this.onStep(event);
+ } else if (event.data.type === 'SUCCESS') {
+ this.onSuccess(event);
+ this.worker.terminate();
+ } else if (event.data.type === 'ERROR') {
+ this.onError(event);
+ this.worker.terminate();
+ }
+ };
+
+ ns.ImageProcessor.prototype.importAll__ = function (classToImport, classpath) {
+ this.createNamespace(classpath);
+ for (var key in classToImport) {
+ if (classToImport.hasOwnProperty(key)) {
+ this.addMethod(classToImport[key], classpath + '.' + key);
+ }
+ }
+ };
+
+ ns.ImageProcessor.prototype.addMethod__ = function (method, name) {
+ this.runScript(name + "=" + method);
+ };
+
+ ns.ImageProcessor.prototype.runScript__ = function (script) {
+ this.worker.postMessage({
+ type : 'RUN_SCRIPT',
+ script : this.getScriptAsUrl(script)
+ });
+ };
+
+ ns.ImageProcessor.prototype.getScriptAsUrl__ = function (script) {
+ var blob = new Blob([script], {type: "application/javascript"}); // pass a useful mime type here
+ return window.URL.createObjectURL(blob);
+ };
+})();
+
+
+
diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js
index bd66a181..1e9e38cb 100644
--- a/src/piskel-script-list.js
+++ b/src/piskel-script-list.js
@@ -25,6 +25,8 @@
"js/utils/PiskelFileUtils.js",
"js/utils/Template.js",
"js/utils/UserSettings.js",
+ "js/utils/Uuid.js",
+ "js/utils/WorkerUtils.js",
"js/utils/Xhr.js",
"js/utils/serialization/Serializer.js",
"js/utils/serialization/Deserializer.js",
@@ -50,6 +52,7 @@
"js/model/Layer.js",
"js/model/piskel/Descriptor.js",
"js/model/frame/CachedFrameProcessor.js",
+ "js/model/Palette.js",
"js/model/Piskel.js",
// Selection
@@ -82,6 +85,7 @@
"js/controller/ToolController.js",
"js/controller/PaletteController.js",
"js/controller/PalettesListController.js",
+ "js/controller/ProgressBarController.js",
"js/controller/NotificationController.js",
"js/controller/CanvasBackgroundController.js",
@@ -99,13 +103,18 @@
// Dialogs sub-controllers
"js/controller/dialogs/AbstractDialogController.js",
- "js/controller/dialogs/PaletteManagerController.js",
+ "js/controller/dialogs/CreatePaletteController.js",
"js/controller/dialogs/ImportImageController.js",
"js/controller/dialogs/BrowseLocalController.js",
+
// Dialogs controller
"js/controller/dialogs/DialogsController.js",
+ // Widget controller
+ "js/controller/widgets/ColorsList.js",
+ "js/controller/widgets/HslRgbColorPicker.js",
+
// Services
"js/service/LocalStorageService.js",
"js/service/GithubStorageService.js",
@@ -113,6 +122,14 @@
"js/service/BackupService.js",
"js/service/BeforeUnloadService.js",
"js/service/HistoryService.js",
+ "js/service/color/ColorSorter.js",
+ "js/service/palette/CurrentColorsPalette.js",
+ "js/service/palette/PaletteService.js",
+ "js/service/palette/PaletteTxtReader.js",
+ "js/service/palette/PaletteGplReader.js",
+ "js/service/palette/PaletteGplWriter.js",
+ "js/service/palette/PaletteImageReader.js",
+ "js/service/palette/PaletteImportService.js",
"js/service/SavedStatusService.js",
"js/service/keyboard/ShortcutService.js",
"js/service/keyboard/KeycodeTranslator.js",
@@ -149,6 +166,9 @@
"js/devtools/TestRecordController.js",
"js/devtools/init.js",
+ // Workers
+ "js/worker/ImageProcessor.js",
+
// Application controller and initialization
"js/app.js",
// Bonus features !!
diff --git a/src/piskel-style-list.js b/src/piskel-style-list.js
index ab92a308..fdb8e0c9 100644
--- a/src/piskel-style-list.js
+++ b/src/piskel-style-list.js
@@ -11,10 +11,12 @@
"css/tools.css",
"css/icons.css",
"css/cheatsheet.css",
+ "css/color-picker-slider.css",
"css/dialogs.css",
"css/dialogs-import-image.css",
- "css/dialogs-manage-palettes.css",
"css/dialogs-browse-local.css",
+ "css/dialogs-create-palette.css",
+ "css/notifications.css",
"css/toolbox.css",
"css/toolbox-layers-list.css",
"css/toolbox-palettes-list.css",
diff --git a/src/templates/dialogs/browse-local.html b/src/templates/dialogs/browse-local.html
index 6758c5c6..cd28bdad 100644
--- a/src/templates/dialogs/browse-local.html
+++ b/src/templates/dialogs/browse-local.html
@@ -1,7 +1,7 @@
Browse Local Piskels
- X
+ X
diff --git a/src/templates/dialogs/create-palette.html b/src/templates/dialogs/create-palette.html
new file mode 100644
index 00000000..eb8c26ea
--- /dev/null
+++ b/src/templates/dialogs/create-palette.html
@@ -0,0 +1,80 @@
+
+
+ Create palette
+ X
+
+
+
+ Name
+
+
+
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+ S
+
+
+
+
+ V
+
+
+
+
+
+ R
+
+
+
+
+ G
+
+
+
+
+ B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/templates/dialogs/import-image.html b/src/templates/dialogs/import-image.html
index aa7ddd65..95795799 100644
--- a/src/templates/dialogs/import-image.html
+++ b/src/templates/dialogs/import-image.html
@@ -1,7 +1,7 @@
Import Image
- X
+ X
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
diff --git a/src/templates/misc-templates.html b/src/templates/misc-templates.html
new file mode 100644
index 00000000..56d5a0b7
--- /dev/null
+++ b/src/templates/misc-templates.html
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/templates/palettes-list.html b/src/templates/palettes-list.html
index 8cc447e5..09e4ce16 100644
--- a/src/templates/palettes-list.html
+++ b/src/templates/palettes-list.html
@@ -1,19 +1,30 @@