✂️

Vueで画像マスキングができるv-use-edit-image

2021/01/04に公開

こんにちはすぱいすです。
Vueで画像をマスキングするためのライブラリを作りました!
https://github.com/Spice-Z/v-use-edit-image#readme

こんな風にブラウザ上でマスキング範囲を複数箇所設定し、マスキングした画像を新たに生成することができます。(タッチスクリーンは非対応)
(じつはマスキング以外にも、切り取りや文字入れなどができる)

npmにも公開してあるので、npm installすることができます。
https://www.npmjs.com/package/v-use-edit-image

このライブラリの特徴は、UIコンポーネントを持たないことです。
Vueのcomposition APIを用いて、画像の編集に必要なロジックだけをライブラリ化してあります。
スタイルは利用者側が独自に定義すればいいかなー、と思いUIコンポーネントは作っていません。

本記事では、このライブラリを作ったモチベーションや、使っている技術、苦労したところなどを書いています。

あと、初めて作って公開しているpackageなので、issueなりPRなりお待ちしています!
(本当は一番Starが欲しい!!)

要約

  • Vue Composition APIで画像編集に必要なロジックだけ定義している
  • 画像の編集自体はCanvas APIを使っている
  • 個人開発で作っていたものが業務で使えそうだったのでOSS化した
  • テストやドキュメント整備などは今後行っていきたい

モチベーションについて

元々、個人でコラ画像を作るサイトを趣味で作っていました。
ここで画像を編集するためのコードを色々書いていたのですが、ちょうどその開発をしていた時に業務でも似たような開発をする機会がありました。

どうせ似たようなコード書くんなら、いっそのことライブラリを作ってOSS化しよう!と思ってv-use-edit-imageは作られました。

元々コラ画像を作っていたので、主なコードは文字入れや画像の色調変化、サブ画像の貼り付けだったのですが、マスキングや切り取りの機能を拡張してライブラリにしています。

画像を編集するためのコードは根本的に同じなので、機能の追加もすんなりといって、かなり気分の良い開発でした。

技術について

Vue composition API

Vue composition APIは、Vueコンポーネントロジックをいい感じに分割したり注入できるAPIです。
composition APIについて説明する記事はたくさん存在するので、詳しいことは割愛します。
Vue 3.xでは標準搭載、Vue 2.xではライブラリを入れることで使用できます。
https://composition-api.vuejs.org/api.html#setup

Canvas API

Canvas APIは、HTMLのcanvas要素に対してJavascriptからのグラフィック操作を実現するWebAPIです。

画像などの描画や加工だけでなく、文字の入力やアニメーションなども実現することができるAPIです。

マスキングのために使っているCanvas APIは以下の通りです。

  • マスキング元画像の描画のためdrawImage関数
  • 指定範囲をマスキングするためにbeginPath関数、moveTo関数、lineTo関数、closePath関数、fill関数

マスキング描画の処理がちょっと複雑なので、詳細を説明します。

マスキングするためにbeginPath、moveTo、lineTo、closePath、fill

v-use-edit-imageでマスキングのために使っているそれぞれの関数の機能はざっくりこんな感じです。

  • beginPath:新しいpath情報作成が始まる
  • moveTo:指定した座標に移動
  • lineTo:現在の座標と、指定された座標を結ぶ直線を作る
  • closePath:現在の座標とpath開始時点を結ぶ直線を作る
  • fill:pathで作った図形の中身を任意の色で塗りつぶす

コードはではこのように、座標情報ごとにlineToをすることになります。

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const coordinates = [{x: 10, y:10}, {x: 100, y:10}, {x:100, y:100}, {x:10, y:100}]

context.beginPath();
coordinates.forEach((v, i) => {
    if (i === 0) {
      context.moveTo(v.x, v.y);
      return;
    }
    context.lineTo(v.x, v.y);
});
context.closePath();

context.fillStyle = 'orange';
context.fill();

このように、一つの図形を描画するために5つのステップを経ています。

しかし、実は単に四角形を描画したいならば、Canvas APIのfillRectを使う方が早いです。

このfillRectを用いれば、塗り潰し図形の作成が一回のAPI実行で完結できます。

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'orange';
ctx.fillRect(10, 10, 100, 100);

ただこのfillRectには弱点があり、四角形しか描画できません。
つまり、三角形や五角形などが描画できないということです。

v-use-edit-imageでは、どんな多角形も描画できる機能を用意したかったので、を使って図形を描画することにしました。

そのおかげで、どんな多角形でも描画できるようになっています。
(コラ画像作成サイトのこのページは、ここに書いた仕組みによって複雑な図形の描画が可能になっています。)

苦労したロジック

ブラウザ上での選択範囲サイズとCanvasサイズ比率の計算

Canvas要素のサイズと、実際にブラウザ上に配置したときの描画サイズは異なることがほとんどです。

例えば、1000×500の画像をCanvas要素を用いてブラウザに表示した場合、

こんな感じで、styleの当て方によってCanvasサイズと実際のDOMに描画されている大きさに差がうまれます。

もちろん同様に、ブラウザ上で指定したマスキング範囲を実際にCanvasに反映させる際に、描画したいマスキング範囲の始点位置、たて、横の長さについて、比率を計算した値で描画をしなければなりません。

その比率の計算のための関数をarea2CanvasAreaとして実装しました。

単純にcanvasの大きさ / 実際のDOMサイズで大きさの比率を出し、その比率をかけているだけですが、初めてこの部分を実装するときは少し複雑でした。

マスキング位置の保持と削除

マスキングをするたびに画像の変更を確定させてしまうと、マスキング位置を間違えた時の修正が難しくなります。
なので、マスキング位置の保持はそれ単体で行い、全てのマスキング位置が確定した時に初めて画像の変更を行う流れが必要でした。

開発当初はそんなこと考えず、マスキング範囲が1つ確定するごとにCanvasに描画していたので、後からのリファクタリングが結構面倒でした。仕様の整理は大事。

このライブラリの今後について

テストの整備

現在v-use-edit-imageには、ビルドテストしか存在していません。
マスキングなどの位置情報の保持や、Canvasサイズとブラウザ描画サイズの比率計算などのテストを整備などはわりと早く整備できると思うので、そのあたりからテストを整備していきたいと考えています。

ドキュメントの整備

現在のドキュメントは、ライブラリが備えている機能の半分くらいしか触れられていません。
触れられていない部分について記述するのは当然のこと、すでに書いてある部分についても、表を使うなどして情報を整理したい。

インターフェースの修正

ロジックとして必要なものは揃っているのですが、それを使う関数などのインターフェースがかなりイケてないと思っています。
もっとシンプルに、使用する側が使いやすい設計にしたいと思っていますが、個人的な技術力の課題であるとも感じています。

おわりに

今回作ったv-use-edit-imageですが、まだまだ改善の余地しかありません。
引き続き開発を続けてこうと思います(のでStarが増えたら嬉しいな)!

https://github.com/Spice-Z/v-use-edit-image#readme

Discussion