
レイヤーパレットを作ったことでヘッダからレイヤー関連を一旦削除したが、キャンバス上部にも最低限のレイヤー操作インターフェースがあったほうがいいかなということで追加しなおした。
レイヤーパレット上のサムネイルは、レイヤー全体を対象とするのではなく、透明色を除いた実際の内容部分を対象とするようにした。
次は範囲選択時の演算を作る。
レイヤーパレットを作ったことでヘッダからレイヤー関連を一旦削除したが、キャンバス上部にも最低限のレイヤー操作インターフェースがあったほうがいいかなということで追加しなおした。
レイヤーパレット上のサムネイルは、レイヤー全体を対象とするのではなく、透明色を除いた実際の内容部分を対象とするようにした。
次は範囲選択時の演算を作る。
というわけでテストを書く。
テストは mocha に実行させる。一方、桃は esm の文法に基づいてモジュールを分けてある。ここで問題が出てくる。ググればたくさんその手の話が出てくるが、要するに mocha、ひいては node.js が今現在 cjs と esm の過渡期にあって、まだあんまり esm の対応が行き届いていない。
mocha においても、import/export を使ったソースを渡してもエラーになる。その辺をどうするかはまさに議論中のようだ。
で、いくつかのワークアラウンドが考えられて:
前2者はワークアラウンドにしては大掛かりになるので、最後の奴で行ってみよう。
const assert = require('assert');
let testfunc1, testfunc2;
before(() => import('./path/to/module').then(module => {
({testfunc1, testfunc2} = module);
});
describe('test', () => {
it('works', () => {
// test code with testfunc[12]
});
});
どうでもいいが最近のこの wordpress のエディタが使いにくい。
「レイヤーの複製」という機能を実装した。これも、処理の本体よりいろいろな周辺の事柄の方が面倒なパターンである。
Photoshopにも同じようなメニュー項目がある。こちらは「レイヤーの複製…」であり、後ろに点々があるというのはつまりダイアログを経由するということだ。といってもこのダイアログではそんなにできることは多くない。レイヤー名と、出力先を指定できるくらいだ。
この出力先としてPhotoshopで開いている別のドキュメントを選択できるのだが、これはなかなか重要なようなそうでないような微妙な位置づけだ。これがないとレイヤーがもともと属するドキュメント上で複製し、それをカットして、別のドキュメント上でペーストする手順を踏まなければならないのだが、ダイアログで出力先を指定できることでこの手順を飛ばせる。
一方で、PhotoshopをいわゆるMDIアプリケーションとして運用するならレイヤーパレットからドラッグを開始して目的の文書上でドロップすればだいたい同じことはできる。こちらのほうが分かりやすいし手っ取り早い。微妙というのはそういうことだ。
さて桃の場合、いまのところ同一ページで複数の画像を取り扱うことはないのですなわち「…」もこの微妙なダイアログもない。メニュー項目を選択したら即レイヤーが複製される。いいことだ。この時、複製されたレイヤー名は自動的に付けられるのだが、それがこの記事の主題だ。
この複製後のレイヤー名の自動生成がなかなかめんどくさい。複製元のレイヤーの名前が「のコピー」で終わっている、「のコピー #\d+」で終わっている、そうではない場合のそれぞれについてふさわしい名前を生成しないといけない。めんどくさい。
めんどくさいが、しかし入力と出力は決定的でユーザのインタラクティブな操作に左右されることもないので、むしろユニットテストに回しやすいネタだ。そうだテストを書こう。
ドラッグ&ドロップのついでに、画像ファイルを桃にドロップした場合レイヤーとして読み込むようにした。また、Alt+[、Alt+] でアクティブなレイヤーを前後に切り替えられるようにした(Photoshopと同じ)。
その他、Ctrl+Shift+[、Ctrl+Shift+] などアクティブなレイヤー自体を前後に動かす系のショートカットも実装したのだがこれがとても難しい。
処理自体が難しいのではなく、ショートカットの扱いが難しい。キー入力は keydown イベントで受け、KeyboardEvent#key で判断することになる。一方、Shift を押しているか否かでマッピングが変わる系のキーというものがある。こういったキーを keydown イベントで取得すると例えば Shift+[ というストロークは Shift+{ という扱いになる。これが正しいのか間違っているのかよく分からない。入力された文字と押されたキーの混同が起こっている。はて。
大量にレイヤーを追加した場合、当然ながらレイヤーパレットの最大表示幅を超えるレイヤーサムネイルが生成されることになり、隠れているサムネイルの表示の制御はユーザーのスクロール操作によって行われる。
さて、レイヤーを並べ替えるためにドラッグを開始して、スクロール領域から隠れている場所にドロップしたい場合、スクロール領域の端っこにポインタを持っていくと自動的にスクロールするような機能が必要になるのでそれを作らないといけない。
ところが驚くことに。そういう機能作らないとだなーめんどくさいなーなどと思いながらドロップを試してみた所、すでにそういう動作をするではありませんか。んん〜? ブラウザの標準の機能なの?
Chromium ではだいたい想定したとおりの動作をし、Firefox ではスクロール領域の端っこでポインタをちょこちょこ動かした時に限り自動スクロールするようだ。ふーんじゃあ別にコードを書かなくてもいいのかな。
だいたい、というのはスクロール領域内部の端っこだけではなく領域の外側も自動スクロールのトリガーになっててほしいのだけど、まあ、いいかな。
さてレイヤーパレットができたことでヘッダ部分のレイヤー周りは不要になったので、色々と整理した。また、描画ツールのオプションはポップアップ形式をやめ、ヘッダの下部に表示するようにした。
レイヤーパレットの実装がもうちょっと続く。ドラッグ&ドロップによるレイヤーの並べ替えが必要。
ということでクリップボードからのペーストができるようになった。
貼り付ける画像のサイズは無制限でいいんだろうか? 制限をかけるとすると、閾値を越えたときはエラーにする? 適当にリサイズして貼り付ける? どちらも良くない感じがする。制限はかけず、32768×32768ピクセルの画像をペーストしてブラウザがぶっ飛んでもまあそういう日もあるかもね的精神でいきたい。
ちなみにレイヤーの枚数には制限があり、とりあえず100枚を限度にしている。
ところで navigator.clipboard.read() で画像を読み込む場合、それで得たクリップボードアイテムをblobに変換し、そこからオブジェクトURLを生成し、img要素に読み込ませ、それをcanvasに描画という手順が一般的なのだが、最近はImageBitmapというものがあるそうなのでそれを使った。javascriptの世界は日に日に新しい機能が追加されて困っちゃう。ImageBitmapはblobから生成できて、そのままcanvasへの描画ソースになれる。
ぼちぼちレイヤーパレットの機能を埋めていく。
レイヤーとレイヤーの隙間に意味を持たせて hover 時に追加ボタンを出すようにした。
だいたいこんな感じでいいかな。
レイヤーパレット上のツールバー的なものをどこに配置したものだろうか。
もっとも、ツールバーと言っても、とりあえず実装するのはカレントレイヤーの削除と新規レイヤーの追加くらいである。前者は対象がカレントレイヤーであるのだから、そのためのボタンをパレット上のカレントレイヤー領域のどこかに配置するのが当然、自然だろう。
一方で後者が難しい。新規レイヤーが常に最前面への追加なら、カレントレイヤーは関係ないのでツールバー的な領域を新設してそこへボタンを置けばいいが、カレントレイヤーの手前に追加と言った動作ならこれもパレット内のカレントレイヤー領域内に置くのが多少は自然に思える。
多少はというのは、確かに動作の起点はカレントレイヤーなのだが、動作の対象は別の新規レイヤーなのでそこに若干のズレを感じないこともないということだ。
難しい。どうしたものか。