processingを使ってweb上で画像を加工してTwitterで画像とソースコードを共有できる #pictureAlive を作った
前回までphpをメインに使って開発してましたが、今回はJavaScriptです。
表題にあるようにweb上で簡単に画像編集できるツールをおねき (@revC_Rain) | Twitterと一緒に作りました。
http://wada.nkmr.io/pictureAlive/
https://nkmr.io/picturealive/
なお、一部機能は使えなくなっています。追記ここまで(2018/05/20)
processingのコードを使って画像をwebで編集できるツール
— 又 (@matatsuna) 2016年6月10日
「pictureAlive」
を作成しました。
クソコラをwebで!
ソースコードをurlに埋め込むのでソースコードの共有可https://t.co/AgTSFlBvbu#cmp0 #pictureAlive
自分が担当した主な部分を紹介します。
概要
- 画像の編集をprocessingのプログラムを使ってweb上で行う
- 作った画像をツイートできる
- 書いたソースコードを共有できる
画像の編集をprocessingのプログラムを使ってweb上で行う
今回一番苦労したのがこの部分です。
processingをブラウザで動かすライブラリは大きく以下の二つがあると思います。
Processing.js
p5.js | home
今回はprocessing初心者にも使ってもらえるように書き方がprocessingと同じprocessingjsを使用することにしました。
以下のサイトを参考にしました。
Processing.js の練習場
ところがこれ、エディター部分にjsのコードを書くとそのまま動いてしまうというセキュリティー的にあまりよくないので対策したいと思い、先輩に聞いたところ
「iframe使ってsandbox入れたらいいんじゃない?」
というお話をいただいて、サンプルをいただきました。
iframeとsandboxについて
HTML5/埋め込み/iframe要素 インラインフレーム内のコンテンツに制限をかける - TAG index
ざっくり説明するとページの中にセキュリティー的な制限をかけたページを表示するってことです。(たぶん)
さて、どうやってテキストエリアに書かれたプログラムコードをiframeに送るかというと
var iframe = document.getElementById('iframe');//iframeを取得 var head = document.createElement('head'); var body = document.createElement('body'); var processingjs = document.createElement('script'); // processing.jsの読み込み var canvas = document.createElement('canvas'); // canvasタグ var usercode = document.createElement('script'); // ユーザーのソースコードが入るタグ processingjs.setAttribute("src", "http://wada.nkmr.io/pictureAlive/lib/processing2.js");//src属性に代入 usercode.setAttribute("type", "application/processing");//type属性にprocessingであることを代入 usercode.innerHTML = editor.getValue();//テキストエリアに書かれたコードを入れる head.innerHTML = processingjs.outerHTML;//headに入れるスクリプトタグなどを追加 body.innerHTML = usercode.outerHTML+canvas.outerHTML;//描画するためのコードとcanvasを追加 iframe.setAttribute('src', 'data:text/html;charset=utf-8;base64,' + Base64.encode(head.outerHTML+body.outerHTML));//iframeに作ったコードを追加
このような感じでテキストエリアに書かれたコードをiframeの中に追加して実行させます。
(実際に上記のコードを実行させるにはprocessingjsの初期化の処理などのコードが必要です。)
タグ関係の命令はググってもらえれば分かると思いますが、
iframe.setAttribute('src', 'data:text/html;charset=utf-8;base64,' + Base64.encode(head.outerHTML+body.outerHTML));//iframeに作ったコードを追加
ここの部分のdata:txt/html~~~
の部分について説明したいと思います。
まず、この書き方自体はData URI スキームと呼ばれてます。
http://engineer.blue-corporation.jp/dev/data-uri-scheme/
この記事を読んでもらえれば理解できると思います。
data:text/html;charset=utf-8;base64,
この部分はData URIスキームのhtmlのテキストで文字コードはUTF-8でbase64で変換
という意味です。
どうしてわざわざutf-8でbase64変換したかというと日本語で書かれたprocessingのtext命令を反映したかったからです。
そうしないと文字化けしてきれいに表示されないです。
標準のbtoa()はアスキー文字(英数字)しか対応してないためライブラリを追加しました。
github.com
(urlエンコードかけてからbtoaしてもだめっだった)
これでリアルタイムにprocessing.jsを実行させることができます。
作った画像をツイートできる
サイトにアクセスしたらまず、タイムスタンプから生成した識別番号を付与します。
iframeにsandboxつけた状態でcanvasの中身を取得することはできないのでsetIntervalを使って数秒ごとに一度スクリーンショットを撮影します。
データベースに識別番号とスクリーンショットを一緒に追加します。
iframeの外にあるツイートボタンが押されたら、識別番号を使ってデータベースに最新の画像の問い合わせを行います。
その画像を以下のアカウントでつぶやきます。
pictureAlive (@picture_Alive) | Twitter
つぶやいた画像のURLを取得して、個人のアカウントをintentという機能を使ってつぶやきます。
cartman0.hatenablog.com
本文(text)に画像のURLを入れて、URLにサイトとのURLを入れればOKです。
そうすると本人がつぶやいたように見えます。
画像だけつぶやいた例
2016/06/11 00:15:39 pic.twitter.com/ug7wuZVbdQ
— pictureAlive (@picture_Alive) 2016年6月10日
個人のアカウントから見た例
作ったよ!
— 又 (@matatsuna) 2016年6月10日
マイキャラを提供していただいたみなさん、ありがとうございました。
スタンプの要領で画像に付け加えることができます。pic.twitter.com/kxhhllwPqW https://t.co/arvaRCby9a #pictureAlive
実はこの機能、「ついすた」を参考に実装しました。
twitterスタンプの #ついすた
その他
アップロードした画像もTwitterでつぶやく
つぶやいてURLを発行することでファイルサーバーの代わりをさせてます。
外部urlの画像をprocessing.jsで読み込む
iframeの中のcanvasに外部のURLの画像を持ってくることはクロスオリジン制約に引っかかってしまって普通は読み込むことは禁止されてます。
d.hatena.ne.jp
でも今回、読み込まないといけないのでprocessing.jsの中身をすこし書き換えました。
p.loadImage = function(file, type, callback) { // if type is specified, we just ignore it var pimg; // if image is in the preloader cache return a new PImage if (curSketch.imageCache.images[file]) { pimg = new PImage(curSketch.imageCache.images[file]); pimg.loaded = true; return pimg; } // else async load it pimg = new PImage(); var img = document.createElement('img'); pimg.sourceImg = img; img.onload = (function(aImage, aPImage, aCallback) { var image = aImage; var pimg = aPImage; var callback = aCallback; return function() { // change the <img> object into a PImage now that its loaded pimg.fromHTMLImageData(image); pimg.loaded = true; if (callback) { callback(); } }; }(img, pimg, callback)); img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera return pimg; };
https://github.com/processing-js/processing-js/blob/master/processing.js#L18917
これの最後から4行目に少しコードを追加します。
p.loadImage = function(file, type, callback) { // if type is specified add it with a . to file to make the filename if (type) { file = file + "." + type; } var pimg; // if image is in the preloader cache return a new PImage if (curSketch.imageCache.images[file]) { pimg = new PImage(curSketch.imageCache.images[file]); pimg.loaded = true; return pimg; } // else async load it pimg = new PImage(); var img = document.createElement('img'); pimg.sourceImg = img; img.onload = (function(aImage, aPImage, aCallback) { var image = aImage; var pimg = aPImage; var callback = aCallback; return function() { // change the <img> object into a PImage now that its loaded pimg.fromHTMLImageData(image); pimg.loaded = true; if (callback) { callback(); } }; }(img, pimg, callback)); img.crossOrigin = "Anonymous"; img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera return pimg; };
これで大丈夫になるはずです。
urlエンコードの違い
今回ソースコードをurlに埋め込むということで、エンコードをjs側でデコードをphp側で処理してたらある問題が起こりました。
ツイートしたときについてくるリンクのurlエンコードでコードの中の+がスペースに置換される問題が発生してます。
— 又 (@matatsuna) 2016年6月10日
対応中ですのでしばらくお待ちください。#pictureAlive
はじめ、訳がわかりませんでした。
原因は、js側でencodeURIComponent()しててphp側でdecodeurlしてたから。
どうしてかというと以下のサイトの表で判明しました。
http://www.programming-magic.com/file/20071227231138/urlencode.php
プラスが空白になってる!
結果を言うと以下のサイトにあるように
http://www.programming-magic.com/20071228223355/
PHP側はrawurlencode関数、JavaScript側はencodeURIComponent関数
を使えば解決です。
正直、URLエンコードは1種類だけしかないと思い込んでいたので、いい勉強になりました。
phpのヘッダーにも追加
iframeの中からサーバーに画像を送る時にもクロスオリジン制約がうるさく、受け手のphpの一番上に以下のソースを追加しないといけませんでした。
header("Access-Control-Allow-Origin: *"); header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
Ajaxのためだけに使用したライブラリー
上の画像を保存するphpにアクセスするためにAjaxを使いたく、以下のライブラリーを使用しました。
構造が簡単で使いやすかったと思います。
github.com
まとめ
初めて本格的にJSをいじって、超えるべき技術的な壁が多くて死にそうでした。
途中でコンソールの赤いエラーに何回も挫けそうになりました。
でもなんとか動くまでもっていけたので嬉しいのとホットしました。
JSの闇に足を踏み入れて行くことになりそうです。