split app.js into 10 files before it goes supernova

10
main
Isaiah Odhner 2014-05-04 09:32:02 -04:00
parent d0b68cc7aa
commit 949bc6e986
11 changed files with 1698 additions and 1680 deletions

101
$ColorBox.js Normal file
View File

@ -0,0 +1,101 @@
function $ColorBox(){
var $cb = $(E("div")).addClass("jspaint-color-box");
$cb.addClass("jspaint-color-box");
var $current_colors = $(E("div")).addClass("jspaint-current-colors");
var $palette = $(E("div")).addClass("jspaint-palette");
$cb.append($current_colors, $palette);
var $color0 = $(E("div")).addClass("jspaint-color-selection");
var $color1 = $(E("div")).addClass("jspaint-color-selection");
$current_colors.append($color0, $color1);
$current_colors.css({
position: "relative",
});
$color0.css({
position: "absolute",
zIndex: 1,
left: 2,
top: 4,
});
$color1.css({
position: "absolute",
right: 3,
bottom: 3,
});
function update_colors(){
$current_colors.css({background:colors[2]});
$color0.css({background:colors[0]});
$color1.css({background:colors[1]});
}
$.each(palette, function(i, color){
var $b = $(E("button")).addClass("jspaint-color-button");
$b.appendTo($palette);
$b.css("background-color", color);
var $i = $(E("input")).attr({type:"color"});
$i.appendTo($b);
$i.on("change", function(){
color = $i.val();
$b.css("background-color", color);
set_color(color);
});
$i.css("opacity", 0);
$i.prop("enabled", false);
$i.val(rgb2hex($b.css("background-color")));
var button, ctrl;
$b.on("mousedown", function(e){
ctrl = e.ctrlKey;
button = e.button;
set_color($b.css("background-color"));
$i.val(rgb2hex($b.css("background-color")));
$i.prop("enabled", true);
setTimeout(function(){
$i.prop("enabled", false);
}, 400);
});
$i.on("mousedown", function(e){
if(e.button === button && $i.prop("enabled")){
$i.trigger("click", "synthetic");
}
});
$i.on("click", function(e, synthetic){
if(!synthetic){
e.preventDefault();
}
});
function set_color(col){
if(ctrl){
colors[2] = col;
}else if(button === 0){
colors[0] = col;
}else if(button === 2){
colors[1] = col;
}
update_colors();
};
function rgb2hex(col){
var rgb = col.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x){
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return rgb ? ("#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3])) : col;
}
});
var $c = $Component("Colors", "wide", $cb);
$c.update_colors = update_colors;
return $c;
}

110
$Component.js Normal file
View File

@ -0,0 +1,110 @@
function $Component(name, orientation, $el){
//a draggable widget that can be undocked into a window
var $c = $(E("div")).addClass("jspaint-component");
$c.addClass("jspaint-"+name+"-component");
$c.append($el);
$c.appendTo({
tall: $left,
wide: $bottom,
}[orientation]);
var ox, oy, w, h, pos, pos_axis;
var dragging = false;
var $dock_to;
var $ghost;
$c.on("mousedown", function(e){
if(e.button !== 0) return;
var rect = $c[0].getBoundingClientRect();
w = (~~(rect.width/2))*2 + 1; //make sure these dimensions are odd numbers
h = (~~(rect.height/2))*2 + 1;
ox = $c.position().left - e.clientX;
oy = $c.position().top - e.clientY;
dragging = true;
if(!$ghost){
$ghost = $(E("div")).addClass("jspaint-component-ghost dock");
$ghost.css({
position: "absolute",
display: "block",
width: w,
height: h,
left: e.clientX + ox,
top: e.clientY + oy
});
$ghost.appendTo("body");
}
e.preventDefault();
});
$el.on("mousedown", function(e){
return false;
});
$G.on("mousemove", function(e){
if(!dragging) return;
$ghost.css({
left: e.clientX + ox,
top: e.clientY + oy,
});
$dock_to = null;
var ghost_rect = $ghost[0].getBoundingClientRect();
var q = 5;
if(orientation === "tall"){
pos_axis = "top";
if(ghost_rect.left-q < $left[0].getBoundingClientRect().right){
$dock_to = $left;
}
if(ghost_rect.right+q > $right[0].getBoundingClientRect().left){
$dock_to = $right;
}
}else{
pos_axis = "left";
if(ghost_rect.top-q < $top[0].getBoundingClientRect().bottom){
$dock_to = $top;
}
if(ghost_rect.bottom+q > $bottom[0].getBoundingClientRect().top){
$dock_to = $bottom;
}
}
pos = ghost_rect[pos_axis];
if($dock_to){
var dock_to_rect = $dock_to[0].getBoundingClientRect();
pos -= dock_to_rect[pos_axis];
$ghost.addClass("dock");
}else{
$ghost.removeClass("dock");
}
e.preventDefault();
});
$G.on("mouseup", function(e){
if(!dragging) return;
dragging = false;
if($dock_to){
$dock_to.append($c);
pos = Math.max(pos, 0);
if(pos_axis === "top"){
pos = Math.min(pos, $dock_to.height() - $ghost.height());
}else{
pos = Math.min(pos, $dock_to.width() - $ghost.width());
}
$c.css("position", "relative");
$c.css(pos_axis, pos);
}else{
//put component in window
}
$ghost && $ghost.remove(), $ghost = null;
update_handles();
});
return $c;
}

75
$ToolBox.js Normal file
View File

@ -0,0 +1,75 @@
function $ToolBox(){
var $tb = $(E("div")).addClass("jspaint-tool-box");
var $tools = $(E("div")).addClass("jspaint-tools");
var $tool_options = $(E("div")).addClass("jspaint-tool-options");
$tool_options_area = $tool_options;
var showing_tooltips = false;
$tools.on("mouseleave", function(){
showing_tooltips = false;
$status_text.default();
});
var $buttons;
$.each(tools, function(i, tool){
var $b = $(E("button")).addClass("jspaint-tool");
$b.appendTo($tools);
tool.$button = $b;
$b.attr("title", tool.name);
var $icon = $(E("span"));
$icon.appendTo($b);
var bx = (i%2)*24;
var by = (~~(i/2))*25;
$icon.css({
display: "block",
width: "100%",
height: "100%",
backgroundImage: "url(images/toolbar-icons.png)",
backgroundPositionX: bx,
backgroundPositionY: -by,
});
$b.on("click", function(){
if(selected_tool === tool && tool.deselect){
selected_tool = previous_tool;
}else{
if(!tool.deselect){
previous_tool = tool;
}
selected_tool = tool;
}
$c.update_selected_tool();
});
$b.on("mouseenter", function(){
var show_tooltip = function(){
showing_tooltips = true;
$status_text.text(tool.description);
};
if(showing_tooltips){
show_tooltip();
}else{
var tid = setTimeout(show_tooltip, 300);
$b.on("mouseleave", function(){
clearTimeout(tid);
});
}
});
});
$buttons = $tools.find("button");
var $c = $Component("Tools", "tall", $tools.add($tool_options));
$c.update_selected_tool = function(){
$buttons.removeClass("selected");
selected_tool.$button.addClass("selected");
$canvas.css({
cursor: Cursor(selected_tool.cursor)
});
deselect();
};
$c.update_selected_tool();
return $c;
}

42
$Window.js Normal file
View File

@ -0,0 +1,42 @@
function $Window(){
var $w = $(E("div")).addClass("jspaint-window").appendTo("body");
$w.$titlebar = $(E("div")).addClass("jspaint-window-titlebar").appendTo($w);
$w.$title = $(E("span")).addClass("jspaint-window-title").appendTo($w.$titlebar);
$w.$x = $(E("button")).addClass("jspaint-window-close-button").appendTo($w.$titlebar);
$w.$content = $(E("div")).addClass("jspaint-window-content").appendTo($w);
$w.$x.on("click", function(){
$w.close();
});
$w.$Button = function(text, handler){
$w.$content.append(
$(E("button"))
.text(text)
.on("click", function(){
handler();
$w.close();
})
);
};
$w.title = function(title){
if(title){
$w.$title.text(title);
return $w;
}else{
return $w.$title.text();
}
};
$w.close = function(){
$w.remove();
};
$w.css({
position: "absolute",
right: 50,
top: 50
});
return $w;
}

108
Selection.js Normal file
View File

@ -0,0 +1,108 @@
function Selection(x, y, w, h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this._x = x;
this._y = y;
this._w = w;
this._h = h;
this.$ghost = $(E("div")).addClass("jspaint-selection").appendTo($canvas_area);
$canvas_handles.hide();
}
Selection.prototype.instantiate = function(_img){
var sel = this;
sel.$ghost.addClass("instantiated").css({
cursor: Cursor(["move", [8, 8], "move"])
});
sel.position();
if(!undoable()){
sel.destroy();
return;
}
if(_img){
sel.canvas = _img;
sel.canvas.width = sel._w;
sel.canvas.height = sel._h;
}else{
sel.canvas = document.createElement("canvas");
sel.canvas.width = sel._w;
sel.canvas.height = sel._h;
sel.ctx = sel.canvas.getContext("2d");
sel.ctx.drawImage(
canvas,
sel._x, sel._y,
sel._w, sel._h,
0, 0,
sel._w, sel._h
);
// cut the selection from the canvas
//@TODO: transparency
//ctx.globalCompositeOperation = "destination-out";
//ctx.drawImage()...
ctx.fillStyle = colors[1];
ctx.fillRect(
sel._x, sel._y,
sel._w, sel._h
);
}
sel.$ghost.append(sel.canvas);
var mox, moy;
var mousemove = function(e){
var m = e2c(e);
sel._x = Math.max(Math.min(m.x - mox, canvas.width), -sel._w);
sel._y = Math.max(Math.min(m.y - moy, canvas.height), -sel._h);
sel.position();
if(e.shiftKey){
sel.draw();
}
};
sel.$ghost.on("mousedown", function(e){
e.preventDefault();
mox = e.offsetX;
moy = e.offsetY;
$G.on("mousemove", mousemove);
$G.one("mouseup", function(){
$G.off("mousemove", mousemove);
});
});
$status_position.text("");
$status_size.text("");
};
Selection.prototype.position = function(){
this.$ghost.css({
position: "absolute",
left: this._x + 3,
top: this._y + 3,
width: this._w,
height: this._h,
});
$status_position.text(this._x + "," + this._y);
$status_size.text(this._w + "," + this._h);
};
Selection.prototype.draw = function(){
try{ctx.drawImage(this.canvas, this._x, this._y);}catch(e){}
};
Selection.prototype.destroy = function(){
this.$ghost.remove();
$canvas_handles.show();
};
Selection.prototype.crop = function(){
if(this.canvas && undoable()){
canvas.width = this.canvas.width;
canvas.height = this.canvas.height;
ctx.drawImage(this.canvas, 0, 0);
}
};

2021
app.js

File diff suppressed because it is too large Load Diff

408
functions.js Normal file
View File

@ -0,0 +1,408 @@
function reset_colors(){
colors = ["black", "white", ""];
$colorbox && $colorbox.update_colors();
}
function reset(){
undos = [];
redos = [];
reset_colors();
file_name = "untitled";
update_title();
canvas.width = default_width;
canvas.height = default_height;
ctx.fillStyle = colors[1];
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function update_title(){
document.title = file_name + " - Paint";
}
function open_from_Image(img, new_file_name){
are_you_sure(function(){
undos = [];
redos = [];
reset_colors();
file_name = new_file_name;
update_title();
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
});
}
function open_from_URI(uri, new_file_name){
var img = new Image();
img.onload = function(){
open_from_Image(img, new_file_name);
};
img.src = uri;
}
function open_from_File(file){
var reader = new FileReader();
reader.onload = function(e){
open_from_URI(e.target.result, file.name);
};
reader.readAsDataURL(file);
}
function open_from_FileList(files){
$.each(files, function(i, file){
if(file.type.match(/image/)){
open_from_File(file);
return false;
}
});
}
function open_from_FileEntry(entry){
entry.file(open_from_File);
}
function save_to_FileEntry(entry){
entry.createWriter(function(file_writer){
file_writer.onwriteend = function(e){
if(this.error){
console.error(this.error + '\n\n\n@ ' + e);
}else{
console.log("File written!");
}
};
canvas.toBlob(function(blob){
file_writer.write(blob);
});
});
}
function file_new(){
are_you_sure(reset);
}
function file_open(){
if(window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry){
chrome.fileSystem.chooseEntry({
type: "openFile",
accepts: [{mimeTypes: ["image/*"]}]
}, function(entry){
file_entry = entry;
if(chrome.runtime.lastError){
return console.error(chrome.runtime.lastError.message);
}
open_from_FileEntry(entry);
});
}else{
var $input = $(E("input")).attr({type:"file"})
.on("change", function(){
open_from_FileList(this.files);
$input.remove();
})
.appendTo("body")
.hide()
.click();
}
}
function file_save(){
if(window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry){
if(window.file_entry){
save_to_FileEntry(file_entry);
}else{
file_save_as();
}
}else{
window.open(canvas.toDataURL());
}
}
function file_save_as(){
if(window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry){
chrome.fileSystem.chooseEntry({
type: 'saveFile',
suggestedName: file_name,
accepts: [{mimeTypes: ["image/*"]}]
}, function(entry){
if(chrome.runtime.lastError){
return console.error(chrome.runtime.lastError.message);
}
file_entry = entry;
file_name = entry.name;
update_title();
save_to_FileEntry(file_entry);
});
}else{
window.open(canvas.toDataURL());
}
}
function are_you_sure(action){
if(undos.length || redos.length){
var $w = new $Window();
$w.title("Paint");
$w.$content.text("Save changes to "+file_name+"?");
$w.$Button("Save", function(){
$w.close();
file_save();
action();
});
$w.$Button("Discard", function(){
$w.close();
action();
});
$w.$Button("Cancel", function(){
$w.close();
});
}else{
action();
}
}
function render_history_as_gif(){
var $win = $Window();
$win.title("Rendering GIF");
var $output = $win.$content;
var gif = new GIF({
workers: Math.min(5, Math.floor(undos.length/50)+1),
workerScript: 'lib/gif.js/gif.worker.js',
width: canvas.width,
height: canvas.height,
});
gif.on('progress', function(p){
$output.text(~~(p*100)+'%');
});
gif.on('finished', function(blob){
$win.title("Rendered GIF");
var url = URL.createObjectURL(blob);
$output.empty().append(
$(E("a")).attr({href: url, target: "_blank"}).append(
$(E("img")).attr({src: url})
).on("click", function(e){
$win.close();
if(window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry){
chrome.fileSystem.chooseEntry({
type: 'saveFile',
suggestedName: file_name+" history",
accepts: [{mimeTypes: ["image/gif"]}]
}, function(entry){
if(chrome.runtime.lastError){
return console.error(chrome.runtime.lastError.message);
}
entry.createWriter(function(file_writer){
file_writer.onwriteend = function(e){
if(this.error){
console.error(this.error + '\n\n\n@ ' + e);
}else{
console.log("File written!");
}
};
file_writer.write(blob);
});
});
}
})
);
});
for(var i=0; i<undos.length; i++){
gif.addFrame(undos[i], {delay: 200});
}
gif.addFrame(canvas, {delay: 200, copy: true});
gif.render();
}
function undoable(){
if(redos.length > 5){
var $w = new $Window();
$w.title("Paint");
$w.$content.html("Discard "+redos.length+" possible redo-able actions?<br>(Ctrl+Y to redo)<br>");
$w.$Button("Discard", function(){
$w.close();
redos = [];
});
$w.$Button("Keep", function(){
$w.close();
});
return false;
}else{
redos = [];
}
var c = document.createElement("canvas");
c.width = canvas.width;
c.height = canvas.height;
var x = c.getContext("2d");
x.drawImage(canvas, 0, 0);
undos.push(c);
return true;
}
function undo(){
if(undos.length<1) return false;
var c = document.createElement("canvas");
c.width = canvas.width;
c.height = canvas.height;
var x = c.getContext("2d");
x.drawImage(canvas, 0, 0);
redos.push(c);
c = undos.pop();
canvas.width = c.width;
canvas.height = c.height;
ctx.drawImage(c, 0, 0);
$canvas_handles.trigger("update");
return true;
}
function redo(){
if(redos.length<1) return false;
var c = document.createElement("canvas");
c.width = canvas.width;
c.height = canvas.height;
var x = c.getContext("2d");
x.drawImage(canvas, 0, 0);
undos.push(c);
c = redos.pop();
canvas.width = c.width;
canvas.height = c.height;
ctx.drawImage(c, 0, 0);
$canvas_handles.trigger("update");
return true;
}
function cancel(){
if(!selected_tool.passive) undo();
$G.triggerHandler("mouseup", "cancel");
}
function deselect(){
if(selection){
selection.draw();
selection.destroy();
selection = null;
}
}
function delete_selection(){
if(selection){
selection.destroy();
selection = null;
}
}
function select_all(){
deselect();
selection = new Selection(0, 0, canvas.width, canvas.height);
selection.instantiate();
}
function invert(){
if(undoable()){
var id = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(var i=0; i<id.data.length; i+=4){
id.data[i+0] = 255 - id.data[i+0];
id.data[i+1] = 255 - id.data[i+1];
id.data[i+2] = 255 - id.data[i+2];
}
ctx.putImageData(id, 0, 0);
}
}
function $Handle(y_axis, x_axis){
var $h = $(E("div")).addClass("jspaint-handle");
$h.appendTo($canvas_area);
var resizes_height = x_axis !== "left" && y_axis === "bottom";
var resizes_width = x_axis === "right" && y_axis !== "top";
var width = default_width;
var height = default_height;
var dragged = false;
if(!(resizes_width || resizes_height)){
$h.addClass("jspaint-useless-handle");
}else{
var cursor;
if(resizes_width && resizes_height){
cursor = "nwse-resize";
}else if(resizes_width){
cursor = "ew-resize";
}else if(resizes_height){
cursor = "ns-resize";
}
if(cursor){
cursor = Cursor([cursor, [16, 16], cursor]);
}
$h.css({cursor:cursor});
var mousemove = function(e){
$resize_ghost.appendTo("body");
dragged = true;
var rect = canvas.getBoundingClientRect();
$resize_ghost.css({
position: "relative",
left: 0,
top: 0,
width: width = (resizes_width? (e.clientX - rect.left) : (rect.width)),
height: height = (resizes_height? (e.clientY - rect.top) : (rect.height)),
});
};
$h.on("mousedown", function(e){
dragged = false;
if(e.button === 0){
$G.on("mousemove", mousemove);
$body.css({cursor:cursor});
$canvas.css({pointerEvents:"none"});
}
$G.one("mouseup", function(e){
$G.off("mousemove", mousemove);
$body.css({cursor:"auto"});
$canvas.css({pointerEvents:""});
$resize_ghost.remove();
if(dragged){
if(undoable()){
canvas.width = Math.max(1, width);
canvas.height = Math.max(1, height);
ctx.fillStyle = colors[1];
ctx.fillRect(0, 0, width, height);
var previous_canvas = undos[undos.length-1];
if(previous_canvas){
ctx.drawImage(previous_canvas, 0, 0);
}
}
}
$canvas_handles.trigger("update");
});
});
}
$h.on("update", function(){
var rect = canvas.getBoundingClientRect();
var hs = $h.width();
if(x_axis === "middle"){
$h.css({ left: (rect.width + hs) / 2 });
}else if(x_axis === "left"){
$h.css({ left: 0 });
}else if(x_axis === "right"){
$h.css({ left: rect.width + hs });
}
if(y_axis === "middle"){
$h.css({ top: (rect.height + hs) / 2 });
}else if(y_axis === "top"){
$h.css({ top: 0 });
}else if(y_axis === "bottom"){
$h.css({ top: rect.height + hs });
}
});
}

32
helpers.js Normal file
View File

@ -0,0 +1,32 @@
var TAU = //////|//////
///// | /////
/// tau ///
/// ...--> | <--... ///
/// -' one | turn '- ///
// .' | '. //
// / | \ //
// | | <-.. | //
// | .->| \ | //
// | / | | | //
- - - - - - Math.PI + Math.PI - - - - - 0
// | \ | | | //
// | '->| / | //
// | | <-'' | //
// \ | / //
// '. | .' //
/// -. | .- ///
/// '''----|----''' ///
/// | ///
////// | /////
//////|////// C/r;
function Cursor(cursor_def){
return "url(images/cursors/" + cursor_def[0] + ".png) "
+ cursor_def[1].join(" ")
+ ", " + cursor_def[2]
}
function E(t){
return document.createElement(t);
}

127
image-manipulation.js Normal file
View File

@ -0,0 +1,127 @@
function draw_ellipse(ctx, x, y, w, h){
var r1 = Math.round;
var r2 = Math.round;
var cx = x + w/2;
var cy = y + h/2;
if(aliasing){
ctx.fillStyle = stroke_color;
for(var r=0; r<TAU; r+=0.01){
var rx = Math.cos(r) * w/2;
var ry = Math.sin(r) * h/2;
var rect_x = r1(cx+rx);
var rect_y = r1(cy+ry);
var rect_w = r2(-rx*2);
var rect_h = r2(-ry*2);
ctx.fillRect(rect_x+1, rect_y, rect_w, rect_h);
ctx.fillRect(rect_x, rect_y+1, rect_w, rect_h);
ctx.fillRect(rect_x-1, rect_y, rect_w, rect_h);
ctx.fillRect(rect_x, rect_y-1, rect_w, rect_h);
}
ctx.fillStyle = fill_color;
for(var r=0; r<TAU; r+=0.01){
var rx = Math.cos(r) * w/2;
var ry = Math.sin(r) * h/2;
ctx.fillRect(
r1(cx+rx),
r1(cy+ry),
r2(-rx*2),
r2(-ry*2)
);
}
}else{
if(w<0){ x+=w; w=-w; }
if(h<0){ y+=h; h=-h; }
ctx.beginPath();
ctx.ellipse(cx, cy, w/2, h/2, 0, TAU, false);
ctx.stroke();
ctx.fill();
}
}
function draw_rounded_rectangle(ctx, x, y, width, height, radius){
if(aliasing){
var iw = width - radius*2;
var ih = height - radius*2;
var ix = x+radius;
var iy = y+radius;
var r1 = Math.round;
var r2 = Math.round;
ctx.fillStyle = stroke_color;
for(var r=0; r<TAU; r+=0.05){
var rx = Math.cos(r) * radius;
var ry = Math.sin(r) * radius;
var rect_x = r1(ix+rx);
var rect_y = r1(iy+ry);
var rect_w = r2(iw-rx*2);
var rect_h = r2(ih-ry*2);
ctx.fillRect(rect_x+1, rect_y, rect_w, rect_h);
ctx.fillRect(rect_x, rect_y+1, rect_w, rect_h);
ctx.fillRect(rect_x-1, rect_y, rect_w, rect_h);
ctx.fillRect(rect_x, rect_y-1, rect_w, rect_h);
}
ctx.fillStyle = fill_color;
for(var r=0; r<TAU; r+=0.05){
var rx = Math.cos(r) * radius;
var ry = Math.sin(r) * radius;
ctx.fillRect(
r1(ix+rx),
r1(iy+ry),
r2(iw-rx*2),
r2(ih-ry*2)
);
}
}else{
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
function draw_line(ctx, x1, y1, x2, y2){
if(aliasing){
bresenham(x1, y1, x2, y2, function(x, y){
ctx.fillRect(x, y, 1, 1);
});
}else{
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
}
function bresenham(x1, y1, x2, y2, callback){
// Bresenham's line algorithm
x1=~~x1, x2=~~x2, y1=~~y1, y2=~~y2;
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var sx = (x1 < x2) ? 1 : -1;
var sy = (y1 < y2) ? 1 : -1;
var err = dx - dy;
while(1){
callback(x1, y1);
if(x1===x2 && y1===y2) break;
var e2 = err*2;
if(e2 >-dy){ err -= dy; x1 += sx; }
if(e2 < dx){ err += dx; y1 += sy; }
}
}

View File

@ -1,16 +1,31 @@
<!doctype html><html><head><meta charset='utf-8'>
<title>Paint</title>
<link href='layout.css' rel='stylesheet' type='text/css'>
<link href='print.css' rel='stylesheet' type='text/css' media='print'>
<link href='classic.css' rel='stylesheet' type='text/css'>
<link rel='icon' href='images/icons/16.png' sizes="16x16" type='image/png'>
<link rel='icon' href='images/icons/32.png' sizes="32x32" type='image/png'>
<link rel='icon' href='images/icons/48.png' sizes="48x48" type='image/png'>
<link rel='icon' href='images/icons/128.png' sizes="128x128" type='image/png'>
<link rel='icon' href='images/icons/windows.ico' sizes="16x16,32x32,48x48" type='image/icon'>
<intent action="http://webintents.org/edit" type="image/*"></intent>
<script src='lib/jquery.min.js'></script>
<script src='lib/canvas.toBlob.js'></script>
<script src='lib/gif.js/gif.js'></script>
<script src='app.js'></script>
</head><body></body></html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Paint</title>
<link href="layout.css" rel="stylesheet" type="text/css">
<link href="print.css" rel="stylesheet" type="text/css" media="print">
<link href="classic.css" rel="stylesheet" type="text/css">
<link rel="icon" href="images/icons/16.png" sizes="16x16" type="image/png">
<link rel="icon" href="images/icons/32.png" sizes="32x32" type="image/png">
<link rel="icon" href="images/icons/48.png" sizes="48x48" type="image/png">
<link rel="icon" href="images/icons/128.png" sizes="128x128" type="image/png">
<link rel="icon" href="images/icons/windows.ico" sizes="16x16,32x32,48x48" type="image/icon">
<intent action="http://webintents.org/edit" type="image/*"></intent>
<script src="lib/jquery.min.js"></script>
<script src="lib/canvas.toBlob.js"></script>
<script src="lib/gif.js/gif.js"></script>
<script src="helpers.js"></script>
<script src="$Component.js"></script>
<script src="$Window.js"></script>
<script src="$ToolBox.js"></script>
<script src="$ColorBox.js"></script>
<script src="Selection.js"></script>
<script src="image-manipulation.js"></script>
<script src="tools.js"></script>
<script src="functions.js"></script>
</head>
<body>
<script src="app.js"></script>
</body>
</html>

307
tools.js Normal file
View File

@ -0,0 +1,307 @@
var brush_image = new Image();
brush_image.src = "images/scroll-left.png";
var brush_canvas = E("canvas");
var brush_ctx = brush_canvas.getContext("2d");
var brush_rendered_color;
tools = [{
name: "Free-Form Select",
description: "Selects a free-form part of the picture to move, copy, or edit.",
cursor: ["precise", [16, 16], "crosshair"],
passive: true,
implemented: false,
}, {
name: "Select",
description: "Selects a rectangular part of the picture to move, copy, or edit.",
cursor: ["precise", [16, 16], "crosshair"],
passive: true,
implemented: "kinda",
mousedown: function(){
if(selection){
selection.draw();
selection.destroy();
}
var mouse_has_moved = false;
$G.one("mousemove", function(){
mouse_has_moved = true;
});
$G.one("mouseup", function(){
if(!mouse_has_moved && selection){
selection.draw();
selection.destroy();
selection = null;
}
});
var s = selection = new Selection(mouse.x, mouse.y, 1, 1);
$canvas.one("mousedown", function(){
if(selection === s){
selection.draw();
selection.destroy();
selection = null;
}
});
},
paint: function(){
if(!selection){return;}
selection.w = selection.x - mouse.x;
selection.h = selection.y - mouse.y;
var x1 = Math.max(0, Math.min(selection.x, mouse.x));
var y1 = Math.max(0, Math.min(selection.y, mouse.y));
var x2 = Math.min(canvas.width, Math.max(selection.x, mouse.x));
var y2 = Math.min(canvas.height, Math.max(selection.y, mouse.y));
selection._x = x1;
selection._y = y1;
selection._w = Math.max(1, x2 - x1);
selection._h = Math.max(1, y2 - y1);
selection.position();
},
mouseup: function(){
if(!selection){return;}
selection.instantiate();
if(ctrl){
selection.crop();
deselect();
}
},
cancel: function(){
if(!selection){return;}
selection.destroy();
selection = null;
},
}, {
name: "Eraser/Color Eraser",
description: "Erases a portion of the picture, using the selected eraser shape.",
continuous: "space",
cursor: ["precise", [16, 16], "crosshair"], //@todo: draw square on canvas
implemented: "partially",
paint: function(ctx, x, y){
ctx.fillStyle = colors[1];
ctx.fillRect(x-4, y-4, 8, 8);
}
}, {
name: "Fill With Color",
description: "Fills an area with the selected drawing color.",
cursor: ["fill-bucket", [8, 22], "crosshair"],
mousedown: function(ctx, x, y){
var _c = E("canvas");
_c.width = _c.height = 1;
var _ctx = _c.getContext("2d");
_ctx.fillStyle = fill_color;
_ctx.fillRect(0, 0, 1, 1);
var _id = _ctx.getImageData(0, 0, 1, 1);
var fill_r = _id.data[0];
var fill_g = _id.data[1];
var fill_b = _id.data[2];
var stack = [[x, y]];
var c_width = canvas.width;
var c_height = canvas.height;
var id = ctx.getImageData(0, 0, c_width, c_height);
pixel_pos = (y*c_width + x) * 4;
var start_r = id.data[pixel_pos+0];
var start_g = id.data[pixel_pos+1];
var start_b = id.data[pixel_pos+2];
if(fill_r === start_r
&& fill_g === start_g
&& fill_b === start_b){
return;
}
while(stack.length){
var new_pos, x, y, pixel_pos, reach_left, reach_right;
new_pos = stack.pop();
x = new_pos[0];
y = new_pos[1];
pixel_pos = (y*c_width + x) * 4;
while(match_start_color(pixel_pos)){
pixel_pos -= c_width * 4, y--;
}
pixel_pos += c_width * 4, y++;
reach_left = false;
reach_right = false;
while(y++ < c_height && match_start_color(pixel_pos)){
color_pixel(pixel_pos);
if(x > 0){
if(match_start_color(pixel_pos - 4)){
if(!reach_left){
stack.push([x - 1, y]);
reach_left = true;
}
}else if(reach_left){
reach_left = false;
}
}
if(x < c_width-1){
if(match_start_color(pixel_pos + 4)){
if(!reach_right){
stack.push([x + 1, y]);
reach_right = true;
}
}else if(reach_right){
reach_right = false;
}
}
pixel_pos += c_width * 4;
}
}
ctx.putImageData(id, 0, 0);
function match_start_color(pixel_pos){
return (id.data[pixel_pos+0] === start_r
&& id.data[pixel_pos+1] === start_g
&& id.data[pixel_pos+2] === start_b);
}
function color_pixel(pixel_pos){
id.data[pixel_pos+0] = fill_r;
id.data[pixel_pos+1] = fill_g;
id.data[pixel_pos+2] = fill_b;
id.data[pixel_pos+3] = 255;
}
}
}, {
name: "Pick Color",
description: "Picks up a color from the picture for drawing.",
cursor: ["eye-dropper", [9, 22], "crosshair"],
deselect: true,
passive: true,
current_color: "",
display_current_color: function(){
$tool_options_area.css({
background: this.current_color
});
},
mousedown: function(){
$G.one("mouseup", function(){
$tool_options_area.css({
background: ""
});
});
},
paint: function(ctx, x, y){
if(x >= 0 && y >= 0 && x < canvas.width && y < canvas.height){
var id = ctx.getImageData(~~x, ~~y, 1, 1);
var r = id.data[0];
var g = id.data[1];
var b = id.data[2];
var a = id.data[3];
this.current_color = "rgba("+r+","+g+","+b+","+a/255+")";
}else{
this.current_color = "white";
}
this.display_current_color();
},
mouseup: function(){
colors[fill_color_i] = this.current_color;
$colorbox && $colorbox.update_colors();
}
}, {
name: "Magnifier",
description: "Changes the magnification.",
cursor: ["magnifier", [16, 16], "zoom-in"], //@todo: use zoom-in/zoom-out
deselect: true,
passive: true,
implemented: false,
}, {
name: "Pencil",
description: "Draws a free-form line one pixel wide.",
cursor: ["pencil", [13, 23], "crosshair"],
continuous: "space",
stroke_only: true,
paint: function(ctx, x, y){
ctx.fillRect(x, y, 1, 1);
}
}, {
name: "Brush",
description: "Draws using a brush with the selected shape and size.",
cursor: ["precise-dotted", [16, 16], "crosshair"],
continuous: "space",
paint: function(ctx, x, y){
var sz = 16;
if(brush_rendered_color !== stroke_color){
brush_canvas.width = sz;
brush_canvas.height = sz;
brush_ctx.clearRect(0, 0, sz, sz);
brush_ctx.drawImage(brush_image, sz/2-brush_image.width/2, sz/2-brush_image.height/2);
brush_ctx.globalCompositeOperation = "source-atop";
brush_ctx.fillStyle = stroke_color;
brush_ctx.fillRect(0, 0, sz, sz);
brush_ctx.globalCompositeOperation = "source-over";
brush_rendered_color = stroke_color;
}
ctx.drawImage(brush_canvas, x-sz/2, y-sz/2);
}
}, {
name: "Airbrush",
description: "Draws using an airbrush of the selected size.",
cursor: ["airbrush", [7, 22], "crosshair"],
continuous: "time",
paint: function(ctx, x, y){
var radius = 15; //@todo: options
var sqr = radius * radius;
for(var i=0; i<100; i++){
var rx = (Math.random()*2-1)*radius;
var ry = (Math.random()*2-1)*radius;
var d = rx*rx + ry*ry;
if(d <= radius){
ctx.fillRect(x + ~~rx, y + ~~ry, 1, 1);
}
}
}
}, {
name: "Text",
description: "Inserts text into the picture.",
cursor: ["precise", [16, 16], "crosshair"],
implemented: false,
}, {
name: "Line",
description: "Draws a straight line with the selected line width.",
cursor: ["precise", [16, 16], "crosshair"],
stroke_only: true,
shape: function(ctx, x, y, w, h){
draw_line(ctx, x, y, x+w, y+h);
}
}, {
name: "Curve",
description: "Draws a curved line with the selected line width.",
cursor: ["precise", [16, 16], "crosshair"],
implemented: false,
}, {
name: "Rectangle",
description: "Draws a rectangle with the selected fill style.",
cursor: ["precise", [16, 16], "crosshair"],
shape: function(ctx, x, y, w, h){
ctx.beginPath();
ctx.rect(x-0.5, y-0.5, w, h);
ctx.fill();
ctx.stroke();
}
}, {
name: "Polygon",
description: "Draws a polygon with the selected fill style.",
cursor: ["precise", [16, 16], "crosshair"],
implemented: false,
}, {
name: "Ellipse",
description: "Draws an ellipse with the selected fill style.",
cursor: ["precise", [16, 16], "crosshair"],
shape: draw_ellipse
}, {
name: "Rounded Rectangle",
description: "Draws a rounded rectangle with the selected fill style.",
cursor: ["precise", [16, 16], "crosshair"],
shape: function(ctx, x, y, w, h){
if(w<0){ x+=w; w=-w; }
if(h<0){ y+=h; h=-h; }
var radius = Math.min(7, w/2, h/2);
draw_rounded_rectangle(ctx, x, y, w, h, radius);
}
}];