;;; css-color.el --- Highlight and edit CSS colors (defconst css-color:version "0.03") ;; Copyright (C) 2008 Niels Giesen ;; Author: Niels Giesen ;; Keywords: processes, css, extensions, tools ;; Some smaller changes made by Lennart Borgman ;; Last-Updated: 2009-10-19 Mon ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Edit css-colors in hex, rgb or hsl notation in-place, with ;; immediate feedback by font-locking. Cycle between color-spaces. ;; Usage: ;; (autoload 'css-color-mode "css-color" "" t) ;; (add-hook 'css-mode-hook 'css-color-mode-turn-on) ;; Css-Css-color.el propertizes colours in a CSS stylesheet found by ;; font-locking code with a keymap. From that keymap, you can easily ;; adjust values such as red green and blue, hue, saturation and ;; value, or switch between different color (space) notations. ;; It supports all 'css-colors', so hex, rgb(), hsl() and even HTML ;; color names (although I wouldn't use them myself, it is nice to be ;; able to quickly convert those), can be used and switched between. ;; The rgb() notation can be expressed either in percentages or in ;; values between 0-255. ;; You can cycle between the different formats (with SPACE), so that ;; it is possible to edit the color in hsl mode (which is more ;; intuitive than hsv, although hsv has its merits too), and switch ;; back to rgb or hex if so desired. ;; With point on a color, the keys - and = to are bound to the down ;; and up functions for channels (or 'fields'). Toggling percentage ;; in rgb() is done with the % key (not sure if that is wise ;; though). The TAB key is bound to go to the next channel, cycling ;; when at the end. color.el propertizes the longhand hexcolours ;; found by the ;; Caveats: ;; Notation cycling can often introduce small errors inherent to ;; switching color spaces. Currently there is no check nor a warning ;; for that. ;; ToDo: ;; Try and fix those conversion inaccuracies. This cannot be done ;; completely I guess. But maybe we can check whether this has ;; occured, and then warn. ;;; Change log: ;; 2009-01-11 Lennart Borgman ;; - Minor code clean up. ;; 2009-05-23 Lennart Borgman ;; - Let bound m1 and m2. ;;; Code: (eval-when-compile (require 'cl)) (eval-when-compile (require 'mumamo nil t)) ;;;###autoload (defgroup css-color () "Customization group for library `css-color'." :group 'css :group 'nxhtml) (defconst css-color-hex-chars "0123456789abcdefABCDEF" "Composing chars in hexadecimal notation, save for the hash (#) sign.") (defconst css-color-hex-re "#\\([a-fA-F[:digit:]]\\{6\\}\\|[a-fA-F[:digit:]]\\{3\\}\\)") (defconst css-color-hsl-re "hsla?(\\([[:digit:]]\\{1,3\\}\\),[[:space:]]*\\([[:digit:]]\\{1,3\\}\\(?:\.?[[:digit:]]*\\)\\)%,[[:space:]]*\\([[:digit:]]\\{1,3\\}\\)\\(?:\.?[[:digit:]]*\\)%)") (defconst css-color-rgb-re "rgba?(\\([[:digit:]]\\{1,3\\}\\(?:\.?[[:digit:]]*%\\)?\\),[[:space:]]*\\([[:digit:]]\\{1,3\\}\\(?:\.?[[:digit:]]*%\\)?\\),[[:space:]]*\\([[:digit:]]\\{1,3\\}\\(?:\.?[[:digit:]]*%\\)?\\)\\(:?,[[:space:]]*\\(0\.[0-9]+\\|1\\)\\)?)") (defconst css-color-html-colors '(("AliceBlue" "#F0F8FF") ("AntiqueWhite" "#FAEBD7") ("Aqua" "#00FFFF") ("Aquamarine" "#7FFFD4") ("Azure" "#F0FFFF") ("Beige" "#F5F5DC") ("Bisque" "#FFE4C4") ("Black" "#000000") ("BlanchedAlmond" "#FFEBCD") ("Blue" "#0000FF") ("BlueViolet" "#8A2BE2") ("Brown" "#A52A2A") ("BurlyWood" "#DEB887") ("CadetBlue" "#5F9EA0") ("Chartreuse" "#7FFF00") ("Chocolate" "#D2691E") ("Coral" "#FF7F50") ("CornflowerBlue" "#6495ED") ("Cornsilk" "#FFF8DC") ("Crimson" "#DC143C") ("Cyan" "#00FFFF") ("DarkBlue" "#00008B") ("DarkCyan" "#008B8B") ("DarkGoldenRod" "#B8860B") ("DarkGray" "#A9A9A9") ("DarkGrey" "#A9A9A9") ("DarkGreen" "#006400") ("DarkKhaki" "#BDB76B") ("DarkMagenta" "#8B008B") ("DarkOliveGreen" "#556B2F") ("Darkorange" "#FF8C00") ("DarkOrchid" "#9932CC") ("DarkRed" "#8B0000") ("DarkSalmon" "#E9967A") ("DarkSeaGreen" "#8FBC8F") ("DarkSlateBlue" "#483D8B") ("DarkSlateGray" "#2F4F4F") ("DarkSlateGrey" "#2F4F4F") ("DarkTurquoise" "#00CED1") ("DarkViolet" "#9400D3") ("DeepPink" "#FF1493") ("DeepSkyBlue" "#00BFFF") ("DimGray" "#696969") ("DimGrey" "#696969") ("DodgerBlue" "#1E90FF") ("FireBrick" "#B22222") ("FloralWhite" "#FFFAF0") ("ForestGreen" "#228B22") ("Fuchsia" "#FF00FF") ("Gainsboro" "#DCDCDC") ("GhostWhite" "#F8F8FF") ("Gold" "#FFD700") ("GoldenRod" "#DAA520") ("Gray" "#808080") ("Grey" "#808080") ("Green" "#008000") ("GreenYellow" "#ADFF2F") ("HoneyDew" "#F0FFF0") ("HotPink" "#FF69B4") ("IndianRed" "#CD5C5C") ("Indigo" "#4B0082") ("Ivory" "#FFFFF0") ("Khaki" "#F0E68C") ("Lavender" "#E6E6FA") ("LavenderBlush" "#FFF0F5") ("LawnGreen" "#7CFC00") ("LemonChiffon" "#FFFACD") ("LightBlue" "#ADD8E6") ("LightCoral" "#F08080") ("LightCyan" "#E0FFFF") ("LightGoldenRodYellow" "#FAFAD2") ("LightGray" "#D3D3D3") ("LightGrey" "#D3D3D3") ("LightGreen" "#90EE90") ("LightPink" "#FFB6C1") ("LightSalmon" "#FFA07A") ("LightSeaGreen" "#20B2AA") ("LightSkyBlue" "#87CEFA") ("LightSlateGray" "#778899") ("LightSlateGrey" "#778899") ("LightSteelBlue" "#B0C4DE") ("LightYellow" "#FFFFE0") ("Lime" "#00FF00") ("LimeGreen" "#32CD32") ("Linen" "#FAF0E6") ("Magenta" "#FF00FF") ("Maroon" "#800000") ("MediumAquaMarine" "#66CDAA") ("MediumBlue" "#0000CD") ("MediumOrchid" "#BA55D3") ("MediumPurple" "#9370D8") ("MediumSeaGreen" "#3CB371") ("MediumSlateBlue" "#7B68EE") ("MediumSpringGreen" "#00FA9A") ("MediumTurquoise" "#48D1CC") ("MediumVioletRed" "#C71585") ("MidnightBlue" "#191970") ("MintCream" "#F5FFFA") ("MistyRose" "#FFE4E1") ("Moccasin" "#FFE4B5") ("NavajoWhite" "#FFDEAD") ("Navy" "#000080") ("OldLace" "#FDF5E6") ("Olive" "#808000") ("OliveDrab" "#6B8E23") ("Orange" "#FFA500") ("OrangeRed" "#FF4500") ("Orchid" "#DA70D6") ("PaleGoldenRod" "#EEE8AA") ("PaleGreen" "#98FB98") ("PaleTurquoise" "#AFEEEE") ("PaleVioletRed" "#D87093") ("PapayaWhip" "#FFEFD5") ("PeachPuff" "#FFDAB9") ("Peru" "#CD853F") ("Pink" "#FFC0CB") ("Plum" "#DDA0DD") ("PowderBlue" "#B0E0E6") ("Purple" "#800080") ("Red" "#FF0000") ("RosyBrown" "#BC8F8F") ("RoyalBlue" "#4169E1") ("SaddleBrown" "#8B4513") ("Salmon" "#FA8072") ("SandyBrown" "#F4A460") ("SeaGreen" "#2E8B57") ("SeaShell" "#FFF5EE") ("Sienna" "#A0522D") ("Silver" "#C0C0C0") ("SkyBlue" "#87CEEB") ("SlateBlue" "#6A5ACD") ("SlateGray" "#708090") ("SlateGrey" "#708090") ("Snow" "#FFFAFA") ("SpringGreen" "#00FF7F") ("SteelBlue" "#4682B4") ("Tan" "#D2B48C") ("Teal" "#008080") ("Thistle" "#D8BFD8") ("Tomato" "#FF6347") ("Turquoise" "#40E0D0") ("Violet" "#EE82EE") ("Wheat" "#F5DEB3") ("White" "#FFFFFF") ("WhiteSmoke" "#F5F5F5") ("Yellow" "#FFFF00") ("YellowGreen" "#9ACD32"))) (defvar css-color-html-re (concat "\\<\\(" (funcall 'regexp-opt (mapcar 'car css-color-html-colors)) "\\)\\>")) (defconst css-color-color-re "\\(?:#\\(?:[a-fA-F[:digit:]]\\{6\\}\\|[a-fA-F[:digit:]]\\{3\\}\\)\\|hsl(\\(?:[[:digit:]]\\{1,3\\}\\),[[:space:]]*\\(?:[[:digit:]]\\{1,3\\}\\)%,[[:space:]]*\\(?:[[:digit:]]\\{1,3\\}\\)%)\\|rgba?(\\(?:[[:digit:]]\\{1,3\\}%?\\),[[:space:]]*\\(?:[[:digit:]]\\{1,3\\}%?\\),[[:space:]]*\\(?:[[:digit:]]\\{1,3\\}%?\\)\\(?:,[[:space:]]*\\(?:0.[0-9]+\\|1\\)\\)?)\\)" "Regular expression containing only shy groups matching any type of CSS color") ;; (defconst css-color-color-re ;; (concat "\\(?1:" ;; (mapconcat ;; 'identity ;; (list css-color-hex-re ;; css-color-hsl-re ;; css-color-rgb-re) "\\|") ;; "\\)")) (defvar css-color-keywords `((,css-color-hex-re (0 (progn (when (= 7 (- (match-end 0) (match-beginning 0))) (put-text-property (match-beginning 0) (match-end 0) 'keymap css-color-map)) (put-text-property (match-beginning 0) (match-end 0) 'css-color-type 'hex) (put-text-property (match-beginning 0) (match-end 0) 'rear-nonsticky t) (put-text-property (match-beginning 0) (match-end 0) 'face (list :background (match-string-no-properties 0) :foreground (css-color-foreground-color (match-string-no-properties 0))))))) (,css-color-html-re (0 (let ((color (css-color-string-name-to-hex (match-string-no-properties 0)))) (put-text-property (match-beginning 0) (match-end 0) 'keymap css-color-generic-map) (put-text-property (match-beginning 0) (match-end 0) 'css-color-type 'name) (put-text-property (match-beginning 0) (match-end 0) 'rear-nonsticky t) (put-text-property (match-beginning 0) (match-end 0) 'face (list :background color :foreground (css-color-foreground-color color)))))) (,css-color-hsl-re (0 (let ((color (concat "#" (apply 'css-color-hsl-to-hex (mapcar 'string-to-number (list (match-string-no-properties 1) (match-string-no-properties 2) (match-string-no-properties 3))))))) (put-text-property (match-beginning 0) (match-end 0) 'keymap css-color-generic-map) (put-text-property (match-beginning 0) (match-end 0) 'css-color-type 'hsl) (put-text-property (match-beginning 0) (match-end 0) 'rear-nonsticky t) (put-text-property (match-beginning 0) (match-end 0) 'face (list :background color :foreground (css-color-foreground-color color)))))) (,css-color-rgb-re (0 (let ((color (css-color-string-rgb-to-hex (match-string-no-properties 0)))) (put-text-property (match-beginning 0) (match-end 0) 'keymap css-color-generic-map) (put-text-property (match-beginning 0) (match-end 0) 'css-color-type 'rgb) (put-text-property (match-beginning 0) (match-end 0) 'rear-nonsticky t) (put-text-property (match-beginning 0) (match-end 0) 'face (list :background color :foreground (css-color-foreground-color color)))))))) ;;;###autoload (define-minor-mode css-color-mode "Show hex color literals with the given color as background. In this mode hexadecimal colour specifications like #6600ff are displayed with the specified colour as background. Certain keys are bound to special colour editing commands when point is at a hexadecimal colour: \\{css-color-map}" :initial-value nil :group 'css-color (unless font-lock-defaults (error "Can't use css-color-mode for this major mode")) (if css-color-mode (progn (unless font-lock-mode (font-lock-mode 1)) (css-color-font-lock-hook-fun) (add-hook 'font-lock-mode-hook 'css-color-font-lock-hook-fun nil t)) (remove-hook 'font-lock-mode-hook 'css-color-font-lock-hook-fun t) (font-lock-remove-keywords nil css-color-keywords)) ;;(font-lock-fontify-buffer) (save-restriction (widen) (mumamo-mark-for-refontification (point-min) (point-max)))) (put 'css-color-mode 'permanent-local t) (defun css-color-turn-on-in-buffer () "Turn on `css-color-mode' in `css-mode'." (when (derived-mode-p 'css-mode) (css-color-mode 1))) ;;;###autoload (define-globalized-minor-mode css-color-global-mode css-color-mode css-color-turn-on-in-buffer :group 'css-color) (defun css-color-font-lock-hook-fun () "Add css-color pattern to font-lock's." (if font-lock-mode (font-lock-add-keywords nil css-color-keywords t) (css-color-mode -1))) (defvar css-color-map (let ((m (make-sparse-keymap "css-color"))) (define-key m "=" 'css-color-up) (define-key m "-" 'css-color-down) (define-key m "h" 'css-color-hue-up) (define-key m "H" 'css-color-hue-down) (define-key m "s" 'css-color-saturation-up) (define-key m "S" 'css-color-saturation-down) (define-key m "v" 'css-color-value-up) (define-key m "V" 'css-color-value-down) (define-key m "\t" 'css-color-next-channel) (define-key m " " 'css-color-cycle-type) m) "Mode map for `css-color-minor-mode'") (defvar css-color-generic-map (let ((m (make-sparse-keymap "css-color"))) (define-key m "=" 'css-color-num-up) (define-key m "-" 'css-color-num-down) (define-key m " " 'css-color-cycle-type) (define-key m "%" 'css-color-toggle-percentage) (define-key m "\t" 'css-color-next-channel) m) "Mode map for simple numbers in `css-color-minor-mode'") (defun css-color-pal-lumsig (r g b) "Return PAL luminance signal, but in range 0-255." (+ (* 0.3 r) (* 0.59 g) (* 0.11 b))) (defun css-color-foreground-color (hex-color) (multiple-value-bind (r g b) (css-color-hex-to-rgb hex-color) (if (< (css-color-pal-lumsig r g b) 128) "#fff" "#000"))) ;; Normalizing funs (defun css-color-normalize-hue (h) (mod (+ (mod h 360) 360) 360)) (defun css-color-within-bounds (num min max) (min (max min num) max)) ;; Source: hex (defun css-color-hex-to-rgb (str) (cond ((not (string-match "^#?[a-fA-F[:digit:]]*$" str)) (error "No valid hexadecimal: %s" str)) ((= 0 (length str)) nil) ((= (aref str 0) 35) (css-color-hex-to-rgb (substring str 1))) (;;(oddp (length str)) (= (mod (length str) 2) 1) (css-color-hex-to-rgb (mapconcat (lambda (c) (make-string 2 c)) (string-to-list str) ""))) (t (cons (string-to-number (substring str 0 2) 16) (css-color-hex-to-rgb (substring str 2)))))) (defun css-color-hex-to-hsv (hex) (multiple-value-bind (r g b) (css-color-hex-to-rgb hex) (css-color-rgb-to-hsv r g b))) ;; Source: rgb (defun css-color-rgb-to-hex (r g b) "Return r g b as #rrggbb in hexadecimal, propertized to have the keymap `css-color-map'" (format "%02x%02x%02x" r g b)) ;val (defun css-color-rgb-to-hsv (r g b) "Return list of (hue saturation value). Arguments are: R = red; G = green; B = blue. Measure saturation and value on a scale from 0 - 100. GIMP-style, that is." (let* ((r (float r)) (g (float g)) (b (float b)) (max (max r g b)) (min (min r g b))) (values (round (cond ((and (= r g) (= g b)) 0) ((and (= r max) (>= g b)) (* 60 (/ (- g b) (- max min)))) ((and (= r max) (< g b)) (+ 360 (* 60 (/ (- g b) (- max min))))) ((= max g) (+ 120 (* 60 (/ (- b r) (- max min))))) ((= max b) (+ 240 (* 60 (/ (- r g) (- max min))))))) ;hue (round (* 100 (if (= max 0) 0 (- 1 (/ min max))))) ;sat (round (/ max 2.55))))) (defun css-color-rgb-to-hsl (r g b) "Return R G B (in range 0-255) converted to HSL (0-360 for hue, rest in %)" (let* ((r (/ r 255.0)) (g (/ g 255.0)) (b (/ b 255.0)) (h 0) (s 0) (l 0) (v (max r g b)) (m (min r g b)) (l (/ (+ m v) 2.0)) (vm 0) (r2 0) (g2 0) (b2 0)) (multiple-value-bind (h s v) (if (<= l 0) (values h s l) (setq vm (- v m) s vm) (if (>= 0 s) (values h s l) (setq s (/ s (if (<= l 0.5) (+ v m) (- 2.0 v m)))) (if (not (= 0 vm)) (setq r2 (/ (- v r) vm) g2 (/ (- v g) vm) b2 (/ (- v b) vm))) (cond ((= r v) (setq h (if (= g m) (+ 5.0 b2) (- 1.0 g2)))) ((= g v) (setq h (if (= b m) (+ 1.0 r2) (- 3.0 b2)))) (t (setq h (if (= r m) (+ 3.0 g2) (- 5.0 r2))))) (values (/ h 6.0) s l))) (list (round(* 360 h)) (* 100 s) (* 100 l))))) ;; Source: hsv (defun css-color-hsv-to-hsl (h s v) (multiple-value-bind (r g b) (css-color-hsv-to-rgb h s v) (css-color-rgb-to-hsl r g b))) (defun css-color-hsv-to-hex (h s v) (apply 'css-color-rgb-to-hex (css-color-hsv-to-rgb h s v))) (defun css-color-hsv-to-rgb (h s v) "Convert a point in the Hue, Saturation, Value (aka Brightness) color space to list of normalized Red, Green, Blue values. HUE is an angle in the range of 0 degrees inclusive to 360 exclusive. The remainder of division by 360 is used for out-of-range values. SATURATION is in the range of 0 to 100. VALUE is in the range of 0 to 100. Returns a list of values in the range of 0 to 255. " ;; Coerce to float and get hue into range. (setq h (mod h 360.0) s (/ (float s) 100) v (/ (float v) 100)) (let* ((hi (floor h 60.0)) (f (- (/ h 60.0) hi)) (p (* v (- 1.0 s))) (q (* v (- 1.0 (* f s)))) ;; cannot use variable t, obviously. (u (* v (- 1.0 (* (- 1.0 f) s)))) r g b) (case hi (0 (setq r v g u b p)) (1 (setq r q g v b p)) (2 (setq r p g v b u)) (3 (setq r p g q b v)) (4 (setq r u g p b v)) (5 (setq r v g p b q))) (mapcar (lambda (color) (round (* 255 color))) (list r g b)))) (defun css-color-hsv-to-prop-hexstring (color-data) (propertize (apply 'css-color-hsv-to-hex color-data) 'keymap css-color-map 'css-color color-data)) ;; Source: hsl (defun css-color-hsl-to-rgb-fractions (h s l) (let (m1 m2) (if (<= l 0.5) (setq m2 (* l (+ s 1))) (setq m2 (- (+ l s) (* l s)))) (setq m1 (- (* l 2) m2)) (values (css-color-hue-to-rgb m1 m2 (+ h (/ 1 3.0))) (css-color-hue-to-rgb m1 m2 h) (css-color-hue-to-rgb m1 m2 (- h (/ 1 3.0)))))) (defun css-color-hsl-to-rgb (h s l) (multiple-value-bind (r g b) (css-color-hsl-to-rgb-fractions (/ h;; (css-color-normalize-hue h) 360.0) (/ s 100.0) (/ l 100.0)) (values (css-color-within-bounds (* 256 r) 0 255) (css-color-within-bounds (* 256 g) 0 255) (css-color-within-bounds (* 256 b) 0 255)))) (defun css-color-hsl-to-hex (h s l) (apply 'css-color-rgb-to-hex (css-color-hsl-to-rgb h s l))) (defun css-color-hue-to-rgb (x y h) (when (< h 0) (incf h)) (when (> h 1) (decf h)) (cond ((< h (/ 1 6.0)) (+ x (* (- y x) h 6))) ((< h 0.5) y) ((< h (/ 2.0 3.0)) (+ x (* (- y x) (- (/ 2.0 3.0) h) 6))) (t x))) (defun css-color-parse-hsl (str) (string-match css-color-hsl-re str) (mapcar 'string-to-number (list (match-string 1 str) (match-string 2 str) (match-string 3 str)))) (defun css-color-inchue (color incr) (multiple-value-bind (h s v) color (css-color-hsv-to-prop-hexstring (list (+ incr h) s v)))) (defun css-color-incsat (color incr) (multiple-value-bind (h s v) color (css-color-hsv-to-prop-hexstring (list h (css-color-within-bounds (+ incr s) 0 100) v)))) (defun css-color-incval (color incr) (multiple-value-bind (h s v) color (css-color-hsv-to-prop-hexstring (list h s (css-color-within-bounds (+ incr v) 0 100))))) (defun css-color-hexval-beginning () (skip-chars-backward css-color-hex-chars) (if (= (char-after) 35) (forward-char 1))) (defun css-color-replcolor-at-p (fun increment) (let ((pos (point))) (css-color-hexval-beginning) (insert (funcall fun (css-color-get-color-at-point) increment)) (delete-region (point) (+ (point) 6)) (goto-char pos))) (defun css-color-get-color-at-point () (save-excursion (css-color-hexval-beginning) (let ((saved-color (get-text-property (point) 'css-color))) (or saved-color (css-color-hex-to-hsv (buffer-substring-no-properties (point) (+ (point) 6))))))) (defun css-color-adj-hue-at-p (increment) (interactive "p") (css-color-replcolor-at-p 'css-color-inchue increment)) (defun css-color-adj-saturation-at-p (increment) (interactive "p") (css-color-replcolor-at-p 'css-color-incsat increment)) (defun css-color-adj-value-at-p (increment) (interactive "p") (css-color-replcolor-at-p 'css-color-incval increment)) (defun css-color-what-channel () (let ((pos (point))) (prog1 (/ (skip-chars-backward css-color-hex-chars) -2) (goto-char pos)))) (defun css-color-adjust-hex-at-p (incr) (interactive "p") (let ((pos (point)) (channel (css-color-what-channel))) (css-color-hexval-beginning) (let ((rgb (css-color-hex-to-rgb (buffer-substring-no-properties (point) (+ 6 (point)))))) (setf (nth channel rgb) (css-color-within-bounds (+ incr (nth channel rgb)) 0 255)) (delete-region (point) (+ 6 (point))) (insert (propertize (apply 'format "%02x%02x%02x" rgb) 'keymap css-color-map 'css-color nil 'rear-nonsticky t))) (goto-char pos))) ;; channels (r, g, b) (defun css-color-up (val) "Adjust R/G/B up." (interactive "p") (css-color-adjust-hex-at-p val)) (defun css-color-down (val) "Adjust R/G/B down." (interactive "p") (css-color-adjust-hex-at-p (- val))) ;; hue (defun css-color-hue-up (val) "Adjust Hue up." (interactive "p") (css-color-adj-hue-at-p val)) (defun css-color-hue-down (val) "Adjust Hue down." (interactive "p") (css-color-adj-hue-at-p (- val))) ;; saturation (defun css-color-saturation-up (val) "Adjust Saturation up." (interactive "p") (css-color-adj-saturation-at-p val)) (defun css-color-saturation-down (val) "Adjust Saturation down." (interactive "p") (css-color-adj-saturation-at-p (- val))) ;; value (defun css-color-value-up (val) "Adjust Value up." (interactive "p") (css-color-adj-value-at-p val)) (defun css-color-value-down (val) "Adjust Value down." (interactive "p") (css-color-adj-value-at-p (- val))) (defun css-color-num-up (arg) "Adjust HEX number up." (interactive "p") (save-excursion (let ((digits "1234567890")) (skip-chars-backward digits) (when (looking-at "[[:digit:]]+") (replace-match (propertize (let ((num (+ (string-to-number (match-string 0)) arg))) ;max = 100 when at percentage (save-match-data (cond ((looking-at "[[:digit:]]+%") (setq num (min num 100))) ((looking-back "hsla?(") (setq num (css-color-normalize-hue num))) ((memq 'css-color-type (text-properties-at (point))) (setq num (min num 255))))) (number-to-string num)) 'keymap css-color-generic-map)))))) (defun css-color-num-down (arg) "Adjust HEX number down." (interactive "p") (save-excursion (let ((digits "1234567890")) (skip-chars-backward digits) (when (looking-at "[[:digit:]]+") (replace-match (propertize (let ((num (- (string-to-number (match-string 0)) arg))) ;max = 100 when at percentage (save-match-data (cond ((looking-back "hsla?(") (setq num (css-color-normalize-hue num))) (t (setq num (max 0 num))))) (number-to-string num)) 'keymap css-color-generic-map)))))) (defun css-color-beginning-of-color () "Skip to beginning of color. Return list of point and color-type." (while (memq 'css-color-type (text-properties-at (point))) (backward-char 1)) (forward-char 1) (cons (point) (plist-get (text-properties-at (point)) 'css-color-type))) (defun css-color-end-of-color () "Skip to beginning of color. Return list of point and color-type." (while (plist-get (text-properties-at (point)) 'css-color-type) (forward-char 1)) (cons (point) (plist-get (text-properties-at (1- (point))) 'css-color-type))) (defun css-color-color-info () (destructuring-bind ((beg . type) (end . type)) (list (css-color-beginning-of-color) (css-color-end-of-color)) (list beg end type (buffer-substring-no-properties beg end)))) (defconst css-color-type-circle '#1=(hex hsl rgb name . #1#)) (defun css-color-next-type (sym) (cadr (member sym css-color-type-circle))) (defun css-color-cycle-type () "Cycle color type." (interactive) (destructuring-bind (beg end type color) (css-color-color-info) (if (or (= 0 (length color)) (null type)) (error "Not at color")) (delete-region beg end) (insert (propertize (funcall (intern-soft (format "css-color-string-%s-to-%s" type (css-color-next-type type))) color) 'keymap (if (eq (css-color-next-type type) 'hex) css-color-map css-color-generic-map) 'rear-nonsticky t)) (goto-char beg))) (defun css-color-string-hex-to-hsl (str) (multiple-value-bind (h s l) (apply 'css-color-rgb-to-hsl (css-color-hex-to-rgb str)) (format "hsl(%d,%d%%,%d%%)" h s l))) (defun css-color-string-hsl-to-rgb (str) (multiple-value-bind (h s l) (css-color-parse-hsl str) (apply 'format "rgb(%d,%d,%d)" (mapcar 'round (css-color-hsl-to-rgb h s l))))) (defun css-color-string-rgb-to-name (str) (let ((color (css-color-string-rgb-to-hex str))) (or (car (rassoc (list (upcase color)) css-color-html-colors)) ;if name ok color))) ;else return hex (defun css-color-string-name-to-hex (str) (let ((str (downcase str))) (cadr (assoc-if (lambda (a) (string= (downcase a) str)) css-color-html-colors)))) (defun css-color-string-rgb-to-hex (str) (save-match-data (string-match css-color-rgb-re str) (concat "#" (apply 'css-color-rgb-to-hex (mapcar ;;'string-to-number (lambda (s) (if (= (aref s (1- (length s))) ?\%) (round (* (string-to-number s) 2.55)) (string-to-number s))) (list (match-string-no-properties 1 str) (match-string-no-properties 2 str) (match-string-no-properties 3 str))))))) (defun css-color-string-hsl-to-hex (str) (concat "#" (apply 'css-color-hsl-to-hex (css-color-parse-hsl str)))) (defun css-color-next-channel () "Cycle color channel." (interactive) (multiple-value-bind (beg end type color) (save-excursion (css-color-color-info)) (case type ((hsl rgb) (if (not (re-search-forward ",\\|(" end t)) (goto-char (+ beg 4)))) (hex (cond ((> (point) (- end 3)) (goto-char (+ 1 beg))) ((= (char-after) 35) (forward-char 1)) ((evenp (- (point) beg)) (forward-char 1)) (t (forward-char 2))))))) (defun css-color-hexify-anystring (str) (cond ((string-match "^hsl" str) (css-color-string-hsl-to-hex str)) ((string-match "^rgb" str) (css-color-string-rgb-to-hex str)) (t str))) (defun css-color-toggle-percentage () "Toggle percent ??" (interactive) (let ((pos (point))) (if (eq (nth 2 (save-excursion (css-color-color-info))) 'rgb) (let ((chars "%1234567890.")) (skip-chars-backward chars) (when (looking-at "[[:digit:]]+\\(?:\.?[[:digit:]]*%\\)?%?") (let ((s (match-string 0))) (replace-match (propertize (if (= (aref s (1- (length s))) ?\%) (number-to-string (round (* (string-to-number s) 2.55))) (format "%d%%" (/ (string-to-number s) 2.55))) 'keymap css-color-generic-map 'rear-nonsticky t))) ;;(goto-char pos) )) (message "No toggling at point.")))) ;; provide some backwards-compatibility to hexcolor.el: (defvar css-color-fg-history nil) (defvar css-color-bg-history nil) ;;;###autoload (defun css-color-test (fg-color bg-color) "Test colors interactively. The colors are displayed in the echo area. You can specify the colors as any viable css color. Example: red #f00 #0C0 #b0ff00 hsla(100, 50%, 25%) rgb(255,100,120)" (interactive (list (completing-read "Foreground color: " css-color-html-colors nil nil nil nil css-color-fg-history) (completing-read "Background color: " css-color-html-colors nil nil nil nil css-color-bg-history))) (let* ((s (concat " Foreground: " fg-color ", Background: " bg-color " "))) (put-text-property 0 (length s) 'face (list :foreground (css-color-hexify-anystring fg-color) :background (css-color-hexify-anystring bg-color)) s) (message "Here are the colors: %s" s))) (defun css-color-run-tests () (interactive) (unless (progn (assert (string= (css-color-string-hex-to-hsl "#ffff00") "hsl(60,100%,50%)")) (assert (string= (css-color-string-rgb-to-hex "rgb(255, 50%, 0)")"#ff7f00")) (assert (string= (css-color-string-hsl-to-rgb "hsl(60, 100%, 50%)") "rgb(255,255,0)")) (assert (string= (css-color-string-hsl-to-hex "hsl(60, 100%, 50%)") "#ffff00"))) (message "All tests passed"))) (provide 'css-color) ;;; css-color.el ends here