MAAAJOOOOR

This commit is contained in:
Mahdi Dibaiee 2014-02-05 16:32:07 +03:30
parent e7d1c2399f
commit cfe3bb7e99
17 changed files with 1307 additions and 94 deletions

90
css/color-picker.css Normal file
View File

@ -0,0 +1,90 @@
/* Purty Picker Copyright 2013 Jayden Seric (MIT license): https://github.com/jaydenseric/Purty-Picker */
/* Core: No touchy! */
.color-picker .spectrum {
position: relative;
/* To position pin, luminosity filter */
background: linear-gradient(gray, transparent), linear-gradient(90deg, red, #ff2b00, #ff5500, #ff8000, #ffaa00, #ffd500, yellow, #d4ff00, #aaff00, #80ff00, #55ff00, #2bff00, lime, #00ff2b, #00ff55, #00ff80, #00ffaa, #00ffd5, cyan, #00d4ff, #00aaff, #007fff, #0055ff, #002bff, blue, #2a00ff, #5500ff, #7f00ff, #aa00ff, #d400ff, magenta, #ff00d4, #ff00aa, #ff0080, #ff0055, #ff002b, red);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* Prevent pin interaction causing content selection */
cursor: crosshair;
}
.color-picker .spectrum.active {
cursor: none;
}
.color-picker .spectrum.active .pin {
cursor: none;
}
.color-picker .spectrum > div {
/* Luminosity filter */
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.color-picker .spectrum .pin {
position: absolute;
cursor: move;
}
/* Customization: Default skin */
.color-picker {
margin: 20px;
padding: 11px;
border: 1px solid #e3e3e3;
border-radius: 4px;
background-color: #f5f5f5;
}
.color-picker .color,
.color-picker .luminosity {
-moz-box-sizing: border-box;
box-sizing: border-box;
display: block;
width: 100%;
}
.color-picker .format {
display: block;
margin: 0 auto 10px auto;
}
.color-picker .color {
-webkit-appearance: none;
border: 0;
border-radius: 2px;
padding: 10px;
text-align: center;
font-size: 11px;
letter-spacing: 1px;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
color: rgba(0, 0, 0, 0.6);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
transition: color 0.2s;
}
.color-picker .color.dark {
color: rgba(255, 255, 255, 0.7);
}
.color-picker .spectrum {
height: 150px;
/* Arbitary but required */
overflow: hidden;
/* Prevent pin overflowing container */
border-radius: 2px;
margin: 10px 0;
}
.color-picker .spectrum > div {
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
}
.color-picker .spectrum .pin {
margin-left: -4px;
margin-top: -4px;
width: 4px;
height: 4px;
border: 2px solid white;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
border-radius: 100%;
}
.color-picker .luminosity {
margin: 0;
}

BIN
css/imgs/clear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
css/imgs/redo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
css/imgs/undo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -65,7 +65,25 @@ button {
border: none;
}
.picker {
width: 30rem;
height: 30rem;
position: absolute;
left: 50%;
top: 50%;
margin-top: -15rem;
margin-left: -15rem;
#closePicker {
width: 2rem;
height: 2rem;
border: 1px solid rgb(227, 227, 227);
border-radius: 50%;
position: absolute;
top: 1rem;
left: 27rem;
}
}
header {
@ -117,12 +135,14 @@ header {
color: white;
font-family: 'MozTT-Light';
font-size: 8pt;
transition: left 0.2s ease-out;
&.pulled {
left: 0;
transition: left 0.2s ease-out;
}
button[id^='set'], p {
button[id^='set'], p, .bottom button {
background: none;
display: block;
width: 75%;
@ -132,6 +152,7 @@ header {
font-family: 'MozTT-Light';
font-size: 8pt;
padding: 0 0.6rem;
cursor: pointer;
}
span {
float: right;
@ -150,13 +171,22 @@ header {
}
}
}
.icon-settings {
hr {
clear: both;
padding: 0.7rem 0;
margin-bottom: 0.7rem;
border: none;
border-bottom: 1px solid rgba(255,255,255,0.3);
}
*[class^='icon'] {
display: block;
margin: 1rem 0.5rem;
&:nth-of-type(2) {
padding-top: 0.5rem;
}
&:before {
content: '';
background: url('imgs/settings.png') center center no-repeat;
background-size: 2rem;
width: 2rem;
height: 2rem;
@ -166,10 +196,30 @@ header {
}
}
.icon-settings:before {
background-image: url('imgs/settings.png');
}
.icon-clear:before {
background-image: url('imgs/clear.png');
}
.icon-undo:before {
background-image: url('imgs/undo.png');
}
.icon-redo:before {
background-image: url('imgs/redo.png');
}
.options {
display: block;
margin-top: 1rem;
}
.bottom {
position: absolute;
bottom: 5rem;
button {
margin-left: 3.5rem;
}
}
}

View File

@ -114,7 +114,7 @@ form[role="dialog"][data-type="value-selector"] [role="listbox"] li:active label
}
/* Checked status */
form[role="dialog"][data-type="value-selector"] [role="listbox"] li[aria-selected="true"] span {
form[role="dialog"][data-type="value-selector"] [role="listbox"] li[aria-selected="true"]:not([data-input]) span {
padding-right: 2.6rem;
margin-right: 1.2rem;
color: #00abcd;

View File

@ -8,6 +8,7 @@
<link rel='stylesheet' href='css/switches.css'>
<link rel='stylesheet' href='css/value_selector.css'>
<link rel='stylesheet' href='css/seekbars.css'>
<link rel='stylesheet' href='css/color-picker.css'>
<script src='js/less-1.5.0.min.js'></script>
<script src='js/libs/mobilebrowsers.js'></script>
</head>
@ -36,24 +37,59 @@
<button id='setlineJoin'>Line Join<span>Round</span></button>
<button id='setlineCap'>Line Cap<span>Round</span></button>
<p id='lineWidth'>Line Width <span>2</span></p>
<div role="slider" aria-valuemin="0" aria-valuenow="80" aria-valuemax="100" aria-valuetext="slider description">
<div role="slider" class='lineWidth'>
<div>
<progress value="2" max="100"></progress>
<button>handler</button>
<progress value="2" min="1" max="100"></progress>
<button style='left: 2%'>handler</button>
</div>
</div>
</div>
<br />
<hr>
<div class='sketch'>
<p class='icon-settings'>Sketch</p>
<p id='connectTelorance'>Connet Telorance<span>40</span></p>
<div role="slider" class='connectTelorance'>
<div>
<progress value="40" min="1" max="100"></progress>
<button style='left: 40%'>handler</button>
</div>
</div>
</div>
<div class='fur hidden'>
<p class='icon-settings'>Fur</p>
<p id='connectTelorance'>Connet Telorance<span>40</span></p>
<div role="slider" class='connectTelorance'>
<div>
<progress value="40" min="10" max="100"></progress>
<button style='left: 40%'>handler</button>
</div>
</div>
<p id='furLength'>Fur Length<span>20</span></p>
<div role="slider" class='furLength'>
<div>
<progress value="20" min="1" max="100"></progress>
<button style='left: 20%'>handler</button>
</div>
</div>
</div>
</div>
<div class='bottom'>
<button id='clear' class='icon-clear'>Clear</button>
<button id='undo' class='icon-undo'>Undo</button>
<button id='redo' class='icon-redo'>Redo</button>
</div>
</div>
<form role='dialog' data-type='value-selector' id='type' class='hidden'>
<form role='dialog' data-type='value-selector' id='type' class='hidden single'>
<section class='scrollable'>
<h1>Brush Type</h1>
<ol role='listbox'>
<li role='option' aria-selected='true'><label role='presentation'><span>Sketch</span></label></li>
<li role='option'><label role='presentation'><span>Fur</span></label></li>
<li role='option'><label role='presentation'><span>Pen</span></label></li>
<li role='option'><label role='presentation'><span>Line</span></label></li>
<li aria-selected='true'><label><span>Sketch</span></label></li>
<li><label><span>Fur</span></label></li>
<li><label><span>Pen</span></label></li>
<li><label><span>Line</span></label></li>
</ol>
</section>
<menu>
@ -61,13 +97,13 @@
</menu>
</form>
<form role='dialog' data-type='value-selector' id='lineJoin' class='hidden'>
<form role='dialog' data-type='value-selector' id='lineJoin' class='hidden single'>
<section class='scrollable'>
<h1>Line Join</h1>
<ol role='listbox'>
<li role='option' aria-selected='true'><label role='presentation'><span>Round</span></label></li>
<li role='option'><label role='presentation'><span>Bevel</span></label></li>
<li role='option'><label role='presentation'><span>Miter</span></label></li>
<li aria-selected='true'><label><span>Round</span></label></li>
<li><label><span>Bevel</span></label></li>
<li><label><span>Miter</span></label></li>
</ol>
</section>
<menu>
@ -75,13 +111,13 @@
</menu>
</form>
<form role='dialog' data-type='value-selector' id='lineCap' class='hidden'>
<form role='dialog' data-type='value-selector' id='lineCap' class='hidden single'>
<section class='scrollable'>
<h1>Line Cap</h1>
<ol role='listbox'>
<li role='option' aria-selected='true'><label role='presentation'><span>Round</span></label></li>
<li role='option'><label role='presentation'><span>Square</span></label></li>
<li role='option'><label role='presentation'><span>Butt</span></label></li>
<li aria-selected='true'><label><span>Round</span></label></li>
<li><label><span>Square</span></label></li>
<li><label><span>Butt</span></label></li>
</ol>
</section>
<menu>
@ -89,8 +125,45 @@
</menu>
</form>
<form role='dialog' data-type='value-selector' id='save' class='hidden confirm'>
<section class='scrollable'>
<h1>Save</h1>
<h1>Background</h1>
<ol role='listbox'>
<li aria-selected='true'><label><span>White</span></label></li>
<li><label><span>Current Color</span></label></li>
<li><label><span>Transparent</span></label></li>
</ol>
<h1>File Name</h1>
<ol role='listbox'>
<li aria-selected='true' data-input><label><span contenteditable>My Sketch</span></label></li>
</ol>
</section>
<menu>
<button class='affirmative' type='button'>Cancel</button>
<button class='affirmative' type='button'>Save</button>
</menu>
</form>
<!-- COLOR PICKER -->
<div class='picker overlay hidden'>
<div class='color-picker'>
<button id='closePicker'>×</button>
<input value='Hex' class='format' type='hidden' selected>
<input class='color' val='#000000'>
<div class="spectrum">
<div>
<div class="pin"></div>
</div>
</div>
<input class="luminosity" type="range" min="0" max="100" />
</div>
</div>
<script src='js/libs/yepnope.min.js'></script>
<script src='js/libs/zepto.min.js'></script>
<script src='js/libs/touch.js'></script>
<script src='js/functions.js'></script>
<script src='js/main.js'></script>

View File

@ -1,22 +1,56 @@
"use strict";
function save() {
switch(save.background) {
case 'white': {
var cache = {
color: c.color,
composite: c.globalCompositeOperation
}
c.fillStyle = 'white';
c.globalCompositeOperation = 'destination-over';
c.fillRect(0, 0, $c.width(), $c.height());
c.fillStyle = cache.fillStyle;
c.globalCompositeOperation = cache.composite;
break;
}
case 'current color': {
var cache = {
fillStyle: c.color,
composite: c.globalCompositeOperation
}
c.fillStyle = settings.strokeStyle;
c.globalCompositeOperation = 'destination-over';
c.fillRect(0, 0, $c.width(), $c.height());
c.fillStyle = cache.fillStyle;
c.globalCompositeOperation = cache.composite;
break;
}
}
var data = $c[0].toDataURL();
window.open(data, save['file name']);
c.putImageData(window.points.history[window.points.history.length-1].data, 0, 0);
}
$('.menu').click(function() {
$('#menu').toggleClass('pulled');
})
$('.save').click(function() {
var data = $c[0].toDataURL();
var img = $('<img src="' + data + '" width="' + $c.width() + '" height="' + $c.height() + '" class="overlay"/>');
$('body').append(img);
$('#save').removeClass('hidden');
})
$c.last().bind('mousedown', function(e) {
$c.last().on('mousedown', function(e) {
e.preventDefault();
var xy = relative(e.pageX, e.pageY);
startPoint(xy.x, xy.y);
window.active = true;
}).bind('mousemove', function(e) {
}).on('mousemove', function(e) {
e.preventDefault();
if (!window.active || settings.type == 'line') return;
var xy = relative(e.pageX, e.pageY);
drawPoint(xy.x, xy.y);
}).bind('mouseup mouseout', function(e) {
}).on('mouseup', function(e) {
e.preventDefault();
window.active = false;
@ -33,32 +67,68 @@
// Value Selector
var $selector = $('form[data-type="value-selector"]');
// Single
var $single = $('form[data-type="value-selector"].single');
$selector.find('li').on('mouseup', function(e) {
alert(e.type);
$(this).parent().find('li').removeAttr('aria-selected');
$single.find('li').click(function(e) {
$(this).parent().find('li[aria-selected]').removeAttr('aria-selected');
$(this).attr('aria-selected', 'true');
var prop = $(this).parents('form').attr('id'),
key = $(this).find('label span').html().toLowerCase();
window.settings[prop] = key;
var key = $(this).parents('form').attr('id'),
value = $(this).find('label span').html().toLowerCase();
window.settings[key] = value;
$('button[id="set' + prop + '"] span').html(key[0].toUpperCase() + key.substr(1));
$('button[id="set' + key + '"] span').html(value[0].toUpperCase() + value.substr(1));
$('#menu div.options > div').addClass('hidden');
$('#menu div.options > .general, #menu div.options > .'+value).removeClass('hidden');
$(this).parents('form').addClass('hidden');
})
$selector.submit(function(e) {
$single.submit(function(e) {
e.preventDefault();
$(this).addClass('hidden');
})
// Confirm
var $confirm = $('form[data-type="value-selector"].confirm');
$confirm.find('li').click(function(e) {
$(this).parent().find('li[aria-selected]').removeAttr('aria-selected');
$(this).attr('aria-selected', 'true');
})
$confirm.find('button').last().click(function(e) {
e.preventDefault();
var v = $(this).parents('form').attr('id');
$(this).parents('form').find('h1').each(function(i) {
if( i > 0 ) {
var key = $(this).html().toLowerCase();
var value = $(this).parent().find('ol:nth-of-type('+i+') li[aria-selected] span').html();
if( key !== 'file name' ) value = key.toLowerCase();
window[v][key] = value;
}
})
$(this).parents('form').addClass('hidden');
window[v]();
})
$confirm.find('button').first().click(function(e) {
e.preventDefault();
$(this).parents('form').addClass('hidden');
})
// Value Selector Callers
var $btn = $('button[id^="set"]');
$btn.each(function() {
var target = /set(.*)/.exec($(this).attr('id'))[1];
$(this).on('mouseup', function(e) {
// Exception for Color
if( target == 'color' ) {
return $(this).click(function() {
$('.picker').removeClass('hidden');
})
}
$(this).click(function(e) {
e.preventDefault();
$('form[id="' + target + '"]').removeClass('hidden');
})
@ -66,19 +136,50 @@
// Seekbar
var sliderLeft = $('div[role="slider"] button').offset().left;
var sliderLeft;
$('div[role="slider"] button').on('mousedown', function() {
$(this).attr('data-moving','true');
if( !sliderLeft ) sliderLeft = $('div[role="slider"] button').offset().left;
}).on('mousemove', function(e) {
if( $(this).attr('data-moving') ) {
var x = e.pageX - sliderLeft - 15;
if( x <= 100 && x > 0 ) {
var x = parseInt(e.pageX - sliderLeft - 15);
var progress = $(this).parent().children().first();
var max = +progress.attr('max');
var min = +progress.attr('min');
if( x <= max && x >= min ) {
$(this).css('left', x+'%');
$(this).parent().find('progress').attr('value', x);
settings.lineWidth = x / 10;
$('#lineWidth span').html(x);
var key = $(this).parents('div[role="slider"]').attr('class');
settings[key] = x;
$('#'+ key +' span').html(x);
}
}
}).on('mouseup mouseleave', function() {
$(this).removeAttr('data-moving');
})
// Color Picker
$('#closePicker').click(function() {
$('.picker').addClass('hidden');
})
// Bottom
$('#clear').click(function() {
c.clearRect(0, 0, $c.width(), $c.height());
if(window.points.history.last < window.points.history.length-1) {
window.points.history.splice(window.points.history.last+1);
}
window.points.history.push({
data: c.getImageData(0, 0, $c.width(), $c.height()),
points: []
})
window.points.history.last = window.points.history.length-1;
})
$('#undo').click(undo);
$('#redo').click(redo);

View File

@ -6,16 +6,20 @@ function sizeAndPos() {
var data = c.getImageData(0,0, $c.width(), $c.height());
var w = $(window).width(),
h = $(window).height();
$c.attr('width', w);
$c.attr('height',h - 53);
$c.attr('width', w * window.devicePixelRatio);
$c.attr('height',h * window.devicePixelRatio - 53 * window.devicePixelRatio);
$c.css({
'width' : w,
'height' : h - 53
});
c.clearRect(0,0, $c.width(), $c.height());
c.putImageData(data, 0, 0);
}
function relative(x,y) {
return {
x : x - $c.offset().left,
y : y - $c.offset().top
x : x*window.devicePixelRatio,
y : y*window.devicePixelRatio - 53 * window.devicePixelRatio
}
}
@ -32,11 +36,13 @@ function line(x1, y1, x2, y2, opts, overlay) {
c.beginPath();
c.lineCap = opts.lineCap || settings.lineCap;
c.lineJoin = opts.lineJoin || settings.lineJoin;
c.strokeStyle = opts.strokeStyle || settings.strokeStyle;
c.lineWidth = opts.lineWidth || settings.lineWidth;
c.strokeStyle = opts.color || settings.color;
c.fillStyle = opts.color || settings.color;
c.lineWidth = ( opts.lineWidth || settings.lineWidth ) / 10;
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
if( !opts.noStroke ) c.stroke();
if( opts.fill ) c.fill();
}
function erase(x1, y1, x2, y2, overlay) {
@ -73,6 +79,13 @@ function redo() {
}
}
function dataToBlob(data) {
var binary = atob(data.split(',')[1]), array = [];
var type = data.split(',')[0].split(':')[1].split(';')[0];
for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
return new Blob([new Uint8Array(array)], {type: type});
}
/*** END ***/
@ -90,7 +103,10 @@ function startPoint(x, y) {
type : settings.type
}
if( old.type !== 'line' && current.type == 'line' ) {
line(x,y,x,y, {lineWidth: 5, strokeStyle: 'red'}, true);
window.o.beginPath();
window.o.fillStyle = 'red';
window.o.arc(x,y, 3, 0, 2*Math.PI);
window.o.fill();
}
if( old.type == 'line' ) {
@ -135,11 +151,11 @@ function drawPoint(x,y) {
points.push(current);
for( var i = 0, len = points.length-1; i < len; i++ ) {
if(threshold(points[i].x, points[i].y, current.x, current.y, 40)) {
if(threshold(points[i].x, points[i].y, current.x, current.y, settings.connectTelorance)) {
var x = points[i].x - current.x,
y = points[i].y - current.y;
line(points[i].x - x*0.2, points[i].y - y*0.2, current.x + x*0.2, current.y + y*0.2, {strokeStyle: 'rgba(0,0,0,0.4)', lineWidth: settings.lineWidth/2})
line(points[i].x - x*0.2, points[i].y - y*0.2, current.x + x*0.2, current.y + y*0.2, {strokeStyle: 'rgba(0,0,0,0.4)', lineWidth: settings.lineWidth/20})
}
}
break;
@ -155,10 +171,10 @@ function drawPoint(x,y) {
points.push(current);
for( var i = 0, len = points.length-1; i < len; i++ ) {
if(threshold(points[i].x, points[i].y, current.x, current.y, settings.lineWidth*20)) {
if(threshold(points[i].x, points[i].y, current.x, current.y, settings.connectTelorance)) {
var x = points[i].x - current.x,
y = points[i].y - current.y;
var l = settings.furLength || 0.2;
var l = settings.furLength / 100 || 0.2;
line(points[i].x + x*l, points[i].y + y*l, current.x - x*l, current.y - y*l, {strokeStyle: 'rgba(0,0,0,0.4)', lineWidth: settings.lineWidth/2})
}
}

View File

@ -0,0 +1,290 @@
/*
Purty Picker Copyright 2013 Jayden Seric (MIT license).
A super lightweight visual HSL, RGB and hex color picker with a responsive, touch-friendly and customizable UI.
Requires jQuery or Zepto with core and event modules.
https://github.com/jaydenseric/Purty-Picker
*/
// DOM ready
$(function() {
'use strict';
//-------------------------------------------- Color conversions
//---------------------- Convert HSL to RGB
// Source: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
function HSLToRGB(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
var r, g, b;
if(s == 0) {
r = g = b = l; // Achromatic
} else {
var hueToRGB = function(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
r = hueToRGB(p, q, h + 1/3);
g = hueToRGB(p, q, h);
b = hueToRGB(p, q, h - 1/3);
}
return {
red: Math.round(r * 255),
green: Math.round(g * 255),
blue: Math.round(b * 255)
};
}
//---------------------- Convert RGB to HSL
// Source: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
function RGBToHSL(r, g, b) {
r /= 255,
g /= 255,
b /= 255;
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // Achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
hue: Math.round(h * 360),
saturation: Math.round(s * 100),
luminosity: Math.round(l * 100)
};
}
//---------------------- Convert RGB to Hex
// Source: http://stackoverflow.com/a/5624139
function RGBToHex(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
//---------------------- Convert hex to RGB
// Source: http://stackoverflow.com/a/11508164
function hexToRGB(hex) {
var bigInt = parseInt(hex.replace('#', ''), 16),
r = (bigInt >> 16) & 255,
g = (bigInt >> 8) & 255,
b = bigInt & 255;
return {
red: r,
green: g,
blue: b
};
}
//---------------------- Convert hex to HSL
function hexToHSL(hex) {
var RGB = hexToRGB(hex);
return RGBToHSL(RGB.red, RGB.green, RGB.blue);
}
//---------------------- Convert HSL to hex
function HSLToHex(h, s, l) {
var RGB = HSLToRGB(h, s, l);
return RGBToHex(RGB.red, RGB.green, RGB.blue);
}
//-------------------------------------------- Setup each color picker
$.each($('.color-picker'), function() {
//---------------------- Find componenets
var picker = $(this),
formatInput = picker.find('.format'),
colorInput = picker.find('.color'),
luminosityInput = picker.find('input[type=range]'),
spectrum = picker.find('.spectrum'),
pin = picker.find('.pin');
//---------------------- Get current color in HSL
function getHSL() {
var position = picker.find('.pin').position(),
width = spectrum.width(),
height = spectrum.height();
return {
hue: Math.round(position.left / width * 360),
saturation: Math.round(position.top / height * 100),
luminosity: luminosityInput.val()
};
}
//---------------------- Output color in desired format
function updateColorInput() {
var HSL = getHSL();
switch (formatInput.val()) {
case 'HSL':
colorInput.val('hsl(' + HSL.hue + ', ' + HSL.saturation + '%, ' + HSL.luminosity + '%)');
break;
case 'RGB':
var RGB = HSLToRGB(HSL.hue, HSL.saturation, HSL.luminosity);
colorInput.val('rgb(' + RGB.red + ', ' + RGB.green + ', ' + RGB.blue + ')');
break;
case 'Hex':
colorInput.val(HSLToHex(HSL.hue, HSL.saturation, HSL.luminosity));
break;
}
// Trigger color picker change event for custom callbacks
picker.trigger('change');
}
//---------------------- Set color format
formatInput.on('change', function() {
updateColorInput();
});
//---------------------- Set color
colorInput.on('change', function() {
// Get the color values in HSL format
var HSL;
switch (formatInput.val()) {
case 'HSL':
var values = $(this).val().match(/\d+/g);
HSL = {
hue: values[0],
saturation: values[1],
luminosity: values[2]
};
break;
case 'RGB':
var values = $(this).val().match(/\d+/g);
HSL = RGBToHSL(values[0], values[1], values[2]);
break;
case 'Hex':
HSL = hexToHSL($(this).val());
break;
}
// Set the luminosity
luminosityInput.val(HSL.luminosity);
setLuminosity(HSL.luminosity);
// Place the pin
pin.css({
left: HSL.hue / 360 * 100 + '%',
top: HSL.saturation + '%'
});
// Trigger color picker change event for custom callbacks
picker.trigger('change');
});
//---------------------- Set luminosity
//---------- Set the luminosity spectrum overlay
function setLuminosity(luminosity) {
var color,
alpha;
if (luminosity <= 50) {
color = '0, 0, 0';
alpha = 1 - luminosity / 100 * 2;
} else {
color = '255, 255, 255';
alpha = luminosity / 100 * 2 - 1;
}
// Apply luminosity to the spectrum
spectrum.children().css('background-color', 'rgba(' + color + ', ' + alpha + ')');
}
//---------- Luminosity input interaction
luminosityInput.on('change', function() {
setLuminosity($(this).val());
updateColorInput();
});
//---------------------- Set hue, saturation via pin
//---------- Move the pin
var movePin = function(event) {
var offset = spectrum.offset(),
width = spectrum.width(),
height = spectrum.height(),
x = event.changedTouches[0].clientX - offset.left,
y = event.changedTouches[0].clientY - offset.top;
// Account for pin being dragged outside the spectrum area
// Sanatize x
if (x < 0) {
x = 0;
} else if (x >= width) {
x = width;
}
// Sanatize y
if (y < 0) {
y = 0;
} else if (y >= height) {
y = height;
}
// Place the pin
pin.css({
left: x / width * 100 + '%',
top: y / height * 100 + '%'
});
// Output new color value
updateColorInput();
};
//---------- Pin interaction
spectrum.on('touchstart', function(event) {
event.preventDefault();
movePin(event);
spectrum.addClass('active');
$(document).on('touchmove', movePin);
});
$(document).on('touchend', function() {
spectrum.removeClass('active');
$(document).off('touchmove', movePin);
});
spectrum.on('touchmove touchstart', movePin);
//---------------------- Output color preview
picker.on('change', function() {
colorInput.css('background-color', colorInput.val()).toggleClass('dark', luminosityInput.val() <= 50);
});
//---------------------- Initialize this color picker
colorInput.trigger('change');
});
});

289
js/libs/color-picker.js Normal file
View File

@ -0,0 +1,289 @@
/*
Purty Picker Copyright 2013 Jayden Seric (MIT license).
A super lightweight visual HSL, RGB and hex color picker with a responsive, touch-friendly and customizable UI.
Requires jQuery or Zepto with core and event modules.
https://github.com/jaydenseric/Purty-Picker
*/
// DOM ready
$(function() {
'use strict';
//-------------------------------------------- Color conversions
//---------------------- Convert HSL to RGB
// Source: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
function HSLToRGB(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
var r, g, b;
if(s == 0) {
r = g = b = l; // Achromatic
} else {
var hueToRGB = function(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
r = hueToRGB(p, q, h + 1/3);
g = hueToRGB(p, q, h);
b = hueToRGB(p, q, h - 1/3);
}
return {
red: Math.round(r * 255),
green: Math.round(g * 255),
blue: Math.round(b * 255)
};
}
//---------------------- Convert RGB to HSL
// Source: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
function RGBToHSL(r, g, b) {
r /= 255,
g /= 255,
b /= 255;
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // Achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
hue: Math.round(h * 360),
saturation: Math.round(s * 100),
luminosity: Math.round(l * 100)
};
}
//---------------------- Convert RGB to Hex
// Source: http://stackoverflow.com/a/5624139
function RGBToHex(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
//---------------------- Convert hex to RGB
// Source: http://stackoverflow.com/a/11508164
function hexToRGB(hex) {
var bigInt = parseInt(hex.replace('#', ''), 16),
r = (bigInt >> 16) & 255,
g = (bigInt >> 8) & 255,
b = bigInt & 255;
return {
red: r,
green: g,
blue: b
};
}
//---------------------- Convert hex to HSL
function hexToHSL(hex) {
var RGB = hexToRGB(hex);
return RGBToHSL(RGB.red, RGB.green, RGB.blue);
}
//---------------------- Convert HSL to hex
function HSLToHex(h, s, l) {
var RGB = HSLToRGB(h, s, l);
return RGBToHex(RGB.red, RGB.green, RGB.blue);
}
//-------------------------------------------- Setup each color picker
$.each($('.color-picker'), function() {
//---------------------- Find componenets
var picker = $(this),
formatInput = picker.find('.format'),
colorInput = picker.find('.color'),
luminosityInput = picker.find('input[type=range]'),
spectrum = picker.find('.spectrum'),
pin = picker.find('.pin');
//---------------------- Get current color in HSL
function getHSL() {
var position = picker.find('.pin').position(),
width = spectrum.width(),
height = spectrum.height();
return {
hue: Math.round(position.left / width * 360),
saturation: Math.round(position.top / height * 100),
luminosity: luminosityInput.val()
};
}
//---------------------- Output color in desired format
function updateColorInput() {
var HSL = getHSL();
switch (formatInput.val()) {
case 'HSL':
colorInput.val('hsl(' + HSL.hue + ', ' + HSL.saturation + '%, ' + HSL.luminosity + '%)');
break;
case 'RGB':
var RGB = HSLToRGB(HSL.hue, HSL.saturation, HSL.luminosity);
colorInput.val('rgb(' + RGB.red + ', ' + RGB.green + ', ' + RGB.blue + ')');
break;
case 'Hex':
colorInput.val(HSLToHex(HSL.hue, HSL.saturation, HSL.luminosity));
break;
}
// Trigger color picker change event for custom callbacks
picker.trigger('change');
}
//---------------------- Set color format
formatInput.on('change', function() {
updateColorInput();
});
//---------------------- Set color
colorInput.on('change', function() {
// Get the color values in HSL format
var HSL;
switch (formatInput.val()) {
case 'HSL':
var values = $(this).val().match(/\d+/g);
HSL = {
hue: values[0],
saturation: values[1],
luminosity: values[2]
};
break;
case 'RGB':
var values = $(this).val().match(/\d+/g);
HSL = RGBToHSL(values[0], values[1], values[2]);
break;
case 'Hex':
HSL = hexToHSL($(this).val());
break;
}
// Set the luminosity
luminosityInput.val(HSL.luminosity);
setLuminosity(HSL.luminosity);
// Place the pin
pin.css({
left: HSL.hue / 360 * 100 + '%',
top: HSL.saturation + '%'
});
// Trigger color picker change event for custom callbacks
picker.trigger('change');
});
//---------------------- Set luminosity
//---------- Set the luminosity spectrum overlay
function setLuminosity(luminosity) {
var color,
alpha;
if (luminosity <= 50) {
color = '0, 0, 0';
alpha = 1 - luminosity / 100 * 2;
} else {
color = '255, 255, 255';
alpha = luminosity / 100 * 2 - 1;
}
// Apply luminosity to the spectrum
spectrum.children().css('background-color', 'rgba(' + color + ', ' + alpha + ')');
}
//---------- Luminosity input interaction
luminosityInput.on('change', function() {
setLuminosity($(this).val());
updateColorInput();
});
//---------------------- Set hue, saturation via pin
//---------- Move the pin
var movePin = function(event) {
var offset = spectrum.offset(),
width = spectrum.width(),
height = spectrum.height(),
x = event.clientX - offset.left,
y = event.clientY - offset.top;
// Account for pin being dragged outside the spectrum area
// Sanatize x
if (x < 0) {
x = 0;
} else if (x >= width) {
x = width;
}
// Sanatize y
if (y < 0) {
y = 0;
} else if (y >= height) {
y = height;
}
// Place the pin
pin.css({
left: x / width * 100 + '%',
top: y / height * 100 + '%'
});
// Output new color value
updateColorInput();
};
//---------- Pin interaction
spectrum.on('mousedown', function(event) {
event.preventDefault();
movePin(event);
spectrum.addClass('active');
$(document).on('mousemove', movePin);
});
$(document).on('mouseup', function() {
spectrum.removeClass('active');
$(document).off('mousemove', movePin);
});
spectrum.on('touchmove touchstart', movePin);
//---------------------- Output color preview
picker.on('change', function() {
colorInput.css('background-color', colorInput.val()).toggleClass('dark', luminosityInput.val() <= 50);
});
//---------------------- Initialize this color picker
colorInput.trigger('change');
});
});

22
js/libs/stack.js Normal file
View File

@ -0,0 +1,22 @@
// Zepto.js
// (c) 2010-2014 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
;(function($){
$.fn.end = function(){
return this.prevObject || $()
}
$.fn.andSelf = function(){
return this.add(this.prevObject || $())
}
'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property){
var fn = $.fn[property]
$.fn[property] = function(){
var ret = fn.apply(this, arguments)
ret.prevObject = this
return ret
}
})
})(Zepto)

165
js/libs/touch.js Normal file
View File

@ -0,0 +1,165 @@
// Zepto.js
// (c) 2010-2014 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
;(function($){
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture
function swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >=
Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
}
function longTap() {
longTapTimeout = null
if (touch.last) {
touch.el.trigger('longTap')
touch = {}
}
}
function cancelLongTap() {
if (longTapTimeout) clearTimeout(longTapTimeout)
longTapTimeout = null
}
function cancelAll() {
if (touchTimeout) clearTimeout(touchTimeout)
if (tapTimeout) clearTimeout(tapTimeout)
if (swipeTimeout) clearTimeout(swipeTimeout)
if (longTapTimeout) clearTimeout(longTapTimeout)
touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
touch = {}
}
function isPrimaryTouch(event){
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH)
&& event.isPrimary
}
function isPointerEventType(e, type){
return (e.type == 'pointer'+type ||
e.type.toLowerCase() == 'mspointer'+type)
}
$(document).ready(function(){
var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
if ('MSGesture' in window) {
gesture = new MSGesture()
gesture.target = document.body
}
$(document)
.bind('MSGestureEnd', function(e){
var swipeDirectionFromVelocity =
e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null;
if (swipeDirectionFromVelocity) {
touch.el.trigger('swipe')
touch.el.trigger('swipe'+ swipeDirectionFromVelocity)
}
})
.on('touchstart MSPointerDown pointerdown', function(e){
if((_isPointerType = isPointerEventType(e, 'down')) &&
!isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]
if (e.touches && e.touches.length === 1 && touch.x2) {
// Clear out touch movement data if we have it sticking around
// This can occur if touchcancel doesn't fire due to preventDefault, etc.
touch.x2 = undefined
touch.y2 = undefined
}
now = Date.now()
delta = now - (touch.last || now)
touch.el = $('tagName' in firstTouch.target ?
firstTouch.target : firstTouch.target.parentNode)
touchTimeout && clearTimeout(touchTimeout)
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
touch.last = now
longTapTimeout = setTimeout(longTap, longTapDelay)
// adds the current touch contact for IE gesture recognition
if (gesture && _isPointerType) gesture.addPointer(e.pointerId);
})
.on('touchmove MSPointerMove pointermove', function(e){
if((_isPointerType = isPointerEventType(e, 'move')) &&
!isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]
cancelLongTap()
touch.x2 = firstTouch.pageX
touch.y2 = firstTouch.pageY
deltaX += Math.abs(touch.x1 - touch.x2)
deltaY += Math.abs(touch.y1 - touch.y2)
})
.on('touchend MSPointerUp pointerup', function(e){
if((_isPointerType = isPointerEventType(e, 'up')) &&
!isPrimaryTouch(e)) return
cancelLongTap()
// swipe
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
swipeTimeout = setTimeout(function() {
touch.el.trigger('swipe')
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
touch = {}
}, 0)
// normal tap
else if ('last' in touch)
// don't fire tap when delta position changed by more than 30 pixels,
// for instance when moving to a point and back to origin
if (deltaX < 30 && deltaY < 30) {
// delay by one tick so we can cancel the 'tap' event if 'scroll' fires
// ('tap' fires before 'scroll')
tapTimeout = setTimeout(function() {
// trigger universal 'tap' with the option to cancelTouch()
// (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
var event = $.Event('tap')
event.cancelTouch = cancelAll
touch.el.trigger(event)
// trigger double tap immediately
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap')
touch = {}
}
// trigger single tap after 250ms of inactivity
else {
touchTimeout = setTimeout(function(){
touchTimeout = null
if (touch.el) touch.el.trigger('singleTap')
touch = {}
}, 250)
}
}, 0)
} else {
touch = {}
}
deltaX = deltaY = 0
})
// when the browser window loses focus,
// for example when a modal dialog is shown,
// cancel all ongoing events
.on('touchcancel MSPointerCancel pointercancel', cancelAll)
// scrolling the window indicates intention of the user
// to scroll, not tap or swipe, so cancel all ongoing events
$(window).on('scroll', cancelAll)
})
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
$.fn[eventName] = function(callback){ return this.on(eventName, callback) }
})
})(Zepto)

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,17 @@
"use strict";
yepnope({
test: window.mobile,
yep : 'js/mobile-events.js',
nope: 'js/events.js'
})
$(document).ready(function() {
window.c = $('canvas')[0].getContext('2d');
window.o = $('canvas')[1].getContext('2d');
window.settings = {
lineWidth : 0.2,
strokeStyle : 'black',
lineWidth : 2,
color : 'black',
type: 'sketch',
lineCap: 'round',
lineJoin: 'round',
furLength: 5
furLength: 5,
connectTelorance: 40
};
window.points = [];
window.$c = $('canvas');
@ -25,4 +20,17 @@ $(document).ready(function() {
sizeAndPos();
$(window).resize(sizeAndPos);
$('.color-picker').change(function() {
var c = $(this).find('.color').val();
settings.color = c;
$('#setcolor span').html(c);
})
$('.color').val('#000000');
yepnope({
test: window.mobile,
yep : ['js/mobile-events.js', 'js/libs/color-picker-touch.js'],
nope: ['js/events.js', 'js/libs/color-picker.js']
})
})

View File

@ -1,28 +1,63 @@
$('.menu').bind('touchend', function() {
"use strict";
function save() {
switch(save.background) {
case 'white': {
var cache = {
fillStyle: c.color,
composite: c.globalCompositeOperation
}
c.fillStyle = 'white';
c.globalCompositeOperation = 'destination-over';
c.fillRect(0, 0, $c.width(), $c.height());
c.fillStyle = cache.fillStyle;
c.globalCompositeOperation = cache.composite;
break;
}
case 'current color': {
var cache = {
fillStyle: c.color,
composite: c.globalCompositeOperation
}
c.fillStyle = settings.strokeStyle;
c.globalCompositeOperation = 'destination-over';
c.fillRect(0, 0, $c.width(), $c.height());
c.fillStyle = cache.fillStyle;
c.globalCompositeOperation = cache.composite;
break;
}
}
var data = $c[0].toDataURL();
var file = dataToBlob($c[0].toDataURL());
var pics = navigator.getDeviceStorage('pictures');
var r = pics.addNamed(file, save['file name'] + '.png');
r.onsuccess = function() {
alert('Your sketch was successfuly saved to pictures/' + this.result.name);
}
r.onerror = function() {
alert('Something bad happened when we tried to save your file\n Possible problems: \n Duplicate name \n Permission problems')
console.warn(this.error);
}
c.putImageData(window.points.history[window.points.history.length-1].data, 0, 0);
}
$('.menu').on('tap', function() {
$('#menu').toggleClass('pulled');
})
$('.save').bind('touchend', function() {
var data = $c[0].toDataURL();
var img = $('<img src="' + data + '" width="' + $c.width() + '" height="' + $c.height() + '" class="overlay"/>');
$('body').append(img);
var share = new MozActivity({
name: 'share',
data: {
type: 'image/*'
}
})
$('.save').on('tap', function() {
$('#save').removeClass('hidden');
})
$c.last().bind('touchstart', function(e) {
$c.last().on('touchstart', function(e) {
e.preventDefault();
var xy = relative(e.changedTouches[0].pageX, e.changedTouches[0].pageY);
startPoint(xy.x, xy.y);
window.active = true;
}).bind('touchmove', function(e) {
}).on('touchmove', function(e) {
e.preventDefault();
if (!window.active || settings.type == 'line') return;
var xy = relative(e.changedTouches[0].pageX, e.changedTouches[0].pageY);
drawPoint(xy.x, xy.y);
}).bind('touchend', function(e) {
}).on('touchend', function(e) {
e.preventDefault();
window.active = false;
@ -39,31 +74,67 @@
// Value Selector
var $selector = $('form[data-type="value-selector"]');
var $single = $('form[data-type="value-selector"].single');
$selector.find('li').on('touchend', function(e) {
$(this).parent().find('li').removeAttr('aria-selected');
$single.find('li').on('tap', function(e) {
e.preventDefault();
$(this).parent().find('li[aria-selected]').removeAttr('aria-selected');
$(this).attr('aria-selected', 'true');
var prop = $(this).parents('form').attr('id'),
key = $(this).find('label span').html().toLowerCase();
window.settings[prop] = key;
var key = $(this).parents('form').attr('id'),
value = $(this).find('label span').html().toLowerCase();
window.settings[key] = value;
$('button[id="set' + prop + '"] span').html(key[0].toUpperCase() + key.substr(1));
$('button[id="set' + key + '"] span').html(value[0].toUpperCase() + value.substr(1));
$('#menu div.options > div').addClass('hidden');
$('#menu div.options > .general, #menu div.options > .'+value).removeClass('hidden');
$(this).parents('form').addClass('hidden');
})
$selector.submit(function(e) {
$single.submit(function(e) {
e.preventDefault();
$(this).addClass('hidden');
})
// Confirm
var $confirm = $('form[data-type="value-selector"].confirm');
$confirm.find('li').on('tap', function(e) {
$(this).parent().find('li[aria-selected]').removeAttr('aria-selected');
$(this).attr('aria-selected', 'true');
})
$confirm.find('button').last().on('tap', function(e) {
e.preventDefault();
var v = $(this).parents('form').attr('id');
$(this).parents('form').find('h1').each(function(i) {
if( i > 0 ) {
var key = $(this).html().toLowerCase();
var value = $(this).parent().find('ol:nth-of-type('+i+') li[aria-selected] span').html();
if( key !== 'file name' ) value = key.toLowerCase();
window[v][key] = value;
}
})
$(this).parents('form').addClass('hidden');
window[v]();
})
$confirm.find('button').first().on('tap', function(e) {
e.preventDefault();
$(this).parents('form').addClass('hidden');
})
// Value Selector Callers
var $btn = $('button[id^="set"]');
$btn.each(function() {
var target = /set(.*)/.exec($(this).attr('id'))[1];
$(this).on('touchend', function(e) {
if( target == 'color' ) {
return $(this).on('tap', function() {
$('.picker').removeClass('hidden');
})
}
$(this).on('tap', function(e) {
e.preventDefault();
$('form[id="' + target + '"]').removeClass('hidden');
})
@ -71,19 +142,49 @@
// Seekbar
var sliderLeft = $('div[role="slider"] button').offset().left;
var sliderLeft;
$('div[role="slider"] button').on('touchstart', function() {
$(this).attr('data-moving','true');
if( !sliderLeft ) sliderLeft = $('div[role="slider"] button').offset().left;
}).on('touchmove', function(e) {
if( $(this).attr('data-moving') ) {
var x = e.changedTouches[0].pageX - sliderLeft - 15;
if( x <= 100 && x > 0 ) {
if( $(this).attr('data-moving') ) {
var x = parseInt(e.changedTouches[0].pageX - sliderLeft - 15);
var progress = $(this).parent().children().first();
var max = +progress.attr('max');
var min = +progress.attr('min');
if( x <= max && x >= min ) {
$(this).css('left', x+'%');
$(this).parent().find('progress').attr('value', x);
settings.lineWidth = x / 10;
$('#lineWidth span').html(x);
var key = $(this).parents('div[role="slider"]').attr('class');
settings[key] = x;
$('#'+ key +' span').html(x);
}
}
}).on('touchend', function() {
$(this).removeAttr('data-moving');
})
// Color Picker
$('#closePicker').on('tap', function() {
$('.picker').addClass('hidden');
})
// Bottom
$('#clear').on('tap', function() {
c.clearRect(0, 0, $c.width(), $c.height());
if(window.points.history.last < window.points.history.length-1) {
window.points.history.splice(window.points.history.last+1);
}
window.points.history.push({
data: c.getImageData(0, 0, $c.width(), $c.height()),
points: []
})
window.points.history.last = window.points.history.length-1;
})
$('#undo').on('tap', undo);
$('#redo').on('tap', redo);

View File

@ -2,6 +2,8 @@
"name": "Sketchy",
"description": "Free Sketch/Paint app",
"version": 1,
"fullscreen": true,
"type": "privileged",
"launch_path": "/index.html",
"icons": {
"16": "/img/icons/icon16.png",
@ -22,5 +24,11 @@
"name": "Sketchy",
"description": "برنامه‌ی رایگان طراحی/نقاشی"
}
},
"permissions": {
"device-storage:pictures": {
"description": "Required to save sketched pics",
"access": "readwrite"
}
}
}