p5.jsを使って画像にメモ書き

p5.jsは、容易にグラフィックやサウンドを操作できるJavaScriptライブラリです。 詳しいプログラミングの知識がなくても、アプリケーションやアニメーション、ゲームなどをブラウザ上で表現できるようになります。 今回は、ブラウザ上で画像にメモ書きしながらアップロードする機能を作成しました。

サンプル

フォーム設定

コード

本サンプルでは、p5.min.js及びp5.dom.min.jsを利用しています。 p5.min.jsのみでも同様の機能の実装は可能ですが、p5.dom.min.jsを利用することでより簡単にinput要素を作成しています。
まず、p5.min.jsとp5.dom.min.jsを登録したのち、下記sample.jsを登録します。
※p5.js v0.7.1で動作を確認しています。

sample.js

(function(){"use strict";varmyp5;kintone.events.on(['app.record.create.show','app.record.edit.show',],function(event){kintone.app.record.setFieldShown('添付ファイル',false);kintone.app.record.getSpaceElement('space').innerHTML='\<div class="control-label-gaia"\>\<span class="control-label-text-gaia"\>添付ファイル\</span\>\</div\>\<div id="p5Container"\>\</div\>';myp5=newp5((function(p){varcolor,weight;varshowImage=function(image){p.resizeCanvas(image.width,image.height);p.canvas.style.width='';p.canvas.style.height='';p.background(image);};p.setup=function(){if(event.type==='app.record.create.show'){p.file={name:'image.png',type:'image/png',};p.createCanvas(500,300);p.background(255);}else{p.file={name:event.record.添付ファイル.value[0].name,type:event.record.添付ファイル.value[0].contentType,};varxhr=newXMLHttpRequest();xhr.open('GET','/k/v1/file.json?fileKey='+event.record.添付ファイル.value[0].fileKey);xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');xhr.responseType='blob';xhr.addEventListener('load',function(){p.loadImage((window.URL||window.webkitURL).createObjectURL(xhr.response),showImage);});xhr.send();}p.createFileInput(function(file){if(file.type!=='image')return;p.file={name:file.name,type:'image/'+file.subtype,};p.loadImage(file.data,showImage);});color=p.createInput('#000');weight=p.createInput(10,'number');}p.draw=function(){p.stroke(color.elt.value);p.strokeWeight(weight.elt.value);if(p.mouseIsPressed===true){p.line(p.mouseX,p.mouseY,p.pmouseX,p.pmouseY);}}}),'p5Container');});kintone.events.on(['app.record.create.submit.success','app.record.edit.submit.success',],function(event){returnnewkintone.Promise(function(resolve){myp5.canvas.toBlob(function(blob){varformData=newFormData();varxhr=newXMLHttpRequest();formData.append('\_\_REQUEST\_TOKEN\_\_',kintone.getRequestToken());formData.append('file',blob,myp5.file.name);xhr.open('POST',encodeURI('/k/v1/file.json'));xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');xhr.addEventListener('load',function(){kintone.api('/k/v1/record','PUT',{app:kintone.app.getId(),id:event.recordId,record:{添付ファイル:{value:[{fileKey:JSON.parse(xhr.responseText).fileKey}]}}}).then(function(){resolve(event);});});xhr.send(formData);},myp5.file.type);});});})();if(!HTMLCanvasElement.prototype.toBlob){//polyfillObject.defineProperty(HTMLCanvasElement.prototype,'toBlob',{value:function(callback,type,quality){varbinStr=atob(this.toDataURL(type,quality).split(',')[1]),len=binStr.length,arr=newUint8Array(len);for(vari=0;i\<len;i++){arr[i]=binStr.charCodeAt(i);}callback(newBlob([arr],{type:type||'image/png'}));}});}

いつも参考にさせていただいております。

このカスタマイズでデザインの校正アプリが作れそうで、大変助かっています。

 

1つ質問ですが、消しゴム機能を追加することは可能でしょうか。

現状では書き間違えると保存せずに閉じるしかないので、kintone上で書き込みした部分だけ消せるととても便利なのですが。

自分なりに調べてみましたが、どうにも方法が見つかりませんでした。

N_okazaki様

お世話になっております。
コメントありがとうございます。

下記のように変更して、ボタンを追加するとよいと思います。

...
    myp5 = new p5((function(p){
      var color, weight, clear, displayedFile;
      var showImage = function(image){
        p.resizeCanvas(image.width, image.height);
        p.canvas.style.width = '';
        p.canvas.style.height = '';
        p.background(image);
      };
      p.setup = function(){
        if(event.type === 'app.record.create.show'){
          p.file = {
            name: 'image.png',
            type: 'image/png',
          };
          p.createCanvas(500, 300);
          p.background(255);
        }else{
          p.file = {
            name: event.record.添付ファイル.value[0].name,
            type: event.record.添付ファイル.value[0].contentType,
          };
          var xhr = new XMLHttpRequest();
          xhr.open('GET', '/k/v1/file.json?fileKey=' + event.record.添付ファイル.value[0].fileKey);
          xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
          xhr.responseType = 'blob';
          xhr.addEventListener('load', function(){
            displayedFile = (window.URL || window.webkitURL).createObjectURL(xhr.response);
            p.loadImage(displayedFile, showImage);
          });
          xhr.send();
        }
        p.createFileInput(function(file){
          if(file.type !== 'image') return;
          displayedFile = file.data;
          p.file = {
            name: file.name,
            type: 'image/' + file.subtype,
          };
          p.loadImage(displayedFile, showImage);
        });
        color = p.createInput('#000');
        weight = p.createInput(10, 'number');
        clear = p.createButton('clear');
        clear.mousePressed(function(){
          if(displayedFile){
            p.loadImage(displayedFile, showImage);
          }else{
            p.background(255);
          }
        });
      }
      p.draw = function(){
        p.stroke(color.elt.value);
        p.strokeWeight(weight.elt.value);
        if (p.mouseIsPressed === true) {
          p.line(p.mouseX, p.mouseY, p.pmouseX, p.pmouseY);
        }
      }
    }), 'p5Container');
...

江田様

 

早速ご返信いただきありがとうございます。

コードまでいただき、とても助かりました。

まさしく、これがやりたかった!という動作です。早速使っていきたいと思います。

サンプルを動かしたのですが、手書きをして保存、手書きをして保存を繰り返すと画像が大きくなりませんか?

どこを直したらいいのかわからなかったので気づいたことだけお伝えします。

いつもありがとうございます。

この手書きの機能を署名に使用したいと思い、四苦八苦しております。

iPadにて署名をしようと思い下記のように修正をしたのですが、

なぜか「署名に何も書かずに保存→再度開く」としたときに、手書き欄がとても大きくなってしまいます。

(デスクトップでブラウザで同じようにした場合にはそのようなことは起こりませんでした。)

 

コメントアウトしてある部分はモバイルで画像を小さくしてみようと思ったのですが、動きに変化が見られませんでした。

※ひょっとして↑のharada様がおっしゃっていることも同じことなのでしょうか。

 

どなたかお知恵をお貸しください。

よろしくお願いいたします。

 

(function() {
"use strict";
var myp5;
kintone.events.on([
'app.record.create.show',
'app.record.edit.show',
'mobile.app.record.create.show',
'mobile.app.record.edit.show',
], function(event){
kintone.app.record.setFieldShown('添付ファイル', false);
kintone.app.record.getSpaceElement('space').innerHTML = '<div class="control-label-gaia"><span class="control-label-text-gaia">添付ファイル</span></div><div id="p5Container"></div>';

myp5 = new p5((function(p){
var color, weight, clear, displayedFile;
var showImage = function(image){
p.resizeCanvas(image.width, image.height);
p.canvas.style.width = '';
p.canvas.style.height = '';
p.background(image);
};
p.setup = function(){
if(event.type === 'app.record.create.show'){
p.file = {
name: 'image.png',
type: 'image/png',
};
p.createCanvas(500, 150);
p.background(255);
/* //モバイルのときは画像を小さくしてみる
}else if(event.type ==='mobile.app.record.create.show'){
p.file = {
name: 'image.png',
type: 'image/png',
};
p.createCanvas(100, 30);
p.background(255);
*/

}else{
p.file = {
name: event.record.添付ファイル.value[0].name,
type: event.record.添付ファイル.value[0].contentType,
};
var xhr = new XMLHttpRequest();
xhr.open('GET', '/k/v1/file.json?fileKey=' + event.record.添付ファイル.value[0].fileKey);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.responseType = 'blob';
xhr.addEventListener('load', function(){
displayedFile = (window.URL || window.webkitURL).createObjectURL(xhr.response);
p.loadImage(displayedFile, showImage);
});
xhr.send();
}
/* //ファイル読み込みは非表示にしたい
p.createFileInput(function(file){
if(file.type !== 'image') return;
displayedFile = file.data;
p.file = {
name: file.name,
type: 'image/' + file.subtype,
};
p.loadImage(displayedFile, showImage);
});
*/
color = p.createInput('#000');
weight = p.createInput(10, 'number');
clear = p.createButton('clear');
clear.mousePressed(function(){
if(displayedFile){
p.loadImage(displayedFile, showImage);
}else{
p.background(255);
}
});
}
p.draw = function(){
p.stroke(color.elt.value);
p.strokeWeight(weight.elt.value);
if (p.mouseIsPressed === true) {
p.line(p.mouseX, p.mouseY, p.pmouseX, p.pmouseY);
}
}
}), 'p5Container');


});
kintone.events.on([
'app.record.create.submit.success',
'app.record.edit.submit.success',
'mobile.app.record.create.submit.success',
'mobile.app.record.edit.submit.success',
], function(event){
return new kintone.Promise(function(resolve){
myp5.canvas.toBlob(function(blob){
var formData = new FormData();
var xhr = new XMLHttpRequest();
formData.append(' __REQUEST_TOKEN__', kintone.getRequestToken());
formData.append('file', blob, myp5.file.name);
xhr.open('POST', encodeURI('/k/v1/file.json'));
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.addEventListener('load', function(){
kintone.api('/k/v1/record', 'PUT', {
app: kintone.app.getId(),
id: event.recordId,
record: {
添付ファイル: {
value: [
{fileKey: JSON.parse(xhr.responseText).fileKey}
]
}
}
}).then(function(){
resolve(event);
});
});
xhr.send(formData);
}, myp5.file.type);
});
});
})();
if(!HTMLCanvasElement.prototype.toBlob){ //polyfill
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function(callback, type, quality){
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for(var i=0; i<len; i++ ){
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], {type: type || 'image/png'}));
}
});
}

 

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。