├── .codeclimate.yml ├── .eslintignore ├── .github └── ISSUE_TEMPLATE ├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── RELEASE.md ├── bin ├── copy-to-piskel-web.js └── copy-to-piskel-website.js ├── cli ├── README.md ├── export-png.js ├── index.js └── piskel-export.js ├── karma.conf.js ├── misc ├── desktop │ ├── Info.plist │ ├── logo.ico │ ├── nw.icns │ └── package-piskel.evb ├── gif-tests │ ├── low-colors-no-transparency.piskel │ ├── low-colors-with-transparency.piskel │ ├── too-many-colors-no-transparency.piskel │ └── too-many-colors-with-transparency.piskel ├── icons │ ├── SVG │ │ ├── eye.svg │ │ ├── flip.svg │ │ ├── import-icon.svg │ │ ├── lasso.svg │ │ ├── layers.svg │ │ ├── onion.svg │ │ ├── swap-arrow-square.svg │ │ └── swap-arrow.svg │ ├── cloud_export.png │ ├── cursors-resources.jpg │ ├── eraser.ai │ ├── icons.png │ ├── import-icon.png │ ├── mirror.ai │ ├── noun-project │ │ ├── sheep │ │ │ ├── icon_8389.png │ │ │ ├── icon_8389.svg │ │ │ └── license.txt │ │ └── undo-kyle_levi_fox │ │ │ ├── icon_10033.svg │ │ │ └── license.txt │ ├── rectangle.ai │ ├── rectangle_selection.ai │ ├── source │ │ ├── common-backup.svg │ │ ├── common-keyboard-gold.svg │ │ ├── common-swapcolors-arrow-grey.svg │ │ ├── common-warning-red.svg │ │ ├── file-icon-base.svg │ │ ├── frame-dragndrop-white.svg │ │ ├── frame-duplicate-white-base.pdn │ │ ├── frame-duplicate-white-base@2x.pdn │ │ ├── frame-duplicate-white.svg │ │ ├── frame-plus-white.svg │ │ ├── frame-recyclebin-white.svg │ │ ├── minimap-popup-preview-arrow.svg │ │ ├── settings-export-white.svg │ │ ├── settings-gear-white.svg │ │ ├── settings-open-folder-white.svg │ │ ├── settings-resize-white.svg │ │ ├── settings-save-white.svg │ │ ├── tool-center.svg │ │ ├── tool-circle.svg │ │ ├── tool-clone.svg │ │ ├── tool-colorpicker.svg │ │ ├── tool-colorswap.svg │ │ ├── tool-dithering.svg │ │ ├── tool-eraser.svg │ │ ├── tool-flip.svg │ │ ├── tool-lasso-select.svg │ │ ├── tool-lighten.svg │ │ ├── tool-move.svg │ │ ├── tool-paint-bucket.svg │ │ ├── tool-pen.svg │ │ ├── tool-rectangle-select.svg │ │ ├── tool-rectangle.svg │ │ ├── tool-rotate.svg │ │ ├── tool-shape-select.svg │ │ ├── tool-stroke.svg │ │ └── tool-vertical-mirror-pen.svg │ ├── stroke.ai │ ├── swap-arrow-small.png │ ├── swap-arrow-square-small-grey.png │ ├── swap-arrow-square-small.png │ └── swap-colors-tests │ │ ├── swap-colors-square.png │ │ ├── swap-colors-twirl-2.png │ │ ├── swap-colors-twirl-3.png │ │ ├── swap-colors-twirl.png │ │ └── swap.png ├── proto-ui-1 │ ├── img │ │ ├── dragndrop-dark.png │ │ ├── dragndrop.png │ │ ├── eyedropper-dark.png │ │ ├── eyedropper-icon.png │ │ ├── garbage-dark.png │ │ ├── garbage.png │ │ ├── magicwand-icon-dark.png │ │ ├── magicwand-icon.png │ │ ├── paintbucket-icon-dark.png │ │ ├── paintbucket-icon.png │ │ ├── pen-icon-dark.png │ │ ├── pen-icon.png │ │ ├── preview-state-1.png │ │ ├── preview.gif │ │ └── transparent_background.png │ ├── index.html │ ├── jquery-1.8.0.js │ ├── main.css │ └── main.js ├── scripts │ ├── build-mac-application.txt │ ├── package-mac-application.cmd │ ├── package-windows-executable.cmd │ └── piskel-root └── selenium-ide │ ├── change-colors.html │ ├── change-size.html │ ├── select-tools.html │ └── user-preferences.html ├── package-lock.json ├── package.json ├── pre-commit ├── src ├── css │ ├── animations.css │ ├── bootstrap │ │ ├── bootstrap-tooltip-custom.css │ │ ├── bootstrap.css │ │ └── readme.txt │ ├── color-picker-slider.css │ ├── dialogs-browse-backups.css │ ├── dialogs-browse-local.css │ ├── dialogs-cheatsheet.css │ ├── dialogs-create-palette.css │ ├── dialogs-import.css │ ├── dialogs-performance-info.css │ ├── dialogs-unsupported-browser.css │ ├── dialogs.css │ ├── font-icon.css │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ ├── icomoon.woff │ │ ├── piskel.eot │ │ ├── piskel.svg │ │ ├── piskel.ttf │ │ └── piskel.woff │ ├── forms.css │ ├── frames-list.css │ ├── layout.css │ ├── minimap.css │ ├── notifications.css │ ├── reset.css │ ├── settings-application.css │ ├── settings-export.css │ ├── settings-import.css │ ├── settings-resize.css │ ├── settings-save.css │ ├── settings.css │ ├── spectrum │ │ ├── spectrum-overrides.css │ │ └── spectrum.css │ ├── style.css │ ├── toolbox-animated-preview.css │ ├── toolbox-layers-list.css │ ├── toolbox-palettes-list.css │ ├── toolbox.css │ ├── tools.css │ ├── transformations.css │ ├── variables.css │ ├── widgets-anchor.css │ ├── widgets-frame-picker.css │ ├── widgets-size-picker.css │ ├── widgets-tabs.css │ └── widgets-wizard.css ├── img │ ├── canvas-backgrounds │ │ ├── canvas-background-light.png │ │ ├── canvas-background-lowcontrast-dark.png │ │ ├── canvas-background-lowcontrast-medium.png │ │ └── canvas-background-medium.png │ ├── cursors │ │ ├── circle.png │ │ ├── color-palette.png │ │ ├── dither.png │ │ ├── dropper.png │ │ ├── eraser.png │ │ ├── hand.png │ │ ├── lighten.png │ │ ├── mirror-pen.png │ │ ├── paint-bucket.png │ │ ├── pen.png │ │ ├── rectangle.png │ │ ├── select.png │ │ ├── stroke.png │ │ ├── vertical-mirror-pen.png │ │ └── wand.png │ ├── favicon.png │ ├── icons │ │ ├── common │ │ │ ├── common-backup-white.png │ │ │ ├── common-backup-white@2x.png │ │ │ ├── common-keyboard-gold.png │ │ │ ├── common-keyboard-gold@2x.png │ │ │ ├── common-swapcolors-arrow-grey.png │ │ │ ├── common-swapcolors-arrow-grey@2x.png │ │ │ ├── common-warning-red.png │ │ │ └── common-warning-red@2x.png │ │ ├── frame │ │ │ ├── frame-dragndrop-white.png │ │ │ ├── frame-dragndrop-white@2x.png │ │ │ ├── frame-duplicate-white.png │ │ │ ├── frame-duplicate-white@2x.png │ │ │ ├── frame-plus-white.png │ │ │ ├── frame-plus-white@2x.png │ │ │ ├── frame-recyclebin-white.png │ │ │ └── frame-recyclebin-white@2x.png │ │ ├── minimap │ │ │ ├── minimap-grid-gold.png │ │ │ ├── minimap-grid-gold@2x.png │ │ │ ├── minimap-grid-white.png │ │ │ ├── minimap-grid-white@2x.png │ │ │ ├── minimap-popup-preview-arrow-gold.png │ │ │ ├── minimap-popup-preview-arrow-gold@2x.png │ │ │ ├── minimap-popup-preview-arrow-white.png │ │ │ └── minimap-popup-preview-arrow-white@2x.png │ │ ├── settings │ │ │ ├── settings-export-white.png │ │ │ ├── settings-export-white@2x.png │ │ │ ├── settings-gear-white.png │ │ │ ├── settings-gear-white@2x.png │ │ │ ├── settings-open-folder-white.png │ │ │ ├── settings-open-folder-white@2x.png │ │ │ ├── settings-resize-white.png │ │ │ ├── settings-resize-white@2x.png │ │ │ ├── settings-save-white.png │ │ │ └── settings-save-white@2x.png │ │ ├── tools │ │ │ ├── tool-circle.png │ │ │ ├── tool-circle@2x.png │ │ │ ├── tool-colorpicker.png │ │ │ ├── tool-colorpicker@2x.png │ │ │ ├── tool-colorswap.png │ │ │ ├── tool-colorswap@2x.png │ │ │ ├── tool-dithering.png │ │ │ ├── tool-dithering@2x.png │ │ │ ├── tool-eraser.png │ │ │ ├── tool-eraser@2x.png │ │ │ ├── tool-lasso-select.png │ │ │ ├── tool-lasso-select@2x.png │ │ │ ├── tool-lighten.png │ │ │ ├── tool-lighten@2x.png │ │ │ ├── tool-move.png │ │ │ ├── tool-move@2x.png │ │ │ ├── tool-paint-bucket.png │ │ │ ├── tool-paint-bucket@2x.png │ │ │ ├── tool-pen.png │ │ │ ├── tool-pen@2x.png │ │ │ ├── tool-rectangle-select.png │ │ │ ├── tool-rectangle-select@2x.png │ │ │ ├── tool-rectangle.png │ │ │ ├── tool-rectangle@2x.png │ │ │ ├── tool-shape-select.png │ │ │ ├── tool-shape-select@2x.png │ │ │ ├── tool-stroke.png │ │ │ ├── tool-stroke@2x.png │ │ │ ├── tool-vertical-mirror-pen.png │ │ │ └── tool-vertical-mirror-pen@2x.png │ │ └── transform │ │ │ ├── tool-center.png │ │ │ ├── tool-center@2x.png │ │ │ ├── tool-clone.png │ │ │ ├── tool-clone@2x.png │ │ │ ├── tool-crop.png │ │ │ ├── tool-crop@2x.png │ │ │ ├── tool-flip.png │ │ │ ├── tool-flip@2x.png │ │ │ ├── tool-rotate.png │ │ │ └── tool-rotate@2x.png │ └── unused │ │ ├── circle-dark.png │ │ ├── eraser-dark.png │ │ ├── eyedropper-dark.png │ │ ├── gallery.png │ │ ├── hand-dark.png │ │ ├── lasso-dark.png │ │ ├── magicwand-dark.png │ │ ├── paintbucket-dark.png │ │ ├── pen-dark.png │ │ ├── rectangle-dark.png │ │ └── rectangle_selection-dark.png ├── index.html ├── js │ ├── .eslintrc │ ├── Constants.js │ ├── Events.js │ ├── app.js │ ├── controller │ │ ├── CanvasBackgroundController.js │ │ ├── CursorCoordinatesController.js │ │ ├── DrawingController.js │ │ ├── FramesListController.js │ │ ├── HeaderController.js │ │ ├── LayersListController.js │ │ ├── MinimapController.js │ │ ├── NotificationController.js │ │ ├── PaletteController.js │ │ ├── PalettesListController.js │ │ ├── PenSizeController.js │ │ ├── ProgressBarController.js │ │ ├── ToolController.js │ │ ├── TransformationsController.js │ │ ├── UserWarningController.js │ │ ├── dialogs │ │ │ ├── AbstractDialogController.js │ │ │ ├── BrowseLocalController.js │ │ │ ├── CheatsheetController.js │ │ │ ├── CreatePaletteController.js │ │ │ ├── DialogsController.js │ │ │ ├── PerformanceInfoController.js │ │ │ ├── UnsupportedBrowserController.js │ │ │ ├── backups │ │ │ │ ├── BrowseBackups.js │ │ │ │ └── steps │ │ │ │ │ ├── SelectSession.js │ │ │ │ │ └── SessionDetails.js │ │ │ └── importwizard │ │ │ │ ├── ImportWizard.js │ │ │ │ └── steps │ │ │ │ ├── AbstractImportStep.js │ │ │ │ ├── AdjustSize.js │ │ │ │ ├── ImageImport.js │ │ │ │ ├── InsertLocation.js │ │ │ │ └── SelectMode.js │ │ ├── drawing │ │ │ └── DragHandler.js │ │ ├── piskel │ │ │ ├── PiskelController.js │ │ │ └── PublicPiskelController.js │ │ ├── preview │ │ │ ├── PopupPreviewController.js │ │ │ ├── PreviewActionsController.js │ │ │ └── PreviewController.js │ │ └── settings │ │ │ ├── AbstractSettingController.js │ │ │ ├── ImportController.js │ │ │ ├── PreferencesController.js │ │ │ ├── SaveController.js │ │ │ ├── SettingsController.js │ │ │ ├── exportimage │ │ │ ├── ExportController.js │ │ │ ├── GifExportController.js │ │ │ ├── MiscExportController.js │ │ │ ├── PngExportController.js │ │ │ └── ZipExportController.js │ │ │ ├── preferences │ │ │ ├── GridPreferencesController.js │ │ │ ├── MiscPreferencesController.js │ │ │ └── TilePreferencesController.js │ │ │ └── resize │ │ │ ├── DefaultSizeController.js │ │ │ └── ResizeController.js │ ├── database │ │ ├── BackupDatabase.js │ │ ├── PiskelDatabase.js │ │ └── migrate │ │ │ └── MigrateLocalStorageToIndexedDb.js │ ├── devtools │ │ ├── DrawingTestPlayer.js │ │ ├── DrawingTestRecorder.js │ │ ├── DrawingTestRunner.js │ │ ├── DrawingTestSuiteController.js │ │ ├── DrawingTestSuiteRunner.js │ │ ├── MouseEvent.js │ │ ├── TestRecordController.js │ │ ├── init.js │ │ └── lib │ │ │ └── Blob.js │ ├── lib │ │ ├── .DS_Store │ │ ├── bootstrap │ │ │ ├── bootstrap.js │ │ │ └── readme.txt │ │ ├── gif │ │ │ ├── gif.ie.worker.js │ │ │ ├── gif.js │ │ │ ├── gif.worker.js │ │ │ └── libgif.js │ │ ├── iframeLoader-0.1.0.js │ │ ├── jquery-1.8.0.js │ │ ├── jquery-ui-1.10.3.custom.js │ │ ├── jszip │ │ │ └── jszip.min.js │ │ ├── pubsub.js │ │ ├── q.js │ │ ├── scrollifneeded │ │ │ └── scrollifneeded.js │ │ ├── smoothscroll │ │ │ ├── LICENSE.txt │ │ │ └── smoothscroll.js │ │ └── spectrum │ │ │ └── spectrum.js │ ├── model │ │ ├── Frame.js │ │ ├── Layer.js │ │ ├── Palette.js │ │ ├── Piskel.js │ │ ├── frame │ │ │ ├── AsyncCachedFrameProcessor.js │ │ │ ├── CachedFrameProcessor.js │ │ │ └── RenderedFrame.js │ │ └── piskel │ │ │ └── Descriptor.js │ ├── rendering │ │ ├── AbstractRenderer.js │ │ ├── CanvasRenderer.js │ │ ├── CompositeRenderer.js │ │ ├── DrawingLoop.js │ │ ├── FramesheetRenderer.js │ │ ├── OnionSkinRenderer.js │ │ ├── PiskelRenderer.js │ │ ├── frame │ │ │ ├── BackgroundImageFrameRenderer.js │ │ │ ├── CachedFrameRenderer.js │ │ │ └── FrameRenderer.js │ │ └── layer │ │ │ └── LayersRenderer.js │ ├── selection │ │ ├── BaseSelection.js │ │ ├── LassoSelection.js │ │ ├── RectangularSelection.js │ │ ├── SelectionManager.js │ │ └── ShapeSelection.js │ ├── service │ │ ├── BackupService.js │ │ ├── BeforeUnloadService.js │ │ ├── ClipboardService.js │ │ ├── CurrentColorsService.js │ │ ├── FileDropperService.js │ │ ├── HistoryService.js │ │ ├── ImageUploadService.js │ │ ├── ImportService.js │ │ ├── MouseStateService.js │ │ ├── SavedStatusService.js │ │ ├── SelectedColorsService.js │ │ ├── color │ │ │ └── ColorSorter.js │ │ ├── keyboard │ │ │ ├── KeyUtils.js │ │ │ ├── KeycodeTranslator.js │ │ │ ├── Shortcut.js │ │ │ ├── ShortcutService.js │ │ │ └── Shortcuts.js │ │ ├── palette │ │ │ ├── CurrentColorsPalette.js │ │ │ ├── PaletteGplWriter.js │ │ │ ├── PaletteImportService.js │ │ │ ├── PaletteService.js │ │ │ └── reader │ │ │ │ ├── AbstractPaletteFileReader.js │ │ │ │ ├── PaletteGplReader.js │ │ │ │ ├── PaletteImageReader.js │ │ │ │ ├── PalettePalReader.js │ │ │ │ └── PaletteTxtReader.js │ │ ├── pensize │ │ │ └── PenSizeService.js │ │ ├── performance │ │ │ ├── PerformanceReport.js │ │ │ └── PerformanceReportService.js │ │ └── storage │ │ │ ├── DesktopStorageService.js │ │ │ ├── FileDownloadStorageService.js │ │ │ ├── GalleryStorageService.js │ │ │ ├── IndexedDbStorageService.js │ │ │ ├── LocalStorageService.js │ │ │ └── StorageService.js │ ├── snippets.js │ ├── tools │ │ ├── Tool.js │ │ ├── ToolIconBuilder.js │ │ ├── ToolsHelper.js │ │ ├── drawing │ │ │ ├── BaseTool.js │ │ │ ├── Circle.js │ │ │ ├── ColorPicker.js │ │ │ ├── ColorSwap.js │ │ │ ├── DitheringTool.js │ │ │ ├── Eraser.js │ │ │ ├── Lighten.js │ │ │ ├── Move.js │ │ │ ├── PaintBucket.js │ │ │ ├── Rectangle.js │ │ │ ├── ShapeTool.js │ │ │ ├── SimplePen.js │ │ │ ├── Stroke.js │ │ │ ├── VerticalMirrorPen.js │ │ │ └── selection │ │ │ │ ├── AbstractDragSelect.js │ │ │ │ ├── BaseSelect.js │ │ │ │ ├── LassoSelect.js │ │ │ │ ├── RectangleSelect.js │ │ │ │ └── ShapeSelect.js │ │ └── transform │ │ │ ├── AbstractTransformTool.js │ │ │ ├── Center.js │ │ │ ├── Clone.js │ │ │ ├── Crop.js │ │ │ ├── Flip.js │ │ │ ├── Rotate.js │ │ │ └── TransformUtils.js │ ├── utils │ │ ├── Array.js │ │ ├── Base64.js │ │ ├── BlobUtils.js │ │ ├── CanvasUtils.js │ │ ├── ColorUtils.js │ │ ├── DateUtils.js │ │ ├── Dom.js │ │ ├── Environment.js │ │ ├── Event.js │ │ ├── FileUtils.js │ │ ├── FileUtilsDesktop.js │ │ ├── FrameUtils.js │ │ ├── FunctionUtils.js │ │ ├── ImageResizer.js │ │ ├── LayerUtils.js │ │ ├── Math.js │ │ ├── MergeUtils.js │ │ ├── PiskelFileUtils.js │ │ ├── PixelUtils.js │ │ ├── ResizeUtils.js │ │ ├── StringUtils.js │ │ ├── Template.js │ │ ├── TooltipFormatter.js │ │ ├── UserAgent.js │ │ ├── UserSettings.js │ │ ├── Uuid.js │ │ ├── WorkerUtils.js │ │ ├── Xhr.js │ │ ├── core.js │ │ └── serialization │ │ │ ├── Deserializer.js │ │ │ ├── Serializer.js │ │ │ ├── arraybuffer │ │ │ ├── ArrayBufferDeserializer.js │ │ │ └── ArrayBufferSerializer.js │ │ │ └── backward │ │ │ ├── Deserializer_v0.js │ │ │ └── Deserializer_v1.js │ ├── widgets │ │ ├── AnchorWidget.js │ │ ├── ColorsList.js │ │ ├── FramePicker.js │ │ ├── HslRgbColorPicker.js │ │ ├── SizeInput.js │ │ ├── SizePicker.js │ │ ├── SynchronizedInputs.js │ │ ├── Tabs.js │ │ └── Wizard.js │ └── worker │ │ ├── framecolors │ │ ├── FrameColors.js │ │ └── FrameColorsWorker.js │ │ ├── hash │ │ ├── Hash.js │ │ └── HashWorker.js │ │ └── imageprocessor │ │ ├── ImageProcessor.js │ │ └── ImageProcessorWorker.js ├── logo.png ├── piskel-boot.js ├── piskel-script-list.js ├── piskel-style-list.js └── templates │ ├── data-uri-export.html │ ├── debug-header.html │ ├── dialogs │ ├── browse-backups.html │ ├── browse-local.html │ ├── cheatsheet.html │ ├── create-palette.html │ ├── import.html │ ├── performance-info.html │ └── unsupported-browser.html │ ├── drawing-tools.html │ ├── frames-list.html │ ├── layers-list.html │ ├── misc-templates.html │ ├── palettes-list.html │ ├── popup-preview.html │ ├── preview.html │ ├── settings.html │ ├── settings │ ├── export.html │ ├── export │ │ ├── gif.html │ │ ├── misc.html │ │ ├── png.html │ │ └── zip.html │ ├── import.html │ ├── preferences.html │ ├── preferences │ │ ├── grid.html │ │ ├── misc.html │ │ └── tile.html │ ├── resize.html │ └── save.html │ └── transformations.html └── test ├── casperjs ├── DrawingTest.js └── integration │ ├── IntegrationSuite.js │ ├── include.js │ ├── palettes │ └── test-tiny-palettes.js │ ├── preview │ └── test-toggle-grid.js │ └── settings │ ├── test-export-gif-scale.js │ ├── test-export-gif-simple.js │ ├── test-export-gif.js │ ├── test-export-png-scale.js │ ├── test-export-png.js │ ├── test-import-image-empty.js │ ├── test-import-image-twice.js │ ├── test-import-image.js │ ├── test-preferences-main.js │ ├── test-resize-complete.js │ ├── test-resize-content-complete.js │ ├── test-resize-default-size.js │ ├── test-resize-input-synchronization.js │ ├── test-resize-origin.js │ ├── test-resize.js │ └── test-settings-open-panels-on-click.js ├── drawing ├── DrawingTests.browser.js ├── DrawingTests.casper.js ├── DrawingTests.pensize.js ├── DrawingTests.perf.js └── tests │ ├── bucket.drawing.json │ ├── color.picker.2.json │ ├── color.picker.json │ ├── dithering.basic.json │ ├── eraser.bucket.json │ ├── frames.fun.json │ ├── history.basic.json │ ├── layers.duplicate.json │ ├── layers.fun.json │ ├── layers.merge.json │ ├── layers.top.bottom.json │ ├── lighten.darken.json │ ├── move-alllayers-allframes.json │ ├── move.json │ ├── pen.drawing.json │ ├── pen.mirror.pensize.json │ ├── pen.secondary.color.json │ ├── pensize.circle.basic.json │ ├── pensize.circle.undo.json │ ├── pensize.eraser.basic.json │ ├── pensize.eraser.undo.json │ ├── pensize.pen.basic.json │ ├── pensize.pen.undo.json │ ├── pensize.rectangle.basic.json │ ├── pensize.rectangle.undo.json │ ├── pensize.stroke.basic.json │ ├── pensize.stroke.undo.json │ ├── perf.1024.pen.bucket.json │ ├── perf.512.layers.undo.json │ ├── selection.lasso.json │ ├── selection.rectangular.json │ ├── squares.circles.json │ ├── stroke.json │ ├── swapcolor.alllayers.allframes.twice.undo.once.json │ ├── swapcolor.twice.undo.once.json │ ├── transform.center.json │ ├── transform.clone.once.json │ ├── transform.clone.twice.undo.once.json │ ├── transform.crop.json │ ├── transform.crop.selection.json │ ├── transform.flip.once.alt.json │ ├── transform.flip.thrice.undo.all.redo.all.json │ ├── transform.flip.twice.undo.once.json │ ├── transform.rotate.alt.twice.undo.once.json │ ├── transform.rotate.once.alt.json │ ├── transform.rotate.twice.undo.once.json │ └── verticalpen.drawing.json └── js ├── database ├── BackupDatabaseTest.js └── PiskelDatabaseTest.js ├── model ├── LayerTest.js └── PaletteTest.js ├── rendering ├── CanvasRendererTest.js ├── FramesheetRendererTest.js └── OnionSkinRendererTest.js ├── selection └── SelectionManagerTest.js ├── service ├── BackupServiceTest.js ├── HistoryServiceTest.js ├── SelectedColorsServiceTest.js ├── keyboard │ └── ShortcutServiceTest.js ├── palette │ └── PaletteServiceTest.js ├── pensize │ └── PenSizeServiceTest.js └── storage │ └── StorageServiceTest.js ├── testutils └── TestUtils.js ├── tools └── transform │ └── TransformUtilsTest.js └── utils ├── ArrayTest.js ├── ColorUtilsTest.js ├── CoreTest.js ├── FrameUtilsTest.js ├── FrameUtilsTest_add_image.js ├── LayerUtilsTest.js ├── MergeUtilsTest.js ├── PixelUtilsTest.js ├── PixelUtilsTest_visit_connected.js ├── UuidTest.js └── serialization ├── Deserializer_v0Test.js ├── Deserializer_v1Test.js └── SerializerTest.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | csslint: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - javascript 9 | eslint: 10 | enabled: true 11 | checks: 12 | wrap-iife: 13 | enabled: false 14 | fixme: 15 | enabled: true 16 | ratings: 17 | paths: 18 | - "**.css" 19 | - "**.js" 20 | exclude_paths: 21 | - .github/ 22 | - bin/ 23 | - misc/ 24 | - src/js/lib/ 25 | - test/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Exclude libs. 2 | **/lib/**/*.js 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | Bug description 2 | 3 | (this template is intended for bug reports, erase it when requesting features/enhancements) 4 | 5 | ### Steps to reproduce the bug 6 | 1. go to piskelapp.com 7 | 2. select a tool 8 | 3. ... 9 | 10 | ### Environment details 11 | * operating system: 12 | * browser (or offline application version): 13 | 14 | ### Sprite details 15 | * number of frames: 16 | * sprite resolution: 17 | * session duration: 18 | 19 | Feel free to include a screenshot of the bug, a .piskel file of the sprite or anything else that can help us investigate. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # mac artifacts 2 | *.DS_Store 3 | 4 | # nodejs local installs 5 | node_modules 6 | npm-debug.log 7 | 8 | # node webkit cache 9 | cache 10 | 11 | # sublime text stuff (the -project should actually be shared, but then we'd have to share the same disk location) 12 | *.sublime-project 13 | *.sublime-workspace 14 | 15 | # netbeans project folder 16 | nbproject 17 | 18 | # vscode workspace folder 19 | .vscode 20 | 21 | # git stackdumps 22 | *.stackdump 23 | 24 | # diffs 25 | diff.txt 26 | 27 | # build destination 28 | dest 29 | build/closure/closure_compiled_binary.js 30 | out 31 | 32 | # spriting artifacts 33 | src/img/icons.png 34 | src/img/icons@2x.png 35 | src/css/icons.css 36 | 37 | # plato report directory 38 | report 39 | 40 | # marked as private 41 | *.private.* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7.4.0" 4 | before_install: 5 | - npm update -g npm 6 | - npm install -g grunt-cli 7 | before_script: 8 | - phantomjs --version 9 | - casperjs --version 10 | sudo: false 11 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | # Piskel CLI 2 | 3 | Wraps the Piskel pixel editing application to enable similar export options via the command line. 4 | 5 | ## Installation 6 | 7 | Option 1: Globally install Piskel 8 | ``` 9 | npm install -g https://github.com/piskelapp/piskel/tarball/master 10 | ``` 11 | 12 | Option 2: Clone and install Piskel normally and then run npm link inside the installation root 13 | 14 | ## Usage 15 | 16 | **Export provided .piskel file as a png sprite sheet using app defaults** 17 | ``` 18 | piskel-cli snow-monster.piskel 19 | ``` 20 | 21 | **Export scaled sprite sheet** 22 | ``` 23 | piskel-cli snow-monster.piskel --scale 5 24 | ``` 25 | 26 | **Export scaled to specific (single frame) width value** 27 | ``` 28 | piskel-cli snow-monster.piskel --scaledWidth 435 29 | ``` 30 | 31 | **Export scaled to specific (single frame) height value** 32 | ``` 33 | piskel-cli snow-monster.piskel --scaledHeight 435 34 | ``` 35 | 36 | **Export sprite sheet as a single column** 37 | ``` 38 | piskel-cli snow-monster.piskel --columns 1 39 | ``` 40 | 41 | **Export sprite sheet as a single row** 42 | ``` 43 | piskel-cli snow-monster.piskel --rows 1 44 | ``` 45 | 46 | **Export a single frame (0 is first frame)** 47 | ``` 48 | piskel-cli snow-monster.piskel --frame 3 49 | ``` 50 | 51 | **Export a second file containing the data-uri for the exported png** 52 | ``` 53 | piskel-cli snow-monster.piskel --dataUri 54 | ``` 55 | 56 | **Export cropped** 57 | ``` 58 | piskel-cli snow-monster.piskel --crop 59 | ``` 60 | 61 | **Custom output path and/or filename** 62 | ``` 63 | piskel-cli snow-monster.piskel --dest ./output-folder/snah-monstah.png 64 | ``` -------------------------------------------------------------------------------- /cli/piskel-export.js: -------------------------------------------------------------------------------- 1 | // PhantomJS system 2 | const system = require('system'); 3 | 4 | // Exporter 5 | const exporter = require('./export-png'); 6 | 7 | // Get passed args 8 | const args = system.args; 9 | 10 | // Parse input piskel file and options 11 | const piskelFile = JSON.parse(args[1]); 12 | const options = JSON.parse(args[2]); 13 | 14 | // Create page w/ canvas 15 | const page = require('webpage').create(); 16 | 17 | page.content = ''; 18 | 19 | // Inject Piskel JS 20 | page.injectJs(options.piskelAppJsPath); 21 | 22 | // Listen for page console logs 23 | page.onConsoleMessage = function (msg) { 24 | console.log(msg); 25 | }; 26 | 27 | // Run page logic 28 | page.evaluate(function (piskelFile, options, onPageEvaluate) { 29 | // Zero out default body margin 30 | document.body.style.margin = 0; 31 | 32 | // Deserialize piskel file and run exporter's page evaluate task 33 | pskl.utils.serialization.Deserializer.deserialize(piskelFile, function (piskel) { 34 | onPageEvaluate(window, options, piskel); 35 | }); 36 | }, piskelFile, options, exporter.onPageEvaluate); 37 | 38 | // Wait for page to trigger exit 39 | page.onCallback = function (data) { 40 | // Run exporter page exit task 41 | exporter.onPageExit(page, options, data); 42 | 43 | // Exit 44 | phantom.exit(0); 45 | }; 46 | -------------------------------------------------------------------------------- /misc/desktop/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/desktop/logo.ico -------------------------------------------------------------------------------- /misc/desktop/nw.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/desktop/nw.icns -------------------------------------------------------------------------------- /misc/desktop/package-piskel.evb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/desktop/package-piskel.evb -------------------------------------------------------------------------------- /misc/gif-tests/low-colors-no-transparency.piskel: -------------------------------------------------------------------------------- 1 | {"modelVersion":2,"piskel":{"name":"low-colors-no-transparency","description":"","fps":12,"height":60,"width":60,"layers":["{\"name\":\"Layer 1\",\"frameCount\":2,\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAA8CAYAAACtrX6oAAABZUlEQVR4nO3cwRHCMAxE0RSUGlIF1TBDMTRAi1BCsCVZK/kf/n0n7xr5+FzXd6fe57lVR/YHBxhggAEGGGCA9yj7gwMMMMAAAwwwwHsEcPOWAR/Px22dgM/367bywP+grsbORl2NHQJsgY2GVoONhnYH9sSNQFbGjUB2A46AjYBWho2AdgFegeuFXAXXC9kMvBLXA7kSrgeyCTgD14pcDdeKDDDAergW5Iq4FuQp4GxYC3JV3FlkgAHWxZ1Brow7gwwwwACrFQacDemBXB13FBlggAFWDGCAAQYYYNkABhhggAGWLQRYFXlk/wiwKvLIfoABBlitUGA15NHto8BqyKPbAQZYF3lm9wywCvLMbv6qLII8uxlggPWQLXstwFnIlr3cJokjW7dyXSiM7LGT+2BBaM99XPiLIXtv440OEeioTbyyk4i9YgfvZDUP4OYB3DyAmwdw8wBuHsDNA7h5ADcP4Ob9AHU/4CXfXtXyAAAAAElFTkSuQmCC\"}"],"expanded":false}} -------------------------------------------------------------------------------- /misc/gif-tests/low-colors-with-transparency.piskel: -------------------------------------------------------------------------------- 1 | {"modelVersion":2,"piskel":{"name":"low-colors-with-transparency","description":"","fps":12,"height":60,"width":60,"layers":["{\"name\":\"Layer 1\",\"frameCount\":2,\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAA8CAYAAACtrX6oAAABOUlEQVR4nO3bQWoDQQxEUR3Ix8lpAj5MTmovQsNgEmwYdZVG+h9q3+KtO4KIiIiIiIiIiIhoUN9fj7dr1O3n/ng39xvP9wlqI+xPUHtgn4G9IPQZ2OtBZ+JeADkTtzbyDtjC0Dtg60IrcAshK3DrICtxCyArcf3IDlwjsgPXiwxwY2AnrgHZiatHdsOKkd2wemQ3KsAbc4OKkd2gemQ3JsAAZ+bG1AK7IcXIbkg9shsRYIAzcyMCDHBybkSAAc7MjQgwwMm5EQEGODM3oh44oibyxtyQWtwIgAts570AF9jOe39zg4pwV25QLW4EwO2BI2ogC3PDanFXQ3BXs3AjAG4PHMHPhta4qyG4q1m4qyG4q1m4x5rDvjYH9tgQ3NUs3GPNYV+bA/tXTVH/awYqERERERERERFRVk8BxgukicHldgAAAABJRU5ErkJggg==\"}"],"expanded":false}} -------------------------------------------------------------------------------- /misc/icons/cloud_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/cloud_export.png -------------------------------------------------------------------------------- /misc/icons/cursors-resources.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/cursors-resources.jpg -------------------------------------------------------------------------------- /misc/icons/eraser.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/eraser.ai -------------------------------------------------------------------------------- /misc/icons/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/icons.png -------------------------------------------------------------------------------- /misc/icons/import-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/import-icon.png -------------------------------------------------------------------------------- /misc/icons/mirror.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/mirror.ai -------------------------------------------------------------------------------- /misc/icons/noun-project/sheep/icon_8389.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/noun-project/sheep/icon_8389.png -------------------------------------------------------------------------------- /misc/icons/noun-project/sheep/license.txt: -------------------------------------------------------------------------------- 1 | Thank you for using The Noun Project. This icon is licensed under Creative 2 | Commons Attribution and must be attributed as: 3 | 4 | Sheep by Unrecognized MJ from The Noun Project 5 | 6 | If you have a Premium Account or have purchased a license for this icon, you 7 | don't need to worry about attribution! We will share the profits from your 8 | purchase with this icon's designer. 9 | -------------------------------------------------------------------------------- /misc/icons/noun-project/undo-kyle_levi_fox/icon_10033.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /misc/icons/noun-project/undo-kyle_levi_fox/license.txt: -------------------------------------------------------------------------------- 1 | Thank you for using The Noun Project. This icon is licensed under Creative 2 | Commons Attribution and must be attributed as: 3 | 4 | Undo by Kyle Levi Fox from The Noun Project 5 | 6 | If you have a Premium Account or have purchased a license for this icon, you 7 | don't need to worry about attribution! We will share the profits from your 8 | purchase with this icon's designer. 9 | -------------------------------------------------------------------------------- /misc/icons/rectangle.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/rectangle.ai -------------------------------------------------------------------------------- /misc/icons/rectangle_selection.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/rectangle_selection.ai -------------------------------------------------------------------------------- /misc/icons/source/frame-duplicate-white-base.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/source/frame-duplicate-white-base.pdn -------------------------------------------------------------------------------- /misc/icons/source/frame-duplicate-white-base@2x.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/source/frame-duplicate-white-base@2x.pdn -------------------------------------------------------------------------------- /misc/icons/stroke.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/stroke.ai -------------------------------------------------------------------------------- /misc/icons/swap-arrow-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-arrow-small.png -------------------------------------------------------------------------------- /misc/icons/swap-arrow-square-small-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-arrow-square-small-grey.png -------------------------------------------------------------------------------- /misc/icons/swap-arrow-square-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-arrow-square-small.png -------------------------------------------------------------------------------- /misc/icons/swap-colors-tests/swap-colors-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-colors-tests/swap-colors-square.png -------------------------------------------------------------------------------- /misc/icons/swap-colors-tests/swap-colors-twirl-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-colors-tests/swap-colors-twirl-2.png -------------------------------------------------------------------------------- /misc/icons/swap-colors-tests/swap-colors-twirl-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-colors-tests/swap-colors-twirl-3.png -------------------------------------------------------------------------------- /misc/icons/swap-colors-tests/swap-colors-twirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-colors-tests/swap-colors-twirl.png -------------------------------------------------------------------------------- /misc/icons/swap-colors-tests/swap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/icons/swap-colors-tests/swap.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/dragndrop-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/dragndrop-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/dragndrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/dragndrop.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/eyedropper-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/eyedropper-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/eyedropper-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/eyedropper-icon.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/garbage-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/garbage-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/garbage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/garbage.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/magicwand-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/magicwand-icon-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/magicwand-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/magicwand-icon.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/paintbucket-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/paintbucket-icon-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/paintbucket-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/paintbucket-icon.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/pen-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/pen-icon-dark.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/pen-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/pen-icon.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/preview-state-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/preview-state-1.png -------------------------------------------------------------------------------- /misc/proto-ui-1/img/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/preview.gif -------------------------------------------------------------------------------- /misc/proto-ui-1/img/transparent_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/misc/proto-ui-1/img/transparent_background.png -------------------------------------------------------------------------------- /misc/scripts/build-mac-application.txt: -------------------------------------------------------------------------------- 1 | 01 - Run grunt desktop on mac os x 2 | 02 - Retrieve piskel.new.dmg.zip on Dropbox. Unzip it. 3 | 03 - Retrieve nw.icns on Dropbox 4 | 04 - Go to piskel/dest/desktop/releases/mac ; copy piskel.app to your working directory 5 | 05 - Expand piskel.app 6 | 06 - Replace nw.icns by the one from dropbox in Contents/Resources 7 | 07 - Open piskel.new.dmg 8 | 08 - Drag and drop piskel.app in it 9 | 09 - Update readme.txt 10 | 10 - Open disk utility, and open piskel.new.dmg in it 11 | 11 - Convert image, piskel-x.y.z.dmg 12 | 12 - Upload to Dropbox -------------------------------------------------------------------------------- /misc/scripts/package-mac-application.cmd: -------------------------------------------------------------------------------- 1 | setlocal 2 | @ECHO off 3 | 4 | pushd ..\.. 5 | set PISKEL_HOME=%cd% 6 | popd 7 | 8 | set APP_BIN=%PISKEL_HOME%\dest\desktop\cache\mac\0.9.2 9 | set MISC_FOLDER=%PISKEL_HOME%\misc 10 | set RELEASES_FOLDER=%PISKEL_HOME%\dest\desktop\releases 11 | set DEST_FOLDER=%RELEASES_FOLDER%\mac 12 | 13 | ECHO "Building Piskel executable for Windows ..." 14 | 15 | ECHO "Creating release directory ..." 16 | mkdir %DEST_FOLDER% 17 | ECHO "DONE" 18 | 19 | ECHO "Creating application folder ..." 20 | mkdir "%DEST_FOLDER%\piskel.app" 21 | ECHO "DONE" 22 | 23 | ECHO "Unzip application ..." 24 | mkdir "%APP_BIN%\node-webkit-unzipped" 25 | 7za x "%APP_BIN%\node-webkit-v0.9.2-osx-ia32.zip" -o"%APP_BIN%\node-webkit-unzipped" 26 | ECHO "DONE" 27 | 28 | pause 29 | 30 | ECHO "Copy application ..." 31 | xcopy "%APP_BIN%\node-webkit-unzipped\node-webkit.app" "%DEST_FOLDER%\piskel.app" /E 32 | :: xcopy "%APP_BIN%\node-webkit.app" "%DEST_FOLDER%\piskel.app" /E 33 | ECHO "DONE" 34 | 35 | ECHO "Copy Info.plist ..." 36 | set CONTENTS_FOLDER=%DEST_FOLDER%\piskel.app\Contents 37 | copy "%MISC_FOLDER%\desktop\Info.plist" "%CONTENTS_FOLDER%\" 38 | ECHO "DONE" 39 | 40 | ECHO "Copy application ..." 41 | set RESOURCES_FOLDER=%CONTENTS_FOLDER%\Resources 42 | copy "%RELEASES_FOLDER%\piskel\piskel.nw" "%RESOURCES_FOLDER%\" 43 | mv "%RESOURCES_FOLDER%\piskel.nw" "%RESOURCES_FOLDER%\app.nw" 44 | ECHO "%RESOURCES_FOLDER%" 45 | ECHO "DONE" 46 | 47 | ECHO "Copy icon ..." 48 | DEL "%RESOURCES_FOLDER%\nw.icns" 49 | COPY "%MISC_FOLDER%\desktop\nw.icns" "%RESOURCES_FOLDER%\" 50 | ECHO "DONE" 51 | 52 | pause 53 | 54 | explorer "%DEST_FOLDER%" 55 | 56 | endlocal -------------------------------------------------------------------------------- /misc/scripts/package-windows-executable.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | 3 | SETLOCAL 4 | 5 | PUSHD ..\.. 6 | set PISKEL_HOME=%cd% 7 | POPD 8 | 9 | set RESOURCE_HACKER_PATH="C:\Program Files (x86)\Resource Hacker" 10 | 11 | set MISC_FOLDER=%PISKEL_HOME%\misc 12 | set RELEASES_FOLDER=%PISKEL_HOME%\dest\desktop 13 | set DEST_FOLDER=%RELEASES_FOLDER%\piskel\win32 14 | 15 | ECHO "Updating Piskel icon -- Using Resource Hacker" 16 | %RESOURCE_HACKER_PATH%\ResHacker -addoverwrite "%DEST_FOLDER%\piskel.exe", "%DEST_FOLDER%\piskel-logo.exe", "%MISC_FOLDER%\desktop\logo.ico", ICONGROUP, IDR_MAINFRAME, 1033 17 | DEL "%DEST_FOLDER%\piskel.exe" 18 | ECHO "DONE" 19 | 20 | 21 | PAUSE 22 | explorer "%DEST_FOLDER%\" 23 | 24 | ENDLOCAL -------------------------------------------------------------------------------- /misc/scripts/piskel-root: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Writes the absolute path to the release build root to stdout 3 | var path = require('path'); 4 | process.stdout.write(path.resolve(__dirname, '../../dest/prod') + '\n'); 5 | -------------------------------------------------------------------------------- /misc/selenium-ide/change-colors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | change-colors 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
change-colors
open/?debug
clickcss=div.sp-preview-inner
clickcss=input.sp-input
typecss=input.sp-inputrgb(170, 187, 204)
mouseDownid=preview-list-scroller
clickcss=div.swap-colors-icon
46 | 47 | 48 | -------------------------------------------------------------------------------- /misc/selenium-ide/change-size.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | change-size 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
change-size
open/?debug
clickcss=div.tool-icon.resize-icon
typename=resize-width64
typename=resize-height64
clickname=resize-content-checkbox
click//input[@value='Resize']
mouseDownid=column-wrapper
51 | 52 | 53 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # stash unstaged changes, run release task, stage release updates and restore stashed files 3 | 4 | NAME=$(git branch | grep '*' | sed 's/* //') 5 | 6 | # don't run on rebase 7 | if [ $NAME != '(no branch)' ] 8 | then 9 | NOT_STAGED=$(git status | grep 'not staged') 10 | # if [ "${NOT_STAGED#*not staged}" != "$NOT_STAGED" ] 11 | if [ "$NOT_STAGED" != "" ] 12 | then 13 | echo "Unclean directory, aborting commit. Run git status." 14 | exit 1 15 | fi 16 | 17 | grunt precommit 18 | RETVAL=$? 19 | 20 | if [ $RETVAL -ne 0 ] 21 | then 22 | echo "grunt test failed, aborting commit. Run grunt test" 23 | exit 1 24 | fi 25 | fi -------------------------------------------------------------------------------- /src/css/animations.css: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | 50% { opacity: 0.5; } 3 | } 4 | 5 | @keyframes glow { 6 | 0% { opacity: 0.66; } 7 | 50% { opacity: 1; } 8 | 100% { opacity: 0.66; } 9 | } 10 | -------------------------------------------------------------------------------- /src/css/bootstrap/bootstrap-tooltip-custom.css: -------------------------------------------------------------------------------- 1 | .tooltip.in { 2 | opacity: 0.95; 3 | filter: alpha(opacity=95); 4 | } 5 | 6 | .tooltip { 7 | line-height: 20px; 8 | } -------------------------------------------------------------------------------- /src/css/bootstrap/readme.txt: -------------------------------------------------------------------------------- 1 | Bootstrap custom build containing only the tooltip component -------------------------------------------------------------------------------- /src/css/dialogs-browse-local.css: -------------------------------------------------------------------------------- 1 | 2 | /************************************************************************************************/ 3 | /* Browse local piskels panel */ 4 | /************************************************************************************************/ 5 | 6 | #dialog-container.browse-local { 7 | width: 700px; 8 | height: 500px; 9 | top : 50%; 10 | left : 50%; 11 | position : absolute; 12 | margin-left: -350px; 13 | } 14 | 15 | .show #dialog-container.browse-local { 16 | margin-top: -250px; 17 | } 18 | 19 | .local-piskel-list { 20 | width: 100%; 21 | } 22 | 23 | .local-piskel-item { 24 | height: 3em; 25 | } 26 | 27 | .local-piskel-name { 28 | width: 40%; 29 | } 30 | 31 | .local-piskel-save-date { 32 | font-weight : normal; 33 | } 34 | 35 | .local-piskel-list a { 36 | text-decoration: none; 37 | } 38 | 39 | .local-piskel-list a:hover { 40 | text-decoration: underline; 41 | } 42 | 43 | .local-piskel-list-head { 44 | font-weight: bold; 45 | color: var(--highlight-color); 46 | } 47 | 48 | .local-piskel-load-button, 49 | .local-piskel-delete-button { 50 | width : 75px; 51 | } -------------------------------------------------------------------------------- /src/css/dialogs-performance-info.css: -------------------------------------------------------------------------------- 1 | .performance-link { 2 | display: none; 3 | position: fixed; 4 | bottom: 10px; 5 | right: 10px; 6 | z-index: 11000; 7 | cursor: pointer; 8 | opacity: 0; 9 | transition : opacity 0.3s; 10 | } 11 | 12 | .performance-link.visible { 13 | display: block; 14 | opacity: 0.66; 15 | animation: glow 2s infinite; 16 | } 17 | 18 | .performance-link.visible:hover { 19 | opacity: 1; 20 | animation: none; 21 | } 22 | 23 | #dialog-container.performance-info { 24 | width: 500px; 25 | height: 525px; 26 | top : 50%; 27 | left : 50%; 28 | position : absolute; 29 | margin-left: -250px; 30 | margin-top: -260px; 31 | 32 | } 33 | 34 | .dialog-performance-info-body { 35 | font-size: 13px; 36 | letter-spacing: 1px; 37 | padding: 10px 20px; 38 | } 39 | 40 | .dialog-performance-info-body ul { 41 | border: 1px solid #666; 42 | padding: 5px; 43 | border-radius: 3px; 44 | } 45 | 46 | .dialog-performance-info-body li { 47 | list-style-type: initial; 48 | margin: 0 20px; 49 | } 50 | 51 | .dialog-performance-info-body sup { 52 | color: var(--highlight-color); 53 | cursor: pointer; 54 | } 55 | 56 | .show #dialog-container.performance-info { 57 | margin-top: -300px; 58 | } 59 | 60 | .dialog-performance-info-body .warning-icon { 61 | float: left; 62 | margin-top: 10px; 63 | } 64 | 65 | .dialog-performance-info-body .warning-icon-info { 66 | overflow: hidden; 67 | margin-left: 30px; 68 | } 69 | -------------------------------------------------------------------------------- /src/css/dialogs-unsupported-browser.css: -------------------------------------------------------------------------------- 1 | /************************************************************************************************/ 2 | /* Unsupported browser dialog */ 3 | /************************************************************************************************/ 4 | 5 | #dialog-container.unsupported-browser { 6 | width: 600px; 7 | height: 260px; 8 | top : 50%; 9 | left : 50%; 10 | position : absolute; 11 | margin-top: -130px; 12 | margin-left: -300px; 13 | } 14 | 15 | .unsupported-browser .dialog-content { 16 | font-size:1.2em; 17 | letter-spacing: 1px; 18 | padding:10px 20px; 19 | overflow: auto; 20 | } 21 | 22 | .unsupported-browser .supported-browser-list { 23 | padding: 5px 20px; 24 | } 25 | 26 | .unsupported-browser .supported-browser-list li { 27 | list-style-type: square; 28 | } 29 | 30 | #current-user-agent { 31 | color: var(--highlight-color); 32 | } 33 | -------------------------------------------------------------------------------- /src/css/dialogs.css: -------------------------------------------------------------------------------- 1 | #dialog-container-wrapper { 2 | position: absolute; 3 | z-index: 20000; 4 | 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | 10 | padding: 50px 150px; 11 | overflow: hidden; 12 | 13 | box-sizing: border-box; 14 | -moz-box-sizing : border-box; 15 | 16 | background-color: rgba(0,0,0,0.8); 17 | opacity: 0; 18 | pointer-events: none; 19 | 20 | color: white; 21 | } 22 | 23 | #dialog-container-wrapper.animated { 24 | transition: opacity 0.2s; 25 | } 26 | 27 | #dialog-container-wrapper.show { 28 | opacity: 1; 29 | pointer-events: auto; 30 | } 31 | 32 | #dialog-container { 33 | width: 100%; 34 | height: 100%; 35 | 36 | margin-top: -1500px; 37 | 38 | box-sizing: border-box; 39 | -moz-box-sizing : border-box; 40 | 41 | border-radius: 3px; 42 | border : 3px solid var(--highlight-color); 43 | background: #444; 44 | overflow: auto; 45 | } 46 | 47 | .show #dialog-container { 48 | margin-top: 0; 49 | } 50 | 51 | .dialog-wrapper { 52 | height: 100%; 53 | position : relative; 54 | } 55 | 56 | .dialog-content { 57 | position: absolute; 58 | top: 45px; 59 | bottom: 0; 60 | width: 100%; 61 | box-sizing: border-box; 62 | } 63 | 64 | .dialog-head { 65 | width: 100%; 66 | background: var(--highlight-color); 67 | margin: 0; 68 | padding: 10px; 69 | color: black; 70 | font-size: 1.8em; 71 | height: 45px; 72 | box-sizing: border-box; 73 | -moz-box-sizing: border-box; 74 | } 75 | 76 | .dialog-close { 77 | position: absolute; 78 | top: 0; 79 | right: 0; 80 | line-height: 45px; 81 | margin-right: 10px; 82 | font-size: 1.3em; 83 | cursor: pointer; 84 | } -------------------------------------------------------------------------------- /src/css/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/icomoon.eot -------------------------------------------------------------------------------- /src/css/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/css/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/css/fonts/piskel.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/piskel.eot -------------------------------------------------------------------------------- /src/css/fonts/piskel.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/piskel.ttf -------------------------------------------------------------------------------- /src/css/fonts/piskel.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/css/fonts/piskel.woff -------------------------------------------------------------------------------- /src/css/forms.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: block; 3 | } 4 | 5 | .textfield { 6 | box-sizing:border-box; 7 | 8 | background : black; 9 | border : 1px solid #888; 10 | border-radius : 2px; 11 | padding : 3px 10px; 12 | color : white; 13 | 14 | height: 23px; 15 | } 16 | 17 | .textfield[readonly="true"] { 18 | background: transparent; 19 | } 20 | 21 | .textfield[disabled=disabled] { 22 | background : #3a3a3a; 23 | } 24 | 25 | .textfield:focus { 26 | border-color: var(--highlight-color); 27 | outline: none; 28 | } 29 | 30 | .textfield-small { 31 | width : 50px; 32 | } 33 | 34 | .button { 35 | box-sizing: border-box; 36 | height: 24px; 37 | background-color: #666; 38 | border-style: none; 39 | border-radius: 2px; 40 | cursor: pointer; 41 | text-decoration: none; 42 | 43 | color: white; 44 | font-weight: bold; 45 | font-size: 1rem; 46 | text-align: center; 47 | 48 | transition: background-color 0.2s linear; 49 | } 50 | 51 | .button:hover { 52 | color: var(--highlight-color); 53 | } 54 | 55 | .button-primary { 56 | background-color: var(--highlight-color); 57 | color: black; 58 | } 59 | 60 | .button-primary:hover { 61 | background-color: white; 62 | color: black; 63 | } 64 | 65 | .button[disabled], 66 | .button[disabled]:hover { 67 | cursor:default; 68 | background-color: #aaa; 69 | color: #777; 70 | } 71 | 72 | .import-size-field, 73 | .resize-size-field, 74 | .export-size-field { 75 | width: 50px; 76 | margin-right: 8px; 77 | text-align: right; 78 | } -------------------------------------------------------------------------------- /src/css/minimap.css: -------------------------------------------------------------------------------- 1 | .minimap-crop-frame { 2 | position: absolute; 3 | border: 2px solid gold; 4 | z-index: 100; 5 | box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | cursor: pointer; 8 | } -------------------------------------------------------------------------------- /src/css/notifications.css: -------------------------------------------------------------------------------- 1 | .user-message { 2 | position: absolute; 3 | right: 0; 4 | bottom: 0; 5 | padding: 10px 47px; 6 | max-width: 300px; 7 | 8 | border-top-left-radius: 7px; 9 | border: #e1a325 2px solid; 10 | border-right: 0; 11 | border-bottom: 0; 12 | 13 | color: #222; 14 | background-color: var(--highlight-color); 15 | 16 | font-weight: bold; 17 | font-size: 13px; 18 | 19 | z-index: 30000; 20 | } 21 | 22 | .user-message .close { 23 | position: absolute; 24 | top: 6px; 25 | right: 17px; 26 | 27 | font-size: 18px; 28 | font-weight: bold; 29 | 30 | cursor: pointer; 31 | } 32 | 33 | .user-message .close:hover { 34 | color: black; 35 | } 36 | 37 | .progress-bar-container { 38 | position: absolute; 39 | left: 0; 40 | bottom: 0; 41 | padding: 10px; 42 | width: 360px; 43 | border-top-right-radius: 2px; 44 | border: var(--highlight-color) 2px solid; 45 | border-left: 0; 46 | border-bottom: 0; 47 | background-color: #444; 48 | font-size: 14px; 49 | z-index: 30000; 50 | color: #eee; 51 | } 52 | 53 | .progress-bar-item { 54 | float: left; 55 | height:20px; 56 | } 57 | 58 | .progress-bar-status { 59 | line-height: 20px; 60 | width : 40px; 61 | overflow : hidden; 62 | margin: 0 0 0 10px; 63 | } 64 | 65 | .progress-bar { 66 | border : 1px solid grey; 67 | margin-top: 8px; 68 | height : 4px; 69 | width : 300px; 70 | background : linear-gradient(to left, var(--highlight-color), var(--highlight-color)) no-repeat -300px 0; 71 | background-color : black; 72 | } -------------------------------------------------------------------------------- /src/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height : 100%; width: 100%; 3 | margin : 0; 4 | overflow: hidden; 5 | cursor : default; 6 | font-family: Arial; 7 | font-size: 11px; 8 | line-height: 1.1; 9 | -webkit-touch-callout: none; 10 | -webkit-user-select: none; 11 | -khtml-user-select: none; 12 | -moz-user-select: none; 13 | -ms-user-select: none; 14 | user-select: none; 15 | } 16 | 17 | ul, li { 18 | margin : 0; 19 | padding : 0; 20 | list-style-type: none; 21 | } 22 | 23 | /** Firefox overrides this with -moz-use-system-font */ 24 | button, 25 | input, 26 | input[type="submit"] { 27 | font-family: Arial; 28 | } 29 | 30 | /* IE11 applies a big default margin for range inputs */ 31 | input[type="range"] { 32 | padding: 0; 33 | } 34 | 35 | /* Force apparition of scrollbars on leopard */ 36 | ::-webkit-scrollbar { 37 | -webkit-appearance: none; 38 | width: 6px; 39 | } 40 | 41 | ::-webkit-scrollbar-thumb { 42 | border-radius: 2px; 43 | background-color: #666; 44 | } 45 | 46 | ::-webkit-scrollbar-track { 47 | background-color: rgba(50, 50, 50, 0.4); 48 | } 49 | 50 | a, a:visited { 51 | color: var(--highlight-color); 52 | } -------------------------------------------------------------------------------- /src/css/settings-import.css: -------------------------------------------------------------------------------- 1 | /************************************************************************************************/ 2 | /* Import panel */ 3 | /************************************************************************************************/ 4 | 5 | .import-section, 6 | .resize-section { 7 | margin: 10px 0; 8 | } 9 | 10 | .file-input-button { 11 | margin-right: 8px; 12 | border-radius: 2px; 13 | } 14 | 15 | .import-highlight { 16 | font-weight: bold; 17 | color: white; 18 | } -------------------------------------------------------------------------------- /src/css/settings-resize.css: -------------------------------------------------------------------------------- 1 | .resize-section-title { 2 | vertical-align: top; 3 | display: inline-block; 4 | padding-top: 5px; 5 | width: 25%; 6 | } 7 | 8 | .resize-anchor-container { 9 | position: relative; 10 | margin-top: 5px; 11 | display: inline-block; 12 | } 13 | -------------------------------------------------------------------------------- /src/css/settings-save.css: -------------------------------------------------------------------------------- 1 | .save-field { 2 | width: 100%; 3 | } 4 | 5 | .save-status { 6 | margin-top: 10px; 7 | margin-bottom: -10px; 8 | vertical-align: middle; 9 | font-weight: normal; 10 | text-shadow: none; 11 | font-style: italic; 12 | } 13 | 14 | .save-file-name { 15 | white-space: nowrap; 16 | font-weight: bold; 17 | color: white; 18 | font-style: normal; 19 | } 20 | 21 | .save-desktop-file-name { 22 | word-wrap: break-word; 23 | font-weight: bold; 24 | color: white; 25 | font-style: normal; 26 | } 27 | 28 | .save-status-warning-icon { 29 | float: left; 30 | margin-top: 5px; 31 | } 32 | 33 | .save-status-warning-icon { 34 | overflow: hidden; 35 | padding-left: 10px; 36 | } 37 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #1D1D1D; 3 | } 4 | 5 | /* Browser fixes */ 6 | ::-ms-clear { 7 | display: none; 8 | } 9 | 10 | .allow-user-select { 11 | -webkit-touch-callout: initial; 12 | -webkit-user-select: initial; 13 | -khtml-user-select: initial; 14 | -moz-user-select: initial; 15 | -ms-user-select: initial; 16 | user-select: initial; 17 | } 18 | 19 | .no-overflow { 20 | overflow: hidden; 21 | } 22 | 23 | .highlight { 24 | color: var(--highlight-color); 25 | } 26 | 27 | .pull-top, 28 | .pull-right, 29 | .pull-bottom, 30 | .pull-left { 31 | position:absolute; 32 | } 33 | 34 | .pull-top { 35 | top:0; 36 | } 37 | 38 | .pull-right { 39 | right:0; 40 | } 41 | 42 | .pull-bottom { 43 | bottom:0; 44 | } 45 | 46 | .pull-left { 47 | left:0; 48 | } 49 | 50 | .uppercase { 51 | text-transform: uppercase; 52 | } 53 | 54 | .checkbox-fix { 55 | margin: 3px 3px 3px 0; 56 | } 57 | 58 | .checkbox-container { 59 | display: flex; 60 | align-items: center; 61 | } 62 | 63 | .hidden { 64 | display: none; 65 | } 66 | 67 | /** 68 | * TOOLTIPS 69 | */ 70 | .tooltip-shortcut { 71 | color: var(--highlight-color); 72 | } 73 | 74 | .tooltip-container { 75 | text-align: left; 76 | } 77 | 78 | .tooltip-descriptor { 79 | color:#999; 80 | } 81 | 82 | .tooltip-descriptor:last-child { 83 | padding-bottom: 5px; 84 | } 85 | 86 | .tooltip-descriptor-button { 87 | padding: 2px; 88 | border: 1px Solid #999; 89 | font-size: 0.8em; 90 | margin-right: 5px; 91 | width: 35px; 92 | text-align: center; 93 | border-radius: 3px; 94 | display: inline-block; 95 | line-height: 10px; 96 | } -------------------------------------------------------------------------------- /src/css/toolbox-layers-list.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Layers container 4 | */ 5 | .layers-list-container { 6 | min-height: 85px; 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | /** 12 | * Layers title and toggle preview 13 | */ 14 | 15 | .layers-title { 16 | position: relative; 17 | flex-shrink: 0; 18 | } 19 | 20 | .layers-toggle-preview { 21 | position: absolute; 22 | top: 0.3em; 23 | right: 0.5em; 24 | 25 | color: #999; 26 | font-size: 1.3em; 27 | cursor: pointer; 28 | 29 | transition: 0.2s linear; 30 | } 31 | 32 | .layers-toggle-preview:hover { 33 | color: white; 34 | } 35 | 36 | .layers-toggle-preview-enabled, 37 | .layers-toggle-preview-enabled:hover { 38 | color : var(--highlight-color); 39 | } 40 | 41 | /** 42 | * Layers buttons 43 | */ 44 | 45 | .layers-button { 46 | margin: 0; 47 | width: 16.66667%; 48 | float : left; 49 | } 50 | 51 | /** 52 | * Layers list 53 | */ 54 | 55 | .layers-list { 56 | font-size : 12px; 57 | overflow: auto; 58 | } 59 | 60 | .layer-item { 61 | position: relative; 62 | display: flex; 63 | height:24px; 64 | line-height: 24px; 65 | border-top: 1px solid #444; 66 | cursor: pointer; 67 | } 68 | 69 | .layer-item .layer-name, 70 | .layer-item .layer-name-input { 71 | padding: 0 0 0 10px; 72 | flex: 1 auto; 73 | white-space: nowrap; 74 | } 75 | 76 | .layer-item .layer-name-input { 77 | width: 80%; 78 | } 79 | 80 | .layer-item .layer-name.overflowing-name { 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | } 84 | 85 | .layer-item:hover { 86 | background : #222; 87 | } 88 | 89 | .layer-item-opacity { 90 | padding: 0 8px 0 8px; 91 | flex: 0 auto; 92 | } 93 | 94 | .current-layer-item, 95 | .current-layer-item:hover { 96 | background : #333; 97 | color: var(--highlight-color); 98 | } -------------------------------------------------------------------------------- /src/css/toolbox.css: -------------------------------------------------------------------------------- 1 | .toolbox-container { 2 | border: 2px solid #888; 3 | font-size: medium; 4 | color: white; 5 | text-align: left; 6 | border-radius: 2px; 7 | margin-top: 5px; 8 | overflow: hidden; 9 | } 10 | 11 | .toolbox-title { 12 | padding: 8px; 13 | margin: 0; 14 | font-size: 15px; 15 | /* reset for firefox */ 16 | height: 16px; 17 | background: #222; 18 | } 19 | 20 | .toolbox-buttons { 21 | flex-shrink: 0; 22 | overflow: hidden; 23 | border-top: 1px solid #666; 24 | border-bottom: 1px solid #222; 25 | } 26 | 27 | .toolbox-buttons .button { 28 | /* Override border propery on .button elements from form.css */ 29 | border-style: solid; 30 | border-color: #333; 31 | border-width: 0 1px 0 0; 32 | border-radius: 0; 33 | 34 | background-color: #3f3f3f; 35 | } 36 | 37 | .toolbox-buttons .button[disabled], 38 | .toolbox-buttons .button[disabled]:hover { 39 | background-color: #aaa; 40 | } 41 | 42 | .toolbox-buttons button:last-child { 43 | border-right-width: 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/css/transformations.css: -------------------------------------------------------------------------------- 1 | .transformations-container { 2 | flex-shrink: 0; 3 | } 4 | 5 | .transformations-container .tool-icon { 6 | margin: 0 0 5px 0; 7 | } 8 | 9 | .transformations-container .tools-wrapper { 10 | display: flex; 11 | flex-wrap: wrap; 12 | justify-content: space-between; 13 | height: 46px; 14 | 15 | /* Override the float:left set on tools-wrapper in layout.css; */ 16 | float: initial; 17 | } 18 | 19 | .transformations-container.show-more .tools-wrapper { 20 | height: auto; 21 | /* Compensate the 5px bottom-margin coming from the tool-icon */ 22 | margin-bottom: -5px; 23 | } 24 | 25 | .transformations-show-more-link { 26 | position: absolute; 27 | color: #999; 28 | right: 10px; 29 | font-weight: normal; 30 | cursor: pointer; 31 | transition: 0.2s linear; 32 | } 33 | 34 | .transformations-show-more-link:hover { 35 | color: white; 36 | } 37 | 38 | .show-more .transformations-show-more-link { 39 | color: gold; 40 | } -------------------------------------------------------------------------------- /src/css/variables.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | --highlight-color: gold; 3 | } -------------------------------------------------------------------------------- /src/css/widgets-frame-picker.css: -------------------------------------------------------------------------------- 1 | /***********************/ 2 | /* FRAME PICKER WIDGET */ 3 | /***********************/ 4 | 5 | .frame-picker-wrapper { 6 | width: 150px; 7 | height: 150px; 8 | border: 3px solid #666; 9 | border-radius: 3px; 10 | } 11 | 12 | .frame-viewer { 13 | width: 100%; 14 | height: calc(100% - 25px); 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | 19 | } 20 | 21 | .frame-viewer > canvas, 22 | .frame-viewer > img { 23 | max-width: 100%; 24 | max-height: 100%; 25 | } 26 | 27 | .frame-nav { 28 | display: flex; 29 | width: 100%; 30 | height: 24px; 31 | border-top: 1px solid #666; 32 | } 33 | 34 | .frame-nav .button { 35 | flex-shrink: 0; 36 | border-radius: 0; 37 | height: 24px; 38 | background-color: #3f3f3f; 39 | } 40 | 41 | .frame-nav .button[disabled], 42 | .frame-nav .button[disabled]:hover { 43 | background-color: #aaa; 44 | } 45 | 46 | .frame-nav .button + .button { 47 | border-left: 1px solid #333; 48 | } 49 | 50 | .frame-nav-input { 51 | min-width: 1px; 52 | border-style: none; 53 | height: 24px; 54 | text-align: center; 55 | } 56 | -------------------------------------------------------------------------------- /src/css/widgets-size-picker.css: -------------------------------------------------------------------------------- 1 | /***********************/ 2 | /* SIZE PICKER WIDGET */ 3 | /***********************/ 4 | 5 | .size-picker-container { 6 | overflow: hidden; 7 | padding: 5px 5px; 8 | } 9 | 10 | .size-picker-option { 11 | float: left; 12 | box-sizing: border-box; 13 | width: 20px; 14 | height: 20px; 15 | margin-right: 2px; 16 | border-style: solid; 17 | border-width: 2px; 18 | border-color: #444; 19 | cursor: pointer; 20 | } 21 | 22 | .size-picker-option[data-size='1'] { 23 | padding: 5px; 24 | } 25 | .size-picker-option[data-size='2'] { 26 | padding: 4px; 27 | } 28 | .size-picker-option[data-size='3'] { 29 | padding: 3px; 30 | } 31 | .size-picker-option[data-size='4'] { 32 | padding: 2px; 33 | } 34 | 35 | .size-picker-option:before { 36 | content: ''; 37 | width: 100%; 38 | height: 100%; 39 | background-color: white; 40 | display: block; 41 | text-align: center; 42 | line-height: 12px; 43 | font-size: 90%; 44 | } 45 | 46 | .size-picker-option:hover { 47 | border-color: #888; 48 | } 49 | 50 | .size-picker-option.selected:before { 51 | background-color: var(--highlight-color); 52 | } 53 | 54 | .size-picker-option.selected { 55 | border-color: var(--highlight-color); 56 | } 57 | 58 | .size-picker-option.labeled:before { 59 | content: attr(real-size); 60 | color: black; 61 | } 62 | -------------------------------------------------------------------------------- /src/css/widgets-tabs.css: -------------------------------------------------------------------------------- 1 | /*****************/ 2 | /* TABS WIDGET */ 3 | /*****************/ 4 | 5 | .tab-list { 6 | overflow: hidden; 7 | position: relative; 8 | } 9 | 10 | .tab-list:after { 11 | content: ""; 12 | display: block; 13 | position: absolute; 14 | bottom: 0; 15 | width: 100%; 16 | height: 1px; 17 | z-index: 0; 18 | background-color: var(--highlight-color); 19 | } 20 | 21 | .tab-item { 22 | float: left; 23 | cursor: pointer; 24 | padding: 5px; 25 | border: 1px solid transparent; 26 | border-radius: 2px 2px 0 0; 27 | /* Make sure the tab and its border are positioned above the :after element; */ 28 | position: relative; 29 | z-index: 1; 30 | } 31 | 32 | .tab-item.selected, 33 | .tab-item:hover { 34 | color: var(--highlight-color); 35 | } 36 | 37 | .tab-item.selected { 38 | border-color: var(--highlight-color); 39 | border-bottom-color: #444; 40 | border-style: solid; 41 | border-width: 1px; 42 | } -------------------------------------------------------------------------------- /src/css/widgets-wizard.css: -------------------------------------------------------------------------------- 1 | .wizard-wrapper { 2 | z-index: 1; 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .wizard-step { 10 | z-index: -1; 11 | margin-left: calc(100% + 5px); 12 | position: absolute; 13 | } 14 | 15 | .current-step { 16 | z-index: 1; 17 | margin-left: 0; 18 | } 19 | 20 | .current-step-in, 21 | .current-step-out { 22 | z-index: 10; 23 | transition: margin-left 200ms; 24 | } 25 | 26 | .current-step-in { 27 | margin-left: 0; 28 | } 29 | 30 | .current-step-out { 31 | margin-left: 100%; 32 | } 33 | -------------------------------------------------------------------------------- /src/img/canvas-backgrounds/canvas-background-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/canvas-backgrounds/canvas-background-light.png -------------------------------------------------------------------------------- /src/img/canvas-backgrounds/canvas-background-lowcontrast-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/canvas-backgrounds/canvas-background-lowcontrast-dark.png -------------------------------------------------------------------------------- /src/img/canvas-backgrounds/canvas-background-lowcontrast-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/canvas-backgrounds/canvas-background-lowcontrast-medium.png -------------------------------------------------------------------------------- /src/img/canvas-backgrounds/canvas-background-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/canvas-backgrounds/canvas-background-medium.png -------------------------------------------------------------------------------- /src/img/cursors/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/circle.png -------------------------------------------------------------------------------- /src/img/cursors/color-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/color-palette.png -------------------------------------------------------------------------------- /src/img/cursors/dither.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/dither.png -------------------------------------------------------------------------------- /src/img/cursors/dropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/dropper.png -------------------------------------------------------------------------------- /src/img/cursors/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/eraser.png -------------------------------------------------------------------------------- /src/img/cursors/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/hand.png -------------------------------------------------------------------------------- /src/img/cursors/lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/lighten.png -------------------------------------------------------------------------------- /src/img/cursors/mirror-pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/mirror-pen.png -------------------------------------------------------------------------------- /src/img/cursors/paint-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/paint-bucket.png -------------------------------------------------------------------------------- /src/img/cursors/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/pen.png -------------------------------------------------------------------------------- /src/img/cursors/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/rectangle.png -------------------------------------------------------------------------------- /src/img/cursors/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/select.png -------------------------------------------------------------------------------- /src/img/cursors/stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/stroke.png -------------------------------------------------------------------------------- /src/img/cursors/vertical-mirror-pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/vertical-mirror-pen.png -------------------------------------------------------------------------------- /src/img/cursors/wand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/cursors/wand.png -------------------------------------------------------------------------------- /src/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/favicon.png -------------------------------------------------------------------------------- /src/img/icons/common/common-backup-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-backup-white.png -------------------------------------------------------------------------------- /src/img/icons/common/common-backup-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-backup-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/common/common-keyboard-gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-keyboard-gold.png -------------------------------------------------------------------------------- /src/img/icons/common/common-keyboard-gold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-keyboard-gold@2x.png -------------------------------------------------------------------------------- /src/img/icons/common/common-swapcolors-arrow-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-swapcolors-arrow-grey.png -------------------------------------------------------------------------------- /src/img/icons/common/common-swapcolors-arrow-grey@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-swapcolors-arrow-grey@2x.png -------------------------------------------------------------------------------- /src/img/icons/common/common-warning-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-warning-red.png -------------------------------------------------------------------------------- /src/img/icons/common/common-warning-red@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/common/common-warning-red@2x.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-dragndrop-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-dragndrop-white.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-dragndrop-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-dragndrop-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-duplicate-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-duplicate-white.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-duplicate-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-duplicate-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-plus-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-plus-white.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-plus-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-plus-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-recyclebin-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-recyclebin-white.png -------------------------------------------------------------------------------- /src/img/icons/frame/frame-recyclebin-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/frame/frame-recyclebin-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-grid-gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-grid-gold.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-grid-gold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-grid-gold@2x.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-grid-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-grid-white.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-grid-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-grid-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-popup-preview-arrow-gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-popup-preview-arrow-gold.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-popup-preview-arrow-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-popup-preview-arrow-white.png -------------------------------------------------------------------------------- /src/img/icons/minimap/minimap-popup-preview-arrow-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/minimap/minimap-popup-preview-arrow-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-export-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-export-white.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-export-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-export-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-gear-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-gear-white.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-gear-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-gear-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-open-folder-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-open-folder-white.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-open-folder-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-open-folder-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-resize-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-resize-white.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-resize-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-resize-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-save-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-save-white.png -------------------------------------------------------------------------------- /src/img/icons/settings/settings-save-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/settings/settings-save-white@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-circle.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-circle@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-colorpicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-colorpicker.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-colorpicker@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-colorpicker@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-colorswap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-colorswap.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-colorswap@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-colorswap@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-dithering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-dithering.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-dithering@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-dithering@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-eraser.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-eraser@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-eraser@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-lasso-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-lasso-select.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-lasso-select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-lasso-select@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-lighten.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-lighten@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-lighten@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-move.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-move@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-move@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-paint-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-paint-bucket.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-paint-bucket@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-paint-bucket@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-pen.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-pen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-pen@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-rectangle-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-rectangle-select.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-rectangle-select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-rectangle-select@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-rectangle.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-rectangle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-rectangle@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-shape-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-shape-select.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-shape-select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-shape-select@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-stroke.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-stroke@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-stroke@2x.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-vertical-mirror-pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-vertical-mirror-pen.png -------------------------------------------------------------------------------- /src/img/icons/tools/tool-vertical-mirror-pen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/tools/tool-vertical-mirror-pen@2x.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-center.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-center@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-center@2x.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-clone.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-clone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-clone@2x.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-crop.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-crop@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-crop@2x.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-flip.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-flip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-flip@2x.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-rotate.png -------------------------------------------------------------------------------- /src/img/icons/transform/tool-rotate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/icons/transform/tool-rotate@2x.png -------------------------------------------------------------------------------- /src/img/unused/circle-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/circle-dark.png -------------------------------------------------------------------------------- /src/img/unused/eraser-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/eraser-dark.png -------------------------------------------------------------------------------- /src/img/unused/eyedropper-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/eyedropper-dark.png -------------------------------------------------------------------------------- /src/img/unused/gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/gallery.png -------------------------------------------------------------------------------- /src/img/unused/hand-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/hand-dark.png -------------------------------------------------------------------------------- /src/img/unused/lasso-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/lasso-dark.png -------------------------------------------------------------------------------- /src/img/unused/magicwand-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/magicwand-dark.png -------------------------------------------------------------------------------- /src/img/unused/paintbucket-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/paintbucket-dark.png -------------------------------------------------------------------------------- /src/img/unused/pen-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/pen-dark.png -------------------------------------------------------------------------------- /src/img/unused/rectangle-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/rectangle-dark.png -------------------------------------------------------------------------------- /src/img/unused/rectangle_selection-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/img/unused/rectangle_selection-dark.png -------------------------------------------------------------------------------- /src/js/controller/CanvasBackgroundController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller'); 3 | 4 | ns.CanvasBackgroundController = function () { 5 | this.body = document.body; 6 | }; 7 | 8 | ns.CanvasBackgroundController.prototype.init = function () { 9 | $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this)); 10 | this.updateBackgroundClass_(pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND)); 11 | }; 12 | 13 | ns.CanvasBackgroundController.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) { 14 | if (settingName == pskl.UserSettings.CANVAS_BACKGROUND) { 15 | this.updateBackgroundClass_(settingValue); 16 | } 17 | }; 18 | 19 | ns.CanvasBackgroundController.prototype.updateBackgroundClass_ = function (newClass) { 20 | var currentClass = this.body.dataset.currentBackgroundClass; 21 | if (currentClass) { 22 | this.body.classList.remove(currentClass); 23 | } 24 | this.body.classList.add(newClass); 25 | this.body.dataset.currentBackgroundClass = newClass; 26 | }; 27 | })(); 28 | -------------------------------------------------------------------------------- /src/js/controller/NotificationController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller'); 3 | 4 | ns.NotificationController = function () {}; 5 | 6 | /** 7 | * @public 8 | */ 9 | ns.NotificationController.prototype.init = function() { 10 | $.subscribe(Events.SHOW_NOTIFICATION, this.displayMessage_.bind(this)); 11 | $.subscribe(Events.HIDE_NOTIFICATION, this.removeMessage_.bind(this)); 12 | }; 13 | 14 | /** 15 | * @private 16 | */ 17 | ns.NotificationController.prototype.displayMessage_ = function (evt, messageInfo) { 18 | this.removeMessage_(); 19 | 20 | var message = document.createElement('div'); 21 | message.id = 'user-message'; 22 | message.className = 'user-message'; 23 | message.innerHTML = messageInfo.content; 24 | message.innerHTML = message.innerHTML + '
x
'; 25 | document.body.appendChild(message); 26 | 27 | message.querySelector('.close').addEventListener('click', this.removeMessage_.bind(this)); 28 | 29 | if (messageInfo.hideDelay) { 30 | window.setTimeout(this.removeMessage_.bind(this), messageInfo.hideDelay); 31 | } 32 | }; 33 | 34 | /** 35 | * @private 36 | */ 37 | ns.NotificationController.prototype.removeMessage_ = function (evt) { 38 | var message = document.querySelector('#user-message'); 39 | if (message) { 40 | message.parentNode.removeChild(message); 41 | } 42 | }; 43 | })(); 44 | -------------------------------------------------------------------------------- /src/js/controller/PenSizeController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller'); 3 | 4 | ns.PenSizeController = function () { 5 | this.sizePicker = new pskl.widgets.SizePicker(this.onSizePickerChanged_.bind(this)); 6 | }; 7 | 8 | ns.PenSizeController.prototype.init = function () { 9 | this.sizePicker.init(document.querySelector('.pen-size-container')); 10 | 11 | $.subscribe(Events.PEN_SIZE_CHANGED, this.onPenSizeChanged_.bind(this)); 12 | this.updateSelectedOption_(); 13 | }; 14 | 15 | ns.PenSizeController.prototype.onSizePickerChanged_ = function (size) { 16 | pskl.app.penSizeService.setPenSize(size); 17 | }; 18 | 19 | ns.PenSizeController.prototype.onPenSizeChanged_ = function (e) { 20 | this.updateSelectedOption_(); 21 | }; 22 | 23 | ns.PenSizeController.prototype.updateSelectedOption_ = function () { 24 | var size = pskl.app.penSizeService.getPenSize(); 25 | this.sizePicker.setSize(size); 26 | }; 27 | })(); 28 | -------------------------------------------------------------------------------- /src/js/controller/dialogs/AbstractDialogController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.dialogs'); 3 | 4 | ns.AbstractDialogController = function () {}; 5 | 6 | ns.AbstractDialogController.prototype.init = function () { 7 | var closeButton = document.querySelector('.dialog-close'); 8 | this.addEventListener(closeButton, 'click', this.closeDialog); 9 | }; 10 | 11 | ns.AbstractDialogController.prototype.addEventListener = function (el, type, cb) { 12 | pskl.utils.Event.addEventListener(el, type, cb, this); 13 | }; 14 | 15 | ns.AbstractDialogController.prototype.destroy = function () { 16 | pskl.utils.Event.removeAllEventListeners(this); 17 | }; 18 | 19 | ns.AbstractDialogController.prototype.closeDialog = function () { 20 | $.publish(Events.DIALOG_HIDE); 21 | }; 22 | 23 | ns.AbstractDialogController.prototype.setTitle = function (title) { 24 | var dialogTitle = document.querySelector('.dialog-title'); 25 | if (dialogTitle) { 26 | dialogTitle.innerText = title; 27 | } 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /src/js/controller/dialogs/PerformanceInfoController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.dialogs'); 3 | 4 | ns.PerformanceInfoController = function () {}; 5 | 6 | pskl.utils.inherit(ns.PerformanceInfoController, ns.AbstractDialogController); 7 | 8 | ns.PerformanceInfoController.prototype.init = function () { 9 | this.superclass.init.call(this); 10 | }; 11 | })(); 12 | -------------------------------------------------------------------------------- /src/js/controller/dialogs/UnsupportedBrowserController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.dialogs'); 3 | 4 | ns.UnsupportedBrowserController = function () {}; 5 | 6 | pskl.utils.inherit(ns.UnsupportedBrowserController, ns.AbstractDialogController); 7 | 8 | ns.UnsupportedBrowserController.prototype.init = function () { 9 | this.superclass.init.call(this); 10 | var currentUserAgentElement = document.querySelector('#current-user-agent'); 11 | currentUserAgentElement.innerText = pskl.utils.UserAgent.getDisplayName(); 12 | }; 13 | })(); 14 | -------------------------------------------------------------------------------- /src/js/controller/dialogs/importwizard/steps/SelectMode.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.dialogs.importwizard.steps'); 3 | 4 | ns.SelectMode = function (piskelController, importController, container) { 5 | this.superclass.constructor.apply(this, arguments); 6 | }; 7 | 8 | ns.SelectMode.MODES = { 9 | REPLACE : 'replace', 10 | MERGE : 'merge' 11 | }; 12 | 13 | pskl.utils.inherit(ns.SelectMode, ns.AbstractImportStep); 14 | 15 | ns.SelectMode.prototype.init = function () { 16 | this.superclass.init.call(this); 17 | 18 | var replaceButton = this.container.querySelector('.import-mode-replace-button'); 19 | var mergeButton = this.container.querySelector('.import-mode-merge-button'); 20 | 21 | this.addEventListener(replaceButton, 'click', this.onReplaceButtonClick_); 22 | this.addEventListener(mergeButton, 'click', this.onMergeButtonClick_); 23 | }; 24 | 25 | ns.SelectMode.prototype.onShow = function () { 26 | this.superclass.onShow.call(this); 27 | }; 28 | 29 | ns.SelectMode.prototype.destroy = function () { 30 | this.superclass.destroy.call(this); 31 | }; 32 | 33 | ns.SelectMode.prototype.onReplaceButtonClick_ = function () { 34 | this.mergeData.importMode = ns.SelectMode.MODES.REPLACE; 35 | this.onNextClick(); 36 | }; 37 | 38 | ns.SelectMode.prototype.onMergeButtonClick_ = function () { 39 | this.mergeData.importMode = ns.SelectMode.MODES.MERGE; 40 | this.onNextClick(); 41 | }; 42 | })(); 43 | -------------------------------------------------------------------------------- /src/js/controller/settings/AbstractSettingController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.settings'); 3 | ns.AbstractSettingController = function () {}; 4 | 5 | ns.AbstractSettingController.prototype.addEventListener = function (el, type, callback) { 6 | pskl.utils.Event.addEventListener(el, type, callback, this); 7 | }; 8 | 9 | ns.AbstractSettingController.prototype.destroy = function () { 10 | pskl.utils.Event.removeAllEventListeners(this); 11 | this.nullifyDomReferences_(); 12 | }; 13 | 14 | ns.AbstractSettingController.prototype.nullifyDomReferences_ = function () { 15 | for (var key in this) { 16 | if (this.hasOwnProperty(key)) { 17 | var isHTMLElement = this[key] && this[key].nodeName; 18 | if (isHTMLElement) { 19 | this[key] = null; 20 | } 21 | } 22 | } 23 | }; 24 | })(); 25 | -------------------------------------------------------------------------------- /src/js/controller/settings/PreferencesController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.controller.settings'); 3 | 4 | var tabs = { 5 | 'misc' : { 6 | template : 'templates/settings/preferences/misc.html', 7 | controller : ns.preferences.MiscPreferencesController 8 | }, 9 | 'grid' : { 10 | template : 'templates/settings/preferences/grid.html', 11 | controller : ns.preferences.GridPreferencesController 12 | }, 13 | 'tile' : { 14 | template : 'templates/settings/preferences/tile.html', 15 | controller : ns.preferences.TilePreferencesController 16 | } 17 | }; 18 | 19 | ns.PreferencesController = function () { 20 | this.tabsWidget = new pskl.widgets.Tabs(tabs, this, pskl.UserSettings.PREFERENCES_TAB); 21 | }; 22 | 23 | pskl.utils.inherit(ns.PreferencesController, pskl.controller.settings.AbstractSettingController); 24 | 25 | ns.PreferencesController.prototype.init = function() { 26 | var container = document.querySelector('.settings-section-preferences'); 27 | this.tabsWidget.init(container); 28 | }; 29 | 30 | ns.PreferencesController.prototype.destroy = function () { 31 | this.tabsWidget.destroy(); 32 | this.superclass.destroy.call(this); 33 | }; 34 | 35 | })(); 36 | -------------------------------------------------------------------------------- /src/js/devtools/DrawingTestRunner.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.devtools'); 3 | 4 | ns.DrawingTestRunner = function (testName) { 5 | this.testName = testName; 6 | $.subscribe(Events.TEST_RECORD_END, this.onTestRecordEnd_.bind(this)); 7 | }; 8 | 9 | ns.DrawingTestRunner.prototype.start = function () { 10 | pskl.utils.Xhr.get(this.testName, function (response) { 11 | var res = response.responseText; 12 | var recordPlayer = new ns.DrawingTestPlayer(JSON.parse(res)); 13 | recordPlayer.start(); 14 | }.bind(this)); 15 | }; 16 | 17 | ns.DrawingTestRunner.prototype.onTestRecordEnd_ = function (evt, success) { 18 | var testResult = document.createElement('div'); 19 | testResult.id = 'drawing-test-result'; 20 | testResult.setAttribute('data-test-name', this.testName); 21 | testResult.innerHTML = success ? 'OK' : 'KO'; 22 | document.body.appendChild(testResult); 23 | }; 24 | })(); 25 | -------------------------------------------------------------------------------- /src/js/devtools/MouseEvent.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.devtools'); 3 | 4 | ns.MouseEvent = function (event, coords) { 5 | this.event = { 6 | type : event.type, 7 | button : event.button, 8 | shiftKey : event.shiftKey, 9 | altKey : event.altKey, 10 | ctrlKey : event.ctrlKey 11 | }; 12 | this.coords = coords; 13 | this.type = 'mouse-event'; 14 | }; 15 | 16 | ns.MouseEvent.prototype.equals = function (otherEvent) { 17 | if (otherEvent && otherEvent instanceof ns.MouseEvent) { 18 | var sameEvent = JSON.stringify(otherEvent.event) == JSON.stringify(this.event); 19 | var sameCoords = JSON.stringify(otherEvent.coords) == JSON.stringify(this.coords); 20 | return sameEvent && sameCoords; 21 | } else { 22 | return false; 23 | } 24 | }; 25 | })(); 26 | -------------------------------------------------------------------------------- /src/js/devtools/init.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.devtools'); 3 | 4 | ns.init = function () { 5 | var href = document.location.href.toLowerCase(); 6 | // test tools 7 | var testModeOn = href.indexOf('test=true') !== -1; 8 | if (testModeOn) { 9 | this.testRecorder = new pskl.devtools.DrawingTestRecorder(pskl.app.piskelController); 10 | this.testRecorder.init(); 11 | 12 | this.testRecordController = new pskl.devtools.TestRecordController(this.testRecorder); 13 | this.testRecordController.init(); 14 | } 15 | 16 | // test tools 17 | var runTestModeOn = href.indexOf('test-run=') !== -1; 18 | if (runTestModeOn) { 19 | var testPath = href.split('test-run=')[1]; 20 | this.testRunner = new pskl.devtools.DrawingTestRunner(testPath); 21 | this.testRunner.start(); 22 | } 23 | 24 | // test tools 25 | var runSuiteModeOn = href.indexOf('test-suite=') !== -1; 26 | if (runSuiteModeOn) { 27 | var suitePath = href.split('test-suite=')[1]; 28 | this.testSuiteController = new pskl.devtools.DrawingTestSuiteController(suitePath); 29 | this.testSuiteController.init(); 30 | this.testSuiteController.start(); 31 | } 32 | }; 33 | 34 | })(); 35 | -------------------------------------------------------------------------------- /src/js/lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/js/lib/.DS_Store -------------------------------------------------------------------------------- /src/js/lib/bootstrap/readme.txt: -------------------------------------------------------------------------------- 1 | Bootstrap custom build containing only the tooltip component -------------------------------------------------------------------------------- /src/js/lib/pubsub.js: -------------------------------------------------------------------------------- 1 | /* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011 2 | * http://benalman.com/ 3 | * Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */ 4 | 5 | (function($) { 6 | 7 | var o = $({}); 8 | 9 | $.subscribe = function() { 10 | //console.log("SUBSCRIBE: " + arguments[0]); 11 | o.on.apply(o, arguments); 12 | }; 13 | 14 | $.unsubscribe = function() { 15 | o.off.apply(o, arguments); 16 | }; 17 | 18 | $.publish = function() { 19 | //console.log("PUBLISH: " + arguments[0]); 20 | o.trigger.apply(o, arguments); 21 | }; 22 | 23 | }(jQuery)); 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/js/lib/scrollifneeded/scrollifneeded.js: -------------------------------------------------------------------------------- 1 | if (!Element.prototype.scrollIntoViewIfNeeded) { 2 | Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { 3 | centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded; 4 | 5 | var parent = this.parentNode, 6 | parentComputedStyle = window.getComputedStyle(parent, null), 7 | parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')), 8 | parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')), 9 | overTop = this.offsetTop - parent.offsetTop < parent.scrollTop, 10 | overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight), 11 | overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft, 12 | overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth), 13 | alignWithTop = overTop && !overBottom; 14 | 15 | if ((overTop || overBottom) && centerIfNeeded) { 16 | parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2; 17 | } 18 | 19 | if ((overLeft || overRight) && centerIfNeeded) { 20 | parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2; 21 | } 22 | 23 | if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) { 24 | this.scrollIntoView(alignWithTop); 25 | } 26 | }; 27 | } -------------------------------------------------------------------------------- /src/js/lib/smoothscroll/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Dustan Kasten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/js/model/Palette.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.model'); 3 | 4 | ns.Palette = function (id, name, colors) { 5 | this.id = id; 6 | this.name = name; 7 | this.colors = colors; 8 | }; 9 | 10 | ns.Palette.fromObject = function (paletteObj) { 11 | var colors = paletteObj.colors.slice(0 , paletteObj.colors.length); 12 | return new ns.Palette(paletteObj.id, paletteObj.name, colors); 13 | }; 14 | 15 | ns.Palette.prototype.getColors = function () { 16 | return this.colors; 17 | }; 18 | 19 | ns.Palette.prototype.setColors = function (colors) { 20 | this.colors = colors; 21 | }; 22 | 23 | ns.Palette.prototype.get = function (index) { 24 | return this.colors[index]; 25 | }; 26 | 27 | ns.Palette.prototype.set = function (index, color) { 28 | this.colors[index] = color; 29 | }; 30 | 31 | ns.Palette.prototype.add = function (color) { 32 | this.colors.push(color); 33 | }; 34 | 35 | ns.Palette.prototype.size = function () { 36 | return this.colors.length; 37 | }; 38 | 39 | ns.Palette.prototype.removeAt = function (index) { 40 | this.colors.splice(index, 1); 41 | }; 42 | 43 | ns.Palette.prototype.move = function (oldIndex, newIndex) { 44 | this.colors.splice(newIndex, 0, this.colors.splice(oldIndex, 1)[0]); 45 | }; 46 | })(); 47 | -------------------------------------------------------------------------------- /src/js/model/frame/AsyncCachedFrameProcessor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.model.frame'); 3 | 4 | ns.AsyncCachedFrameProcessor = function (cacheResetInterval) { 5 | ns.CachedFrameProcessor.call(this, cacheResetInterval); 6 | }; 7 | 8 | pskl.utils.inherit(ns.AsyncCachedFrameProcessor, ns.CachedFrameProcessor); 9 | 10 | /** 11 | * Retrieve the processed frame from the cache, in the (optional) namespace 12 | * If the first level cache is empty, attempt to clone it from 2nd level cache. 13 | * If second level cache is empty process the frame. 14 | * @param {pskl.model.Frame} frame 15 | * @param {String} namespace 16 | * @return {Object} the processed frame 17 | */ 18 | ns.AsyncCachedFrameProcessor.prototype.get = function (frame, namespace) { 19 | var processedFrame = null; 20 | namespace = namespace || this.defaultNamespace; 21 | 22 | if (!this.cache_[namespace]) { 23 | this.cache_[namespace] = {}; 24 | } 25 | 26 | var deferred = Q.defer(); 27 | 28 | var cache = this.cache_[namespace]; 29 | 30 | var key1 = frame.getHash(); 31 | if (cache[key1]) { 32 | processedFrame = cache[key1]; 33 | } else { 34 | var callback = this.onProcessorComplete_.bind(this, deferred, cache, key1); 35 | this.frameProcessor(frame, callback); 36 | } 37 | 38 | if (processedFrame) { 39 | deferred.resolve(processedFrame); 40 | } 41 | 42 | return deferred.promise; 43 | }; 44 | 45 | ns.AsyncCachedFrameProcessor.prototype.onProcessorComplete_ = function (deferred, cache, key1, result) { 46 | cache[key1] = result; 47 | deferred.resolve(result); 48 | }; 49 | })(); 50 | -------------------------------------------------------------------------------- /src/js/model/piskel/Descriptor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.model.piskel'); 3 | 4 | ns.Descriptor = function (name, description, isPublic) { 5 | this.name = name; 6 | this.description = description; 7 | this.isPublic = isPublic; 8 | }; 9 | })(); 10 | -------------------------------------------------------------------------------- /src/js/rendering/AbstractRenderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.rendering'); 3 | 4 | ns.AbstractRenderer = function () {}; 5 | 6 | ns.AbstractRenderer.prototype.clear = Constants.ABSTRACT_FUNCTION; 7 | ns.AbstractRenderer.prototype.render = Constants.ABSTRACT_FUNCTION; 8 | 9 | ns.AbstractRenderer.prototype.getCoordinates = Constants.ABSTRACT_FUNCTION; 10 | 11 | ns.AbstractRenderer.prototype.setGridWidth = Constants.ABSTRACT_FUNCTION; 12 | ns.AbstractRenderer.prototype.getGridWidth = Constants.ABSTRACT_FUNCTION; 13 | 14 | ns.AbstractRenderer.prototype.setZoom = Constants.ABSTRACT_FUNCTION; 15 | ns.AbstractRenderer.prototype.getZoom = Constants.ABSTRACT_FUNCTION; 16 | 17 | ns.AbstractRenderer.prototype.setOffset = Constants.ABSTRACT_FUNCTION; 18 | ns.AbstractRenderer.prototype.getOffset = Constants.ABSTRACT_FUNCTION; 19 | 20 | ns.AbstractRenderer.prototype.setDisplaySize = Constants.ABSTRACT_FUNCTION; 21 | ns.AbstractRenderer.prototype.getDisplaySize = Constants.ABSTRACT_FUNCTION; 22 | })(); 23 | -------------------------------------------------------------------------------- /src/js/rendering/CanvasRenderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var ns = $.namespace('pskl.rendering'); 4 | ns.CanvasRenderer = function (frame, zoom) { 5 | this.frame = frame; 6 | this.zoom = zoom; 7 | this.opacity_ = 1; 8 | this.transparentColor_ = 'white'; 9 | }; 10 | 11 | /** 12 | * Decide which color should be used to represent transparent pixels 13 | * Default : white 14 | * @param {String} color the color to use either as '#ABCDEF' or 'red' or 'rgb(x,y,z)' or 'rgba(x,y,z,a)' 15 | */ 16 | ns.CanvasRenderer.prototype.drawTransparentAs = function (color) { 17 | this.transparentColor_ = color; 18 | }; 19 | 20 | ns.CanvasRenderer.prototype.setOpacity = function (opacity) { 21 | this.opacity_ = opacity; 22 | }; 23 | 24 | ns.CanvasRenderer.prototype.render = function () { 25 | var canvas = this.createCanvas_(); 26 | 27 | // Draw in canvas 28 | pskl.utils.FrameUtils.drawToCanvas(this.frame, canvas, this.transparentColor_, this.opacity_); 29 | 30 | var scaledCanvas = this.createCanvas_(this.zoom); 31 | var scaledContext = scaledCanvas.getContext('2d'); 32 | pskl.utils.CanvasUtils.disableImageSmoothing(scaledCanvas); 33 | scaledContext.scale(this.zoom, this.zoom); 34 | scaledContext.drawImage(canvas, 0, 0); 35 | 36 | return scaledCanvas; 37 | }; 38 | 39 | ns.CanvasRenderer.prototype.createCanvas_ = function (zoom) { 40 | zoom = zoom || 1; 41 | var width = this.frame.getWidth() * zoom; 42 | var height = this.frame.getHeight() * zoom; 43 | return pskl.utils.CanvasUtils.createCanvas(width, height); 44 | }; 45 | })(); 46 | -------------------------------------------------------------------------------- /src/js/rendering/FramesheetRenderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.rendering'); 3 | 4 | /** 5 | * Render an array of frames 6 | * @param {Array.} frames 7 | */ 8 | ns.FramesheetRenderer = function (frames) { 9 | if (frames.length > 0) { 10 | this.frames = frames; 11 | } else { 12 | throw 'FramesheetRenderer : Invalid argument : frames is empty'; 13 | } 14 | }; 15 | 16 | ns.FramesheetRenderer.prototype.renderAsCanvas = function (columns) { 17 | columns = columns || this.frames.length; 18 | var rows = Math.ceil(this.frames.length / columns); 19 | 20 | var canvas = this.createCanvas_(columns, rows); 21 | 22 | for (var i = 0 ; i < this.frames.length ; i++) { 23 | var frame = this.frames[i]; 24 | var posX = (i % columns) * frame.getWidth(); 25 | var posY = Math.floor(i / columns) * frame.getHeight(); 26 | this.drawFrameInCanvas_(frame, canvas, posX, posY); 27 | } 28 | return canvas; 29 | }; 30 | 31 | ns.FramesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) { 32 | var context = canvas.getContext('2d'); 33 | var imageData = context.createImageData(frame.getWidth(), frame.getHeight()); 34 | var pixels = frame.getPixels(); 35 | var data = new Uint8ClampedArray(pixels.buffer); 36 | imageData.data.set(data); 37 | context.putImageData(imageData, offsetWidth, offsetHeight); 38 | }; 39 | 40 | ns.FramesheetRenderer.prototype.createCanvas_ = function (columns, rows) { 41 | var sampleFrame = this.frames[0]; 42 | var width = columns * sampleFrame.getWidth(); 43 | var height = rows * sampleFrame.getHeight(); 44 | return pskl.utils.CanvasUtils.createCanvas(width, height); 45 | }; 46 | })(); 47 | -------------------------------------------------------------------------------- /src/js/rendering/PiskelRenderer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var ns = $.namespace('pskl.rendering'); 4 | 5 | ns.PiskelRenderer = function (piskelController) { 6 | var frames = []; 7 | for (var i = 0 ; i < piskelController.getFrameCount() ; i++) { 8 | frames.push(piskelController.renderFrameAt(i, true)); 9 | } 10 | this.piskelController = piskelController; 11 | this.frames = frames; 12 | }; 13 | 14 | ns.PiskelRenderer.prototype.renderAsCanvas = function (columns) { 15 | columns = columns || this.frames.length; 16 | var rows = Math.ceil(this.frames.length / columns); 17 | 18 | var canvas = this.createCanvas_(columns, rows); 19 | 20 | for (var i = 0 ; i < this.frames.length ; i++) { 21 | var frame = this.frames[i]; 22 | var posX = (i % columns) * this.piskelController.getWidth(); 23 | var posY = Math.floor(i / columns) * this.piskelController.getHeight(); 24 | this.drawFrameInCanvas_(frame, canvas, posX, posY); 25 | } 26 | return canvas; 27 | }; 28 | 29 | ns.PiskelRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) { 30 | var context = canvas.getContext('2d'); 31 | context.drawImage(frame, offsetWidth, offsetHeight, frame.width, frame.height); 32 | }; 33 | 34 | ns.PiskelRenderer.prototype.createCanvas_ = function (columns, rows) { 35 | var width = columns * this.piskelController.getWidth(); 36 | var height = rows * this.piskelController.getHeight(); 37 | return pskl.utils.CanvasUtils.createCanvas(width, height); 38 | }; 39 | })(); 40 | -------------------------------------------------------------------------------- /src/js/selection/BaseSelection.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.selection'); 3 | 4 | ns.BaseSelection = function () { 5 | this.reset(); 6 | }; 7 | 8 | ns.BaseSelection.prototype.stringify = function () { 9 | return JSON.stringify({ 10 | pixels: this.pixels, 11 | time: this.time 12 | }); 13 | }; 14 | 15 | ns.BaseSelection.prototype.parse = function (str) { 16 | var selectionData = JSON.parse(str); 17 | this.pixels = selectionData.pixels; 18 | this.time = selectionData.time; 19 | }; 20 | 21 | ns.BaseSelection.prototype.reset = function () { 22 | this.pixels = []; 23 | this.hasPastedContent = false; 24 | this.time = -1; 25 | }; 26 | 27 | ns.BaseSelection.prototype.move = function (colDiff, rowDiff) { 28 | var movedPixels = []; 29 | 30 | for (var i = 0, l = this.pixels.length; i < l; i++) { 31 | var movedPixel = this.pixels[i]; 32 | movedPixel.col += colDiff; 33 | movedPixel.row += rowDiff; 34 | movedPixels.push(movedPixel); 35 | } 36 | 37 | this.pixels = movedPixels; 38 | }; 39 | 40 | ns.BaseSelection.prototype.fillSelectionFromFrame = function (targetFrame) { 41 | this.pixels.forEach(function (pixel) { 42 | var color = targetFrame.getPixel(pixel.col, pixel.row); 43 | pixel.color = color || Constants.TRANSPARENT_COLOR; 44 | }); 45 | 46 | this.hasPastedContent = true; 47 | // Keep track of the selection time to compare between local selection and 48 | // paste event selections. 49 | this.time = Date.now(); 50 | }; 51 | })(); 52 | -------------------------------------------------------------------------------- /src/js/selection/RectangularSelection.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.selection'); 3 | 4 | ns.RectangularSelection = function (x0, y0, x1, y1) { 5 | this.pixels = pskl.PixelUtils.getRectanglePixels(x0, y0, x1, y1); 6 | }; 7 | 8 | pskl.utils.inherit(ns.RectangularSelection, ns.BaseSelection); 9 | })(); 10 | -------------------------------------------------------------------------------- /src/js/selection/ShapeSelection.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.selection'); 3 | 4 | ns.ShapeSelection = function (pixels) { 5 | this.pixels = pixels; 6 | }; 7 | 8 | pskl.utils.inherit(ns.ShapeSelection, ns.BaseSelection); 9 | })(); 10 | -------------------------------------------------------------------------------- /src/js/service/BeforeUnloadService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | 4 | ns.BeforeUnloadService = function (piskelController) { 5 | this.piskelController = piskelController; 6 | }; 7 | 8 | ns.BeforeUnloadService.prototype.init = function () { 9 | if (pskl.utils.Environment.detectNodeWebkit()) { 10 | // Add a dedicated listener to window 'close' event in nwjs environment. 11 | var win = require('nw.gui').Window.get(); 12 | win.on('close', this.onNwWindowClose.bind(this, win)); 13 | } 14 | 15 | window.addEventListener('beforeunload', this.onBeforeUnload.bind(this)); 16 | }; 17 | 18 | /** 19 | * In nw.js environment "onbeforeunload" is not triggered when closing the window. 20 | * Polyfill the behavior here. 21 | */ 22 | ns.BeforeUnloadService.prototype.onNwWindowClose = function (win) { 23 | var msg = this.onBeforeUnload(); 24 | if (msg) { 25 | if (!window.confirm(msg)) { 26 | return false; 27 | } 28 | } 29 | win.close(true); 30 | }; 31 | 32 | ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) { 33 | // Attempt one last backup. Some of it may fail due to the asynchronous 34 | // nature of IndexedDB. 35 | pskl.app.backupService.backup(); 36 | if (pskl.app.savedStatusService.isDirty()) { 37 | var confirmationMessage = 'Your current sprite has unsaved changes. Are you sure you want to quit?'; 38 | 39 | evt = evt || window.event; 40 | if (evt) { 41 | evt.returnValue = confirmationMessage; 42 | } 43 | return confirmationMessage; 44 | } 45 | }; 46 | 47 | })(); 48 | -------------------------------------------------------------------------------- /src/js/service/ClipboardService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | 4 | ns.ClipboardService = function (piskelController) { 5 | this.piskelController = piskelController; 6 | }; 7 | 8 | ns.ClipboardService.prototype.init = function () { 9 | window.addEventListener('copy', this._onCopy.bind(this), true); 10 | window.addEventListener('cut', this._onCut.bind(this), true); 11 | window.addEventListener('paste', this._onPaste.bind(this), true); 12 | }; 13 | 14 | ns.ClipboardService.prototype._onCut = function (event) { 15 | $.publish(Events.CLIPBOARD_CUT, event); 16 | }; 17 | 18 | ns.ClipboardService.prototype._onCopy = function (event) { 19 | $.publish(Events.CLIPBOARD_COPY, event); 20 | }; 21 | 22 | ns.ClipboardService.prototype._onPaste = function (event) { 23 | $.publish(Events.CLIPBOARD_PASTE, event); 24 | }; 25 | })(); 26 | -------------------------------------------------------------------------------- /src/js/service/ImageUploadService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | ns.ImageUploadService = function () {}; 4 | ns.ImageUploadService.prototype.init = function () {}; 5 | 6 | /** 7 | * Upload a base64 image data to distant service. 8 | * If successful, will call provided callback with the image URL as first argument; 9 | * @param {String} imageData base64 image data (such as the return value of canvas.toDataUrl()) 10 | * @param {Function} success success callback. 1st argument will be the uploaded image URL 11 | * @param {Function} error error callback 12 | */ 13 | ns.ImageUploadService.prototype.upload = function (imageData, success, error) { 14 | var data = { 15 | data : imageData 16 | }; 17 | 18 | var protocol = pskl.utils.Environment.isHttps() ? 'https' : 'http'; 19 | var wrappedSuccess = function (response) { 20 | var getUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_GET_URL, { 21 | protocol: protocol 22 | }); 23 | success(getUrl + response.responseText); 24 | }; 25 | 26 | var uploadUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_UPLOAD_URL, { 27 | protocol: protocol 28 | }); 29 | pskl.utils.Xhr.post(uploadUrl, data, wrappedSuccess, error); 30 | }; 31 | })(); 32 | -------------------------------------------------------------------------------- /src/js/service/MouseStateService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | 4 | var BUTTON_UNSET = null; 5 | 6 | /** 7 | * This service exists mostly due to a FF/IE bug. 8 | * For mousemove events, the button type is set to 0 (e.g. left button type) whatever was the 9 | * pressed button on mousedown. We use this service to cache the button type value on mousedown 10 | * and make it available to mousemove events. 11 | */ 12 | ns.MouseStateService = function () { 13 | this.lastButtonPressed_ = BUTTON_UNSET; 14 | }; 15 | 16 | ns.MouseStateService.prototype.init = function () { 17 | $.subscribe(Events.MOUSE_EVENT, this.onMouseEvent_.bind(this)); 18 | }; 19 | 20 | ns.MouseStateService.prototype.isLeftButtonPressed = function () { 21 | return this.isMouseButtonPressed_(Constants.LEFT_BUTTON); 22 | }; 23 | 24 | ns.MouseStateService.prototype.isRightButtonPressed = function () { 25 | return this.isMouseButtonPressed_(Constants.RIGHT_BUTTON); 26 | }; 27 | 28 | ns.MouseStateService.prototype.isMiddleButtonPressed = function () { 29 | return this.isMouseButtonPressed_(Constants.MIDDLE_BUTTON); 30 | }; 31 | 32 | ns.MouseStateService.prototype.isMouseButtonPressed_ = function (mouseButton) { 33 | return this.lastButtonPressed_ != BUTTON_UNSET && this.lastButtonPressed_ == mouseButton; 34 | }; 35 | 36 | ns.MouseStateService.prototype.onMouseEvent_ = function(evt, mouseEvent) { 37 | if (mouseEvent.type == 'mousedown') { 38 | this.lastButtonPressed_ = mouseEvent.button; 39 | } else if (mouseEvent.type == 'mouseup') { 40 | this.lastButtonPressed_ = BUTTON_UNSET; 41 | } 42 | }; 43 | })(); 44 | -------------------------------------------------------------------------------- /src/js/service/SavedStatusService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | 4 | ns.SavedStatusService = function (piskelController, historyService) { 5 | this.piskelController = piskelController; 6 | this.historyService = historyService; 7 | this.lastSavedStateIndex = ''; 8 | 9 | this.publishStatusUpdateEvent_ = this.publishStatusUpdateEvent_.bind(this); 10 | }; 11 | 12 | ns.SavedStatusService.prototype.init = function () { 13 | $.subscribe(Events.TOOL_RELEASED, this.publishStatusUpdateEvent_); 14 | $.subscribe(Events.PISKEL_RESET, this.publishStatusUpdateEvent_); 15 | $.subscribe(Events.PISKEL_SAVED, this.onPiskelSaved.bind(this)); 16 | this.lastSavedStateIndex = this.historyService.getCurrentStateId(); 17 | }; 18 | 19 | ns.SavedStatusService.prototype.onPiskelSaved = function () { 20 | this.lastSavedStateIndex = this.historyService.getCurrentStateId(); 21 | this.publishStatusUpdateEvent_(); 22 | }; 23 | 24 | ns.SavedStatusService.prototype.publishStatusUpdateEvent_ = function () { 25 | $.publish(Events.PISKEL_SAVED_STATUS_UPDATE); 26 | }; 27 | 28 | ns.SavedStatusService.prototype.isDirty = function () { 29 | return (this.lastSavedStateIndex != this.historyService.getCurrentStateId()); 30 | }; 31 | })(); 32 | -------------------------------------------------------------------------------- /src/js/service/SelectedColorsService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service'); 3 | 4 | ns.SelectedColorsService = function () { 5 | this.primaryColor_ = Constants.DEFAULT_PEN_COLOR; 6 | this.secondaryColor_ = Constants.TRANSPARENT_COLOR; 7 | }; 8 | 9 | ns.SelectedColorsService.prototype.init = function () { 10 | $.subscribe(Events.PRIMARY_COLOR_SELECTED, this.onPrimaryColorUpdate_.bind(this)); 11 | $.subscribe(Events.SECONDARY_COLOR_SELECTED, this.onSecondaryColorUpdate_.bind(this)); 12 | }; 13 | 14 | ns.SelectedColorsService.prototype.getPrimaryColor = function () { 15 | return this.primaryColor_; 16 | }; 17 | 18 | ns.SelectedColorsService.prototype.getSecondaryColor = function () { 19 | return this.secondaryColor_; 20 | }; 21 | 22 | ns.SelectedColorsService.prototype.onPrimaryColorUpdate_ = function (evt, color) { 23 | this.primaryColor_ = color; 24 | }; 25 | 26 | ns.SelectedColorsService.prototype.onSecondaryColorUpdate_ = function (evt, color) { 27 | this.secondaryColor_ = color; 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /src/js/service/keyboard/KeycodeTranslator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var specialKeys = { 3 | 8 : 'back', 4 | 13 : 'enter', 5 | 27 : 'esc', 6 | 32 : 'space', 7 | 37 : 'left', 8 | 38 : 'up', 9 | 39 : 'right', 10 | 40 : 'down', 11 | 46 : 'del', 12 | 189 : '-', 13 | // 109 for numpad - 14 | 109 : '-', 15 | // 173 on Firefox for minus key 16 | 173 : '-', 17 | 187 : '+', 18 | // 107 for numpad + 19 | 107 : '+', 20 | // 61 on Firefox for =/+ key 21 | 61 : '+', 22 | 188 : '<', 23 | 190 : '>', 24 | 191 : '?', 25 | 219 : '[', 26 | 221 : ']' 27 | }; 28 | 29 | var ns = $.namespace('pskl.service.keyboard'); 30 | 31 | ns.KeycodeTranslator = { 32 | toChar : function (keycode) { 33 | if (keycode >= 48 && keycode <= 57) { 34 | // key is 0-9 35 | return (keycode - 48) + ''; 36 | } else if (keycode >= 96 && keycode <= 105) { 37 | // key is numpad 0-9 38 | return (keycode - 96) + ''; 39 | } else if (keycode >= 65 && keycode <= 90) { 40 | // key is a-z, use base 36 to get the string representation 41 | return (keycode - 65 + 10).toString(36); 42 | } else { 43 | return specialKeys[keycode]; 44 | } 45 | } 46 | }; 47 | })(); 48 | -------------------------------------------------------------------------------- /src/js/service/palette/CurrentColorsPalette.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette'); 3 | 4 | ns.CurrentColorsPalette = function () { 5 | this.name = 'Current colors'; 6 | this.id = Constants.CURRENT_COLORS_PALETTE_ID; 7 | this.colorSorter = new pskl.service.color.ColorSorter(); 8 | }; 9 | 10 | ns.CurrentColorsPalette.prototype.getColors = function () { 11 | var currentColors = pskl.app.currentColorsService.getCurrentColors(); 12 | currentColors = currentColors.slice(0, Constants.MAX_PALETTE_COLORS); 13 | return this.colorSorter.sort(currentColors); 14 | }; 15 | })(); 16 | -------------------------------------------------------------------------------- /src/js/service/palette/PaletteGplWriter.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette'); 3 | 4 | ns.PaletteGplWriter = function (palette) { 5 | this.palette = palette; 6 | }; 7 | 8 | ns.PaletteGplWriter.prototype.write = function () { 9 | var lines = []; 10 | lines.push('GIMP Palette'); 11 | lines.push('Name: ' + this.palette.name); 12 | lines.push('Columns: 0'); 13 | lines.push('#'); 14 | this.palette.getColors().forEach(function (color) { 15 | lines.push(this.writeColorLine(color)); 16 | }.bind(this)); 17 | lines.push('\r\n'); 18 | 19 | return lines.join('\r\n'); 20 | }; 21 | 22 | ns.PaletteGplWriter.prototype.writeColorLine = function (color) { 23 | var tinycolor = window.tinycolor(color); 24 | var rgb = tinycolor.toRgb(); 25 | var strBuffer = []; 26 | strBuffer.push(this.padString(rgb.r, 3)); 27 | strBuffer.push(this.padString(rgb.g, 3)); 28 | strBuffer.push(this.padString(rgb.b, 3)); 29 | strBuffer.push('Untitled'); 30 | 31 | return strBuffer.join(' '); 32 | }; 33 | 34 | ns.PaletteGplWriter.prototype.padString = function (str, size) { 35 | str = str.toString(); 36 | var pad = (new Array(1 + size - str.length)).join(' '); 37 | return pad + str; 38 | }; 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /src/js/service/palette/PaletteImportService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette'); 3 | 4 | var fileReaders = { 5 | 'gpl' : ns.reader.PaletteGplReader, 6 | 'pal' : ns.reader.PalettePalReader, 7 | 'txt' : ns.reader.PaletteTxtReader, 8 | 'img' : ns.reader.PaletteImageReader 9 | }; 10 | 11 | ns.PaletteImportService = function () {}; 12 | ns.PaletteImportService.prototype.init = function () {}; 13 | 14 | ns.PaletteImportService.prototype.read = function (file, onSuccess, onError) { 15 | var reader = this.getReader_(file, onSuccess, onError); 16 | if (reader) { 17 | reader.read(); 18 | } else { 19 | console.error('Could not find reader for file : %s', file.name); 20 | } 21 | }; 22 | 23 | ns.PaletteImportService.prototype.isImage_ = function (file) { 24 | return file.type.indexOf('image') === 0; 25 | }; 26 | 27 | ns.PaletteImportService.prototype.getReader_ = function (file, onSuccess, onError) { 28 | var ReaderClass = this.getReaderClass_(file); 29 | if (ReaderClass) { 30 | return new ReaderClass(file, onSuccess, onError); 31 | } else { 32 | return null; 33 | } 34 | }; 35 | 36 | ns.PaletteImportService.prototype.getReaderClass_ = function (file) { 37 | var ReaderClass; 38 | if (this.isImage_(file)) { 39 | ReaderClass = fileReaders.img; 40 | } else { 41 | var extension = this.getExtension_(file); 42 | ReaderClass = fileReaders[extension]; 43 | } 44 | return ReaderClass; 45 | }; 46 | 47 | ns.PaletteImportService.prototype.getExtension_ = function (file) { 48 | var parts = file.name.split('.'); 49 | var extension = parts[parts.length - 1]; 50 | return extension.toLowerCase(); 51 | }; 52 | })(); 53 | -------------------------------------------------------------------------------- /src/js/service/palette/reader/AbstractPaletteFileReader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette.reader'); 3 | 4 | ns.AbstractPaletteFileReader = function (file, onSuccess, onError, colorLineRegexp) { 5 | this.file = file; 6 | this.onSuccess = onSuccess; 7 | this.onError = onError; 8 | this.colorLineRegexp = colorLineRegexp; 9 | }; 10 | 11 | ns.AbstractPaletteFileReader.prototype.extractColorFromLine = Constants.ABSTRACT_FUNCTION; 12 | 13 | ns.AbstractPaletteFileReader.prototype.read = function () { 14 | pskl.utils.FileUtils.readFile(this.file, this.onFileLoaded_.bind(this)); 15 | }; 16 | 17 | ns.AbstractPaletteFileReader.prototype.onFileLoaded_ = function (content) { 18 | var text = pskl.utils.Base64.toText(content); 19 | var lines = text.match(/[^\r\n]+/g); 20 | 21 | var colorLines = lines.filter(function (l) { 22 | return this.colorLineRegexp.test(l); 23 | }.bind(this)); 24 | 25 | var colors = colorLines.map(this.extractColorFromLine.bind(this)); 26 | 27 | if (colors.length) { 28 | var uuid = pskl.utils.Uuid.generate(); 29 | var palette = new pskl.model.Palette(uuid, this.file.name, colors); 30 | this.onSuccess(palette); 31 | } else { 32 | this.onError(); 33 | } 34 | }; 35 | })(); 36 | -------------------------------------------------------------------------------- /src/js/service/palette/reader/PaletteGplReader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette.reader'); 3 | 4 | var RE_COLOR_LINE = /^(\s*\d{1,3})(\s*\d{1,3})(\s*\d{1,3})/; 5 | var RE_EXTRACT_NAME = /^name\s*\:\s*(.*)$/i; 6 | 7 | ns.PaletteGplReader = function (file, onSuccess, onError) { 8 | this.superclass.constructor.call(this, file, onSuccess, onError, RE_COLOR_LINE); 9 | }; 10 | 11 | pskl.utils.inherit(ns.PaletteGplReader, ns.AbstractPaletteFileReader); 12 | 13 | ns.PaletteGplReader.prototype.extractColorFromLine = function (line) { 14 | var matches = line.match(RE_COLOR_LINE); 15 | var color = window.tinycolor({ 16 | r : parseInt(matches[1], 10), 17 | g : parseInt(matches[2], 10), 18 | b : parseInt(matches[3], 10) 19 | }); 20 | 21 | return color.toHexString(); 22 | }; 23 | })(); 24 | -------------------------------------------------------------------------------- /src/js/service/palette/reader/PalettePalReader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette.reader'); 3 | 4 | var RE_COLOR_LINE = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})/; 5 | 6 | ns.PalettePalReader = function (file, onSuccess, onError) { 7 | this.superclass.constructor.call(this, file, onSuccess, onError, RE_COLOR_LINE); 8 | }; 9 | 10 | pskl.utils.inherit(ns.PalettePalReader, ns.AbstractPaletteFileReader); 11 | 12 | ns.PalettePalReader.prototype.extractColorFromLine = function (line) { 13 | var matches = line.match(RE_COLOR_LINE); 14 | var rgbColor = 'rgb(' + matches[1] + ',' + matches[2] + ',' + matches[3] + ')'; 15 | var color = window.tinycolor(rgbColor); 16 | 17 | return color.toHexString(); 18 | }; 19 | })(); 20 | -------------------------------------------------------------------------------- /src/js/service/palette/reader/PaletteTxtReader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.palette.reader'); 3 | 4 | var RE_COLOR_LINE = /^[A-F0-9]{2}([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})/; 5 | 6 | ns.PaletteTxtReader = function (file, onSuccess, onError) { 7 | this.superclass.constructor.call(this, file, onSuccess, onError, RE_COLOR_LINE); 8 | }; 9 | 10 | pskl.utils.inherit(ns.PaletteTxtReader, ns.AbstractPaletteFileReader); 11 | 12 | ns.PaletteTxtReader.prototype.extractColorFromLine = function (line) { 13 | var matches = line.match(RE_COLOR_LINE); 14 | var color = '#' + matches[1] + matches[2] + matches[3]; 15 | return color.toLowerCase(); 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /src/js/service/pensize/PenSizeService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.pensize'); 3 | 4 | var MIN_PENSIZE = 1; 5 | var MAX_PENSIZE = 32; 6 | 7 | /** 8 | * Service to retrieve and modify the current pen size. 9 | */ 10 | ns.PenSizeService = function () { 11 | this.size = MIN_PENSIZE; 12 | }; 13 | 14 | ns.PenSizeService.prototype.init = function () { 15 | this.size = pskl.UserSettings.get(pskl.UserSettings.PEN_SIZE); 16 | 17 | var shortcuts = pskl.service.keyboard.Shortcuts; 18 | pskl.app.shortcutService.registerShortcut(shortcuts.MISC.INCREASE_PENSIZE, this.increasePenSize_.bind(this)); 19 | pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DECREASE_PENSIZE, this.decreasePenSize_.bind(this)); 20 | }; 21 | 22 | ns.PenSizeService.prototype.increasePenSize_ = function () { 23 | this.setPenSize(this.size + 1); 24 | }; 25 | 26 | ns.PenSizeService.prototype.decreasePenSize_ = function () { 27 | this.setPenSize(this.size - 1); 28 | }; 29 | 30 | ns.PenSizeService.prototype.getPenSize = function () { 31 | return this.size; 32 | }; 33 | 34 | ns.PenSizeService.prototype.setPenSize = function (size) { 35 | if (this.isPenSizeValid_(size) && size != this.size) { 36 | this.size = size; 37 | pskl.UserSettings.set(pskl.UserSettings.PEN_SIZE, size); 38 | $.publish(Events.PEN_SIZE_CHANGED); 39 | } 40 | }; 41 | 42 | ns.PenSizeService.prototype.isPenSizeValid_ = function (size) { 43 | if (isNaN(size)) { 44 | return false; 45 | } 46 | 47 | return size >= MIN_PENSIZE && size <= MAX_PENSIZE; 48 | }; 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /src/js/service/performance/PerformanceReportService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.performance'); 3 | 4 | ns.PerformanceReportService = function (piskelController, currentColorsService) { 5 | this.piskelController = piskelController; 6 | this.currentColorsService = currentColorsService; 7 | 8 | this.currentReport = null; 9 | }; 10 | 11 | ns.PerformanceReportService.prototype.init = function () { 12 | $.subscribe(Events.HISTORY_STATE_SAVED, this.createReport_.bind(this)); 13 | }; 14 | 15 | ns.PerformanceReportService.prototype.createReport_ = function () { 16 | var report = new ns.PerformanceReport( 17 | this.piskelController.getPiskel(), 18 | this.currentColorsService.getCurrentColors().length); 19 | 20 | if (!report.equals(this.currentReport)) { 21 | $.publish(Events.PERFORMANCE_REPORT_CHANGED, [report]); 22 | this.currentReport = report; 23 | } 24 | }; 25 | 26 | ns.PerformanceReportService.prototype.hasProblem = function () { 27 | return this.currentReport && this.currentReport.hasProblem(); 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /src/js/service/storage/FileDownloadStorageService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.service.storage'); 3 | 4 | ns.FileDownloadStorageService = function () {}; 5 | ns.FileDownloadStorageService.prototype.init = function () {}; 6 | 7 | ns.FileDownloadStorageService.prototype.save = function (piskel) { 8 | var serialized = pskl.utils.serialization.Serializer.serialize(piskel); 9 | var deferred = Q.defer(); 10 | 11 | pskl.utils.BlobUtils.stringToBlob(serialized, function(blob) { 12 | var piskelName = piskel.getDescriptor().name; 13 | var timestamp = pskl.utils.DateUtils.format(new Date(), '{{Y}}{{M}}{{D}}-{{H}}{{m}}{{s}}'); 14 | var fileName = piskelName + '-' + timestamp + '.piskel'; 15 | 16 | try { 17 | pskl.utils.FileUtils.downloadAsFile(blob, fileName); 18 | deferred.resolve(); 19 | } catch (e) { 20 | deferred.reject(e.message); 21 | } 22 | }.bind(this), 'application/piskel+json'); 23 | 24 | return deferred.promise; 25 | }; 26 | 27 | })(); 28 | -------------------------------------------------------------------------------- /src/js/snippets.js: -------------------------------------------------------------------------------- 1 | (function () {})(); 2 | -------------------------------------------------------------------------------- /src/js/tools/Tool.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools'); 3 | 4 | ns.Tool = function () { 5 | this.toolId = 'tool'; 6 | this.helpText = 'Abstract tool'; 7 | this.tooltipDescriptors = []; 8 | }; 9 | 10 | ns.Tool.prototype.getHelpText = function() { 11 | return this.helpText; 12 | }; 13 | 14 | ns.Tool.prototype.getId = function() { 15 | return this.toolId; 16 | }; 17 | 18 | ns.Tool.prototype.raiseSaveStateEvent = function (replayData) { 19 | $.publish(Events.PISKEL_SAVE_STATE, { 20 | type : pskl.service.HistoryService.REPLAY, 21 | scope : this, 22 | replay : replayData 23 | }); 24 | }; 25 | })(); 26 | -------------------------------------------------------------------------------- /src/js/tools/ToolIconBuilder.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools'); 3 | 4 | ns.ToolIconBuilder = function () {}; 5 | 6 | ns.ToolIconBuilder.prototype.createIcon = function (tool, tooltipPosition) { 7 | tooltipPosition = tooltipPosition || 'right'; 8 | var tpl = pskl.utils.Template.get('drawingTool-item-template'); 9 | return pskl.utils.Template.replace(tpl, { 10 | cssclass : ['tool-icon', 'icon-' + tool.toolId].join(' '), 11 | toolid : tool.toolId, 12 | title : this.getTooltipText(tool), 13 | tooltipposition : tooltipPosition 14 | }); 15 | }; 16 | 17 | ns.ToolIconBuilder.prototype.getTooltipText = function(tool) { 18 | var descriptors = tool.tooltipDescriptors; 19 | return pskl.utils.TooltipFormatter.format(tool.getHelpText(), tool.shortcut, descriptors); 20 | }; 21 | })(); 22 | -------------------------------------------------------------------------------- /src/js/tools/ToolsHelper.js: -------------------------------------------------------------------------------- 1 | var ns = $.namespace('pskl.tools'); 2 | 3 | ns.ToolsHelper = { 4 | /** 5 | * Retrieve a list of frames containing either : 6 | * - only the current frame (useAllLayers = false, useAllFrames = false) 7 | * - only the frames of the current layer (useAllLayers = false, useAllFrames = true) 8 | * - only the frames at the currentIndex in each layer (useAllLayers = true, useAllFrames = false) 9 | * - all frames (useAllLayers = true, useAllFrames = true) 10 | * 11 | * @param {Boolean} useAllLayers true if frames from all layers should be returned 12 | * @param {Boolean} useAllFrames true if frames at any index should be returned 13 | * @return {Array[Frame]} list of Frame instances, can be empty 14 | */ 15 | getTargetFrames : function (useAllLayers, useAllFrames) { 16 | var currentFrameIndex = pskl.app.piskelController.getCurrentFrameIndex(); 17 | var layers = useAllLayers ? pskl.app.piskelController.getLayers() : [pskl.app.piskelController.getCurrentLayer()]; 18 | return layers.reduce(function (previous, layer) { 19 | var frames = useAllFrames ? layer.getFrames() : [layer.getFrameAt(currentFrameIndex)]; 20 | return previous.concat(frames); 21 | }, []); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/js/tools/drawing/ColorPicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.ColorPicker 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing'); 8 | 9 | ns.ColorPicker = function() { 10 | this.toolId = 'tool-colorpicker'; 11 | this.helpText = 'Color picker'; 12 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.COLORPICKER; 13 | }; 14 | 15 | pskl.utils.inherit(ns.ColorPicker, ns.BaseTool); 16 | 17 | /** 18 | * @override 19 | */ 20 | ns.ColorPicker.prototype.applyToolAt = function(col, row, frame, overlay, event) { 21 | if (frame.containsPixel(col, row)) { 22 | var sampledColor = pskl.utils.intToColor(frame.getPixel(col, row)); 23 | if (pskl.app.mouseStateService.isLeftButtonPressed()) { 24 | $.publish(Events.SELECT_PRIMARY_COLOR, [sampledColor]); 25 | } else if (pskl.app.mouseStateService.isRightButtonPressed()) { 26 | $.publish(Events.SELECT_SECONDARY_COLOR, [sampledColor]); 27 | } 28 | } 29 | }; 30 | })(); 31 | -------------------------------------------------------------------------------- /src/js/tools/drawing/DitheringTool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.DitheringTool 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing'); 8 | 9 | ns.DitheringTool = function() { 10 | ns.SimplePen.call(this); 11 | this.toolId = 'tool-dithering'; 12 | this.helpText = 'Dithering tool'; 13 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.DITHERING; 14 | }; 15 | 16 | pskl.utils.inherit(ns.DitheringTool, ns.SimplePen); 17 | 18 | ns.DitheringTool.prototype.supportsDynamicPenSize = function() { 19 | return true; 20 | }; 21 | 22 | /** 23 | * @override 24 | */ 25 | ns.DitheringTool.prototype.applyToolAt = function(col, row, frame, overlay, event) { 26 | this.previousCol = col; 27 | this.previousRow = row; 28 | 29 | var penSize = pskl.app.penSizeService.getPenSize(); 30 | var points = pskl.PixelUtils.resizePixel(col, row, penSize); 31 | points.forEach(function (point) { 32 | this.applyToolOnPixel(point[0], point[1], frame, overlay, event); 33 | }.bind(this)); 34 | }; 35 | 36 | ns.DitheringTool.prototype.applyToolOnPixel = function(col, row, frame, overlay, event) { 37 | var usePrimaryColor = (col + row) % 2; 38 | 39 | if (pskl.app.mouseStateService.isRightButtonPressed()) { 40 | usePrimaryColor = !usePrimaryColor; 41 | } 42 | 43 | var ditheringColor = usePrimaryColor ? 44 | pskl.app.selectedColorsService.getPrimaryColor() : 45 | pskl.app.selectedColorsService.getSecondaryColor(); 46 | 47 | this.draw(ditheringColor, col, row, frame, overlay); 48 | }; 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /src/js/tools/drawing/Eraser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.Eraser 3 | * 4 | * @require Constants 5 | * @require pskl.utils 6 | */ 7 | (function() { 8 | var ns = $.namespace('pskl.tools.drawing'); 9 | 10 | ns.Eraser = function() { 11 | this.superclass.constructor.call(this); 12 | 13 | this.toolId = 'tool-eraser'; 14 | this.helpText = 'Eraser tool'; 15 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.ERASER; 16 | }; 17 | 18 | pskl.utils.inherit(ns.Eraser, ns.SimplePen); 19 | 20 | /** 21 | * @override 22 | */ 23 | ns.Eraser.prototype.getToolColor = function() { 24 | return Constants.TRANSPARENT_COLOR; 25 | }; 26 | })(); 27 | -------------------------------------------------------------------------------- /src/js/tools/drawing/PaintBucket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.PaintBucket 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing'); 8 | 9 | ns.PaintBucket = function() { 10 | this.toolId = 'tool-paint-bucket'; 11 | this.helpText = 'Paint bucket tool'; 12 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.PAINT_BUCKET; 13 | }; 14 | 15 | pskl.utils.inherit(ns.PaintBucket, ns.BaseTool); 16 | 17 | /** 18 | * @override 19 | */ 20 | ns.PaintBucket.prototype.applyToolAt = function(col, row, frame, overlay, event) { 21 | var color = this.getToolColor(); 22 | pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, col, row, color); 23 | 24 | this.raiseSaveStateEvent({ 25 | col : col, 26 | row : row, 27 | color : color 28 | }); 29 | }; 30 | 31 | ns.PaintBucket.prototype.replay = function (frame, replayData) { 32 | pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, replayData.col, replayData.row, replayData.color); 33 | }; 34 | })(); 35 | -------------------------------------------------------------------------------- /src/js/tools/drawing/Rectangle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.Rectangle 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing'); 8 | 9 | ns.Rectangle = function() { 10 | ns.ShapeTool.call(this); 11 | 12 | this.toolId = 'tool-rectangle'; 13 | this.helpText = 'Rectangle tool'; 14 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.RECTANGLE; 15 | }; 16 | 17 | pskl.utils.inherit(ns.Rectangle, ns.ShapeTool); 18 | 19 | /** 20 | * @override 21 | */ 22 | ns.Rectangle.prototype.draw = function (col, row, color, targetFrame, penSize) { 23 | var rectangle = pskl.PixelUtils.getOrderedRectangleCoordinates(this.startCol, this.startRow, col, row); 24 | 25 | for (var x = rectangle.x0; x <= rectangle.x1; x++) { 26 | for (var y = rectangle.y0; y <= rectangle.y1; y++) { 27 | if ( 28 | x > rectangle.x1 - penSize || 29 | x < rectangle.x0 + penSize || 30 | y > rectangle.y1 - penSize || 31 | y < rectangle.y0 + penSize 32 | ) { 33 | targetFrame.setPixel(x, y, color); 34 | } 35 | } 36 | } 37 | }; 38 | })(); 39 | -------------------------------------------------------------------------------- /src/js/tools/drawing/selection/RectangleSelect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.selection.RectangleSelect 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing.selection'); 8 | 9 | ns.RectangleSelect = function() { 10 | ns.AbstractDragSelect.call(this); 11 | 12 | this.toolId = 'tool-rectangle-select'; 13 | this.helpText = 'Rectangle selection'; 14 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.RECTANGLE_SELECT; 15 | 16 | }; 17 | 18 | pskl.utils.inherit(ns.RectangleSelect, ns.AbstractDragSelect); 19 | 20 | /** @override */ 21 | ns.RectangleSelect.prototype.onDragSelectStart_ = function (col, row) { 22 | $.publish(Events.DRAG_START, [col, row]); 23 | }; 24 | 25 | /** 26 | * When creating the rectangle selection, we clear the current overlayFrame and 27 | * redraw the current rectangle based on the origin coordinate and 28 | * the current mouse coordinate in sprite. 29 | * @override 30 | */ 31 | ns.RectangleSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) { 32 | overlay.clear(); 33 | this.selection = new pskl.selection.RectangularSelection(this.startCol, this.startRow, col, row); 34 | $.publish(Events.SELECTION_CREATED, [this.selection]); 35 | this.drawSelectionOnOverlay_(overlay); 36 | }; 37 | 38 | /** @override */ 39 | ns.RectangleSelect.prototype.onDragSelectEnd_ = function (col, row, frame, overlay) { 40 | this.onSelect_(col, row, frame, overlay); 41 | $.publish(Events.DRAG_END); 42 | }; 43 | 44 | })(); 45 | -------------------------------------------------------------------------------- /src/js/tools/drawing/selection/ShapeSelect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @provide pskl.tools.drawing.selection.ShapeSelect 3 | * 4 | * @require pskl.utils 5 | */ 6 | (function() { 7 | var ns = $.namespace('pskl.tools.drawing.selection'); 8 | 9 | ns.ShapeSelect = function() { 10 | ns.BaseSelect.call(this); 11 | 12 | this.toolId = 'tool-shape-select'; 13 | this.helpText = 'Shape selection'; 14 | this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.SHAPE_SELECT; 15 | }; 16 | 17 | pskl.utils.inherit(ns.ShapeSelect, ns.BaseSelect); 18 | 19 | /** 20 | * For the shape select tool, you just need to click one time to create a selection. 21 | * So we just need to implement onSelectStart_ (no need for onSelect_ & onSelectEnd_) 22 | * @override 23 | */ 24 | ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) { 25 | if (this.hasSelection) { 26 | this.hasSelection = false; 27 | this.commitSelection(); 28 | } else { 29 | this.hasSelection = true; 30 | // From the pixel clicked, get shape using an algorithm similar to the paintbucket one: 31 | var pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, col, row); 32 | this.selection = new pskl.selection.ShapeSelection(pixels); 33 | 34 | $.publish(Events.SELECTION_CREATED, [this.selection]); 35 | this.drawSelectionOnOverlay_(overlay); 36 | } 37 | }; 38 | 39 | })(); 40 | -------------------------------------------------------------------------------- /src/js/tools/transform/AbstractTransformTool.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools.transform'); 3 | 4 | ns.AbstractTransformTool = function () {}; 5 | 6 | pskl.utils.inherit(ns.AbstractTransformTool, pskl.tools.Tool); 7 | 8 | ns.AbstractTransformTool.prototype.applyTransformation = function (evt) { 9 | var allFrames = evt.shiftKey; 10 | var allLayers = pskl.utils.UserAgent.isMac ? evt.metaKey : evt.ctrlKey; 11 | 12 | this.applyTool_(evt.altKey, allFrames, allLayers); 13 | 14 | $.publish(Events.PISKEL_RESET); 15 | 16 | this.raiseSaveStateEvent({ 17 | altKey : evt.altKey, 18 | allFrames : allFrames, 19 | allLayers : allLayers 20 | }); 21 | }; 22 | 23 | ns.AbstractTransformTool.prototype.applyTool_ = function (altKey, allFrames, allLayers) { 24 | var currentFrameIndex = pskl.app.piskelController.getCurrentFrameIndex(); 25 | var layers = allLayers ? pskl.app.piskelController.getLayers() : [pskl.app.piskelController.getCurrentLayer()]; 26 | layers.forEach(function (layer) { 27 | var frames = allFrames ? layer.getFrames() : [layer.getFrameAt(currentFrameIndex)]; 28 | frames.forEach(function (frame) { 29 | this.applyToolOnFrame_(frame, altKey); 30 | }.bind(this)); 31 | }.bind(this)); 32 | }; 33 | 34 | ns.AbstractTransformTool.prototype.replay = function (frame, replayData) { 35 | this.applyTool_(replayData.altKey, replayData.allFrames, replayData.allLayers); 36 | }; 37 | 38 | })(); 39 | -------------------------------------------------------------------------------- /src/js/tools/transform/Center.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools.transform'); 3 | 4 | ns.Center = function () { 5 | this.toolId = 'tool-center'; 6 | this.helpText = 'Align image to the center'; 7 | this.tooltipDescriptors = [ 8 | {key : 'ctrl', description : 'Apply to all layers'}, 9 | {key : 'shift', description : 'Apply to all frames'} 10 | ]; 11 | }; 12 | 13 | pskl.utils.inherit(ns.Center, ns.AbstractTransformTool); 14 | 15 | ns.Center.prototype.applyToolOnFrame_ = function (frame) { 16 | ns.TransformUtils.center(frame); 17 | }; 18 | 19 | })(); 20 | -------------------------------------------------------------------------------- /src/js/tools/transform/Clone.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools.transform'); 3 | 4 | ns.Clone = function () { 5 | this.toolId = 'tool-clone'; 6 | this.helpText = 'Clone current layer to all frames'; 7 | this.tooltipDescriptors = []; 8 | }; 9 | 10 | pskl.utils.inherit(ns.Clone, ns.AbstractTransformTool); 11 | 12 | ns.Clone.prototype.applyTool_ = function (altKey, allFrames, allLayers) { 13 | var ref = pskl.app.piskelController.getCurrentFrame(); 14 | var layer = pskl.app.piskelController.getCurrentLayer(); 15 | layer.getFrames().forEach(function (frame) { 16 | if (frame !== ref) { 17 | frame.setPixels(ref.getPixels()); 18 | } 19 | }); 20 | }; 21 | })(); 22 | -------------------------------------------------------------------------------- /src/js/tools/transform/Flip.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools.transform'); 3 | 4 | ns.Flip = function () { 5 | this.toolId = 'tool-flip'; 6 | this.helpText = 'Flip horizontally'; 7 | this.tooltipDescriptors = [ 8 | {key : 'alt', description : 'Flip vertically'}, 9 | {key : 'ctrl', description : 'Apply to all layers'}, 10 | {key : 'shift', description : 'Apply to all frames'} 11 | ]; 12 | }; 13 | 14 | pskl.utils.inherit(ns.Flip, ns.AbstractTransformTool); 15 | 16 | ns.Flip.prototype.applyToolOnFrame_ = function (frame, altKey) { 17 | var axis; 18 | 19 | if (altKey) { 20 | axis = ns.TransformUtils.HORIZONTAL; 21 | } else { 22 | axis = ns.TransformUtils.VERTICAL; 23 | } 24 | 25 | ns.TransformUtils.flip(frame, axis); 26 | }; 27 | 28 | })(); 29 | -------------------------------------------------------------------------------- /src/js/tools/transform/Rotate.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.tools.transform'); 3 | 4 | ns.Rotate = function () { 5 | this.toolId = 'tool-rotate'; 6 | this.helpText = 'Counter-clockwise rotation'; 7 | this.tooltipDescriptors = [ 8 | {key : 'alt', description : 'Clockwise rotation'}, 9 | {key : 'ctrl', description : 'Apply to all layers'}, 10 | {key : 'shift', description : 'Apply to all frames'}]; 11 | }; 12 | 13 | pskl.utils.inherit(ns.Rotate, ns.AbstractTransformTool); 14 | 15 | ns.Rotate.prototype.applyToolOnFrame_ = function (frame, altKey) { 16 | var direction; 17 | 18 | if (altKey) { 19 | direction = ns.TransformUtils.CLOCKWISE; 20 | } else { 21 | direction = ns.TransformUtils.COUNTERCLOCKWISE; 22 | } 23 | 24 | ns.TransformUtils.rotate(frame, direction); 25 | }; 26 | 27 | })(); 28 | -------------------------------------------------------------------------------- /src/js/utils/Array.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.Array = { 5 | find : function (array, filterFn) { 6 | var match = null; 7 | array = Array.isArray(array) ? array : []; 8 | var filtered = array.filter(filterFn); 9 | if (filtered.length) { 10 | match = filtered[0]; 11 | } 12 | return match; 13 | }, 14 | 15 | /** 16 | * Split a provided array in a given amount of chunks. 17 | * For instance [1,2,3,4] chunked in 2 parts will be [1,2] & [3,4]. 18 | * @param {Array} array the array to chunk 19 | * @param {Number} chunksCount the number of chunks to create 20 | * @return {Array} array of arrays containing the items of the original array 21 | */ 22 | chunk : function (array, chunksCount) { 23 | var chunks = []; 24 | 25 | // We cannot have more chunks than array items. 26 | chunksCount = Math.min(chunksCount, array.length); 27 | 28 | // chunksCount should be at least 1 29 | chunksCount = Math.max(1, chunksCount); 30 | 31 | var step = Math.round(array.length / chunksCount); 32 | for (var i = 0 ; i < chunksCount ; i++) { 33 | var isLast = i == chunksCount - 1; 34 | var end = isLast ? array.length : (i + 1) * step; 35 | chunks.push(array.slice(i * step, end)); 36 | } 37 | return chunks; 38 | } 39 | }; 40 | 41 | })(); 42 | -------------------------------------------------------------------------------- /src/js/utils/BlobUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | var BASE64_REGEX = /\s*;\s*base64\s*(?:;|$)/i; 5 | 6 | ns.BlobUtils = { 7 | dataToBlob : function(dataURI, type, callback) { 8 | var headerEnd = dataURI.indexOf(','); 9 | var data = dataURI.substring(headerEnd + 1); 10 | var isBase64 = BASE64_REGEX.test(dataURI.substring(0, headerEnd)); 11 | var blob; 12 | 13 | if (Blob.fake) { 14 | // no reason to decode a data: URI that's just going to become a data URI again 15 | blob = new Blob(); 16 | blob.encoding = isBase64 ? 'base64' : 'URI'; 17 | blob.data = data; 18 | blob.size = data.length; 19 | } else if (Uint8Array) { 20 | var blobData = isBase64 ? pskl.utils.Base64.decode(data) : decodeURIComponent(data); 21 | blob = new Blob([blobData], {type: type}); 22 | } 23 | callback(blob); 24 | }, 25 | 26 | canvasToBlob : function (canvas, callback, type /*, ...args*/) { 27 | type = type || 'image/png'; 28 | 29 | if (canvas.mozGetAsFile) { 30 | callback(canvas.mozGetAsFile('canvas', type)); 31 | } else { 32 | var args = Array.prototype.slice.call(arguments, 2); 33 | var dataURI = canvas.toDataURL.apply(canvas, args); 34 | pskl.utils.BlobUtils.dataToBlob(dataURI, type, callback); 35 | } 36 | }, 37 | 38 | stringToBlob : function (string, callback, type) { 39 | type = type || 'text/plain'; 40 | pskl.utils.BlobUtils.dataToBlob('data:' + type + ',' + string, type, callback); 41 | } 42 | }; 43 | })(); 44 | -------------------------------------------------------------------------------- /src/js/utils/ColorUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.ColorUtils = { 5 | getUnusedColor : function (usedColors) { 6 | usedColors = usedColors || []; 7 | // create check map 8 | var colorMap = {}; 9 | usedColors.forEach(function (color) { 10 | colorMap[color.toUpperCase()] = true; 11 | }); 12 | 13 | // start with white 14 | var color = { 15 | r : 255, 16 | g : 255, 17 | b : 0 18 | }; 19 | var match = null; 20 | while (true) { 21 | var hex = window.tinycolor(color).toHexString().toUpperCase(); 22 | 23 | if (!colorMap[hex]) { 24 | match = hex; 25 | break; 26 | } else { 27 | // pick a non null component to decrease its value 28 | var component = (color.r && 'r') || (color.g && 'g') || (color.b && 'b'); 29 | if (component) { 30 | color[component] = color[component] - 1; 31 | } else { 32 | // no component available, no match found 33 | break; 34 | } 35 | } 36 | } 37 | 38 | return match; 39 | } 40 | }; 41 | })(); 42 | -------------------------------------------------------------------------------- /src/js/utils/DateUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | var pad = function (num) { 5 | if (num < 10) { 6 | return '0' + num; 7 | } else { 8 | return '' + num; 9 | } 10 | }; 11 | 12 | ns.DateUtils = { 13 | format : function (date, format) { 14 | date = new Date(date); 15 | return pskl.utils.Template.replace(format, { 16 | Y : date.getFullYear(), 17 | M : pad(date.getMonth() + 1), 18 | D : pad(date.getDate()), 19 | H : pad(date.getHours()), 20 | m : pad(date.getMinutes()), 21 | s : pad(date.getSeconds()) 22 | }); 23 | } 24 | }; 25 | })(); 26 | -------------------------------------------------------------------------------- /src/js/utils/Dom.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.Dom = { 5 | /** 6 | * Check if a given HTML element is nested inside another 7 | * @param {HTMLElement} node Element to test 8 | * @param {HTMLElement} parent Potential Ancestor for node 9 | * @param {Boolean} excludeParent set to true if the parent should be excluded from potential matches 10 | * @return {Boolean} true if parent was found amongst the parentNode chain of node 11 | */ 12 | isParent : function (node, parent, excludeParent) { 13 | if (node && parent) { 14 | 15 | if (excludeParent) { 16 | node = node.parentNode; 17 | } 18 | 19 | while (node) { 20 | if (node === parent) { 21 | return true; 22 | } 23 | node = node.parentNode; 24 | } 25 | } 26 | return false; 27 | }, 28 | 29 | getParentWithData : function (node, dataName) { 30 | while (node) { 31 | if (node.dataset && typeof node.dataset[dataName] !== 'undefined') { 32 | return node; 33 | } 34 | node = node.parentNode; 35 | } 36 | return null; 37 | }, 38 | 39 | getData : function (node, dataName) { 40 | var parent = ns.Dom.getParentWithData(node, dataName); 41 | if (parent !== null) { 42 | return parent.dataset[dataName]; 43 | } 44 | }, 45 | 46 | removeClass : function (className, container) { 47 | container = container || document; 48 | var elements = container.querySelectorAll('.' + className); 49 | for (var i = 0 ; i < elements.length ; i++) { 50 | elements[i].classList.remove(className); 51 | } 52 | } 53 | }; 54 | })(); 55 | -------------------------------------------------------------------------------- /src/js/utils/Environment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * detection method from: 3 | * http://videlais.com/2014/08/23/lessons-learned-from-detecting-node-webkit/ 4 | */ 5 | 6 | (function () { 7 | 8 | var ns = $.namespace('pskl.utils'); 9 | 10 | ns.Environment = { 11 | detectNodeWebkit : function () { 12 | var isNode = (typeof window.process !== 'undefined' && typeof window.require !== 'undefined'); 13 | var isNodeWebkit = false; 14 | if (isNode) { 15 | try { 16 | isNodeWebkit = (typeof window.require('nw.gui') !== 'undefined'); 17 | } catch (e) { 18 | isNodeWebkit = false; 19 | } 20 | } 21 | return isNodeWebkit; 22 | }, 23 | 24 | isIntegrationTest : function () { 25 | return window.location.href.indexOf('integration-test') !== -1; 26 | }, 27 | 28 | isDebug : function () { 29 | return window.location.href.indexOf('debug') !== -1; 30 | }, 31 | 32 | isHttps : function () { 33 | return window.location.href.indexOf('https://') === 0; 34 | }, 35 | }; 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /src/js/utils/Event.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.Event = {}; 5 | 6 | ns.Event.addEventListener = function (el, type, callback, scope, args) { 7 | if (typeof el === 'string') { 8 | el = document.querySelector(el); 9 | } 10 | 11 | var listener = { 12 | el : el, 13 | type : type, 14 | callback : callback, 15 | handler : args ? callback.bind(scope, args) : callback.bind(scope) 16 | }; 17 | 18 | scope.__pskl_listeners = scope.__pskl_listeners || []; 19 | scope.__pskl_listeners.push(listener); 20 | el.addEventListener(type, listener.handler); 21 | }; 22 | 23 | ns.Event.removeEventListener = function (el, type, callback, scope) { 24 | if (scope && scope.__pskl_listeners) { 25 | var listeners = scope.__pskl_listeners; 26 | for (var i = 0 ; i < listeners.length ; i++) { 27 | var listener = listeners[i]; 28 | if (listener.callback === callback && listener.el === el && listener.type === type) { 29 | el.removeEventListener(type, listeners[i].handler); 30 | listeners.splice(i, 1); 31 | break; 32 | } 33 | } 34 | } 35 | }; 36 | 37 | ns.Event.removeAllEventListeners = function (scope) { 38 | if (scope && scope.__pskl_listeners) { 39 | var listeners = scope.__pskl_listeners; 40 | for (var i = 0 ; i < listeners.length ; i++) { 41 | var listener = listeners[i]; 42 | listener.el.removeEventListener(listener.type, listener.handler); 43 | } 44 | scope.__pskl_listeners = []; 45 | } 46 | }; 47 | })(); 48 | -------------------------------------------------------------------------------- /src/js/utils/FileUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | var stopPropagation = function (e) { 5 | e.stopPropagation(); 6 | }; 7 | 8 | ns.FileUtils = { 9 | readFile : function (file, callback) { 10 | var reader = new FileReader(); 11 | reader.addEventListener('loadend', function() { 12 | callback(reader.result); 13 | }); 14 | reader.readAsDataURL(file); 15 | }, 16 | 17 | readFileAsArrayBuffer : function (file, callback) { 18 | var reader = new FileReader(); 19 | reader.addEventListener('loadend', function() { 20 | callback(reader.result); 21 | }); 22 | reader.readAsArrayBuffer(file); 23 | }, 24 | 25 | readImageFile : function (file, callback) { 26 | ns.FileUtils.readFile(file, function (content) { 27 | var image = new Image(); 28 | image.onload = callback.bind(null, image); 29 | image.src = content; 30 | }); 31 | }, 32 | 33 | downloadAsFile : function (content, filename) { 34 | var saveAs = window.saveAs || (navigator.msSaveBlob && navigator.msSaveBlob.bind(navigator)); 35 | if (saveAs) { 36 | saveAs(content, filename); 37 | } else { 38 | var downloadLink = document.createElement('a'); 39 | content = window.URL.createObjectURL(content); 40 | downloadLink.setAttribute('href', content); 41 | downloadLink.setAttribute('download', filename); 42 | document.body.appendChild(downloadLink); 43 | downloadLink.addEventListener('click', stopPropagation); 44 | downloadLink.click(); 45 | downloadLink.removeEventListener('click', stopPropagation); 46 | document.body.removeChild(downloadLink); 47 | } 48 | } 49 | 50 | }; 51 | })(); 52 | -------------------------------------------------------------------------------- /src/js/utils/FunctionUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.FunctionUtils = { 5 | /** 6 | * Returns a memoized version of the provided function. 7 | */ 8 | memo : function (fn, cache, scope) { 9 | var memoized = function () { 10 | var key = Array.prototype.join.call(arguments, '-'); 11 | if (!cache[key]) { 12 | cache[key] = fn.apply(scope, arguments); 13 | } 14 | return cache[key]; 15 | }; 16 | return memoized; 17 | }, 18 | 19 | /** 20 | * Returns a throttled version of the provided method, that will be called at most 21 | * every X milliseconds, where X is the provided interval. 22 | */ 23 | throttle : function (fn, interval) { 24 | var last; 25 | var timer; 26 | return function () { 27 | var now = Date.now(); 28 | if (last && now < last + interval) { 29 | clearTimeout(timer); 30 | timer = setTimeout(function () { 31 | last = now; 32 | fn(); 33 | }, interval); 34 | } else { 35 | last = now; 36 | fn(); 37 | } 38 | }; 39 | } 40 | }; 41 | })(); 42 | -------------------------------------------------------------------------------- /src/js/utils/ImageResizer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.ImageResizer = { 5 | scale : function (image, factor, smoothingEnabled) { 6 | return ns.ImageResizer.resize(image, image.width * factor, image.height * factor, smoothingEnabled); 7 | }, 8 | 9 | resize : function (image, targetWidth, targetHeight, smoothingEnabled) { 10 | var canvas = pskl.utils.CanvasUtils.createCanvas(targetWidth, targetHeight); 11 | var context = canvas.getContext('2d'); 12 | context.save(); 13 | 14 | if (!smoothingEnabled) { 15 | pskl.utils.CanvasUtils.disableImageSmoothing(canvas); 16 | } 17 | 18 | context.translate(canvas.width / 2, canvas.height / 2); 19 | context.scale(targetWidth / image.width, targetHeight / image.height); 20 | context.drawImage(image, -image.width / 2, -image.height / 2); 21 | context.restore(); 22 | 23 | return canvas; 24 | } 25 | }; 26 | })(); 27 | -------------------------------------------------------------------------------- /src/js/utils/Math.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.Math = { 5 | minmax : function (val, min, max) { 6 | return Math.max(Math.min(val, max), min); 7 | }, 8 | 9 | /** 10 | * Calculate the distance between {x0, y0} and {x1, y1} 11 | */ 12 | distance : function (x0, x1, y0, y1) { 13 | var dx = Math.abs(x1 - x0); 14 | var dy = Math.abs(y1 - y0); 15 | return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); 16 | } 17 | }; 18 | })(); 19 | -------------------------------------------------------------------------------- /src/js/utils/StringUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.StringUtils = { 5 | leftPad : function (input, length, pad) { 6 | var padding = new Array(length).join(pad); 7 | return (padding + input).slice(-length); 8 | }, 9 | 10 | formatSize : function (width, height) { 11 | return width + '\u00D7' + height; 12 | } 13 | }; 14 | })(); 15 | -------------------------------------------------------------------------------- /src/js/utils/TooltipFormatter.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | ns.TooltipFormatter = {}; 5 | 6 | ns.TooltipFormatter.format = function(helpText, shortcut, descriptors) { 7 | var tpl = pskl.utils.Template.get('tooltip-container-template'); 8 | shortcut = shortcut ? '(' + shortcut.getDisplayKey() + ')' : ''; 9 | return pskl.utils.Template.replace(tpl, { 10 | helptext : helpText, 11 | shortcut : shortcut, 12 | // Avoid sanitization for descriptors (markup) 13 | '!descriptors!' : this.formatDescriptors_(descriptors) 14 | }); 15 | }; 16 | 17 | ns.TooltipFormatter.formatDescriptors_ = function(descriptors) { 18 | descriptors = descriptors || []; 19 | return descriptors.reduce(function (p, descriptor) { 20 | return p += this.formatDescriptor_(descriptor); 21 | }.bind(this), ''); 22 | }; 23 | 24 | ns.TooltipFormatter.formatDescriptor_ = function(descriptor) { 25 | var tpl; 26 | if (descriptor.key) { 27 | tpl = pskl.utils.Template.get('tooltip-modifier-descriptor-template'); 28 | descriptor.key = descriptor.key.toUpperCase(); 29 | if (pskl.utils.UserAgent.isMac) { 30 | descriptor.key = descriptor.key.replace('CTRL', 'CMD'); 31 | descriptor.key = descriptor.key.replace('ALT', 'OPTION'); 32 | } 33 | } else { 34 | tpl = pskl.utils.Template.get('tooltip-simple-descriptor-template'); 35 | } 36 | return pskl.utils.Template.replace(tpl, descriptor); 37 | }; 38 | })(); 39 | -------------------------------------------------------------------------------- /src/js/utils/Uuid.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | var s4 = function () { 5 | return Math.floor((1 + Math.random()) * 0x10000) 6 | .toString(16) 7 | .substring(1); 8 | }; 9 | 10 | ns.Uuid = { 11 | generate : function () { 12 | return 'ss-s-s-s-sss'.replace(/s/g, function () { 13 | return s4(); 14 | }); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /src/js/utils/WorkerUtils.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | 4 | var workers = {}; 5 | 6 | ns.WorkerUtils = { 7 | createWorker : function (worker, workerId) { 8 | if (!workers[workerId]) { 9 | workers[workerId] = ns.WorkerUtils.createWorkerURL(worker); 10 | } 11 | 12 | return new Worker(workers[workerId]); 13 | }, 14 | 15 | createWorkerURL : function (worker) { 16 | // remove "function () {" at the start of the worker string and the last "}" before the end 17 | var typedArray = [(worker + '').replace(/function\s*\(\)\s*\{/, '').replace(/\}[^}]*$/, '')]; 18 | var blob = new Blob(typedArray, {type: 'application/javascript'}); 19 | return window.URL.createObjectURL(blob); 20 | } 21 | }; 22 | })(); 23 | -------------------------------------------------------------------------------- /src/js/utils/Xhr.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils'); 3 | ns.Xhr = { 4 | get : function (url, success, error) { 5 | var xhr = ns.Xhr.xhr_(url, 'GET', success, error); 6 | xhr.send(); 7 | }, 8 | 9 | post : function (url, data, success, error) { 10 | var xhr = ns.Xhr.xhr_(url, 'POST', success, error); 11 | var formData = new FormData(); 12 | 13 | if (typeof data == 'object') { 14 | for (var key in data) { 15 | if (data.hasOwnProperty(key)) { 16 | formData.append(key, data[key]); 17 | } 18 | } 19 | } 20 | 21 | xhr.send(formData); 22 | }, 23 | 24 | xhr_ : function (url, method, success, error) { 25 | success = success || function () {}; 26 | error = error || function () {}; 27 | 28 | var xhr = new XMLHttpRequest(); 29 | xhr.open(method, url, true); 30 | 31 | xhr.onload = function (e) { 32 | if (this.status == 200) { 33 | success(this); 34 | } else { 35 | this.onerror(this, e); 36 | } 37 | }; 38 | 39 | xhr.onerror = function (e) { 40 | error(e, this); 41 | }; 42 | 43 | return xhr; 44 | } 45 | }; 46 | })(); 47 | -------------------------------------------------------------------------------- /src/js/utils/serialization/backward/Deserializer_v0.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils.serialization.backward'); 3 | 4 | ns.Deserializer_v0 = function (data, callback) { 5 | this.data_ = data; 6 | this.callback_ = callback; 7 | }; 8 | 9 | ns.Deserializer_v0.prototype.deserialize = function () { 10 | var pixelGrids = this.data_; 11 | var frames = pixelGrids.map(function (grid) { 12 | return pskl.model.Frame.fromPixelGrid(grid); 13 | }); 14 | var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', ''); 15 | var layer = pskl.model.Layer.fromFrames('Layer 1', frames); 16 | 17 | this.callback_(pskl.model.Piskel.fromLayers([layer], Constants.DEFAULT.FPS, descriptor)); 18 | }; 19 | })(); 20 | -------------------------------------------------------------------------------- /src/js/utils/serialization/backward/Deserializer_v1.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.utils.serialization.backward'); 3 | 4 | ns.Deserializer_v1 = function (data, callback) { 5 | this.callback_ = callback; 6 | this.data_ = data; 7 | }; 8 | 9 | ns.Deserializer_v1.prototype.deserialize = function () { 10 | var piskelData = this.data_.piskel; 11 | var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', ''); 12 | var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height, Constants.DEFAULT.FPS, descriptor); 13 | 14 | piskelData.layers.forEach(function (serializedLayer) { 15 | var layer = this.deserializeLayer(serializedLayer); 16 | piskel.addLayer(layer); 17 | }.bind(this)); 18 | 19 | this.callback_(piskel); 20 | }; 21 | 22 | ns.Deserializer_v1.prototype.deserializeLayer = function (layerString) { 23 | var layerData = JSON.parse(layerString); 24 | var layer = new pskl.model.Layer(layerData.name); 25 | layerData.frames.forEach(function (serializedFrame) { 26 | var frame = this.deserializeFrame(serializedFrame); 27 | layer.addFrame(frame); 28 | }.bind(this)); 29 | 30 | return layer; 31 | }; 32 | 33 | ns.Deserializer_v1.prototype.deserializeFrame = function (frameString) { 34 | var framePixelGrid = JSON.parse(frameString); 35 | return pskl.model.Frame.fromPixelGrid(framePixelGrid); 36 | }; 37 | })(); 38 | -------------------------------------------------------------------------------- /src/js/widgets/SizePicker.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.widgets'); 3 | 4 | ns.SizePicker = function (onChange) { 5 | this.onChange = onChange; 6 | }; 7 | 8 | ns.SizePicker.prototype.init = function (container) { 9 | this.container = container; 10 | pskl.utils.Event.addEventListener(this.container, 'click', this.onSizeOptionClick_, this); 11 | }; 12 | 13 | ns.SizePicker.prototype.destroy = function () { 14 | pskl.utils.Event.removeAllEventListeners(this); 15 | }; 16 | 17 | ns.SizePicker.prototype.getSize = function () { 18 | var selectedOption = this.container.querySelector('.selected'); 19 | return selectedOption ? selectedOption.dataset.size : null; 20 | }; 21 | 22 | ns.SizePicker.prototype.setSize = function (size) { 23 | if (this.getSize() === size) { 24 | return; 25 | } 26 | 27 | pskl.utils.Dom.removeClass('labeled', this.container); 28 | pskl.utils.Dom.removeClass('selected', this.container); 29 | var selectedOption; 30 | selectedOption = this.container.querySelector('[data-size="' + size + '"]'); 31 | if (!selectedOption) { 32 | selectedOption = this.container.querySelector('[data-size]:last-child'); 33 | selectedOption.classList.add('labeled'); 34 | selectedOption.setAttribute('real-size', size); 35 | } 36 | if (selectedOption) { 37 | selectedOption.classList.add('selected'); 38 | } 39 | }; 40 | 41 | ns.SizePicker.prototype.onSizeOptionClick_ = function (e) { 42 | var size = e.target.dataset.size; 43 | if (!isNaN(size)) { 44 | size = parseInt(size, 10); 45 | this.onChange(size); 46 | this.setSize(size); 47 | } 48 | }; 49 | })(); 50 | -------------------------------------------------------------------------------- /src/js/widgets/SynchronizedInputs.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.widgets'); 3 | 4 | ns.SynchronizedInputs = function (options) { 5 | this.leftInput = options.leftInput; 6 | this.rightInput = options.rightInput; 7 | this.synchronize = options.synchronize; 8 | 9 | this.syncEnabled = true; 10 | this.lastInput = this.leftInput; 11 | 12 | pskl.utils.Event.addEventListener(this.leftInput, 'input', this.onInput_, this); 13 | pskl.utils.Event.addEventListener(this.rightInput, 'input', this.onInput_, this); 14 | }; 15 | 16 | ns.SynchronizedInputs.prototype.destroy = function () { 17 | pskl.utils.Event.removeAllEventListeners(this); 18 | 19 | this.leftInput = null; 20 | this.rightInput = null; 21 | this.lastInput = null; 22 | }; 23 | 24 | ns.SynchronizedInputs.prototype.enableSync = function () { 25 | this.syncEnabled = true; 26 | this.synchronize(this.lastInput); 27 | }; 28 | 29 | ns.SynchronizedInputs.prototype.disableSync = function () { 30 | this.syncEnabled = false; 31 | }; 32 | 33 | ns.SynchronizedInputs.prototype.onInput_ = function (evt) { 34 | var target = evt.target; 35 | if (this.syncEnabled) { 36 | this.synchronize(target); 37 | } 38 | this.lastInput = target; 39 | }; 40 | })(); 41 | -------------------------------------------------------------------------------- /src/js/worker/framecolors/FrameColors.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.worker.framecolors'); 3 | 4 | ns.FrameColors = function (frame, onSuccess, onStep, onError) { 5 | this.pixels = frame.pixels; 6 | 7 | this.onStep = onStep; 8 | this.onSuccess = onSuccess; 9 | this.onError = onError; 10 | 11 | this.worker = pskl.utils.WorkerUtils.createWorker(ns.FrameColorsWorker, 'frame-colors'); 12 | this.worker.onmessage = this.onWorkerMessage.bind(this); 13 | }; 14 | 15 | ns.FrameColors.prototype.process = function () { 16 | this.worker.postMessage([ 17 | pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR), 18 | Constants.MAX_WORKER_COLORS, this.pixels 19 | ]); 20 | }; 21 | 22 | ns.FrameColors.prototype.onWorkerMessage = function (event) { 23 | if (event.data.type === 'STEP') { 24 | this.onStep(event); 25 | } else if (event.data.type === 'SUCCESS') { 26 | this.onSuccess(event); 27 | this.worker.terminate(); 28 | } else if (event.data.type === 'ERROR') { 29 | this.onError(event); 30 | this.worker.terminate(); 31 | } 32 | }; 33 | })(); 34 | -------------------------------------------------------------------------------- /src/js/worker/hash/Hash.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.worker.hash'); 3 | 4 | ns.Hash = function (str, onSuccess, onStep, onError) { 5 | this.str = str; 6 | 7 | this.onStep = onStep; 8 | this.onSuccess = onSuccess; 9 | this.onError = onError; 10 | 11 | this.worker = pskl.utils.WorkerUtils.createWorker(ns.HashWorker, 'hash'); 12 | this.worker.onmessage = this.onWorkerMessage.bind(this); 13 | }; 14 | 15 | ns.Hash.prototype.process = function () { 16 | this.worker.postMessage({ 17 | str : this.str 18 | }); 19 | }; 20 | 21 | ns.Hash.prototype.onWorkerMessage = function (event) { 22 | if (event.data.type === 'STEP') { 23 | this.onStep(event); 24 | } else if (event.data.type === 'SUCCESS') { 25 | this.onSuccess(event); 26 | this.worker.terminate(); 27 | } else if (event.data.type === 'ERROR') { 28 | this.onError(event); 29 | this.worker.terminate(); 30 | } 31 | }; 32 | })(); 33 | -------------------------------------------------------------------------------- /src/js/worker/hash/HashWorker.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.worker.hash'); 3 | 4 | ns.HashWorker = function () { 5 | var hashCode = function(str) { 6 | var hash = 0; 7 | if (str.length !== 0) { 8 | for (var i = 0, l = str.length; i < l; i++) { 9 | var chr = str.charCodeAt(i); 10 | hash = ((hash << 5) - hash) + chr; 11 | hash |= 0; // Convert to 32bit integer 12 | } 13 | } 14 | return hash; 15 | }; 16 | 17 | this.onmessage = function(event) { 18 | try { 19 | var data = event.data; 20 | var str = data.str; 21 | var hash = hashCode(str); 22 | this.postMessage({ 23 | type : 'SUCCESS', 24 | hash : hash 25 | }); 26 | } catch (e) { 27 | this.postMessage({ 28 | type : 'ERROR', 29 | message : e.message 30 | }); 31 | } 32 | }; 33 | }; 34 | })(); 35 | -------------------------------------------------------------------------------- /src/js/worker/imageprocessor/ImageProcessor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var ns = $.namespace('pskl.worker.imageprocessor'); 3 | 4 | ns.ImageProcessor = function (image, onSuccess, onStep, onError) { 5 | this.image = image; 6 | 7 | this.onStep = onStep; 8 | this.onSuccess = onSuccess; 9 | this.onError = onError; 10 | 11 | this.worker = pskl.utils.WorkerUtils.createWorker(ns.ImageProcessorWorker, 'image-colors-processor'); 12 | this.worker.onmessage = this.onWorkerMessage.bind(this); 13 | }; 14 | 15 | ns.ImageProcessor.prototype.process = function () { 16 | var canvas = pskl.utils.CanvasUtils.createFromImage(this.image); 17 | var imageData = pskl.utils.CanvasUtils.getImageDataFromCanvas(canvas); 18 | this.worker.postMessage({ 19 | imageData : imageData, 20 | width : this.image.width, 21 | height : this.image.height 22 | }); 23 | }; 24 | 25 | ns.ImageProcessor.prototype.onWorkerMessage = function (event) { 26 | if (event.data.type === 'STEP') { 27 | this.onStep(event); 28 | } else if (event.data.type === 'SUCCESS') { 29 | this.onSuccess(event); 30 | this.worker.terminate(); 31 | } else if (event.data.type === 'ERROR') { 32 | this.onError(event); 33 | this.worker.terminate(); 34 | } 35 | }; 36 | })(); 37 | -------------------------------------------------------------------------------- /src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piskelapp/piskel/b988fb256a6273d52f6804bbf167dfd20f2c6ef2/src/logo.png -------------------------------------------------------------------------------- /src/piskel-style-list.js: -------------------------------------------------------------------------------- 1 | // This list is used both by the grunt build and index.html (in debug mode) 2 | 3 | (typeof exports != "undefined" ? exports : pskl_exports).styles = [ 4 | "css/variables.css", 5 | "css/reset.css", 6 | "css/style.css", 7 | "css/animations.css", 8 | "css/layout.css", 9 | "css/font-icon.css", 10 | "css/forms.css", 11 | "css/settings.css", 12 | "css/settings-application.css", 13 | "css/settings-export.css", 14 | "css/settings-import.css", 15 | "css/settings-resize.css", 16 | "css/settings-save.css", 17 | "css/tools.css", 18 | "css/icons.css", 19 | "css/color-picker-slider.css", 20 | "css/dialogs.css", 21 | "css/dialogs-browse-backups.css", 22 | "css/dialogs-browse-local.css", 23 | "css/dialogs-cheatsheet.css", 24 | "css/dialogs-create-palette.css", 25 | "css/dialogs-import.css", 26 | "css/dialogs-performance-info.css", 27 | "css/dialogs-unsupported-browser.css", 28 | "css/notifications.css", 29 | "css/toolbox.css", 30 | "css/toolbox-layers-list.css", 31 | "css/toolbox-palettes-list.css", 32 | "css/toolbox-animated-preview.css", 33 | "css/transformations.css", 34 | "css/spectrum/spectrum.css", 35 | "css/spectrum/spectrum-overrides.css", 36 | "css/bootstrap/bootstrap.css", 37 | "css/bootstrap/bootstrap-tooltip-custom.css", 38 | "css/frames-list.css", 39 | "css/minimap.css", 40 | "css/widgets-anchor.css", 41 | "css/widgets-frame-picker.css", 42 | "css/widgets-size-picker.css", 43 | "css/widgets-tabs.css", 44 | "css/widgets-wizard.css" 45 | ]; 46 | -------------------------------------------------------------------------------- /src/templates/debug-header.html: -------------------------------------------------------------------------------- 1 | 41 |
42 | + 50 | 51 |
-------------------------------------------------------------------------------- /src/templates/dialogs/browse-local.html: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /src/templates/dialogs/unsupported-browser.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/frames-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
    4 |
    5 |
    6 |
    7 |
    -------------------------------------------------------------------------------- /src/templates/misc-templates.html: -------------------------------------------------------------------------------- 1 |
    2 | 9 | 10 | 11 | 17 | 18 | 19 | 25 | 26 | 27 | 32 |
    -------------------------------------------------------------------------------- /src/templates/palettes-list.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Palettes

    3 |
    4 | 7 | 8 | 11 |
    12 |
    13 | 21 | 22 | 27 | 28 |
    29 | 30 | 31 | -------------------------------------------------------------------------------- /src/templates/preview.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    1x
    7 |
    8 |
    Full
    9 |
    10 |
    12 |
    14 |
    15 | 16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    24 | 25 | 26 |
    27 |
    28 |
    29 |
    -------------------------------------------------------------------------------- /src/templates/settings.html: -------------------------------------------------------------------------------- 1 |
    2 |
    7 |
    8 | 9 |
    14 |
    15 | 16 |
    21 |
    22 | 23 |
    28 |
    29 | 30 |
    35 |
    36 | 37 |
    -------------------------------------------------------------------------------- /src/templates/settings/export.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/templates/settings/export/gif.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/settings/export/misc.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/settings/export/zip.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/templates/settings/preferences.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/templates/settings/preferences/tile.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/transformations.html: -------------------------------------------------------------------------------- 1 |
    2 |

    3 | Transform 4 | 9 |

    10 |
      11 |
      -------------------------------------------------------------------------------- /test/casperjs/DrawingTest.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var tests = require('../drawing/DrawingTests.casper.js').tests; 3 | 4 | // Polyfill for Object.assign (missing in PhantomJS) 5 | casper.options.clientScripts.push('./node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js'); 6 | 7 | var baseUrl = casper.cli.get('baseUrl')+"?debug"; 8 | var resultSelector = '#drawing-test-result'; 9 | 10 | 11 | casper.test.begin('Drawing Tests', tests.length, function(test) { 12 | casper.start(); 13 | 14 | var runTest = function (index) { 15 | var currentTest = 'drawing/tests/' + tests[index]; 16 | 17 | casper.open(baseUrl + "&test-run=" + currentTest); 18 | 19 | casper.then(function () { 20 | this.echo('Running test : ' + currentTest); 21 | this.echo('... Waiting for test result : ' + resultSelector); 22 | this.waitForSelector(resultSelector, function () { 23 | // then 24 | var result = this.getHTML(resultSelector); 25 | this.echo('... Test finished : ' + result); 26 | test.assertEquals(result, 'OK'); 27 | }, function () { 28 | // onTimeout 29 | test.fail('Test timed out'); 30 | }, 30 * 1000); 31 | }) 32 | .run(function () { 33 | if (tests[index+1]) { 34 | runTest(index+1); 35 | } else { 36 | test.done(); 37 | } 38 | }); 39 | }; 40 | 41 | runTest(0); 42 | }); 43 | })(); 44 | -------------------------------------------------------------------------------- /test/casperjs/integration/IntegrationSuite.js: -------------------------------------------------------------------------------- 1 | (typeof exports != "undefined" ? exports : pskl_exports).tests = [ 2 | 'palettes/test-tiny-palettes.js', 3 | 'preview/test-toggle-grid.js', 4 | 'settings/test-preferences-main.js', 5 | 'settings/test-export-gif.js', 6 | 'settings/test-export-gif-scale.js', 7 | 'settings/test-export-gif-simple.js', 8 | 'settings/test-export-png.js', 9 | 'settings/test-export-png-scale.js', 10 | 'settings/test-import-image.js', 11 | 'settings/test-import-image-empty.js', 12 | 'settings/test-import-image-twice.js', 13 | 'settings/test-resize-complete.js', 14 | 'settings/test-resize-content-complete.js', 15 | 'settings/test-resize-default-size.js', 16 | 'settings/test-resize-input-synchronization.js', 17 | 'settings/test-resize.js', 18 | 'settings/test-resize-origin.js', 19 | 'settings/test-settings-open-panels-on-click.js', 20 | ]; 21 | -------------------------------------------------------------------------------- /test/drawing/DrawingTests.browser.js: -------------------------------------------------------------------------------- 1 | {"tests" : [ 2 | "pen.drawing.json", 3 | "bucket.drawing.json", 4 | "color.picker.2.json", 5 | "color.picker.json", 6 | "frames.fun.json", 7 | "history.basic.json", 8 | "layers.duplicate.json", 9 | "layers.fun.json", 10 | "layers.merge.json", 11 | "layers.top.bottom.json", 12 | "lighten.darken.json", 13 | "move.json", 14 | "move-alllayers-allframes.json", 15 | "pen.mirror.pensize.json", 16 | "pen.secondary.color.json", 17 | "selection.rectangular.json", 18 | "squares.circles.json", 19 | "stroke.json", 20 | "verticalpen.drawing.json", 21 | "dithering.basic.json", 22 | "transform.center.json", 23 | "transform.clone.once.json", 24 | "transform.clone.twice.undo.once.json", 25 | "transform.crop.json", 26 | "transform.crop.selection.json", 27 | "transform.rotate.once.alt.json", 28 | "transform.rotate.twice.undo.once.json", 29 | "transform.rotate.alt.twice.undo.once.json", 30 | "transform.flip.once.alt.json", 31 | "transform.flip.twice.undo.once.json", 32 | "transform.flip.thrice.undo.all.redo.all.json", 33 | "selection.lasso.json", 34 | "swapcolor.twice.undo.once.json", 35 | "swapcolor.alllayers.allframes.twice.undo.once.json" 36 | ]} -------------------------------------------------------------------------------- /test/drawing/DrawingTests.casper.js: -------------------------------------------------------------------------------- 1 | (typeof exports != "undefined" ? exports : pskl_exports).tests = [ 2 | "pen.drawing.json", 3 | "color.picker.2.json", 4 | "color.picker.json", 5 | "frames.fun.json", 6 | "history.basic.json", 7 | "layers.duplicate.json", 8 | "layers.fun.json", 9 | "layers.merge.json", 10 | "layers.top.bottom.json", 11 | "move.json", 12 | "move-alllayers-allframes.json", 13 | "pen.mirror.pensize.json", 14 | "pen.secondary.color.json", 15 | "selection.rectangular.json", 16 | "squares.circles.json", 17 | "stroke.json", 18 | "verticalpen.drawing.json", 19 | "dithering.basic.json", 20 | "transform.center.json", 21 | "transform.clone.once.json", 22 | "transform.clone.twice.undo.once.json", 23 | "transform.crop.json", 24 | "transform.crop.selection.json", 25 | "transform.rotate.once.alt.json", 26 | "transform.rotate.twice.undo.once.json", 27 | "transform.rotate.alt.twice.undo.once.json", 28 | "transform.flip.once.alt.json", 29 | "transform.flip.twice.undo.once.json", 30 | "transform.flip.thrice.undo.all.redo.all.json", 31 | "selection.lasso.json", 32 | "swapcolor.twice.undo.once.json", 33 | "swapcolor.alllayers.allframes.twice.undo.once.json" 34 | ]; -------------------------------------------------------------------------------- /test/drawing/DrawingTests.pensize.js: -------------------------------------------------------------------------------- 1 | {"tests" : [ 2 | "pensize.circle.basic.json", 3 | "pensize.circle.undo.json", 4 | "pensize.eraser.basic.json", 5 | "pensize.eraser.undo.json", 6 | "pensize.pen.basic.json", 7 | "pensize.pen.undo.json", 8 | "pensize.rectangle.basic.json", 9 | "pensize.rectangle.undo.json", 10 | "pensize.stroke.basic.json", 11 | "pensize.stroke.undo.json" 12 | ]} -------------------------------------------------------------------------------- /test/drawing/DrawingTests.perf.js: -------------------------------------------------------------------------------- 1 | {"tests" : [ 2 | "perf.512.layers.undo.json", 3 | "perf.1024.pen.bucket.json" 4 | ]} -------------------------------------------------------------------------------- /test/drawing/tests/pensize.pen.undo.json: -------------------------------------------------------------------------------- 1 | {"events":[{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"type":"pensize-event","penSize":2},{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":1,"y":0},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":1,"y":0},"type":"mouse-event"},{"type":"pensize-event","penSize":3},{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":4,"y":1},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":4,"y":1},"type":"mouse-event"},{"type":"pensize-event","penSize":4},{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":5,"y":5},"type":"mouse-event"},{"event":{"type":"mousemove","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":5,"y":5},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":5,"y":5},"type":"mouse-event"},{"type":"keyboard-event","event":{"which":90,"shiftKey":false,"altKey":false,"ctrlKey":true,"target":{"nodeName":"BODY"}}}],"initialState":{"size":{"width":8,"height":8},"primaryColor":"#000000","secondaryColor":"rgba(0, 0, 0, 0)","selectedTool":"tool-pen","penSize":1},"png":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAK0lEQVQYV2NkYGD4z4AdMIKEwQQORSgKYGYgm4ZVAYZlMCtwOAPhBhoqAACekgMJrfUpnQAAAABJRU5ErkJggg=="} -------------------------------------------------------------------------------- /test/drawing/tests/transform.flip.once.alt.json: -------------------------------------------------------------------------------- 1 | {"events":[{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"type":"transformtool-event","toolId":"tool-flip","event":{"shiftKey":false,"altKey":true,"ctrlKey":false}}],"initialState":{"size":{"width":2,"height":2},"primaryColor":"#000000","secondaryColor":"rgba(0, 0, 0, 0)","selectedTool":"tool-pen"},"png":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFElEQVQIW2NkgAJGBgaG/wwMDIwABSkBAyQtNbwAAAAASUVORK5CYII="} -------------------------------------------------------------------------------- /test/drawing/tests/transform.rotate.once.alt.json: -------------------------------------------------------------------------------- 1 | {"events":[{"event":{"type":"mousedown","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"event":{"type":"mouseup","button":0,"shiftKey":false,"altKey":false,"ctrlKey":false},"coords":{"x":0,"y":0},"type":"mouse-event"},{"type":"transformtool-event","toolId":"tool-rotate","event":{"shiftKey":false,"altKey":true,"ctrlKey":false}}],"initialState":{"size":{"width":2,"height":2},"primaryColor":"#ff0000","secondaryColor":"rgba(0, 0, 0, 0)","selectedTool":"tool-stroke"},"png":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQIW2NkYGBg+M/A8J8RxAABABcWAgFMzp95AAAAAElFTkSuQmCC"} -------------------------------------------------------------------------------- /test/js/model/LayerTest.js: -------------------------------------------------------------------------------- 1 | describe("Layer model test", function() { 2 | 3 | beforeEach(function() {}); 4 | afterEach(function() {}); 5 | 6 | it("has proper defaults", function() { 7 | var layer = new pskl.model.Layer('layerName'); 8 | 9 | expect(layer.getOpacity()).toBe(1); 10 | expect(layer.getFrames().length).toBe(0); 11 | expect(layer.getName()).toBe('layerName'); 12 | }); 13 | 14 | it("can set opacity", function() { 15 | var layer = new pskl.model.Layer('layerName'); 16 | 17 | layer.setOpacity(0.5); 18 | expect(layer.getOpacity()).toBe(0.5); 19 | }); 20 | 21 | it("ignores bad opacity", function() { 22 | var layer = new pskl.model.Layer('layerName'); 23 | 24 | layer.setOpacity(0.3); 25 | expect(layer.getOpacity()).toBe(0.3); 26 | 27 | layer.setOpacity('Yep I\'m an opacity, let me in !'); 28 | expect(layer.getOpacity()).toBe(0.3); 29 | 30 | layer.setOpacity(9000); 31 | expect(layer.getOpacity()).toBe(0.3); 32 | 33 | layer.setOpacity(-1); 34 | expect(layer.getOpacity()).toBe(0.3); 35 | 36 | layer.setOpacity(null); 37 | expect(layer.getOpacity()).toBe(0.3); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/js/model/PaletteTest.js: -------------------------------------------------------------------------------- 1 | describe("Palette", function() { 2 | 3 | beforeEach(function() {}); 4 | afterEach(function() {}); 5 | 6 | it("moves colors correctly", function() { 7 | // when 8 | var colors = [ 9 | '#000000', 10 | '#111111', 11 | '#222222' 12 | ]; 13 | var palette = new pskl.model.Palette('id', 'name', colors); 14 | 15 | // then 16 | palette.move(2,0); 17 | 18 | // verify 19 | expect(palette.get(0)).toBe('#222222'); 20 | expect(palette.get(1)).toBe('#000000'); 21 | expect(palette.get(2)).toBe('#111111'); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/js/rendering/CanvasRendererTest.js: -------------------------------------------------------------------------------- 1 | describe("Canvas Renderer test", function() { 2 | var BLACK = '#000000'; 3 | var WHITE = '#ffffff'; 4 | var TRANS = Constants.TRANSPARENT_COLOR; 5 | 6 | beforeEach(function() {}); 7 | afterEach(function() {}); 8 | 9 | it("draws transparent as white by default", function() { 10 | // create frame 11 | var frame = pskl.model.Frame.fromPixelGrid(test.testutils.toFrameGrid([ 12 | [BLACK, TRANS], 13 | [TRANS, BLACK] 14 | ])); 15 | 16 | var renderer = new pskl.rendering.CanvasRenderer(frame, 1); 17 | var canvas = renderer.render(); 18 | 19 | var frameFromCanvas = pskl.utils.FrameUtils.createFromImage(canvas); 20 | 21 | test.testutils.colorEqualsColor(frameFromCanvas.getPixel(0,0), BLACK); 22 | test.testutils.colorEqualsColor(frameFromCanvas.getPixel(0,1), WHITE); 23 | test.testutils.colorEqualsColor(frameFromCanvas.getPixel(1,0), WHITE); 24 | test.testutils.colorEqualsColor(frameFromCanvas.getPixel(1,1), BLACK); 25 | }); 26 | }); -------------------------------------------------------------------------------- /test/js/service/SelectedColorsServiceTest.js: -------------------------------------------------------------------------------- 1 | 2 | describe("SelectedColorsService test suite", function() { 3 | it("returns the default selected colors initially", function() { 4 | var service = new pskl.service.SelectedColorsService(); 5 | 6 | expect(service.getPrimaryColor()).toBe(Constants.DEFAULT_PEN_COLOR); 7 | expect(service.getSecondaryColor()).toBe(Constants.TRANSPARENT_COLOR); 8 | }); 9 | 10 | it("reacts to PRIMARY_COLOR_SELECTED event", function() { 11 | var service = new pskl.service.SelectedColorsService(); 12 | service.init(); 13 | 14 | var expectedColor = "#123456"; 15 | $.publish(Events.PRIMARY_COLOR_SELECTED, [expectedColor]); 16 | 17 | expect(service.getPrimaryColor()).toBe(expectedColor); 18 | expect(service.getSecondaryColor()).toBe(Constants.TRANSPARENT_COLOR); 19 | }); 20 | 21 | it("reacts to SECONDARY_COLOR_SELECTED event", function() { 22 | var service = new pskl.service.SelectedColorsService(); 23 | service.init(); 24 | 25 | var expectedColor = "#123456"; 26 | $.publish(Events.SECONDARY_COLOR_SELECTED, [expectedColor]); 27 | 28 | expect(service.getPrimaryColor()).toBe(Constants.DEFAULT_PEN_COLOR); 29 | expect(service.getSecondaryColor()).toBe(expectedColor); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/js/utils/ColorUtilsTest.js: -------------------------------------------------------------------------------- 1 | describe("Color utils", function() { 2 | 3 | beforeEach(function() {}); 4 | afterEach(function() {}); 5 | 6 | it("returns a color when provided with array of colors", function() { 7 | // when/then 8 | var unusedColor = pskl.utils.ColorUtils.getUnusedColor(['#ffff00', '#feff00', '#fdff00']); 9 | // verify 10 | expect(unusedColor).toBe('#FCFF00'); 11 | 12 | // when/then 13 | unusedColor = pskl.utils.ColorUtils.getUnusedColor(['#fcff00', '#feff00', '#fdff00']); 14 | // verify 15 | expect(unusedColor).toBe('#FFFF00'); 16 | }); 17 | 18 | it("returns a color for an empty array", function() { 19 | // when/then 20 | var unusedColor = pskl.utils.ColorUtils.getUnusedColor([]); 21 | // verify 22 | expect(unusedColor).toBe('#FFFF00'); 23 | 24 | // when/then 25 | unusedColor = pskl.utils.ColorUtils.getUnusedColor(); 26 | // verify 27 | expect(unusedColor).toBe('#FFFF00'); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/js/utils/CoreTest.js: -------------------------------------------------------------------------------- 1 | describe("Core utils tests", function() { 2 | 3 | beforeEach(function() {}); 4 | afterEach(function() {}); 5 | 6 | it("colorToInt parses red", function() { 7 | var RED = 4278190335; 8 | 9 | expect(pskl.utils.colorToInt("red")).toBe(RED); 10 | expect(pskl.utils.colorToInt("rgb(255,0,0)")).toBe(RED); 11 | expect(pskl.utils.colorToInt("rgba(255,0,0,1)")).toBe(RED); 12 | expect(pskl.utils.colorToInt("#FF0000")).toBe(RED); 13 | expect(pskl.utils.colorToInt("#ff0000")).toBe(RED); 14 | expect(pskl.utils.colorToInt("#f00")).toBe(RED); 15 | expect(pskl.utils.colorToInt("#f00")).toBe(RED); 16 | }); 17 | 18 | it("colorToInt parses white", function() { 19 | var WHITE = 4294967295; 20 | 21 | expect(pskl.utils.colorToInt("white")).toBe(WHITE); 22 | expect(pskl.utils.colorToInt("rgb(255,255,255)")).toBe(WHITE); 23 | expect(pskl.utils.colorToInt("rgba(255,255,255,1)")).toBe(WHITE); 24 | expect(pskl.utils.colorToInt("#FFFFFF")).toBe(WHITE); 25 | expect(pskl.utils.colorToInt("#ffffff")).toBe(WHITE); 26 | expect(pskl.utils.colorToInt("#FFF")).toBe(WHITE); 27 | expect(pskl.utils.colorToInt("#fff")).toBe(WHITE); 28 | }); 29 | 30 | it("colorToInt parses transparent", function() { 31 | var TRANSPARENT = 0; 32 | 33 | expect(pskl.utils.colorToInt("transparent")).toBe(TRANSPARENT); 34 | expect(pskl.utils.colorToInt("rgba(100,120,150, 0)")).toBe(TRANSPARENT); 35 | expect(pskl.utils.colorToInt("rgba(255,255,255,0)")).toBe(TRANSPARENT); 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/js/utils/PixelUtilsTest_visit_connected.js: -------------------------------------------------------------------------------- 1 | describe("PixelUtils visitor methods tests", function() { 2 | var black = '#000000'; 3 | var red = '#ff0000'; 4 | var transparent = Constants.TRANSPARENT_COLOR; 5 | var B = black, R = red, T = transparent; 6 | 7 | beforeEach(function() {}); 8 | afterEach(function() {}); 9 | 10 | var containsPixel = function (pixels, col, row) { 11 | return pixels.some(function (p) { 12 | return p.col === col && p.row === row; 13 | }); 14 | }; 15 | 16 | it("getSimilarConnectedPixelsFromFrame works", function() { 17 | var frame = pskl.model.Frame.fromPixelGrid(test.testutils.toFrameGrid([ 18 | [T, T, B], 19 | [B, T, B], 20 | [T, T, B], 21 | ])); 22 | 23 | var pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, 0, 0); 24 | expect(pixels.length).toBe(5); 25 | expect(containsPixel(pixels, 0, 0)).toBe(true); 26 | expect(containsPixel(pixels, 1, 0)).toBe(true); 27 | expect(containsPixel(pixels, 1, 1)).toBe(true); 28 | expect(containsPixel(pixels, 0, 2)).toBe(true); 29 | expect(containsPixel(pixels, 1, 2)).toBe(true); 30 | 31 | pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, -1, -1); 32 | expect(Array.isArray(pixels)).toBe(true); 33 | expect(pixels.length).toBe(0); 34 | 35 | pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, 0, 1); 36 | expect(pixels.length).toBe(1); 37 | expect(containsPixel(pixels, 0, 1)).toBe(true); 38 | 39 | pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, 2, 1); 40 | expect(pixels.length).toBe(3); 41 | expect(containsPixel(pixels, 2, 0)).toBe(true); 42 | expect(containsPixel(pixels, 2, 1)).toBe(true); 43 | expect(containsPixel(pixels, 2, 2)).toBe(true); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/js/utils/UuidTest.js: -------------------------------------------------------------------------------- 1 | describe("UUID Generator", function() { 2 | 3 | beforeEach(function() {}); 4 | afterEach(function() {}); 5 | 6 | it("returns valid uuids", function() { 7 | // when 8 | 9 | // then 10 | var uuid1 = pskl.utils.Uuid.generate(); 11 | var uuid2 = pskl.utils.Uuid.generate(); 12 | 13 | // verify 14 | expect(typeof uuid1).toBe("string"); 15 | expect(uuid1.length).toBe(36); 16 | var splits = uuid1.split('-'); 17 | expect(splits.length).toBe(5); 18 | 19 | expect(splits[0].length).toBe(8); 20 | expect(splits[1].length).toBe(4); 21 | expect(splits[2].length).toBe(4); 22 | expect(splits[3].length).toBe(4); 23 | expect(splits[4].length).toBe(12); 24 | 25 | expect(uuid1).not.toBe(uuid2); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/js/utils/serialization/Deserializer_v0Test.js: -------------------------------------------------------------------------------- 1 | describe("Deserializer v0 test", function() { 2 | 3 | var black = '#000000'; 4 | var transparent = Constants.TRANSPARENT_COLOR; 5 | var data = [ 6 | [ 7 | ["#000000", "TRANSPARENT"], 8 | ["TRANSPARENT", "#000000"] 9 | ] 10 | ]; 11 | 12 | it("deserializes data serialized for model v0 correctly", function (done) { 13 | var deserializer = pskl.utils.serialization.Deserializer; 14 | deserializer.deserialize(data, function (p) { 15 | // Check the frame has been properly deserialized 16 | expect(p.getLayerAt(0).getFrames().length).toBe(1); 17 | var frame = p.getLayerAt(0).getFrameAt(0); 18 | test.testutils.frameEqualsGrid(frame, [ 19 | [black, transparent], 20 | [transparent, black] 21 | ]); 22 | done(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/js/utils/serialization/Deserializer_v1Test.js: -------------------------------------------------------------------------------- 1 | describe("Deserializer v1 test", function() { 2 | var B = '#000000'; 3 | var T = Constants.TRANSPARENT_COLOR; 4 | var data = { 5 | "modelVersion": 1, 6 | "piskel": { 7 | "height": 2, 8 | "width": 2, 9 | "layers": [ 10 | "{\"name\":\"Layer 1\",\"frames\":[\"[[\\\"#000000\\\",\\\"TRANSPARENT\\\"],[\\\"TRANSPARENT\\\",\\\"#000000\\\"]]\"]}" 11 | ] 12 | } 13 | }; 14 | 15 | it("deserializes data serialized for model v0 correctly", function (done) { 16 | var deserializer = pskl.utils.serialization.Deserializer; 17 | deserializer.deserialize(data, function (p) { 18 | // Check the frame has been properly deserialized 19 | expect(p.getLayerAt(0).getFrames().length).toBe(1); 20 | var frame = p.getLayerAt(0).getFrameAt(0); 21 | test.testutils.frameEqualsGrid(frame, [ 22 | [B, T], 23 | [T, B] 24 | ]); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | --------------------------------------------------------------------------------