Menu bar

そんなわけでメニューバー的なものを書いた。

ただメニューバー的なものがメニューバー的に動作するだけなので、特に他に書くことがない。

桃に組み合わせるとこんな感じになる。次はクリップボード周りを書いてみよう。

Drawing into the selection

選択範囲がある時、描画がその中へ限定される処理を書いた。

さてこの選択範囲をどうこうするための、クリップボードに関するメニューというものが必要になるのだけど。どこへ追加したものだろうか。

桃の上部のパネルは、別に意識したわけではないのだけど、Microsoft 製アプリで言うところのリボンUIのように見えるので、クリップボード操作に関してもそれを模倣するという方法もあるが、リボンが優れたUIとはちょっと思えないのでそれは採らない。普通に「ファイル」「編集」云々の伝統的なメニューバーがほしい。

そういうものをちょちょいと出してくれる素敵なライブラリなんかないかな。

Ants march over the peach

選択範囲の表示をやってみた。

このトピックについてめんどくさい点は、Photoshop等に倣うなら、範囲表示の境界線がキャンバスの倍率によらず常に1ピクセルであるということだ。このための下準備がめんどくさい。蟻が行進するのは意外とめんどくさくない。

もう一つめんどくさいのは、選択範囲を決定する前段階の仮線の表示だ。何がめんどくさいかと言うと、やはりPhotoshopに倣うならばこの仮線はキャンバスの画像を反転した色で表示する必要があるのだ。

この反転表示と蟻の行進を組み合わせて表示するのが多少めんどくさい。ちなみにPhotoshopではなくgimpに倣えば、この手の仮線はxorではなく縁取りされた実線になるのだけど、これはどのピクセルを選択しているのかわかりづらいのでよくないと思う。

ともあれ、選択範囲の表示についての基礎の部分はできた。まだ実装してないけど選択範囲の集合論的な演算(範囲の追加、削除、インターセクションなど)も無理なく組み入れられると思う。

ということで次に考えるべきめんどくさいことは、この選択範囲をどうするかという点と、選択範囲に対する描画という点だ。前者はクリップボードとのやり取りという話に繋がるのでなかなか大掛かりにめんどくさい。また後者はつまり描画のクリッピングなのだが、おそらくcanvasの機能で一発ということにはならないのでこれまためんどくさい。

この記事めんどくさい言い過ぎだ。

Scan line

範囲選択機能を作る際、めんどくさいことは大まかに分けて2つあり:

  • 選択範囲の内部表現
  • 選択範囲の表示

まず内部表現から考えると選択範囲とは頂点の配列のことだ。それを結んで多角形とし、その内部に描画を限ることで範囲選択がなされる。

多角形によるクリッピングというのはキャンバスのネイティブの機能を用いて実現してもよいのだが、その場合アンチエイリアスが効いてしまう(アンチエイリアス自体はキャンバスの仕様に必要な要件ではなかったはずなのだが、どういうわけか全てのブラウザがそういうふうに動作する)ので自前でアンチエイリアスをかけない多角形塗りつぶし機能というのが必要だ。

というわけで書いた。

アンチエリアスをかけない描画メソッドというのはすでに線分と円についても作ってあるが、どれもなかなか面白く、グラフィックスの教科書をおさらいしてる気分になる。

* * *

ポリゴンの内部をキャンバスの機能で塗りつぶし、輪郭だけアンチエイリアスなしの線分描画関数で上書きすれば10倍くらい速くなることに気付いてしまった。ぐぬぬ…。

All layers are independent in size

従来はすべてのレイヤーの大きさはキャンバスの大きさであり、また背景レイヤーの完全に直上にある構造だったがこれをやめ、レイヤーごとに大きさとオフセットを独立して持てるようにした。それに合わせて、いわゆるレイヤー移動ツールを実装した。

と文章で書くとこれだけなのだが描画のあらゆるところでオフセットを意識するように書き直すのは死ぬほど大変だった…。その割に見た目は何も変わっていないと言う。

次は矩形選択ツールを作る。

Curse of cursor

キャンバスに描かれるペンのサイズを事前に図示するために、カーソルの形状を CSS の cursor プロパティにより変更している。

が、Chrome ではこのプロパティにより設定できるカスタムカーソルの最大サイズに制限がかけられることになると言う。

https://www.chromestatus.com/feature/5825971391299584
https://bugs.chromium.org/p/chromium/issues/detail?id=880863

カーソルの形状の変更はおそらくネイティブなウィンドウシステムの機能に依存しているだろうから、最小公倍数的な仕様に揃えるということなのかな? と思ったらどうもカーソルの形状を変えることで誤クリックを誘導するような邪悪な広告対策のようだ。なんて世知辛い世の中だ。

というわけで桃缶でもそういう対応を入れることになったのであった。

Layer the drawings

ふたばの一部の画像掲示板には手書き機能が備えられている。が、赤福プラスはふたばのページを自前のページ構成で上書きしてしまうので、その備え付けの手書き機能をそのままでは利用できない。そこでだいたい同等の手書き機能を赤福プラス自身が内包している。

しかしこの手書き機能が別に使いやすくもなんともなかったりする。とはいえ手書き機能は長年 dat にも img にも関係なかったのでつまり別に困ってなかったのだが、今回 img でも手書き機能が有効化されたので、そういうわけにも行かなくなったのである。そこで改めて手書き機能を眺めてみると、いろいろと機能が欲しくなってきた。具体的にはレイヤー機能が欲しい。

