GUI 版 Emacs 用のカラーピッカーの作り方


GUI 版 Emacs 用にカラーピッカーを実装しました!(emacs-color-picker)
こちらをご覧ください。

Emacs ではあまり見ない GUI による拡張機能になってますね!

なんか無茶なことしてるなーとお思いでしょうが、実際無茶な事をしていますw
何しろ Linux 版 Emacs では上手く動きません!(Linux メインの方ごめんなさい…)
これは Windows 版 Emacs 26.3 と 27.0.50 で動作確認しています。
Linux 版だとどうなるか一応スクリーンショットを載せます。

これは GTK+3 でビルドしたものですが、これが --with-toolkit=no でビルドしたものであれば、以下の様に

見た目はまともに表示されますが、動作が激重で実用に耐えません。
更にフレームのアルファブレンドが機能しないという問題も有ります。
当然 Linux 上でもちゃんと動くようにしたいですが、まだ問題の原因を調査していません。
いずれ対応して、まともに動いた暁には MELPA にアップしたいと思います。

以下全て Windows 上での動作を前提にします。
現状では、当初想定した機能は全て実装したつもりですが、見栄えの調整やチューニング等やるべき事はまだ沢山残っています。
この記事を書いた後もソースコードの更新は続けるつもりなので、スクリーンショット等は2019/12/24現在の状態という事をご留意ください。

使い方

動作環境

Windows 版 Emacs 26.3 以上

起動方法

MELPA にアップされていないので、以下からソース一式を clone してください。

落としたディレクトリ内で以下のコマンドでバイトコンパイル出来ます。
バイトコンパイルしないと、まともな速度で動きません。
emacs にパスを通してなければ絶対パスで指定してください。
それと、パッケージの posframe がインストールされていないとコンパイルに失敗します。事前にインストールをお願い致します。

posframe のインストール

> emacs --batch -f package-initialize -f package-install
Install package: posframe ← これを入力

バイトコンパイル

> emacs --batch -f package-initialize -L . -f batch-byte-compile *.el

取り敢えず、素の Emacs を起動して試せるようにテストファイルがあるので、それを使う場合(test.el で最初の GIF アニメの様な状態になります)

> emacs -Q -f package-initialize -l test/test.el

これで無事 Emacs が起動して test.css ファイルを表示していたら、マウスで色をクリックしてみてください。
カラーピッカーが(もっさり)起動するはずです。しなかったらコメントにでも症状を書いてくだされば、原因を調べてみます。
カラーピッカーを終了する場合は、上の方色をクリックするか q を押せば消えます。

カラーピッカーを作ろうと思った経緯

以前Emacs 内にペイントツールを実装する方法という記事を書き、そこで Emacs 内でマウスで絵を描く機能(EPaint)を実装しました。

ペイントツールは実装すべき機能が多過ぎて、途中で放置状態になってしまってますが…この機能を生かして何かもっと軽いツールを作りたいなと思ったところに、以下の Atom エディタ用のカラーピッカーを見つけました。

A Color Picker for Atom

これなら実装出来るのではないかなと思ってから大分時間が経ってしまいましたが、Advent Calendar 2019 用に実装しようと思った次第です。

実装の概要

この見出し以降は、今後 Emacs 上で似たような GUI 機能を追加しようとするような(物好きな)方に向けて、少しでも楽が出来るような情報を書こうと思います。

まずは、カラーピッカーの構造を説明します。

白細字で書いた様に

  • current area
  • old area
  • box
  • alpha bar
  • hue bar
  • syntax toggle area

で構成されています。
カラーピッカーのソースを見る時はこれらのキーワードを覚えておくと何となく分かるのではないかと思います。

次は、カラーピッカーを実装する為に使用した Emacs のフレームとイメージについて説明します。

フレーム(frame)

make-frameで作成出来る、一般的な GUI 用語で言えばウィンドウの事です。
C-x 5 2と入力すると新しいフレームが表示されますが、それです。

make-frameに色々パラメータを指定すると、枠が無いフレームを作成出来たりしますが、指定できるパラメータが何十個もあって(29.4 Frame Parameters)、中には意味が分からないものもあり、全部理解出来てません…

そして、Emacs 26.1 から child-frame という機能が追加されました。
これはmake-frame時にパラーメータparent-frameに親フレームのオブジェクトを指定すると、そのフレームの子フレームになると言うものです。
子フレームになると、一般的な GUI 用語の子ウィンドウと同じように、親フレームと一緒に移動して、親フレームにクリッピングされます。指定する座標も親相対になります。

一般的に子ウィンドウがあると、それらを階層的に並べたりして、GUI 部品を作る事がとても簡単になります。

その child-frame をターゲットとした posframe というパッケージが作成されました。
カラーピッカーでも posframe を使わせてもらっています。
ただし、パブリック関数であるposframe-showではなくて、プライベート(であろう)関数のposframe--create-posframeを直接使っています。
中を見ると、結局はパラメータを指定してmake-frameを呼ぶだけですが、かなり楽が出来ます。

cpick--create-posframeでカラーピッカー内で使用するフレームを一括で作成しているので、ここを見るとフレームの作成方法が分かります。

posframe--create-posframeには:override-parametersという便利なパラーメータがあり、これで指定したパラーメータを上書きできます。

カラーピッカー全体が格納されているベースの親フレームでは

'((parent-frame . nil)
  (no-accept-focus . nil)
  (visibility . nil))

と指定し、子フレームでは

