#+SETUPFILE: setup.org * Contents :toc_3_gh: - [[#namespace-thingcolorgradients][Namespace: thi.ng.color.gradients]] - [[#cosine-based-gradient-generation][Cosine based gradient generation]] - [[#presets][Presets]] - [[#implementation][Implementation]] - [[#gradient-coefficient-calculation][Gradient coefficient calculation]] - [[#example-usage][Example usage]] - [[#gradient-presets-visualization][Gradient presets visualization]] - [[#complete-namespace-definition][Complete namespace definition]] * Namespace: thi.ng.color.gradients ** Cosine based gradient generation Based on method by developed iq (Iñigo Quílez): http://v.gd/B2aySt Online gradient designer / editor: http://dev.thi.ng/gradients/ *** Presets Here we define a number of RGB gradient presets, primarily useful for data visualization purposes: #+BEGIN_SRC clojure :noweb-ref generators (def cosine-schemes {:rainbow1 [[0.5 0.5 0.5] [0.5 0.5 0.5] [1.0 1.0 1.0] [0 0.3333 0.6666]] :rainbow2 [[0.5 0.5 0.5] [0.666 0.666 0.666] [1.0 1.0 1.0] [0 0.3333 0.6666]] :rainbow3 [[0.5 0.5 0.5] [0.75 0.75 0.75] [1.0 1.0 1.0] [0 0.3333 0.6666]] :rainbow4 [[0.5 0.5 0.5] [1 1 1] [1.0 1.0 1.0] [0 0.3333 0.6666]] :yellow-magenta-cyan [[1 0.5 0.5] [0.5 0.5 0.5] [0.75 1.0 0.6666] [0.8 1.0 0.3333]] :orange-blue [[0.5 0.5 0.5] [0.5 0.5 0.5] [0.8 0.8 0.5] [0 0.2 0.5]] :green-magenta [[0.6666 0.5 0.5] [0.5 0.6666 0.5] [0.6666 0.666 0.5] [0.2 0.0 0.5]] :green-red [[0.5 0.5 0] [0.5 0.5 0] [0.5 0.5 0] [0.5 0.0 0]] :green-cyan [[0.0 0.5 0.5] [0 0.5 0.5] [0.0 0.3333 0.5] [0.0 0.6666 0.5]] :yellow-red [[0.5 0.5 0] [0.5 0.5 0] [0.1 0.5 0] [0.0 0.0 0]] :blue-cyan [[0.0 0.5 0.5] [0 0.5 0.5] [0.0 0.5 0.3333] [0.0 0.5 0.6666]] :red-blue [[0.5 0 0.5] [0.5 0 0.5] [0.5 0 0.5] [0 0 0.5]] :yellow-green-blue [[0.650 0.5 0.310] [-0.650 0.5 0.6] [0.333 0.278 0.278] [0.660 0.0 0.667]] :blue-white-red [[0.660 0.56 0.680] [0.718 0.438 0.720] [0.520 0.8 0.520] [-0.430 -0.397 -0.083]] :cyan-magenta [[0.610 0.498 0.650] [0.388 0.498 0.350] [0.530 0.498 0.620] [3.438 3.012 4.025]] :yellow-purple-magenta [[0.731 1.098 0.192] [0.358 1.090 0.657] [1.077 0.360 0.328] [0.965 2.265 0.837]] :green-blue-orange [[0.892 0.725 0.000] [0.878 0.278 0.725] [0.332 0.518 0.545] [2.440 5.043 0.732]] :orange-magenta-blue [[0.821 0.328 0.242] [0.659 0.481 0.896] [0.612 0.340 0.296] [2.820 3.026 -0.273]] :blue-magenta-orange [[0.938 0.328 0.718] [0.659 0.438 0.328] [0.388 0.388 0.296] [2.538 2.478 0.168]] :magenta-green [[0.590 0.811 0.120] [0.410 0.392 0.590] [0.940 0.548 0.278] [-4.242 -6.611 -4.045]]}) #+END_SRC Diagrams of the presets showing their channel curves and resulting gradients (using 100 samples): | [[http://media.thi.ng/color/presets/rainbow1.svg]] | [[http://media.thi.ng/color/presets/rainbow2.svg]] | | :rainbow1 | :rainbow2 | | [[http://media.thi.ng/color/presets/rainbow3.svg]] | [[http://media.thi.ng/color/presets/rainbow4.svg]] | | :rainbow3 | :rainbow4 | | [[http://media.thi.ng/color/presets/yellow-magenta-cyan.svg]] | [[http://media.thi.ng/color/presets/orange-blue.svg]] | | :yellow-magenta-cyan preset | :orange-blue | | [[http://media.thi.ng/color/presets/green-magenta.svg]] | [[http://media.thi.ng/color/presets/green-red.svg]] | | :green-magenta | :green-red | | [[http://media.thi.ng/color/presets/green-cyan.svg]] | [[http://media.thi.ng/color/presets/blue-cyan.svg]] | | :green-cyan | :blue-cyan | | [[http://media.thi.ng/color/presets/yellow-red.svg]] | [[http://media.thi.ng/color/presets/red-blue.svg]] | | :yellow-red | :red-blue | | [[http://media.thi.ng/color/presets/yellow-green-blue.svg]] | [[http://media.thi.ng/color/presets/blue-white-red.svg]] | | :yellow-green-blue | :blue-white-red | | [[http://media.thi.ng/color/presets/cyan-magenta.svg]] | [[http://media.thi.ng/color/presets/yellow-purple-magenta.svg]] | | :cyan-magenta | :yellow-purple-magenta | | [[http://media.thi.ng/color/presets/green-blue-orange.svg]] | [[http://media.thi.ng/color/presets/orange-magenta-blue.svg]] | | :green-blue-orange | :orange-magenta-blue | | [[http://media.thi.ng/color/presets/blue-magenta-orange.svg]] | [[http://media.thi.ng/color/presets/magenta-green.svg]] | | :blue-magenta-orange | :magenta-green | *** Implementation The two functions below implement the gradient generation function: =cosine-gradient-color= to compute single colors and =cosine-gradient-scheme= to create a vector of n tuples covering the full gradient range. *Note:* These function are not restricted to RGB colors and can be used in many other contexts - think of it as an ND waveform generator... #+BEGIN_SRC clojure :noweb-ref generators (defn cosine-gradient-color [offset amp fmod phase t] (col/rgba (mapv (fn [a b c d] (m/clamp (+ a (* b (Math/cos (* TWO_PI (+ (* c t) d))))) 0 1)) offset amp fmod phase))) (defn cosine-gradient "Takes a length n and 4 cosine coefficients (for colors usually 3 or 4-element vectors) and produces vector of n new RGBA colors, with each of its elements defined by an AM & FM cosine wave and clamped to the [0 1] interval. The fn `t` is used to ramp the gradient and remap the interpolation interval (e.g. use m/mix-circular)" ([n spec] (apply cosine-gradient n m/mix* spec)) ([n t spec] (apply cosine-gradient n t spec)) ([n t offset amp fmod phase] (mapv #(cosine-gradient-color offset amp fmod phase (t 0 1 %)) (m/norm-range (dec n))))) #+END_SRC *** Gradient coefficient calculation Based on [[https://gist.github.com/dimovich/bc52b77e2280ac56a2ed68a987d5ec57][@dimovich's gist]], this function computes the cosine gradient coefficients for a gradient between two given colors. #+BEGIN_SRC clojure :noweb-ref generators (defn cosine-coefficients "Computes coefficients defining a cosine gradient between the two given colors. The colors can be in any color space, but the resulting gradient will always be computed in RGB. amp = (R1 - R2) / 2 dc = R1 - amp freq = -0.5" ([c1 c2] (let [colors (map #(select-keys (col/as-rgba %) [:r :g :b]) [c1 c2]) amp (apply mapv (fn [[_ v1] [_ v2]] (* 0.5 (- v1 v2))) colors) offset (mapv (fn [[_ v1] a] (- v1 a)) (first colors) amp)] [offset amp [-0.500 -0.500 -0.500] [0.000 0.000 0.000]]))) #+END_SRC *** Example usage #+BEGIN_SRC clojure ;; using a preset (def my-grad (->> :red-blue (grad/cosine-schemes) (grad/cosine-gradient 100))) ;; specifying cosine coefficients directly (def my-grad (grad/cosine-gradient 100 [0.5 0 0.5] [0.5 0 0.5] [0.5 0 0.5] [0 0 0.5])) #+END_SRC *** Gradient presets visualization This following snippet is *not* part of the library and only used to create the above preset visualizations. Requires http://thi.ng/geom to be added to your REPL/project in order to run... When tangling this file, the code below will be saved in the =/babel/dev/= subdir of this project... #+BEGIN_SRC clojure :tangle ../babel/dev/cosine-previews.clj :mkdirp yes :padline no (require '[thi.ng.color.core :as col]) (require '[thi.ng.color.gradients :as grad]) (require '[thi.ng.geom.viz.core :as viz]) (require '[thi.ng.geom.svg.core :as svg]) (require '[thi.ng.math.core :as m]) (defn channel-specs [colors] (map-indexed (fn [idx col] {:values (viz/uniform-domain-points [0 1] (map #(nth (deref %) idx) colors)) :attribs {:fill "none" :stroke col} :layout viz/svg-line-plot}) ["red" "green" "blue"])) (defn color-bars [x1 x2 y w h colors] (let [n (dec (count colors))] (for [i (m/norm-range n)] (svg/rect [(m/mix* x1 x2 i) y] w h {:fill (colors (int (* i n)))})))) (doseq [[id coeffs] grad/cosine-schemes] (let [colors (grad/cosine-gradient 100 coeffs)] (->> {:x-axis (viz/linear-axis {:domain [0 1] :range [50 580] :major 0.5 :minor 0.125 :pos 250}) :y-axis (viz/linear-axis {:domain [0 1] :range [250 20] :major 0.2 :minor 0.1 :pos 50 :label-dist 15 :label-style {:text-anchor "end"}}) :grid {:minor-x true :minor-y true} :data (channel-specs colors)} (viz/svg-plot2d-cartesian) (svg/svg {:width 600 :height 300} (color-bars 50 570 280 10 20 colors)) (svg/serialize) (spit (str (name id) ".svg"))))) #+END_SRC ** Complete namespace definition #+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/color/gradients.cljc :noweb yes :mkdirp yes :padline no (ns thi.ng.color.gradients #?(:cljs (:require-macros [thi.ng.math.macros :as mm])) (:require [thi.ng.math.core :as m :refer [PI TWO_PI]] [thi.ng.color.core :as col] #?(:clj [thi.ng.math.macros :as mm]))) <<generators>> #+END_SRC