processingを使ってweb上で画像を加工してTwitterで画像とソースコードを共有できる #pictureAlive を作った

前回までphpをメインに使って開発してましたが、今回はJavaScriptです。

表題にあるようにweb上で簡単に画像編集できるツールをおねき (@revC_Rain) | Twitterと一緒に作りました。

http://wada.nkmr.io/pictureAlive/

https://nkmr.io/picturealive/
なお、一部機能は使えなくなっています。追記ここまで(2018/05/20)

自分が担当した主な部分を紹介します。

概要

  • 画像の編集を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-8base64で変換
という意味です。
どうしてわざわざutf-8base64変換したかというと日本語で書かれた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です。
そうすると本人がつぶやいたように見えます。
画像だけつぶやいた例


個人のアカウントから見た例
実はこの機能、「ついすた」を参考に実装しました。
twitterスタンプの #ついすた

書いたソースコードを共有できる

ツイートされたリンクをクリックするとその写真が作られた状態でのソースコードごと共有されてます。
リンクにソースコードをgetで渡して上げることでソースコードの共有を可能にしてます。
ところが、URLが長過ぎるとapache側に怒られてアクセスできなくなります。
d.hatena.ne.jp
そこで2000字を超えた場合はデータベースでソースコードを保存することにしています。
最初からそうしたら良かったと今になって後悔してます....

その他

  • アップロードした画像もTwitterでつぶやく
  • 外部urlの画像をprocessing.jsで読み込む
  • urlエンコードの違い
  • phpのヘッダーにも追加
  • Ajaxのためだけに使用したライブラリー

アップロードした画像も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側で処理してたらある問題が起こりました。


はじめ、訳がわかりませんでした。
原因は、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の闇に足を踏み入れて行くことになりそうです。