Interactively recolor a sketch

I wanted to be able to change the colours used in a sketch, all from Emacs. For this, I can reuse my Python script for analyzing colours and changing them and just add some Emacs Lisp to pick colours from Emacs.

2024-11-05-14-15-55.svg
Figure 1: Selecting the colour to replace
2024-11-05-14-16-04.svg
Figure 2: Selecting the new colour
(defvar my-recolor-command "/home/sacha/bin/recolor.py")

(defun my-image-colors-by-frequency (file)
  "Return the colors in FILE."
  (with-temp-buffer
    (call-process my-recolor-command nil t nil (expand-file-name file))
    (goto-char (point-min))
    (delete-region (point-min) (1+ (line-end-position)))
    (mapcar (lambda (o) (concat "#" (car (split-string o "[ \t]"))))
            (split-string (string-trim (buffer-string)) "\n"))))

(defun my-completing-read-color (prompt list)
  "Display PROMPT and select a color from LIST."
  (completing-read
   (or prompt "Color: ")
   (mapcar (lambda (o)
             (faces--string-with-color o o))
           list)))

(defun my-image-recolor-interactively (file)
  (interactive (list (read-file-name "File: " (concat my-sketches-directory "/") nil t
                                     nil
                                     (lambda (file) (string-match "\\.png\\'" file)))))
  (save-window-excursion
    (find-file file)

    ;; Identify the colors by frequency
    (let (choice done)
      (while (not done)
        (let* ((by-freq (my-image-colors-by-frequency file))
               (old-color (my-completing-read-color "Old color: " by-freq))
               (new-color (read-color "New color: " t))
               (temp-file (make-temp-file "recolor" nil (concat "." (file-name-extension file))))
               color-map)
          (when (string-match "#\\(..\\)..\\(..\\)..\\(..\\).." new-color)
            (setq new-color (concat (match-string 1 new-color)
                                    (match-string 2 new-color)
                                    (match-string 3 new-color))))
          (setq color-map (replace-regexp-in-string "#" "" (concat old-color "," new-color)))
          (call-process my-recolor-command nil nil nil
                        (expand-file-name file)
                        "--colors"
                        color-map
                        "--output" temp-file)
          (find-file temp-file)
          (pcase (read-char-choice "(y)es, (m)ore, (r)edo, (c)ancel: " "yrc")
            (?y
             (kill-buffer)
             (rename-file temp-file file t)
             (setq done t))
            (?m
             (kill-buffer)
             (rename-file temp-file file t))
            (?r
             (kill-buffer)
             (delete-file temp-file))
            (?c
             (kill-buffer)
             (delete-file temp-file)
             (setq done t))))))))

It would be nice to update the preview as I selected things in the completion, which I might be able to do by making a consult–read command for it. It would be extra cool if I could use this webkit-based color picker. Maybe someday!

View org source for this post
You can comment with Disqus or you can e-mail me at sacha@sachachua.com.