4

this is related to this question but perhaps a simpler example. i'm curious if there's a reasonable way to calculate a multi-dimensional color gradient given three or four arbitrary colors the way that the r rgb() function does with red, green, blue? the one-dimensional gradient is easy (fig 1) but then it's unclear to me how to compute the two-dimensional gradient (fig 2) inside the triangle. edges are easy. it's what inside that counts

# one dimensional color gradient
one.dimensions <- colorRampPalette( c( "orange" , "blue" ) )( 100 )

plot( 1:100 , rep( 1 , 100 ) , col = one.dimensions , cex = 3 , pch = 16 , main  = 'one dimensional gradient' )

enter image description here

# here are the edges of a three-colored triangle
dimensions13 <- colorRampPalette( c( "orange" , "blue" ) )( 100 )
dimensions12 <- colorRampPalette( c( "orange" , "red" ) )( 100 )
dimensions23 <- colorRampPalette( c( "blue" , "red" ) )( 100 )

plot( 1:100 , c( 1:50 , 50:1 ) , type = 'n' , main = 'two dimensional gradient' )
points( 1:100 , rep( 1 , 100 ) , col = dimensions12 , cex = 3 , pch = 16 )
points( seq( 1 , 50 , length = 100 ) , seq( 1 , 50 , length = 100 ) , col = dimensions13 , cex = 3 , pch = 16 )
points( seq( 50 , 100 , length = 100 ) , seq( 50 , 1 , length = 100 ) , col = dimensions23 , cex = 3 , pch = 16 )

enter image description here

Community
  • 1
  • 1
