Merge branch 'master' into gh-pages

This commit is contained in:
juliandescottes 2012-08-30 17:00:52 +02:00
commit 7cbe90f463
10 changed files with 37 additions and 753 deletions

View File

@ -5,11 +5,24 @@ The goal is to create an easy-to-use/in-the-cloud/web-based 2d animation editor.
Try it at : http://juliandescottes.github.com/piskel/
v0.0something (with 2 pull requests from grosbouddha)
v0.0almostthere
------------------------------------
**30 Aug 2012** : Many new features in 2 days :
* __save animations__, they are persisted in the cloud, and can be retrieved via a __unique URL__
* __color picker__, no longer limited to black and white
* __local storage__, your work is automatically backed up locally
* __color palette__, listing all the colors already used in the animation
* __slider__ for choosing the speed of the preview
UI was slightly updated :
![Screenshot 2](https://dl.dropbox.com/u/17803671/screen_piskel_2.png "Screenshot 2")
v0.0something
------------------------------------
**28 Aug 2012** : Thanks to grosbouddha, new features added to Piskel :
* modify preview speed !
* remove (shitty) frames
* remove frames
* transparent background
v0.0whatever (aka the thing I did last night)

View File

@ -152,7 +152,8 @@ ul, li {
border: #F0C36D 1px solid;
border-bottom: 0;
font-weight: bold;
font-size: 14px;
font-size: 14px;
z-index: 10000;
}
/* Force apparition of scrollbars on leopard */

View File

@ -55,6 +55,7 @@
var frameId = this.getFrameIdFromUrl();
if (frameId) {
this.displayMessage("Loading animation with id : [" + frameId + "]");
this.loadFramesheetFromService(frameId);
} else {
this.finishInit();
@ -86,10 +87,12 @@
xhr.onload = function(e) {
frameSheet.deserialize(this.responseText);
piskel.removeMessage();
piskel.finishInit();
};
xhr.onerror = function () {
piskel.removeMessage();
piskel.finishInit();
};
@ -98,16 +101,25 @@
initLocalStorageBackup: function() {
if(window.localStorage && window.localStorage['snapShot']) {
var message = document.createElement('div');
message.id = "user-message";
message.className = "user-message";
var reloadLink = "<a href='#' onclick='piskel.restoreFromLocalStorage()'>reload</a>";
var discardLink = "<a href='#' onclick='piskel.cleanLocalStorage()'>discard</a>";
message.innerHTML = "Non saved version found. " + reloadLink + " or " + discardLink;
message.onclick = function() {
message.parentNode.removeChild(message);
};
document.body.appendChild(message);
this.displayMessage("Non saved version found. " + reloadLink + " or " + discardLink);
}
},
displayMessage : function (content) {
var message = document.createElement('div');
message.id = "user-message";
message.className = "user-message";
message.innerHTML = content;
message.onclick = this.removeMessage;
document.body.appendChild(message);
},
removeMessage : function () {
var message = $("user-message");
if (message) {
message.parentNode.removeChild(message);
}
},

View File

@ -1,36 +0,0 @@
html, body {
height : 100%;
}
.debug {
border : 1px Solid black;
}
.left-nav {
position:absolute;
top : 0;
bottom : 0;
width : 200px;
background : #000;
}
.main-panel {
position:absolute;
top : 0;
bottom : 0;
left : 200px;
right : 0;
background : #ccc;
}
.preview-container {
position : absolute;
top : 30px;
right : 0;
height : 200px;
width : 200px;
background : white;
border : 0px Solid black;
border-radius:5px 0px 0px 5px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.2);
}

View File

@ -1,140 +0,0 @@
html, body {
height : 100%;
margin : 0;
cursor : default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul, li {
margin : 0;
padding : 0;
}
.debug {
border : 1px Solid black;
}
.left-nav {
position:absolute;
top : 0;
bottom : 0;
width : 200px;
overflow-y: scroll;
background : #000;
padding : 10px;
}
.main-panel {
position:absolute;
top : 0;
bottom : 0;
left : 220px;
right : 0;
background : #ccc;
}
.preview-container {
position : absolute;
top : 30px;
right : 0;
height : 256px;
width : 256px;
background : white;
border : 0px Solid black;
border-radius:5px 0px 0px 5px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.2);
}
.preview-container canvas {
border : 0px Solid transparent;
border-radius:5px 0px 0px 5px;
}
#cursorInfo {
position : fixed;
cursor : default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.action-button {
background-color : white;
width : 150px;
display : inline-block;
}
#preview-list {
list-style-type: none;
}
.preview-tile {
padding : 10px;
overflow: hidden;
background-color: gray;
}
.preview-tile .canvas-container {
float: left;
}
.preview-tile .tile-view {
float: left;
border: blue 1px solid;
}
.preview-tile .tile-action {
display: none;
float: right;
}
.preview-tile:hover .tile-action {
display: block;
}
.preview-tile:hover {
background-color: lightgray;
}
#preview-list .preview-tile.selected {
background-color: lightyellow;
}
.canvas-container {
position: relative;
display: block;
}
.canvas-container .canvas-background {
background: url(../img/transparent_background.png) repeat;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.canvas {
position: relative;
z-index: 1;
}
/* Force apparition of scrollbars on leopard */
::-webkit-scrollbar {
-webkit-appearance: none;
width: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: rgba(180,180,180,.7);
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,42 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Piskel</title>
<meta name="description" content="">
<meta name="author" content="Julian Descottes">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class='debug left-nav'>
<a onclick="piskel.reload()" style="color:white">Get URL</a>
<button id='add-frame-button' class="action-button">
Add a Frame
</button>
<ul id="preview-list">
</ul>
</div>
<div class='main-panel'>
<div id="drawing-canvas-container" class="canvas-container">
<div class="canvas-background"></div>
</div>
<div class='preview-container'>
<div id='preview-canvas-container' class="canvas-container">
<div class="canvas-background"></div>
</div>
<div>
<label>Preview FPS:</label><input id="preview-fps" type="text" value="12" />
</div>
</div>
</div>
<div id="cursorInfo"></div>
<script src="js/frameSheetModel.js"></script>
<script src="js/piskel.js"></script>
</body>
</html>

View File

@ -1,35 +0,0 @@
// Generated by CoffeeScript 1.3.1
(function() {
$(document).ready(function() {
var Piskel;
Piskel = Em.Application.create();
Piskel.mainView = Em.View.create({
templateName: 'main',
onCanvasClick: function() {}
});
Piskel.mainView.append;
Piskel.Art = Ember.Object.extend({
setContext: function(context) {
this.context = context;
},
clear: function() {
this.context.save();
return this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
}
});
$('#main').ready(function() {
var context, mainCanvas;
mainCanvas = document.getElementById('main');
context = mainCanvas.getContext('2d');
context.save();
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.fillStyle = 'white';
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.restore();
return Piskel.mainCanvas = document.getElementById('main');
});
return window.Piskel = Piskel;
});
}).call(this);

View File

@ -1,101 +0,0 @@
var FrameSheetModel = (function() {
var inst;
var frames = [];
var width;
var height;
var createEmptyFrame_ = function() {
var emptyFrame = new Array(width);
for (var columnIndex=0; columnIndex < width; columnIndex++) {
emptyFrame[columnIndex] = new Array(height);
}
return emptyFrame;
};
var serializeFrame_ = function (frame) {
var buffer = [];
for (var i = 0 ; i < frame.length ; i++) {
var serializedLine = "";
for(var j = 0 ; j < frame[i].length ; j++) {
if (typeof frame[i][j] == 'undefined' || frame[i][j] == 'tc') {
serializedLine += "0"
} else {
serializedLine += "1"
}
}
buffer.push(parseInt(serializedLine, 2).toString(36));
}
return buffer.join(",");
};
return {
validate: function() {
return true; // I'm always right dude
},
// Could be use to pass around model using long GET param (good enough for simple models) and
// do some temporary locastorage
serialize: function() {
var buffer = [];
for (var i = 0 ; i < frames.length ; i++) {
buffer.push(serializeFrame_(frames[i]));
}
return buffer.join("+");
//throw "FrameSheet.serialize Not implemented"
},
addEmptyFrame: function() {
this.addFrame(createEmptyFrame_());
},
addFrame : function (frame) {
frames.push(frame);
},
getFrameCount: function() {
return frames.length;
},
getFrameByIndex: function(index) {
if (isNaN(index)) {
throw "Bad argument value for getFrameByIndex method: <" + index + ">"
} else if (index < 0 || index > frames.length) {
throw "Out of bound index for frameSheet object."
}
return frames[index];
},
removeFrameByIndex: function(index) {
if(index < 0 || index > inst.getFrameCount()) {
throw "Bad index value for removeFrameByIndex.";
}
frames.splice(index, 1);
},
duplicateFrameByIndex: function(frameToDuplicateIndex) {
var frame = inst.getFrameByIndex(frameToDuplicateIndex);
var clonedFrame = [];
for(var i=0, l=frame.length; i<l; i++) {
clonedFrame.push(frame[i].slice(0));
}
frames.splice(frameToDuplicateIndex + 1, 0, clonedFrame);
},
getInstance: function(width_, height_) {
if (isNaN(width_) || isNaN(height_)) {
throw "Bad FrameSheetModel initialization in getInstance method.";
}
inst = this;
width = width_;
height = height_;
return inst;
}
}
})();

View File

@ -1,388 +0,0 @@
(function ($) {
var frameSheet,
// Constants:
TRANSPARENT_COLOR = 'tc',
//TRANSPARENT_COLOR = 'pink',
DEFAULT_PEN_COLOR = '#000',
// Configuration:
// Canvas size in pixel size (not dpi related)
framePixelWidth = 32,
framePixelHeight = 32,
// Scaling factors for a given frameSheet rendering:
// Main drawing area:
drawingCanvasDpi = 10,
// Canvas previous in the slideshow:
previewTileCanvasDpi = 4,
// Ainmated canvas preview:
previewAnimationCanvasDpi = 8,
// DOM references:
drawingAreaContainer,
drawingAreaCanvas,
previewCanvas,
// States:
isClicked = false,
isRightClicked = false,
activeFrameIndex = -1,
animIndex = 0,
penColor = DEFAULT_PEN_COLOR;
var piskel = {
init : function () {
if (this.hasGetParam()) {
this.initFromGetParam();
} else {
frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
frameSheet.addEmptyFrame();
}
this.setActiveFrame(0);
this.initDrawingArea();
this.initPreviewSlideshow();
this.initAnimationPreview();
},
hasGetParam : function () {
return window.location.href.indexOf('frame=') != -1;
},
reload : function () {
var baseUrl = window.location.href.replace(window.location.search, "");
window.location = baseUrl + "?frame=" + this.serialize();
},
initFromGetParam : function () {
frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
var href = window.location.href;
// todo find ending ...
var getBase16 = href.substring(href.indexOf('frame=') + 6);
var serializedFrames = getBase16.split("+");
var binaryFrames = [];
for (var i = 0 ; i < serializedFrames.length ; i++) {
var chunks = serializedFrames[i].split(",");
binaryFrames[i] = "";
for (var j =0 ; j < chunks.length ; j++) {
var binaryEnding = parseInt(chunks[j], 36).toString(2);
var missingZeros = (new Array(1 + 32 - binaryEnding.length)).join("0");
binaryFrames[i] += (missingZeros + binaryEnding);
console.log((missingZeros + binaryEnding).length);
}
}
for (var i = 0 ; i < binaryFrames.length ; i++) {
var bFrame = binaryFrames[i];
var frame = new Array(framePixelWidth);
for (var j = 0 ; j < framePixelWidth ; j++) {
frame[j] = new Array(framePixelHeight);
for (var k = 0 ; k < framePixelHeight ; k++) {
if (bFrame.charAt(j*framePixelWidth + k) == "0") {
frame[j][k] = TRANSPARENT_COLOR;
} else {
frame[j][k] = DEFAULT_PEN_COLOR;
}
}
}
frameSheet.addFrame(frame);
}
},
serialize: function () {
return frameSheet.serialize();
},
setActiveFrame: function(index) {
activeFrameIndex = index;
},
setActiveFrameAndRedraw: function(index) {
this.setActiveFrame(index);
// Update drawing canvas:
this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
// Update slideshow:
this.createPreviews();
// Update animation preview:
animIndex = 0;
},
getActiveFrameIndex: function() {
if(-1 == activeFrameIndex) {
throw "Bad active frane initialization."
}
return activeFrameIndex;
},
initDrawingArea : function() {
drawingAreaContainer = $('drawing-canvas-container');
drawingAreaCanvas = document.createElement("canvas");
drawingAreaCanvas.className = 'canvas';
drawingAreaCanvas.setAttribute('width', '' + framePixelWidth * drawingCanvasDpi);
drawingAreaCanvas.setAttribute('height', '' + framePixelHeight * drawingCanvasDpi);
drawingAreaContainer.setAttribute('style',
'width:' + framePixelWidth * drawingCanvasDpi + 'px; height:' + framePixelHeight * drawingCanvasDpi + 'px;');
drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(arguments[0])');
drawingAreaContainer.appendChild(drawingAreaCanvas);
var body = document.getElementsByTagName('body')[0];
body.setAttribute('onmouseup', 'piskel.onCanvasMouseup(arguments[0])');
drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(arguments[0])');
drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(arguments[0])');
this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
},
initPreviewSlideshow: function() {
var addFrameButton = $('add-frame-button');
addFrameButton.addEventListener('mousedown', function() {
frameSheet.addEmptyFrame();
piskel.setActiveFrameAndRedraw(frameSheet.getFrameCount() - 1);
});
this.createPreviews();
},
initAnimationPreview : function() {
var scope = this;
var previewAnimationContainer = $('preview-canvas-container');
previewCanvas = document.createElement('canvas');
previewCanvas.className = 'canvas';
previewAnimationContainer.setAttribute('style',
'width:' + framePixelWidth * previewAnimationCanvasDpi + 'px; height:' + framePixelHeight * previewAnimationCanvasDpi + 'px;');
previewAnimationContainer.appendChild(previewCanvas);
previewCanvas.setAttribute('width', framePixelWidth * previewAnimationCanvasDpi);
previewCanvas.setAttribute('height', framePixelHeight * previewAnimationCanvasDpi);
var animFPSTuner = document.getElementById("preview-fps");
var animPreviewFPS = parseInt(animFPSTuner.value, 10);
var startPreviewRefresh = function() {
return setInterval(scope.refreshAnimatedPreview, 1000/animPreviewFPS);
};
var refreshUpdater = startPreviewRefresh();
animFPSTuner.addEventListener('keyup', function(evt) {
window.clearInterval(refreshUpdater);
animPreviewFPS = parseInt(animFPSTuner.value, 10);
if(isNaN(animPreviewFPS)) {
animPreviewFPS = 1;
}
if(evt.keyCode == 38) {
animPreviewFPS++;
}
else if (evt.keyCode == 40) {
animPreviewFPS--;
}
if(animPreviewFPS < 1) {
animPreviewFPS = 1;
}
if(animPreviewFPS > 100) {
animPreviewFPS = 100;
}
animFPSTuner.value = animPreviewFPS;
refreshUpdater = startPreviewRefresh();
});
},
createPreviews : function () {
var container = $('preview-list'), previewTile;
container.innerHTML = "";
for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) {
previewTile = this.createPreviewTile(i);
container.appendChild(previewTile);
}
},
createPreviewTile: function(tileNumber) {
var previewTileRoot = document.createElement("li");
var classname = "preview-tile";
if (this.getActiveFrameIndex() == tileNumber) {
classname += " selected";
}
previewTileRoot.className = classname;
var canvasContainer = document.createElement("div");
canvasContainer.className = "canvas-container";
canvasContainer.setAttribute('style',
'width:' + framePixelWidth * previewTileCanvasDpi + 'px; height:' + framePixelHeight * previewTileCanvasDpi + 'px;');
var canvasBackground = document.createElement("div");
canvasBackground.className = "canvas-background";
canvasContainer.appendChild(canvasBackground);
var canvasPreview = document.createElement("canvas");
canvasPreview.className = "canvas tile-view"
canvasPreview.setAttribute('width', framePixelWidth * previewTileCanvasDpi);
canvasPreview.setAttribute('height', framePixelHeight * previewTileCanvasDpi);
previewTileRoot.addEventListener('click', function(evt) {
// has not class tile-action:
// TODO: let me know when you want to start using a framework :)
if(!evt.target.className.match(new RegExp('(\\s|^)'+ 'tile-action' +'(\\s|$)'))) {
piskel.setActiveFrameAndRedraw(tileNumber);
}
});
var canvasPreviewDuplicateAction = document.createElement("button");
canvasPreviewDuplicateAction.className = "tile-action"
canvasPreviewDuplicateAction.innerHTML = "dup"
canvasPreviewDuplicateAction.addEventListener('click', function(evt) {
piskel.duplicateFrame(tileNumber);
});
this.drawFrameToCanvas(frameSheet.getFrameByIndex(tileNumber), canvasPreview, previewTileCanvasDpi);
canvasContainer.appendChild(canvasPreview);
previewTileRoot.appendChild(canvasContainer);
previewTileRoot.appendChild(canvasPreviewDuplicateAction);
if(tileNumber > 0 || frameSheet.getFrameCount() > 1) {
var canvasPreviewDeleteAction = document.createElement("button");
canvasPreviewDeleteAction.className = "tile-action"
canvasPreviewDeleteAction.innerHTML = "del"
canvasPreviewDeleteAction.addEventListener('click', function(evt) {
frameSheet.removeFrameByIndex(tileNumber);
animIndex = 0;
piskel.createPreviews();
});
previewTileRoot.appendChild(canvasPreviewDeleteAction);
}
return previewTileRoot;
},
refreshAnimatedPreview : function () {
piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi);
animIndex++;
if (animIndex == frameSheet.getFrameCount()) {
animIndex = 0;
}
},
removeFrame: function(frameIndex) {
frameSheet.removeFrameByIndex(frameIndex);
this.setActiveFrameAndRedraw(frameIndex - 1);
},
duplicateFrame: function(frameIndex) {
frameSheet.duplicateFrameByIndex(frameIndex);
this.setActiveFrameAndRedraw(frameIndex + 1);
},
updateCursorInfo : function (event) {
var cursor = $('cursorInfo');
cursor.style.top = event.clientY + 10 + "px";
cursor.style.left = event.clientX + 10 + "px";
var coordinates = this.getRelativeCoordinates(event.clientX, event.clientY)
cursor.innerHTML = [
"X : " + coordinates.x,
"Y : " + coordinates.y
].join(", ");
},
onCanvasMousedown : function (event) {
isClicked = true;
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
if(event.button == 0) {
this.drawAt(coords.x, coords.y, penColor);
} else {
// Right click used to delete.
isRightClicked = true;
this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
}
},
onCanvasMousemove : function (event) {
//this.updateCursorInfo(event);
if (isClicked) {
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
if(isRightClicked) {
this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
} else {
this.drawAt(coords.x, coords.y, penColor);
}
}
},
onCanvasMouseup : function (event) {
if(isClicked || isRightClicked) {
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
this.createPreviews();
}
isClicked = false;
isRightClicked = false;
},
drawAt : function (x, y, color) {
var pixelWidthIndex = (x - x%drawingCanvasDpi) / 10;
var pixelHeightIndex = (y - y%drawingCanvasDpi) / 10;
// Update model:
var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex());
// TODO: make a better accessor for pixel state update:
// TODO: Make pen color dynamic:
currentFrame[pixelWidthIndex][pixelHeightIndex] = color;
// Update view:
// TODO: Create a per pixel update function for perf ?
this.drawFrameToCanvas(currentFrame, drawingAreaCanvas, drawingCanvasDpi);
},
// TODO: move that to a FrameRenderer (/w cache) ?
drawFrameToCanvas: function(frame, canvasElement, dpi) {
var pixelColor, context = canvasElement.getContext('2d');
for(var col = 0, num_col = frame.length; col < num_col; col++) {
for(var row = 0, num_row = frame[col].length; row < num_row; row++) {
pixelColor = frame[col][row];
if(pixelColor == undefined || pixelColor == TRANSPARENT_COLOR) {
context.clearRect(col * dpi, row * dpi, dpi, dpi);
} else {
context.fillStyle = pixelColor;
context.fillRect(col * dpi, row * dpi, dpi, dpi);
}
}
}
},
onCanvasContextMenu : function (event) {
event.preventDefault();
event.stopPropagation();
event.cancelBubble = true;
return false;
},
getRelativeCoordinates : function (x, y) {
var canvasRect = drawingAreaCanvas.getBoundingClientRect();
return {
x : x - canvasRect.left,
y : y - canvasRect.top
}
}
};
window.piskel = piskel;
piskel.init();
})(function(id){return document.getElementById(id)});