Solving this problem requires the knowledge of three simple tricks:
1. Interpolation:
The process of gradually changing from one value to another is called interpolation. There are multiple ways of interpolating color values: the simplest one is to interpolate each component linearly, i.e. in the form of:
interpolated = start * (1-t) + dest * t
.
Where
start
is the value you are interpolating from towards the value dest
.
t
denotes how close the interpolated value should be to the destination value dest
on a scale of 0
to 1
with 0
being the pure start
color and 1
being the pure dest
color.
You will find that linear interpolation in the RGB color space doesn't produce natural color paths. As an advanced step, you could utilise the HSV color space instead. See this question for further information about color interpolation.
2. Discretisation:
Unfortunately, interpolation produces real numbers. Thus, we have to discretise them to be able to use them as integer color values. The best way to do this is to round to the nearest integer by using e.g. round()
in C++.
3. Finding the interpolation point:
Now, we just need a real-valued interpolation point t
at each row of our image. We can deduce a formula for this by analysing what output we want to see:
- For the bottommost row (row 1) we want to have
t == 0
since that is where we want our pure start color to appear.
- For the topmost row (row m) we want to have
t == 1
since that is where we want the pure destination color to appear.
- For every other row we want
t
to scale linearly with the distance to the bottommost row.
A formula to achieve this result is:
t = rowIndex / m
The approach can readily be adapted to other gradient directions by changing this formula appropriately.
Sample code (using linear interpolation, C++):
#include <algorithm>
#include <cmath>
Color interpolateRGB(Color from, Color to, float t)
{
// Clamp __t__ to range [0,1]
t = std::max(std::min(0.f, t), 1.f);
// Interpolate each RGB component
uint8_t r = std::roundf(from.r * (1-t) + to.r * t);
uint8_t g = std::roundf(from.g * (1-t) + to.g * t);
uint8_t b = std::roundf(from.b * (1-t) + to.b * t);
return Color(r, g, b);
}
void fillWithGradient(Image& img, Color from, Color to)
{
for(size_t row = 0; row < img.numRows(); ++row)
{
Color value = interpolateRGB(from, to, row / (img.numRows()-1));
// Set all pixels of this row to __value__
for(size_t col = 0; col < img.numCols(); ++col)
{
img.setPixel(row, col, value);
}
}
}