素のJavaScriptとCanvasで作る、写真をサーバーに送らないツール(開発ノート)
「かんたんコラージュ」を、どういう考えで作ったのかをまとめた開発ノートです。使い方ではなく、「なぜこう作ったか」を書いています。使い方は写真コラージュの作り方:初心者向け基本レイアウトガイドにあります。
きっかけは、Pythonの自作スクリプトだった
もとは、写真を並べて1枚にまとめる、Pythonのコマンドラインのスクリプトでした。それをブラウザで動くWebアプリに作り直したのが、このツールです。作り直した理由は、機能を増やしたかったからではありません。画面まで含めて作り込んだほうが、自分自身が楽しめると思ったからです。
ですから、「写真をサーバーに送らない」という今の設計は、最初から狙って選んだものではありません。手元のスクリプトは、写真を自分のパソコンの中だけで処理していました。それをブラウザの上で再現したら、自然にそうなった、というのが実際のところです。あとから、この設計がツールの大きな特徴になると気づきました。その理由と仕組みは写真をアップロードしないツールという選択に整理しています。
誰のためのツールか:イベント幹事の「量産」
似たツールは、すでにたくさんあります。機能の多さで競うつもりはありませんでした。そこで、想定する利用者を絞りました。イベントの幹事です。サークル、部活、PTA、保育園、社内イベントなどで、何百枚も撮った写真から、報告用のコラージュを何枚も手早く作りたい人を思い浮かべています。
この人たちに必要なのは、派手な加工よりも、少ない操作で量産できることです。設定の項目やボタンを意図的に絞っているのは、そのためです。新しい機能を足すときも、ボタンを増やすより、タップやドラッグといった今ある操作の延長で実現できないかを先に考えます。具体的な進め方はイベント写真300枚のまとめ方に書きました。
素のJavaScriptとCanvasだけで作った
使っているのは、HTML・CSS・JavaScript(ES Modules)だけです。フレームワークもビルドツールも使っていません。写真の合成と書き出しは、ブラウザ標準のCanvas 2D APIで行っています。
理由は2つあります。ひとつは、このツールの中心が「写真を並べて1枚の画像にする」処理で、それがCanvasの中で完結することです。フレームワークが得意な、画面の状態をたくさん管理する場面が、あまり出てきません。もうひとつは、ページを軽く保てることです。コードは役割ごとにファイルを分けています。状態、描画、指の操作、元に戻す履歴、Exifの読み取り、といった具合です。
9枚まで対応したとき、問題はメモリだった
当初は2〜4枚だけを扱うツールでした。イベントの写真をまとめたい人にとって、4枚では足りないと考え、9枚まで増やしました。このとき、難しかったのは配置ではなく、メモリでした。
写真を「送らない」ということは、すべてを端末のメモリの中で処理するということです。スマホの写真は長辺が4000pxを超えることもあり、9枚を同時に持つと重くなります。そこで、書き出しの上限と同じ4096pxを超える写真だけを、読み込むときに縮小するようにしました。書き出しの大きさがもともと4096pxまでなので、画質には影響しません。標準的なスマホの写真は、縮小せずにそのまま使います。
Exifを、ライブラリなしで読む
撮影日時の順に並べる、撮影日やカメラ名を写真の下に入れる、といった機能には、写真に埋め込まれたExif情報を使っています。読み取りには外部のライブラリを使わず、TIFFの構造(IFD)を読む処理を自分で書きました。JPEGの中からExifの部分を探し、必要な項目だけを取り出します。
壊れたファイルや、範囲を外れた読み取りは、黙って諦めるようにしています。Exifが読めなくても、ツールそのものは止まらないようにするためです。
指で操作する:ピンチズームの設計
集合写真で顔が切れたときは、その写真だけをドラッグで動かし、ピンチで大きさを変えて直せます。ピンチでは、2本の指の中点が、画像の同じ場所に留まり続けるように計算しています。指を動かしながら拡大しても、見ている場所がずれないようにするためです。
この計算は、設計の段階で「2本指を平行に動かしても画像が動かない」という数式の誤りを見つけ、実装の前に直しました。拡大の倍率は1〜4倍です。片方の指を離しても、残った指でそのまま動かし続けられます。
元に戻す機能と、写真のメモリ
元に戻す操作は、最大20手まで記録します。記録するのは、かたちやフレームといった全体の設定だけではありません。写真ごとの状態(位置、収まり方、色味、ポラロイドの文字)も含めています。
気をつけたのは、「削除した写真も元に戻せる」ことと、「メモリを使い続けない」ことの両立です。写真を消しても、その画像は、元に戻す履歴のどこにも残らなくなった時点で、はじめて解放します。それまでは、履歴が写真を持っています。
写真の色味は、画素を直接変換した
写真ごとのモノクロ・セピアは、Canvasのfilter機能を使わず、画素の値を直接変換する方法にしました。環境によってはfilterが使えないことがあり、画面で見たものと、保存した画像で、結果が変わってしまうからです。画素を直接変換すれば、どの環境でも、プレビューと保存した画像が同じになります。
「言い切れる1文」を先に決める
いちばん時間をかけたのは、実は言葉でした。このツールは、写真はサーバーに送りません。一方で、広告とアクセス解析のために、閲覧情報は外部に送信しています。ですから、「通信は一切ありません」とは書けません。
そこで、主語を「写真」に絞って、「写真は端末の中で処理され、サーバーには送信されません」と書いています。機能や記事が増えても、この線引きだけは崩さないと決めています。広告と解析については、プライバシーポリシーに書いています。
まとめ
- Pythonのスクリプトを、ブラウザで動くようにしたところから始まった
- 想定する利用者は、写真を大量にまとめたいイベントの幹事。機能より「量産」を優先している
- 素のJavaScriptとCanvasで、ページを軽く保つ
- 9枚対応の壁は、配置ではなくメモリ。4096pxを超える写真だけ、読み込み時に縮小する
- Exifはライブラリなしで読み、ピンチは指の中点を保つ計算で、元に戻す履歴は写真のメモリと両立させる
- 「写真は送らない」と言い切るために、それ以外の言葉のほうを削る
作った人については運営者情報にまとめています。