Anthony Damico
  • 5,779
  • 7
  • 46
  • 77
  • Perhaps [**this Q&A**](http://stackoverflow.com/questions/11070101/2d-color-gradient-plot-in-r/11103414#11103414) is helpful? – Henrik Oct 26 '14 at 10:39
  • 1
    A tiny side-note: when you write two- and three-dimensional, did you mean one- and two-dimensional (e.g. x- vs. x- and y-dimension)? – Henrik Oct 26 '14 at 10:48
  • @Henrik duh good point :) – Anthony Damico Oct 26 '14 at 10:52
  • No problem! Perhaps also change the plot titles ;) – Henrik Oct 26 '14 at 11:03
  • @Henrik that q&a is very relevant, but i do want those white Xs in the center of the gradient as well and it looks like neither of the answerers provided how to do that? :/ – Anthony Damico Oct 26 '14 at 11:49
  • Shot in the dark but maybe this is of help (discuss ggplot2 though) http://stackoverflow.com/questions/21490210/how-to-plot-a-colour-wheel-by-using-ggplot or http://research.stowers-institute.org/efg/Report/UsingColorInR.pdf – Tyler Rinker Oct 26 '14 at 13:07
  • @TylerRinker thanks tyler, love the geocities feel of that pdf ;) i think i want something [close to this answer](http://stackoverflow.com/questions/11070101/2d-color-gradient-plot-in-r/26573256#26573256) but there has gotta be a smarter way to produce it than the brute force method i've shown :/ – Anthony Damico Oct 26 '14 at 13:37
  • this question might be of interest: [link](http://stackoverflow.com/questions/11773295/how-can-one-mix-2-or-more-color-palettes-to-show-a-combined-color-value) – Marc in the box Oct 26 '14 at 13:53
  • @Marcinthebox thanks! but the answer uses `rgb` which is cheating ;) i need something that takes an arbitrary set of colors (or did i misunderstand something?) – Anthony Damico Oct 26 '14 at 14:00
  • @AnthonyDamico - no, your right. Sorry, I missed that... in the linked question. – Marc in the box Oct 26 '14 at 14:40

2 Answers2

3

you could consider three basic colour mixing strategies:

1- subtractive, using the alpha transparency blending of R graphics. Basically, superimpose multiple layers with their own gradient.

library(grid)

grid.newpage()
grid.raster(scales::alpha(colorRampPalette(c("white","blue"))(10), 0.3),
            width=1,height=1)
grid.raster(t(scales::alpha(colorRampPalette(c("white","red"))(10), 0.3)),
            width=1,height=1)

One drawback is that the final colour depends on the order of the layers.

enter image description here

The CMYK colour model could be another source of inspiration.

2- additive. I came up with a naive implementation as follows. Consider your N basic colours (say yellow, green, orange). Assign them a wavelength of the visible spectrum (570nm, 520nm, 600nm). Each colour is given a weight according to the position in the triangle (think of N lasers with tunable intensity). Now to get the colour associated with this mixture of N laser sources, you need to convolve with CIE colour matching functions. It's a physically sound mixing, mapping numbers to a visual perception. However, there's clearly an issue of uniqueness: several combinations will likely produce the same colour. The eye has only three different types of receptors after all, so N>3 is never going to result in a bijection.

3- pixelated (halftoning). Divide the image into small adjacent regions, like LCD screens, and every pixel is divided into N subpixels, each with its own colour. From far away and/or sufficient screen/print resolution, the eye won't see the details and will blur the adjacent colours for you.

baptiste
  • 75,767
  • 19
  • 198
  • 294
2

Triangle

There is a concept of barycentric coordinates in triangle. Given that A, B and C are points spanning the triangle, every point inside the triangle can be expressed by an equation:

t * A + s * B + p * C

Where 0 <= t, s, p <= 1 and t + s + p = 1. You can evaluate t, s and p and treat them as weights for colors defined for respective triangle vertices, easily evaluating target color for every rendered point.

Note however, that effects of this algorithm looks nice only for triangles. If you think about stiching triangles together and using this method to evaluate colors, you'll end up with triangular gradient pattern, which you probably want to avoid.

Random set of points

For more random set of points, I'm using the following algorithm:

Given point (x, y), for which I want to calculate the color, and set of points (x1, y1, color1), (x2, y2, color2), ..., (xN, yN, colorN):

  1. Calculate distances to all points (x1, y1), ... (xN, yN)
  2. Calculate value of Gaussian function for every point:
  3. Calculate weighted average of color, based on those weights.

Implementation looks more less like this:

private float Gauss(float x, float a, float b, float c)
{
    var v1 = (x - b) / (2d * c * c);
    var v2 = -v1 * v1 / 2d;
    var v3 = (float)(a * Math.Exp(v2));
            
    return v3;
}

private float GetWeight(float distance) => Gauss(distance, Peak, 0, Falloff);

public override Color GetColor(PointF p)
{
    float[] distances = Points
        .Select(pt => (pt.AsVector2 - p.ToVector2()).Length())
        .ToArray();

    float r = 0, g = 0, b = 0, sum = 0;

    for (int i = 0; i < Points.Length; i++)
    {
        var weight = GetWeight(distances[i]);

        r += Points[i].Color.R * weight;
        g += Points[i].Color.G * weight;
        b += Points[i].Color.B * weight;
        sum += weight;
    }

    int targetR = Math.Max(0, Math.Min(255, (int)(r / sum)));
    int targetG = Math.Max(0, Math.Min(255, (int)(g / sum)));
    int targetB = Math.Max(0, Math.Min(255, (int)(b / sum)));

    return Color.FromArgb(targetR, targetG, targetB);
}

The problem is that you need to pick Peak and Falloff values, so that they will satisfy your needs. Peak defines value of weight at distance 0 (so where you are exactly over one of defined points) and Falloff defines, how quickly the weight dissipates towards 0 (it never reaches it though).

For instance, Peak of 100, Falloff of 5 for image 256x256:

Peak of 100, Fallof of 5

Peak of 100, Falloff of 7:

Peak of 100, Falloff of 7:

Peak of 100, Falloff of 9:

Peak of 100, Falloff of 9

Notes

My proposed solution is unfortunately more of a workaround than a real solution, because it relies heavily on picking proper Peak and Falloff values. Generally you are seeking for a 2D function, which behaves in the following way:

  • At defined point X it has (exact) value of X's color
  • The farther you go from point X, the less it influences the final color and the more other points do.

My solution fulfills the second requirement, but does not fulfill the first one (color is near X's color, but not neccesarily equal).

Spook
  • 25,318
  • 18
  • 90
  • 167