Yearly Archives: 2023

2023-08-25 ,

Emacsのcss-modeやcustomize-face等でカラーピッカーを使う設定

以前(と言ってももうずいぶん前になりますが)Emacsで動くSVG実装のカラーピッカーを作りましたが、少し整えてからcss-modeやカスタマイズ機構で使うための設定を用意しました。作図エディタ本体の改良で忙しくカラーピッカー単体での利用の方はおろそかになっていました。ようやく手を付けられたので色々いじっているところです。

2023-08-25-color-picker.png

まず次のelispを導入します。

misohena/el-easydraw: Embedded drawing tool for Emacs

私は自分のemacs設定ディレクトリのサブモジュールになっていますが、package-vcやらstraightやらでも入れられるそうです。その辺り詳しいことは知りません。別にload-pathの通ったところに全部置いておけば済む話です。

それで、css-modeやmhtml-modeやらで使うには例えば次のようにします。

(2025-02-17追記: edraw-color-picker-modeとedraw-color-picker-global-modeを追加したので、それを使う場合は以下の設定は不要になりました。以下のやり方も引き続き利用可能です)

;; この辺りはパッケージ管理システムを使っていると自動的に作られているかも。
;; 一応;;;###autoload指定は入れてあるので。
(autoload 'edraw-color-picker-replace-color-at "edraw-color-picker" nil t)
(autoload 'edraw-color-picker-replace-or-insert-color-at-point "edraw-color-picker" nil t)

;; お好みでキー割り当て。
(defun my-edraw-color-picker-add-keys (map)
  ;; マウスのクリックでそこにある色名を置換。
  (define-key map [mouse-1] #'edraw-color-picker-replace-color-at)
  ;; C-c C-oでそこに色名があれば置換、無ければ挿入。
  (define-key map (kbd "C-c C-o")
              #'edraw-color-picker-replace-or-insert-color-at-point))

;; local-mapにキーを設定する関数。
(defun my-edraw-color-picker-enable ()
  (my-edraw-color-picker-add-keys (or (current-local-map)
                                      (let ((map (make-sparse-keymap)))
                                        (use-local-map map)
                                        map))))

;; お好みのモードにキー設定を追加。
(add-hook 'css-mode-hook 'my-edraw-color-picker-enable)
(add-hook 'mhtml-mode-hook 'my-edraw-color-picker-enable)
(add-hook 'web-mode-hook 'my-edraw-color-picker-enable)

EmacsのCustomize用のバッファ(customize-faceとか)で使うには次のようにします。

;; [追記:2025-02-11]この部分は不要になりました。メジャーモードから自動的に判断されます。
;; ;; Customize用のバッファではEmacsの色名を使う。
;; (defun my-edraw-color-picker-enable-for-custom-mode ()
;;   (setq-local edraw-color-picker-insert-default-color-scheme 'emacs))
;; (add-hook 'Custom-mode-hook 'my-edraw-color-picker-enable-for-custom-mode)

;; フィールドのキーマップにキー設定を追加。
;; local-mapに設定するとフィールド上では効かないので。
(with-eval-after-load "cus-edit"
  (my-edraw-color-picker-add-keys custom-field-keymap))

カラーピッカーは現在デフォルトでは子フレームで表示されるようになっていますが、環境によっては正しく表示されないかもしれません。その場合は (setq edraw-color-picker-use-frame-p nil) にするとオーバーレイを使った表示になります。それはそれで無理矢理行と行の間に差し込むのでレイアウトが崩れることもあるかもしれませんが。

カラーピッカーの大きさは edraw-color-picker-near-point-scale で変えられます。デフォルトは0.75です。

無理にポイントの近くに表示しなくても (edraw-color-picker-read-color) でミニバッファから入力した方が使いやすいような気もしますがどうでしょうね……。

今後の改良点としては:

  • 他の表色系に対応
  • 置換前の表記に出来るだけ合わせる
  • 現在値の表示

辺りでしょうか。

その前に作図エディタ側からの必要性で固定パレットが入ると思います。

基本作図エディタ本体が優先なのでいつになるかは分かりませんが、そのうち。

(2025-02-17追記: 続きをEmacs用のカラーピッカーに対する最近の変更に書きました)

2023-08-18

before-stringに別のオーバーレイのfaceが適用されない

before-stringプロパティを持つオーバーレイ(ov1)があったとします。そのオーバーレイ(ov1)を囲むように別のオーバーレイ(ov2)もあったとします。その別のオーバーレイ(ov2)がfaceプロパティを持っていた場合、そのfaceはov1のbefore-stringに影響するでしょうか。

[OV1BEFORE][OV1TEXT] OV2TEXT][OV2TEXT ov1の範囲ov2の範囲ov1のbefore-stringによって生成された部分青くハイライトするfaceが設定されている
図1: 二つのオーバーレイが重なる様子

色々試してみたのですが、なかなか影響させる方法が見つかりませんでした。

ov1のbefore-stringはov1が囲んでいるテキストの最初の文字のfaceテキストプロパティのみに影響を受けるようです(もちろんbefore-string自体にテキストプロパティが付いている場合は別です)。

これはtransient-mark-modeやhl-line-modeを使ってbefore-stringを持つオーバーレイを囲ってみればよく分かります。before-stringの部分だけハイライトされません。

Emacs Lispで再現するコードは次のようになります。

(let ((beg (point))
      (_ (insert "[OV2TEXT [OV1TEXT] OV2TEXT]"))
      (end (point)))
  (let ((ov1 (make-overlay (+ beg 9) (- end 9))))
    (overlay-put ov1 'evaporate t)
    (overlay-put ov1 'before-string "[OV1BEFORE]"))
  (let ((ov2 (make-overlay beg end)))
    (overlay-put ov2 'evaporate t)
    (overlay-put ov2 'face '(:background "#4080c0"))))

結果は次のようになります。

before-stringに他のオーバーレイのfaceプロパティが適用されない様子
図2: before-stringに他のオーバーレイのfaceプロパティが適用されない様子

色々変えて試してみました。

  • priorityプロパティを色々指定してみても変わりません。
  • ov2の範囲を色々変えても変わりません。
  • after-stringもbefore-stringと同様に影響を受けません。
  • [OV1TEXT]の先頭文字([)にfaceテキストプロパティ(実際にはfont-lock-face)を付けると、before-stringにはそのfaceが適用されます。つまり (put-text-property (+ beg 9) (+ beg 10) 'font-lock-face '(:background "red")) のように。これは回避策には利用できそうですがov2のfaceが適用されているわけではありません。ちなみにafter-stringは[OV1TEXT]の最後の文字……ではなく、その次の文字に設定したfaceが適用されます。
  • displayプロパティで表示した文字列([OV1TEXT]を置き換える)は影響を受けます。

displayプロパティは影響を受ける。その事実を知ったとき、私にはあるアイデアが浮かびました。before-stringにdisplayテキストプロパティを付けたらどうなるんだろう。つまり次のようにするわけです。

(let ((beg (point))
      (_ (insert "[OV2TEXT [OV1TEXT] OV2TEXT]"))
      (end (point)))
  (let ((ov1 (make-overlay (+ beg 9) (- end 9))))
    (overlay-put ov1 'evaporate t)
    (overlay-put ov1 'before-string
                 ;; ↓★displayテキストプロパティを設定する。
                 (propertize "_" 'display "[OV1BEFORE-DISPLAY]")))
  (let ((ov2 (make-overlay beg end)))
    (overlay-put ov2 'evaporate t)
    (overlay-put ov2 'face '(:background "#4080c0"))))

結果は何と……

before-stringに他のオーバーレイのfaceプロパティが適用されている様子
図3: before-stringに他のオーバーレイのfaceプロパティが適用されている様子

ちゃんと適用されました!

これらは一体どう解釈すれば良いのでしょうか。

まずbefore-stringに他のオーバーレイのfaceが適用されないのはバグでしょうか、意図した仕様でしょうか、それとも単に未定義動作(どうなっても文句は言えない)なだけでしょうか。前述したとおりtransient-mark-modeで範囲選択すればすぐに分かるので誰も気が付かないと言うことは無いと思うんですよね。さりとてこの挙動に何かメリットがあるのかと問われればあまり思いつきません。

一方before-stringのdisplayには効くというのはどうなのでしょうか。この挙動に頼って良いものなのでしょうか。

GNUのサイトに行ってバグトラッカーとメールのアーカイブを何度か行ったり来たりした後、嫌になって探すのを諦めました。

個人的にはどちらにも適用されるのが自然な挙動のように感じます。

今回の問題が気になったきっかけは、dired-details-rでhl-line-modeが正しく機能しなかったことです。dired-details-rでは、行末の"\n"部分にオーバーレイをかけてbefore-stringでファイルの詳細情報を表示しています1。なので、ファイルの詳細情報の部分は一切ハイライトされません。これでは現在の行をハイライトする意味がありません。

dired-details-rでhl-line-modeが正しく機能しない様子
図4: dired-details-rでhl-line-modeが正しく機能しない様子

すでに色々回避策を適用してしまったのですが、もしbefore-stringのdisplayに頼って良いのならもっとシンプルで安定したコードに出来そうです。

いやはや、Emacsのテキスト&オーバーレイプロパティまわりは何とも複雑ですね。

(追記:dired-details-rでhl-line-modeが正しく機能しない件は解決しました! ちなみにこのテクニックはall-the-icons-dired(私は色々独自に手を入れて使っています)でも有効です。あれはafter-stringでアイコンを挿入するので、そのままではhl-line-modeでアイコン部分がハイライトされません)

hl-line-modeが完全に機能するようになった様子
図5: hl-line-modeが完全に機能するようになった様子

脚注:

1

その辺りの経緯については以前に書いたと思います。たぶんEmacsでdisplayプロパティを使って改行を置き換えると非常に遅くなる件のあたり

2023-08-17

image-diredにサムネイル画像関連フックを追加する

もっと……もっとフックを……、フックが欲しいのじゃ!

私はdired-details-rを使ってファイルの詳細情報をファイル名の右側に表示していますが、image-diredでファイル名の前にサムネイルを追加すると表示がずれることがあります。

少し前の改良でファイル名の前にあるアイコンやらサムネイルやらの幅を考慮することは出来たのですが、それはdiredがディレクトリを読み込んだ直後の話。その後でサムネイルの状態に何か変化があると、やはりずれてしまいます。

典型的なのは、サムネイルの表示・非表示を切り替える操作をした場合。 image-dired-dired-toggle-marked-thumbs コマンド(C-t C-tで選択した画像のサムネイルをトグル)や私の改造だと my-image-dired-dired-show-all-thumbs コマンド(C-t C-aで全画像のサムネイルを表示)あたりの操作です。表示すれば詳細が右にずれますし、整えてから消せば左にずれます。

もう一つはサムネイル画像を生成し終わったとき。新しくサムネイルを生成するときは、ひとまず無効な画像(30ピクセル四方の正方形です)を表示させておいて、生成が終わったら更新するようにimage-diredは出来ています。問題はこれが非同期であるという点です(もちろん長時間待たされないという意味では良い点です)。サムネイルを生成するための外部プロセス(ImageMagickやらGraphicsMagickやら)が終了したらclear-image-cacheを実行することで画像を表示するオーバーレイが更新されます。その時に詳細情報が右にずれます。非同期なのでそのタイミングは予測できません。

というわけで次図のような画面が出来上がるわけです。

詳細情報が右にずれたdiredバッファ
図1: 詳細情報が右にずれたdiredバッファ

もちろんgを押せばすぐに揃うのですが、面倒くさいせいかなぜか押さないことも多いです。するとずれた部分が時々目の中にチラチラ入ってきて何だか気分が悪いです。

それに何だかみっともないじゃないですか。こうやってずれたスクリーンショットを気が付かずに出してしまうこともあります(実際に最近の記事の中にあります!)。こんなに好き勝手に改造して自己満足に浸っているくせにズレてるのかよ! みたいな。

というわけで直すことにしたのでした。

これを直すには、diredバッファへサムネイルのオーバーレイを挿入した後とサムネイル画像の生成が終わった後に、見た目を更新する処理を挟む必要があります。しかしそのようなタイミングで呼び出してくれる便利なフックはもちろん存在しません。フック……もっとフックを! と思うことはEmacsではいつものことです。というわけで、そのフック(それらのタイミングで任意の関数を呼び出す仕組み)をimage-diredに追加してみます。

まずはサムネイルを表示・消去した直後に呼び出すフック。diredバッファにサムネイルを表示するオーバーレイを挿入するのはimage-dired-dired.el内のimage-dired-dired-toggle-marked-thumbsコマンドです(Emacs 29.1時点)。その中身は次の通り。

;; Emacs 29.1付属のimage-dired-dired.elより
;;;###autoload
(defun image-dired-dired-toggle-marked-thumbs (&optional arg)
  "Toggle thumbnails in front of marked file names in the Dired buffer.
If no file is marked, toggle display of thumbnail on the current file's line.
ARG, if non-nil (interactively, the prefix argument), specifies the files
whose thumbnail display to toggle instead of the marked files: if ARG is an
integer, use the next ARG (or previous -ARG, if ARG<0) files; any other
value of ARG means toggle thumbnail display of the current line's file."
  (interactive "P" dired-mode)
  (setq image-dired--generate-thumbs-start  (current-time))
  (dired-map-over-marks
   (let ((image-pos  (dired-move-to-filename))
         (image-file (dired-get-filename nil t))
         thumb-file
         overlay)
     (when (and image-file
                (string-match-p (image-dired--file-name-regexp) image-file))
       (setq thumb-file (create-image
                         (image-dired--get-create-thumbnail-file image-file)))
       ;; If image is not already added, then add it.
       (let ((thumb-ov (cl-loop for ov in (overlays-in (point) (1+ (point)))
                                if (overlay-get ov 'thumb-file) return ov)))
         (if thumb-ov
             (delete-overlay thumb-ov)
           (put-image thumb-file image-pos)
           (setq overlay
                 (cl-loop for ov in (overlays-in (point) (1+ (point)))
                          if (overlay-get ov 'put-image) return ov))
           (overlay-put overlay 'image-file image-file)
           (overlay-put overlay 'thumb-file thumb-file))
         ;; ★ここに追加したい
         )))
   ;; Show or hide thumbnail on ARG next files.
   arg)
  (add-hook 'dired-after-readin-hook
            'image-dired-dired-after-readin-hook nil t))

この関数の (if thumb-ov (overlay-put overlay 'thumb-file thumb-file)) の後くらいに処理を挟みたいわけです。この関数はトグル動作なので、表示するときと消去するときの両方で通るところに挟むのが良いでしょう。しかしそこに処理を挟むのは困難です。

それ以前に、私はこの前の改造でこの関数をバラしてサムネイルの前後の余白を調整したところでした。

なので、その改造後の関数に手を入れてフック機能を追加してしまいましょう。以下★印の所三箇所を追加しました。

(defun my-image-dired-dired-toggle-marked-thumbs (&optional arg)
  ;; Derived from `image-dired-dired-toggle-marked-thumbs'
  "Toggle thumbnails in front of file names in the Dired buffer.
If no marked file could be found, insert or hide thumbnails on the
current line.  ARG, if non-nil, specifies the files to use instead
of the marked files.  If ARG is an integer, use the next ARG (or
previous -ARG, if ARG<0) files."
  (interactive "P")
  (dired-map-over-marks
   (my-image-dired-dired-set-thumb-visibility 'toggle) ;;ファイル毎の処理を分離
   arg             ; Show or hide image on ARG next files.
   'show-progress) ; Update dired display after each image is updated.
  (add-hook 'dired-after-readin-hook
            'image-dired-dired-after-readin-hook nil t))

;; ★2023-08-17追加
(defvar my-image-dired-dired-change-thumb-hook nil
  "diredバッファ内のサムネイルの表示状態が変化したら呼び出されるフッ
クです。

呼ばれるときの引数は(THUMBNAIL-OVERLAY ORIGINAL-IMAGE-FILENAME
IMAGE-POSITION)です。現在のバッファは変化したdiredバッファです。")

(defun my-image-dired-dired-set-thumb-visibility (visibility)
  ;; Derived from `image-dired-dired-toggle-marked-thumbs'
  (let ((image-pos  (dired-move-to-filename))
        (image-file (dired-get-filename nil t)))
    (when (and image-file
               (string-match-p (image-file-name-regexp) image-file))
      (let* ((thumb-file
              ;; Emacs 28まで
              ;;(image-dired-get-thumbnail-image image-file)
              ;; Emacs 29から
              (create-image
               (image-dired--get-create-thumbnail-file image-file)))
             (thumb-ov (cl-loop for ov in (overlays-in (point) (1+ (point)))
                                if (overlay-get ov 'thumb-file) return ov)))
        ;; 他から使うためにトグル以外もできるようにした
        (if thumb-ov
            (when (memq visibility '(nil toggle))
              (delete-overlay thumb-ov)
              ;; ★2023-08-17追加 : 表示→非表示
              (run-hook-with-args 'my-image-dired-dired-change-thumb-hook
                                  nil image-file image-pos))
          (when (memq visibility '(t toggle))
            ;; ★2023-08-17修正 : 非表示→表示
            (let ((new-thumb-ov
                   ;; 独自の関数を呼ぶ
                   (my-image-dired-dired-create-thumbnail-overlay
                    image-pos image-file thumb-file)))
              (run-hook-with-args 'my-image-dired-dired-change-thumb-hook
                                  new-thumb-ov image-file image-pos))))))))

こうしておくことで、一つのサムネイルが表示・非表示されるたびに任意の処理を挟むことが出来ます。そこでレイアウトを整えてやろうというわけです。

次にサムネイル画像の生成が終わったとき。画像の生成はimage-dired-external.el内のimage-dired-create-thumb-1関数で行っています。内容を見てみましょう。

;; Emacs 29.1付属のimage-dired-external.elより

(defun image-dired-create-thumb-1 (original-file thumbnail-file)
  "For ORIGINAL-FILE, create thumbnail image named THUMBNAIL-FILE."
  (image-dired--check-executable-exists
   'image-dired-cmd-create-thumbnail-program)
  (let* ((size (number-to-string (image-dired--thumb-size)))
         (modif-time (format-time-string
                      "%s" (file-attribute-modification-time
                            (file-attributes original-file))))
         (thumbnail-nq8-file (replace-regexp-in-string ".png\\'" "-nq8.png"
                                                       thumbnail-file))
         (spec `((?s . ,size) (?w . ,size) (?h . ,size)
                 (?m . ,modif-time)
                 (?f . ,original-file)
                 (?q . ,thumbnail-nq8-file)
                 (?t . ,thumbnail-file)))
         (thumbnail-dir (file-name-directory thumbnail-file))
         process)
    (when (not (file-exists-p thumbnail-dir))
      (with-file-modes #o700
        (make-directory thumbnail-dir t))
      (message "Thumbnail directory created: %s" thumbnail-dir))

    ;; Thumbnail file creation processes begin here and are marshaled
    ;; in a queue by `image-dired-create-thumb'.
    (let ((cmd image-dired-cmd-create-thumbnail-program)
          (args (mapcar
                 (lambda (arg) (format-spec arg spec))
                 (if (memq image-dired-thumbnail-storage
                           image-dired--thumbnail-standard-sizes)
                     image-dired-cmd-create-standard-thumbnail-options
                   image-dired-cmd-create-thumbnail-options))))
      (image-dired-debug "Running %s %s" cmd (string-join args " "))
      (setq process
            (apply #'start-process "image-dired-create-thumbnail" nil
                   cmd args)))

    (setf (process-sentinel process)
          (lambda (process status)
            ;; Trigger next in queue once a thumbnail has been created
            (cl-decf image-dired-queue-active-jobs)
            (image-dired-thumb-queue-run)
            (when (= image-dired-queue-active-jobs 0)
              (image-dired-debug
               (format-time-string
                "Generated thumbnails in %s.%3N seconds"
                (time-subtract nil
                               image-dired--generate-thumbs-start))))
            (if (not (and (eq (process-status process) 'exit)
                          (zerop (process-exit-status process))))
                (message "Thumb could not be created for %s: %s"
                         (abbreviate-file-name original-file)
                         (string-replace "\n" "" status))
              (set-file-modes thumbnail-file #o600)
              (clear-image-cache thumbnail-file)
              ;; PNG thumbnail has been created since we are
              ;; following the XDG thumbnail spec, so try to optimize
              (when (memq image-dired-thumbnail-storage
                          image-dired--thumbnail-standard-sizes)
                (cond
                 ((and image-dired-cmd-pngnq-program
                       (executable-find image-dired-cmd-pngnq-program))
                  (image-dired-pngnq-thumb spec))
                 ((and image-dired-cmd-pngcrush-program
                       (executable-find image-dired-cmd-pngcrush-program))
                  (image-dired-pngcrush-thumb spec))
                 ((and image-dired-cmd-optipng-program
                       (executable-find image-dired-cmd-optipng-program))
                  (image-dired-optipng-thumb spec)))))))
    process))

この関数は引数にoriginal-fileとthumbnail-fileを取ります。分かりやすいですね。

start-processで外部プロセスを起動しています。

その直後にプロセスオブジェクトのsentinelとしてlambda関数を設定することでプロセスの終了を検出しています。sentinelはプロセスの状態が変わったときに呼び出されます。

問題なのはこれがlambda、つまり匿名の関数だということ。この関数にadviceを追加して処理を追加するようなマネはできません。

しかしその後を見ると、このimage-dired-create-thumb-1はプロセスオブジェクトを返却しています。やった! ラッキー!! プロセスオブジェクトが得られるなら、そのsentinelを書き替えることが出来ます。次のように。

(defvar my-image-dired-create-thumb-hook nil)

(defun my-image-dired-create-thumb-1-around-for-call-hook
    (orig-fun original-file thumbnail-file &rest args)
  "`image-dired-create-thumb-1'に対するaround adviceです。"
  (let* (;; 元のimage-dired-create-thumb-1を呼び出す。
         (process (apply orig-fun original-file thumbnail-file args))
         ;; 返ってきたprocessオブジェクトの元のsentinelをとっておく。
         (orig-sentinel (process-sentinel process)))
    ;; sentinelを横取りする。
    (set-process-sentinel
     process
     (lambda (process status)
       (prog1
           ;; 元のsentinelを呼ぶ。
           (funcall orig-sentinel process status)
         ;; 成功だったら、フックを呼び出す。
         (when (and (eq (process-status process) 'exit)
                    (zerop (process-exit-status process)))
           (run-hook-with-args 'my-image-dired-create-thumb-hook
                               original-file thumbnail-file)))))
    process))

(advice-add 'image-dired-create-thumb-1 :around
            #'my-image-dired-create-thumb-1-around-for-call-hook)

つまり、image-dired-create-thumb-1が設定した元のsentinelをとっておいて、独自の関数にsentinelを書き替えてしまい、その独自の関数は元のsentinelを呼び出してから追加の処理をするわけです。

こういう横取りの仕方を見ると私はよく割り込みベクタのフックを思い出します。MS-DOSで常駐プログラム書いたり、システムコールをフックして処理を挟んでみたり。やってることは昔と変わらないなー。

というわけでこれで処理を挟む準備は整いました。後は次のようにすればdired-details-rがレイアウトを整えてくれます。

;; サムネイル画像をdiredバッファに挿入した後、または削除した後の処理。
;; 呼び出されるときのcurrent-bufferはdiredバッファです。

(defun my-image-dired-dired-update-on-change-thumb (_ov _original-file pos)
  (save-excursion
    (goto-char pos)
    (when (fboundp 'dired-details-r-update-current-line)
      (dired-details-r-update-current-line))))

(add-hook 'my-image-dired-dired-change-thumb-hook
          #'my-image-dired-dired-update-on-change-thumb)

;; サムネイル画像を生成し終わったときの処理。
;; 呼び出されるときのcurrent-bufferがどこかは分からないので注意すること。

(defun my-image-dired-dired-update-on-create-thumb (original-path thumb-path)
  ;; 念のため確実にフルパスにする。
  (setq original-path (expand-file-name original-path))
  (setq thumb-path (expand-file-name thumb-path))

  (let ((dired-buffers
         ;; 元画像があるディレクトリを表示するdiredバッファを列挙する。
         ;; サブディレクトリを挿入していると複数あり得る。
         (let ((original-dir (file-name-directory original-path)))
           (cl-loop for buf in (buffer-list)
                    when (and
                          (eq (buffer-local-value 'major-mode buf) 'dired-mode)
                          (assoc original-dir
                                 (buffer-local-value 'dired-subdir-alist buf)))
                    collect buf))))

    ;; 各diredバッファについて:
    (dolist (buf dired-buffers)
      (with-current-buffer buf
        ;; @todo サムネイル左右の余白も更新したい。
        ;; dired-details-rでoriginal-pathの行だけ更新する。
        (when (fboundp 'dired-details-r-update-file)
          (dired-details-r-update-file original-path))))))

(add-hook 'my-image-dired-create-thumb-hook
          'my-image-dired-dired-update-on-create-thumb)

画像生成後の方は、いつ呼び出されるのか予測が出来ないので少し込み入っています。