/*
* @title open2chOekakiEX
* @description おーぷん2ちゃんお絵かき機能拡張ブックマークレット
* @private
*/
/*
【更新履歴】
Ver1.0.0 2014/03/29 文字入力機能追加
Ver1.1.0 2014/03/29 お絵かき機能ツールメニュー内に文字入力機能を追加 文字サイズ指定追加
Ver1.2.0 2014/03/30 フォント指定、修飾指定追加
Ver1.3.0 2014/03/30 線描画と文字入力のUNDO/REDO機能追加 イタリックが効かないバグ修正
Ver2.0.0 2014/03/31 塗りつぶし機能追加
Ver2.1.0 2014/03/31 塗りつぶし機能にUNDO/REDO機能追加
Ver2.1.1 2014/03/31 IE、Chromeで塗りつぶしが効かないバグ修正
Ver2.2.0 2014/04/01 背景色以外の色も塗りつぶせるように修正
Ver2.3.0 2014/04/01 透明度を指定して塗りつぶせるように修正
Ver2.4.0 2014/04/02 文字入力でフォントの直接指定&改行入力&縦書出力に対応
Ver3.0.0 2014/04/05 お絵かき再生機能追加
Ver3.1.0 2014/04/06 お絵かき再生機能のエクスポート方式をテキストから画像埋め込みに変更
Ver3.2.0 2014/04/09 お絵かき再生機能で塗りつぶしと消しゴムが再生できるように修正 線の描画時にちらつくバグを修正
*/
drawRedo = [];
canvas = $('#sketch').sketch();
var fillInput = document.createElement('input');
fillInput.setAttribute('id', 'fill');
fillInput.setAttribute('type', 'radio');
fillInput.setAttribute('name', 'pmode');
var fillImg = document.createElement('img');
fillImg.setAttribute('src', 'http://image.open2ch.net/image/oekaki/nuri.png');
var fillLabel = document.createElement('label');
fillLabel.appendChild(fillImg);
var textInput = document.createElement('input');
textInput.setAttribute('id', 'moji');
textInput.setAttribute('type', 'radio');
textInput.setAttribute('name', 'pmode');
var textLabel = document.createElement('label');
textLabel.setAttribute('for', 'moji');
var b = document.createElement('b');
var textNode = document.createTextNode('[A]:');
b.appendChild(textNode);
textLabel.appendChild(b);
var size = [10,12,14,16,18,20,24,28,32,36,40,48,56,64,72,80,90,100];
var mojiSelect = document.createElement('select');
mojiSelect.setAttribute('id', 'mojiSize');
for(var i = 0; i < size.length; i++){
var option = document.createElement('option');
if(i == 5){
option.setAttribute('selected','selected');
}
option.value = size[i];
var text = document.createTextNode(size[i]);
option.appendChild(text);
mojiSelect.appendChild(option);
}
var fontSelect = document.createElement('select');
fontSelect.setAttribute('id', 'mojiFont');
var font = ['ゴシック','明朝','筆記体','装飾','等幅'];
for(var i = 0; i < font.length; i++){
var option = document.createElement('option');
option.value = i;
var text = document.createTextNode(font[i]);
option.appendChild(text);
fontSelect.appendChild(option);
}
var boldCheck = document.createElement('input');
boldCheck.setAttribute('id', 'mojiBold');
boldCheck.setAttribute('type', 'checkbox');
boldCheck.setAttribute('value', 'bold');
var b = document.createElement('b');
var textNode = document.createTextNode(' B:');
b.appendChild(textNode);
var italicCheck = document.createElement('input');
italicCheck.setAttribute('id', 'mojiItalic');
italicCheck.setAttribute('type', 'checkbox');
italicCheck.setAttribute('value', 'italic');
var i = document.createElement('i');
var textNode = document.createTextNode(' I:');
i.appendChild(textNode);
var br = document.createElement('br');
$('#backButton').before(fillInput);
$('#backButton').before(fillLabel);
$('#backButton').before(textInput);
$('#backButton').before(textLabel);
$('#backButton').before(mojiSelect);
$('#backButton').before(fontSelect);
$('#backButton').before(b);
$('#backButton').before(boldCheck);
$('#backButton').before(i);
$('#backButton').before(italicCheck);
$('#backButton').before(br);
var mojiA = document.createElement('a');
mojiA.setAttribute('data-tool','moji');
mojiA.setAttribute('href', '#sketch');
var fillA = document.createElement('a');
fillA.setAttribute('data-tool','fill');
fillA.setAttribute('href', '#sketch');
$('.tools').after(fillA);
$('.tools').after(mojiA);
$("#moji").click(function(){$("[data-tool=moji]").click()});
$("#fill").click(function(){$("[data-tool=fill]").click()});
var exp = document.createElement('input');
exp.setAttribute('id', 'exportButton');
exp.setAttribute('type', 'button');
exp.setAttribute('value', 'エクスポート');
var imp = document.createElement('input');
imp.setAttribute('id', 'importButton');
imp.setAttribute('type', 'button');
imp.setAttribute('value', 'インポート');
$('#saveButton').after(imp);
$('#saveButton').after(exp);
$('#importButton').click(function(){
playback(stringToAction(colorToActionString()), 10);
});
$('#exportButton').click(function(){
actionStringToColor(actionToString());
});
var show = $('#_canvas').next().children();
show.html("ctrl+←:UNDO<br/>ctrl+→:REDO<br/>" + show.html());
function fill(e){
fillCanvas = canvas.context.getImageData(0, 0, canvas.el.width, canvas.el.height);
var pageX = e.pageX;
var pageY = e.pageY;
var offsetX = $('#sketch').offset().left;
var offsetY = $('#sketch').offset().top;
var x = Math.floor(pageX - offsetX);
var y = Math.floor(pageY - offsetY);
var fillRGB = canvas.color.match(/(\d|\.)+/g);
if(fillRGB[3] == undefined){
var alpha = 255;
}else{
var alpha = alphaToInt(fillRGB[3]);
}
var fillColor = ((Number(fillRGB[0]) << 24) + (Number(fillRGB[1]) << 16) + (Number(fillRGB[2]) << 8) + alpha)>>>0;
//console.log('fillColor>' + fillColor.toString(2));
//console.log('fillR>' + fillRGB[0] + ' fillG>' + fillRGB[1] + ' fillB>' + fillRGB[2] + ' fillA' + alpha);
//console.log('fillRbit>' + ((fillColor & 0xFF000000) >>> 24));
//console.log('fillGbit>' + ((fillColor & 0xFF0000) >> 16));
//console.log('fillBbit>' + ((fillColor & 0xFF00) >> 8));
//console.log('fillAbit>' + (fillColor & 0xFF));
var targetColor = getRGBA(x, y);
//console.log('targetRbit>' + ((targetColor & 0xFF000000) >>> 24));
//console.log('targetGbit>' + ((targetColor & 0xFF0000) >> 16));
//console.log('targetBbit>' + ((targetColor & 0xFF00) >> 8));
//console.log('targetAbit>' + (targetColor & 0xFF));
canvas.actions.push({
x: x,
y: y,
tool: canvas.tool,
fillColor: fillColor,
targetColor: targetColor,
fill:null
});
canvas.redraw();
}
function paint(x, y, fillColor, targetColor){
var color = getRGBA(x, y);
if(color == fillColor){
return;
}
fillRGBA(x, y, fillColor);
var rightX = x + 1;
while(rightX < fillCanvas.width){
var color = getRGBA(rightX, y);
if(color == targetColor){
fillRGBA(rightX, y, fillColor);
}else{
break;
}
rightX++;
}
var leftX = x -1;
while(leftX >= 0){
var color = getRGBA(leftX, y);
if(color == targetColor){
fillRGBA(leftX, y, fillColor);
}else{
break;
}
leftX--;
}
if(y -1 >= 0){
scanSeed(leftX, rightX, y-1, targetColor);
}
if(y + 1 < fillCanvas.width){
scanSeed(leftX, rightX, y+1, targetColor);
}
}
function scanSeed(leftX, rightX, y, targetColor){
var seed = false;
for(var x = leftX + 1; x < rightX; x++){
var color = getRGBA(x, y);
if(color == targetColor){
seed = true;
}else if(seed){
seeds.push({"x":x - 1, "y":y});
seed = false;
}
}
if(seed){
seeds.push({"x":rightX - 1, "y":y});
}
}
function fillRGBA(x, y, color) {
var img = fillCanvas.data;
var w = fillCanvas.width;
var h = fillCanvas.height;
var p = ((w * y) + x) * 4;
//console.log('beforeR>' + ((color & 0xFF000000) >>> 24));
//console.log('beforeG>' + ((color & 0xFF0000) >> 16));
//console.log('beforeB>' + ((color & 0xFF00) >> 8));
//console.log('beforeA>' + ((color & 0xFF)));
img[p] = (color & 0xFF000000) >>> 24;
img[p+1] = (color & 0xFF0000) >> 16;
img[p+2] = (color & 0xFF00) >> 8;
img[p+3] = color & 0xFF;
//console.log('afterR>' + img[p]);
//console.log('afterG>' + img[p+1]);
//console.log('afterB>' + img[p+2]);
//console.log('afterA>' + img[p+3]);
}
function fillRGB(x, y, color) {
//console.log('x:' + x + ' y:' + y);
var img = fillCanvas.data;
var w = fillCanvas.width;
var h = fillCanvas.height;
var p = ((w * y) + x) * 4;
//console.log('beforeR>' + img[p]);
//console.log('beforeG>' + img[p+1]);
//console.log('beforeB>' + img[p+2]);
//console.log('beforeA>' + img[p+3]);
img[p] = (color & 0xFF0000) >> 16;
img[p+1] = (color & 0xFF00) >> 8;
img[p+2] = color & 0xFF;
img[p+3] = 0xFF;
//console.log('afterR>' + img[p]);
//console.log('afterG>' + img[p+1]);
//console.log('afterB>' + img[p+2]);
//console.log('afterA>' + img[p+3]);
}
function getRGBA(x, y){
var img = fillCanvas.data;
var w = fillCanvas.width;
var h = fillCanvas.height;
var p = ((w * y) + x) * 4;
//console.log('getR>' + img[p]);
//console.log('getG>' + img[p+1]);
//console.log('getB>' + img[p+2]);
//console.log('getA>' + img[p+3]);
//console.log('putR>' + ((img[p] << 24)>>>0));
//console.log('putG>' + (img[p+1] << 16));
//console.log('putB>' + (img[p+2] << 8));
//console.log('putA>' + (img[p+3]));
//console.log(((Number(img[p]) << 24) + (Number(img[p+1]) << 16) + (Number(img[p+2]) << 8) + img[p+3])>>>0);
return ((img[p] << 24) + (img[p+1] << 16) + (img[p+2] << 8) + img[p+3])>>>0;
}
function getRGB(x, y){
//console.log('x:' + x + ' y:' + y);
var img = fillCanvas.data;
var w = fillCanvas.width;
var h = fillCanvas.height;
var p = ((w * y) + x) * 4;
return ((img[p] << 16) + (img[p+1] << 8) + (img[p+2]));
}
function getRGBAString(color){
return 'rgba('
+ (color & 0xFF000000) >>> 24 + ', '
+ (color & 0xFF0000) >> 16 + ', '
+ (color & 0xFF00) >> 8 + ', '
+ (color & 0xFF) + ')';
}
function getRGBString(color){
return 'rgb('
+ ((color & 0xFF0000) >> 16) + ', '
+ ((color & 0xFF00) >> 8) + ', '
+ (color & 0xFF) + ')';
}
function alphaToInt(floatAlpha){
return Math.floor(Number(floatAlpha * 255));
}
function alphaToFloat(intAlpha){
return Math.floor(intAlpha / 255 * 100) * 100;
}
$.sketch.tools.fill= {
onEvent: function(e) {
switch (e.type) {
case 'mousedown':
case 'touchstart':
fill(e);
break;
}
},
draw: function(action){
if(action.fill){
fillCanvas = action.fill;
}else{
var fillColor = action.fillColor;
fillCanvas = canvas.context.getImageData(0, 0, canvas.el.width, canvas.el.height);
var targetColor = action.targetColor;
seeds = [{
'x':Number(action.x),
'y':Number(action.y)
}];
//var start = new Date();
while(seeds.length > 0){
var seed = seeds.shift();
paint(seed.x, seed.y, fillColor, targetColor);
}
//var end = new Date();
//console.log(end - start + 'msec');
action.fill = fillCanvas;
}
return canvas.context.putImageData(fillCanvas, 0, 0);
}
}
function drawText(e){
var pageX = e.pageX;
var pageY = e.pageY;
var offsetX = $('#sketch').offset().left;
var offsetY = $('#sketch').offset().top;
var x = Math.floor(pageX - offsetX);
var y = Math.floor(pageY - offsetY);
var context = canvas.context;
var text = window.prompt("文字を入力。");
if(!text){
text= '';
}
var str = text.match(/^(kyaha!|\:v)(.*)/i);
var mode = '';
if(str != null){
var mode = str[1];
var text = str[2];
}
str = text.match(/^(?:usa|\[)(.*)(?:min|\])(.*)/i);
var mojiFont;
if(str != null && str[1] != null){
mojiFont = str[1];
text = str[2];
}else{
var fontFamily = ['sans-serif','serif','cursive','fantasy','monospace'];
mojiFont = fontFamily[$('#mojiFont').val()];
}
text = text.replace(/nana|\\n/g, "\n");
var mojiSize = $('#mojiSize').val();
if($('#mojiBold:checked').val()){
var mojiBold = $('#mojiBold:checked').val() + ' ';
}else{
var mojiBold = ' ';
}
if($('#mojiItalic:checked').val()){
var mojiItalic = $('#mojiItalic:checked').val() + ' ';
}else{
var mojiItalic = ' ';
}
canvas.actions.push({
x: x,
y: y,
tool: canvas.tool,
color: canvas.color,
font: mojiBold + mojiItalic + mojiSize + 'px ' + mojiFont,
mode: mode,
text: text
});
canvas.redraw();
}
$.sketch.tools.moji = {
onEvent: function(e) {
switch (e.type) {
case 'mousedown':
case 'touchstart':
drawText(e);
break;
}
},
draw: function(action){
var context = canvas.context;
context.fillStyle = action.color;
context.font = action.font;
context.baseline = 'top';
return fillText(context, action.mode, action.text, action.x, action.y);
}
}
function fillText(context, mode, text, x, y){
var lines = text.split('\n');
var h = context.measureText("あ").width;
jQuery.each(lines, function(i, line) {
if(mode == ''){
context.textAlign = 'left'
context.fillText(this, x, y+h*i);
}else{
jQuery.each(line, function(j, char){
context.textAlign = 'center';
context.fillText(char, x - h*i, y+h*j);
});
}
});
}
function actionToString(){
var actionString = '';
var actDel = '!';
var propDel = '~';
var tools = {
marker: 'm',
eraser: 'e',
moji: 's',
fill: 'f'
};
jQuery.each(canvas.actions, function(i, action){
if(tools[action.tool]){
actionString += actDel + tools[action.tool];
switch(action.tool){
case 'marker':
case 'eraser':
var actionX = Math.floor(action.events[0].x);
var actionY = Math.floor(action.events[0].y);
var beforeX = actionX;
var beforeY = actionY;
actionString += lpad(actionX, 3, '0') + lpad(actionY, 3, '0') + lpad(action.size, 3, '0') + action.color.replace(/ /g, '') + propDel;
jQuery.each(action.events, function(j, event){
actionX = Math.floor(event.x);
actionY = Math.floor(event.y);
actionString += String.fromCharCode((actionX - beforeX + 64), (actionY - beforeY + 64));
beforeX = actionX;
beforeY = actionY;
});
break;
case 'moji':
actionString += lpad(action.x, 3, '0') + lpad(action.y, 3, '0') + action.mode + propDel + action.color + propDel + action.font + propDel + action.text;
break;
case 'fill':
actionString += lpad(action.x, 3, '0') + lpad(action.y, 3, '0') + propDel + action.fillColor + propDel + action.targetColor;
break;
}
}
});
actionString = actionString.substr(1);
return actionString;
}
function actionStringToColor(actionString){
console.log('ExportString'+actionString);
//var undefCharCode = 35;
var ch = canvas.el.height;
var m = -10;
var w = 1;
var h = 1;
//canvas.el.height += Math.floor((actionString.length+3)/(canvas.el.width+m)/n)+10;
fillCanvas = canvas.context.getImageData(0, 0, canvas.el.width, canvas.el.height);
fillN(1, 0, w, h, 0x000000);
for(var i = 0; i < actionString.length/3; i++){
var c1 = actionString.charCodeAt(i*3);
var c2 = actionString.charCodeAt(i*3 + 1);
var c3 = actionString.charCodeAt(i*3 + 2);
if(!c1){c1 = 35;}
if(!c2){c2 = 35;}
if(!c3){c3 = 35;}
var color = (c1 << 16) + (c2 << 8) + c3;
//console.log('x>' + (i+2)%canvas.el.width + ' y>' + (Math.floor((i+2)/canvas.el.width) + h));
fillN(((i+1)*w)%(canvas.el.width+m)+1, Math.floor(((i+1)*w)/(canvas.el.width+m))*h, w, h, color);
}
fillN(((i+1)*w)%(canvas.el.width+m)+1, Math.floor(((i+1)*w)/(canvas.el.width+m))*h, w, h, 0x000000);
orgActions = canvas.actions;
canvas.actions = [''];
var beforeBaseImage = canvas.baseImageURL;
canvas.redraw();
canvas.context.putImageData(fillCanvas, 0, 0);
var src = canvas.el.toDataURL('imgae/png');
canvas.setBaseImageURL(src);
canvas.actions = orgActions;
canvas.redraw();
}
function fillN(x, y, w, h, color){
for(var i = 0; i < w; i++){
for(var j = 0; j < h; j++){
fillRGB(x + i, y + j, color);
}
}
}
function colorToActionString(){
fillCanvas = canvas.context.getImageData(0, 0, canvas.el.width, canvas.el.height);
var actionString = '';
var undefCharCode = 35;
var ch = 1;
var m = -10;
var w = 1;
var h = 1;
var c = getRGB(1, 0);
if(c!=0x000000){return;}
var i = 0;
while(i < canvas.el.width * canvas.el.height / 4){
var color = getRGB(((i+1)*w)%(canvas.el.width+m)+1, (Math.floor(((i+1)*h)/(canvas.el.width+m)))*h);
if(color==0x000000){break;}
c1 = (color&0xFF0000)>>16;
c2 = (color&0xFF00)>>8;
c3 = (color&0xFF);
i++;
actionString += String.fromCharCode(c1);
actionString += String.fromCharCode(c2);
actionString += String.fromCharCode(c3);
}
console.log('ImportString' + actionString.replace(new RegExp(String.fromCharCode(undefCharCode), 'g'), ''));
return actionString.replace(new RegExp(String.fromCharCode(undefCharCode), 'g'), '');
}
function stringToAction(actionString){
if(!actionString){alert('描画情報が見つかりませんでした。');return [];}
if(!confirm('お絵かきを再生します。\n※再生を開始すると、あなたの描いた絵の情報はクリアされます')){return [];}
canvas.clear();
var actionDelimiter = '!';
var propDelimiter = '~';
var splitActions = actionString.split(actionDelimiter);
var actions =[];
var tools = {
m: 'marker',
e: 'eraser',
s: 'moji',
f: 'fill'
};
jQuery.each(splitActions, function(i, actionsLine){
var splitAction = actionsLine.split(propDelimiter);
var actionLine = splitAction[0];
var tool = tools[actionLine.substr(0, 1)];
switch(tool){
case 'marker':
case 'eraser':
var bX = actionLine.substr(1, 3);
var bY = actionLine.substr(4, 3);
var size = actionLine.substr(7, 3);
var color = actionLine.substr(10);
var action = {
tool: tool,
size: parseFloat(size),
color: color,
events: []
}
action.events.push({x: bX, y: bY});
var eventLine = splitAction[1];
for(var i = 1; i <= eventLine.length/2; i++){
action.events.push({
x: Number(action.events[i-1].x) + Number(eventLine.charCodeAt(i*2) - 64),
y: Number(action.events[i-1].y) + Number(eventLine.charCodeAt(i*2+1) - 64)});
}
break;
//case 'moji':
// var action = {
// tool: tool,
// mode: actionLine.substr(1, 1),
// color: splitAction[1],
// font: splitAction[2],
// text: splitAction[3]
// };
// break;
case 'fill':
var action = {
tool: tool,
x: actionLine.substr(1, 3),
y: actionLine.substr(4, 3),
fillColor: splitAction[1],
targetColor: splitAction[2]
};
break;
}
actions.push(action);
});
return actions;
}
function playback(actions, time){
console.log('playback');
console.log(actions);
if(actions.length == 0){return;}
var painting = false;
var id1 = setInterval(function(){
if(actions[0]){
var action = actions[0];
if(!painting){
switch(action.tool){
case 'marker':
case 'eraser':
var i = 0;
var events = action.events;
var id2 = setInterval(function(){
painting = true;
if(events && i+1 < events.length){
canvas.context.beginPath();
canvas.context.moveTo(events[i].x, events[i].y);
canvas.context.lineJoin = 'round';
canvas.context.lineCap = 'round';
canvas.context.lineTo(events[i+1].x, events[i+1].y);
canvas.context.strokeStyle = action.color;
canvas.context.lineWidth = action.size;
canvas.context.stroke();i++;
}else{
clearInterval(id2);
painting = false;
canvas.actions.push(actions.shift());
canvas.redraw();
}
},time);
break;
case 'moji':
case 'fill':
painting = true;
canvas.actions.push(actions.shift());
canvas.redraw();
painting = false;
break;
}
}
}else{
clearInterval(id1);
}
},time*4);
}
function lpad(string, length, char){
var padding;
for(var i = 1; i <= length; i++){
padding += char;
}
return (padding + string).slice(-length);
}
function keydownEvent(e, keyNum, popList, pushList){
if(e.which === keyNum && e.ctrlKey && popList.length > 0){
pushList.push(popList.pop());
canvas.redraw();
}
}
$(document).keydown(function(e){
keydownEvent(e, 37, canvas.actions, drawRedo);
keydownEvent(e, 39, drawRedo, canvas.actions);
});