This is more or less a follow up question to Two dimensional color ramp (256x256 matrix) interpolated from 4 corner colors that was profoundly answered by jadsq today.
For linear gradients the previous answer works very well. However, if one wants to have better control of the stop colors of the gradient, this method seems not to be very practical. What might help in this situation is to have some reference color points in a matrix (lookup table) which are used to interpolate color values for the empty position in the look-up table. What I mean might be easier read out of the below image.
The whole idea is taken from http://cartography.oregonstate.edu/pdf/2006_JennyHurni_SwissStyleShading.pdf page 4 to 6. I've read through the paper, I understand theoretically what is going on but failing miserably because of my low experience with interpolation methods and to be honest, general math skills. What might also be of interest is, that they use a sigmoid Gaussian bell as interpolation method (page 6). They argue that Gaussian weighting yielded the visually best results and was simple to compute (equation 1, with k=0.0002 for a table of 256 per 256 cells).
import numpy as np
import matplotlib.pyplot as plt
# the matrix with the reference color elements
ref=np.full([7, 7, 3], [255,255,255], dtype=np.uint8)
ref = (239,238,185)
ref = (120,131,125)
ref = (184,191,171)
ref = (150,168,158)
ref = (166,180,166)
# s = ref.shape
# from scipy.ndimage.interpolation import zoom
# zooming as in http://stackoverflow.com/a/39485650/1230358 doesn't seem to work here anymore, because we have no corner point as reference but randomly distributed points within the matrix. As far as I know ...
First some questions to better clarify your problem:
For the simple linear interpolation and arbitrary (but at least 3 points not on a single line) I would try this:
Triangulate control points area
To non overlapping triangles covering whole defined area.
So just rasterize see Algorithm to fill triangle and all the sublinks. You should interpolate also the
R,G,B along with the coordinates.
Create a 2 copies of gradient and extrapolate one with H and second with V lines
So scan all the H-horizontal lines of the gradient and if found 2 known pixels far enough from each other (for example quarter or half of gradient size) then extrapolate the whole line unknown colors. So if found known endpoints (Red) are
(x0,y,r0,g0,b0),(x1,y,r1,g1,b1) then set all unknown colors in the same line as:
r = r0+(r1-r0)*(x-x0)/(x1-x0) g = g0+(g1-g0)*(x-x0)/(x1-x0) b = b0+(b1-b0)*(x-x0)/(x1-x0)
Similarly do the same in the copy of gradient for V-vertical lines now. So the points are now (x,y0,r0,g0,b0),(x,y1,r1,g1,b1)` and extrapolation:
r = r0+(r1-r0)*(y-y0)/(y1-y0) g = g0+(g1-g0)*(y-y0)/(y1-y0) b = b0+(b1-b0)*(y-y0)/(y1-y0)
After this compare both copies and if unknown point is computed in both set it as average of both colors in the target gradient image. Loop this whole process (#3) until no new gradient pixel is added.
use single extrapolated color for the rest
depending on how you define the control points some areas will have only 1 extrapolated color (either from H or V lines but not both) so use only the single computed color for those (after #3 is done).
Here an example of what I mean by all this:
If you want something simple instead (but not exact) then you can bleed the known control points colors (with smooth filters) to neighboring pixels until the whole gradient is filled and saturated.
set each pixel to average of its computed neighbors
you may do this in separate image to avoid shifting.
set control points back to original color
loop #2 until area filled/saturated/or predefined number of iterations