とそういうことで作ってみた。とりあえず開発しやすさを鑑み、赤福プラスとは独立したブックマークレットとして仕立てた。そのほうが多くの「」やとっしーも試せるし。

桃の缶詰
https://appsweets.net/momo/

ネーミングは例によって別に意味はない。さて公開後のいろいろな不満を浚ってみると、だいたい以下の2点に集約されるようである:

  • 機能が多すぎるからクソ: つまり、手書きのミニマリズムに反しているのではないか? という指摘だ。しかしこれはその機能を使わなければいいだけなので、批判としては弱い。
  • ペンの補間がクソ: こちらは聞くに値する。手書きの拡張スクリプトとしては肌色キャンバスというものがすでにある。これも描線の補間をするのだがよくできていて描き心地が良い。それ比べると桃缶はクソだな! という評価なのである。

Async moderation

書き込みに del を入れる際、いつの間にかリクエストに間を置かないといけなくなっている。そのインターバル内に複数リクエストすると「操作が早すぎます」云々と怒られる。

現状では del フォームの OK ボタンが押された際に同期的に処理していたのだが、これを改めて del リクエスト専用のグローバルなプロミスを用意し、OK ボタンのハンドラでは単にそのプロミスにリクエスト(及び 10 秒待機するディレイ)をチェインするだけにした。これにより操作が早すぎると怒られることはなくなる。また、del 済の書き込み上の del リンクはイタリック体にするようにした。ただしこれは永続化的な処理はしてないのでページをリロードすると元に戻る。

副作用として、山ほど del を入れて即ページを閉じたりすると予約されたほとんどのリクエストが水の泡となってしまう。まあ普通山ほど del を入れること自体がかなり奇妙な行動なのでこれはこれでいいか。

Tegaki has returned to img

虹裏 (img) でレスに手書き画像を添付できるようになった。以前は逆に、手書き画像でスレッドを立てられる仕様になったことがある。しかしこれにより管理人さん曰く「板が破綻した」結果となって、撤回された…という経緯がある。しかし今回は、今のところ上手く回っているように見える。

そういうわけで、諸々に対応した。最も重要なのは、レス送信モードで手書きが有効な場合に画像の貼り付けに対応したことだ。これの何が面白いのかと言うと2点あり、

  • 手書き画像の持ち越しができる: 虹裏 img のスレッド保持期間は非常に短い。絵を書いてる内にスレッドが落ちてしまっていたということも頻繁に起こりうる。そこで、キャンバス上のコンテキストメニューで画像をコピーし、別のスレッドで貼り付け、そのまま手書きモードに入れば続きを描くことができるようにした。
  • 擬似的な画像レスができる: これは上記の仕様の副作用だ。つまり手書きキャンバスにクリップボード上の画像のみならず、ローカルな画像ファイルの内容を流し込むこともできるようになっている。擬似的なというのはこの手法の場合画像の横幅、縦幅はかなり制限されたものになるし(おそらく最大400pxほど)、フォーマットは png に限定されるのでそれ以外は使えないということだ。それを踏まえても、一応画像レス的なものはできる。ちなみに、つらつらと img を覗いてみたところたまにこの手法ではない本来の画像レスをしていた例もあったので、何か別のハックがあるのかもしれないが、それはわからない。

テストのために適当に手書きをしてみたものの、trackpoint で絵を描くのはとてつもなくストレスフルだ。何か適当なペンタブレットを探してみるかな。

How many bytes did I read?

フルリロード、差分リロードした際に読み込んだバイト数を記録しているのだが、これは実は概算であって正確な値ではない。というのは意外なことに javascript から実際に読み込んだバイト数を得る方法はけっこう限られている。特に、レスポンスボディが圧縮されているとそれが顕著になる。

まずレスポンスヘッダ内の Content-Length があれば、それが読み込んだバイト数となる。終わり。圧縮されている場合は圧縮されている状態の転送バイト数となる。ふたばのサーバでは、html ファイルを読み込むと gzip 圧縮されたものが Content-Length 付きで返ってくる。しかし、差分リロードした際のリクエスト(これも gzip 圧縮されている)には Content-Length はない。したがって差分リロードの際に何バイト読み込んだかは全くわからない。

次に XMLHttpRequest の progress イベントをリスンすると、loaded プロパティと total プロパティで転送バイト数が得られる。ただし、内容が圧縮されている場合はこのイベントは少なくとも Chrome ではあまり役に立たない。全体を読み込み終えたあとで 1 回だけ発生し、その際 total は 0、loaded は展開後のバイト数が格納されている。つまり、圧縮状態の転送バイト数は読み取れない。

また、ここまではレスポンスボディの話だったがレスポンスヘッダのサイズも javascript からは正確なその値を取得できない。XMLHttpRequest#getAllResponseHeaders() が返す値は微妙に加工されている(HTTP/1.1 云々のラインなど)。加えて書くと、プロトコルが HTTP/2 の場合はヘッダも圧縮されているのでなおのこと実際に転送されたバイト数はわからない。

ということで差分リロードの際の転送バイト数および https 上の各読み込み時のヘッダに関してはヒューリスティックな謎の係数を使って圧縮後のサイズを概算しているのであった。これがだいたいの場合だいたい合ってるようなのだが合わないときは盛大に合わない。

さてどうしたものか。