#+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