`((parent-frame . ,cpick-frame-base)
  (no-accept-focus . nil)
  (visibility . t))

と指定したりしています。
親フレームはクリッピングされたくないので、parent-framenilを指定しています。(child-frame ではなくなる)
このようにする事で posframe のデフォルト値を上書きできます。(詳細はソースを参照願います)
no-accept-focusnilにしないと、マウスクリックに反応しなくなります。
それとvisibilityは子フレームでは必ずtにしてください!

親フレームは最初非表示(visibilitynil)にして初期化が完了してからmake-frame-visibleで表示させますが、子フレームのvisibilitynilであると、子フレームが表示されたりされなかったりします…(常に表示されないのではなく、おかしな挙動になります)
これに気付かずに、かなり消耗しちゃったよ!

それと、Emacs のフレームはあくまでも「覗き窓」である以上、何らかのバッファを指定する必要があります。
適当に共通で1つのバッファを指定すると、フレームいっぱいに画像を表示などする時に煩雑になってしまいます。
なので、自分は余計な苦労をしたくなかったので、1フレーム1バッファとしてしまっています。
カラーピッカーにはhsl(255, 0, 153)RGB HEX等の文字列が表示されていますが、それぞれが1フレーム1バッファの構成になっています。

フレームが作成出来れば、あとは

;; 一部抜粋
(modify-frame-parameters cpick-frame-current `((background-color . ,hex)
                                               (alpha . ,alpha)))
(set-frame-size cpick-frame-current cpick-frame-width height t)
(set-frame-position cpick-frame-current-string
                    (/ (- cpick-frame-width
                          (* (frame-char-width cpick-frame-current-string) (length str)))
                       2)
                    (if changed (- 40 (/ (- cpick-current-area-height height) 2)) 40))

というように、背景色やアルファ値を変えたり、リサイズしたり、位置を指定したりできます。(子フレームの場合は位置を指定する座標は親相対)
これで、一応色々な事が出来るようになるはずです。
ただしmake-frameが遅いようで…一度にたくさん子フレームを作ると表示されるまでに時間が掛かります。

フレームの作成速度もそうですが、もう一つ言いたい事があります!
以下はフレームのアルファ値を 30% にした時の状態です。
((modify-frame-parameters (selected-frame) '((alpha . 30)))を評価する)

Why!なんで文字まで薄くなる??こんなの誰がうれしいんだよーー!
お陰で、文字列のバックが薄くならないのはそういう理由でした。
いわゆる「抜き」の処理が出来ないので、このせいで出来る事の制約が大きくなっています…

言いたかった事はフレームに関しては以上です。

イメージ(image)

create-imageで作成出来る、いわゆる画像です。

カラーピッカーは1つ画像を作成して、ベースの親フレーム全体に表示して、更にその上に子フレームを作成するような構成になってます。
一番下に全体が画像の親フレームが有る事により、カラーピッカーのような事が出来てると言えます。

作成している画像のフォーマットは PNM の P6 形式というものです。いわゆるフルカラー画像形式で Emacs の unibyte-string オブジェクトを介して、R G B をそれぞれ 8bit 値として自由に書き換える事が出来ます。

PNM 形式は Emacs を GUI 版としてビルドしていれば必ず作成・表示出来る形式なので追加の DLL 等は必要ありません。

この画像を書き換えるコードは以前作成した EPaint のコードのサブセットをcolor-picker-canvas.elとして切り出しているので、そちらを参照してもらえればと思います。

EPaint のコードは複雑過ぎたので再利用する事も困難だったと思いますが、切り出したコードは必要最小限の状態になってるはずなので、再利用しやすいと思われます。

イメージについては EPaint でやったこと以上の事は何もしてないので、特筆すべき事がないので、これで終わりとします。

色関連の諸々

カラーピッカーである以上、色についても何か書く必要があるでしょうが、色の専門知識がある訳ではないので、取り敢えず実装した内容を書いておきます。

まず、カラーピッカーは HSV 色空間 が基本になっています。
右側にある Hue bar は HSV の 色相(Hue 0~360度)が表示されていて、左側の box は彩度(Saturation 0.0~1.0)と明度(Value 0.0~1.0)を選択している事になります。

Hue bar は S と V を 1.0 に固定して、H を 0 ~ 360 で動かして HSV→RGB 変換して描画しています。(実際は 256 ドットしかないので、256 分割で描画されています)

box の方は、右上の色を Hue bar で選択された H を使用して、HSV(H, 1.0, 1.0) を RGB に変換します。
で、左上を RGB(255, 255, 255)、左下と右下を RGB(0, 0, 0) にして、内部を線形補完して描画しています。
横軸が S、縦軸が V で、それぞれ左下が 0.0 で右上が 1.0 に対応しています。

なので、Hue bar のツマミを動かす度に左の box を全書き換えする必要があり、それを elisp のコードが実行している事により Hue bar のツマミが重くなっています。
回避策としてはコードのチューニングの外に、カラーピッカー自体を小さくする方法があります。(描画面積を減らす)
正直少し大きいかなと思っているので、若干小さくする予定です。

rgb(255, 0, 153) の上でカラーピッカーを起動すると、RGB→HSVへ色変換して、カーソルをそれぞれの位置に移動させます。
hsl の場合は HSL→RGB→HSV としています。

更に、色を判別する正規表現はまんまcss-mode.elのを使用しています。
色関連のコードはcss-mode.elのものをそのまま、またはコピーして一部修正したものを使っています。css-mode.elに感謝です!
ちなみに、color.elからも少し使ってます。

終わりに

Windows 版ですら完璧に動いている訳ではないので、これを公開するのは少し心苦しい物がありますが…Emacs で GUI の拡張を作るきっかけにでもなってくれたらと願うばかりです。
それと、カラーピッカーはちゃんと役に立つ拡張になるように、今後も修正や改良を続けます!

それでは、また。