Why doesn't this noise function handle negative arguments? - c++

I have code adapted from "Improved" Perlin noise
double improved_noise (double x, double y, double z)
{
// Calculate the "unit cube" that the point asked will be located in
// The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
// plus 1. Next we calculate the location (from 0.0 to 1.0) in that
// cube. We also fade the location to smooth the result.
int xi = (int)x & 255;
int yi = (int)y & 255;
int zi = (int)z & 255;
double xf = x - (int) x;
double yf = y - (int) y;
double zf = z - (int) z;
double u = fade (xf);
double v = fade (yf);
double w = fade (zf);
int aaa, aba, aab, abb, baa, bba, bab, bbb;
auto & p = permutation;
aaa = p[p[p[ xi ] + yi ] + zi ];
aba = p[p[p[ xi ] + inc(yi)] + zi ];
aab = p[p[p[ xi ] + yi ] + inc(zi)];
abb = p[p[p[ xi ] + inc(yi)] + inc(zi)];
baa = p[p[p[inc(xi)] + yi ] + zi ];
bba = p[p[p[inc(xi)] + inc(yi)] + zi ];
bab = p[p[p[inc(xi)] + yi ] + inc(zi)];
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
double x1, x2, y1, y2;
// The gradient function calculates the dot product between a
// pseudorandom gradient vector and the vector from the input
// coordinate to the 8 surrounding points in its unit cube.
// This is all then lerped together as a sort of weighted average
// based on the faded (u,v,w) values we made earlier.
x1 = lerp (
grad (aaa, xf , yf , zf),
grad (baa, xf-1, yf , zf),
u);
x2 = lerp (
grad (aba, xf , yf-1, zf),
grad (bba, xf-1, yf-1, zf),
u);
y1 = lerp (x1, x2, v);
x1 = lerp (
grad (aab, xf , yf , zf-1),
grad (bab, xf-1, yf , zf-1),
u);
x2 = lerp (
grad (abb, xf , yf-1, zf-1),
grad (bbb, xf-1, yf-1, zf-1),
u);
y2 = lerp (x1, x2, v);
return (lerp (y1, y2, w) + 1) / 2;
}
I wanted periodic noise in one direction so I wrapped that direction onto a circle in an extra dimension, calling it like this
improved_noise (sin(x*2*M_PI), cos(x*2*M_PI), y))
I got weird results (large and/or negative). Some experimentation revealed that this happens when the arguments to improved_noise are negative.
Why doesn't this function handle negative values nicely, and can it be easily adapted so the full number line is a valid argument?

improved_noise was not designed to handle negative inputs.
A comment in it says:
The left bound is ( |_x_|,|_y_|,|_z_| )…
The |…| notation suggests the absolute value is intended. However, the code computes:
int xi = (int)x & 255;
In common C implementations (where two’s complement is used), this effectively computes the residue of the integer portion of x modulo 256. For example, if x is −3.25, its integer portion is −3, and this will set xi to 253 (which is −3+256).
There are two things wrong with that. First, 253 is not the absolute value of −3, so this code does not match the comment. Second, it is taking the “right” boundary of the unit cube containing the point (the boundary with the greater value), whereas the comments, and the behavior for positive values, suggests the intent is to set xi, yi, and zi to the “left” boundary (the one with lesser value).
Going on from there, the code sets double xf = x - (int) x;. For non-negative values, this produces the fractional part of x. For example, if x were 3.25, xf would be .25. However, with negative values and the prior & 255 operation, this goes astray. For x = −3.25, it computes −3.25 − 253 = −256.25. But the code is likely intended merely to interpolate within a unit cube, for fractional portions from 0 to 1. Whatever function is used to perform the interpolation likely does not support −256.25.
Essentially, this code was never designed to support negative values, and fixing it requires redesigning it from first principles of how it is supposed to operate.
The original code you point to is better:
int X = (int)Math.floor(x) & 255
…
x -= Math.floor(x);
The former correctly uses floor to find the “left” boundary, regardless of whether x is negative or not. Then it applies & 255 to that. Assuming two’s complement, this will provide the correct coordinate in a periodic tiling. (Assuming two’s complement is not purely portable and should be documented or avoided.)
Then it correctly finds the fraction by subtracting the floor of x rather than subtracting the result of & 255. For example, for x = −3.25, this will produce integer coordinate −4 and fraction .75.
Modifying improved_noise to work similarly might help. You might try:
int xi = (int) floor(x) & 255;
int yi = (int) floor(y) & 255;
int zi = (int) floor(z) & 255;
double xf = x - floor(x);
double yf = y - floor(y);
double zf = z - floor(z);
You tagged this question with both C++ and C. In C++, it is preferable to use std::floor instead of floor, and there may be other issues with differences between C++ and C.

Related

What's causing these artefacts in my 2D Perlin noise?

I implemented the improved Perlin noise algorithm. The code as provided for 3D noise works correctly.
I adjusted the algorithm to make a 2D version in what seemed the obvious way. It almost works, but produces artefacts as the images below show.
Here is the correct 3D version:
unsigned inc (unsigned number)
{
return (number + 1) & 255;
}
double fade (double t)
{
// Fade function as defined by Ken Perlin.
// This eases coordinate values
// so that they will "ease" towards integral values.
// This ends up smoothing the final output.
// 6t^5 - 15t^4 + 10t^3
return t * t * t * (t * (t * 6 - 15) + 10);
}
double lerp (double a, double b, double x)
{
return a + x * (b - a);
}
double grad (unsigned hash, double x, double y, double z)
{
// Take the hashed value and take the first 4 bits of it
// (15 == 0b1111)
unsigned h = hash & 15;
// If the most significant bit (MSB) of the hash is 0
// then set u = x. Otherwise y.
double u = h < 8 /* 0b1000 */ ? x : y;
double v;
if (h < 4 /* 0b0100 */)
// If the first and second significant bits
// are 0, set v = y
v = y;
else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)
// If the first and second significant bits
// are 1, set v = x
v = x;
else
// If the first and second significant bits are not
// equal (0/1, 1/0) set v = z
v = z;
// Use the last 2 bits to decide if u and v are positive
// or negative. Then return their addition.
return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}
double
ImprovedNoise :: noise (double x, double y, double z)
{
// Calculate the "unit cube" that the point asked will be located in
// The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
// plus 1. Next we calculate the location (from 0.0 to 1.0) in that
// cube. We also fade the location to smooth the result.
int xi = (int)x & 255;
int yi = (int)y & 255;
int zi = (int)z & 255;
double xf = x - (int) x;
double yf = y - (int) y;
double zf = z - (int) z;
double u = fade (xf);
double v = fade (yf);
double w = fade (zf);
int aaa, aba, aab, abb, baa, bba, bab, bbb;
auto & p = permutation;
aaa = p[p[p[ xi ] + yi ] + zi ];
aba = p[p[p[ xi ] + inc(yi)] + zi ];
aab = p[p[p[ xi ] + yi ] + inc(zi)];
abb = p[p[p[ xi ] + inc(yi)] + inc(zi)];
baa = p[p[p[inc(xi)] + yi ] + zi ];
bba = p[p[p[inc(xi)] + inc(yi)] + zi ];
bab = p[p[p[inc(xi)] + yi ] + inc(zi)];
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
double x1, x2, y1, y2;
// The gradient function calculates the dot product between a
// pseudorandom gradient vector and the vector from the input
// coordinate to the 8 surrounding points in its unit cube.
// This is all then lerped together as a sort of weighted average
// based on the faded (u,v,w) values we made earlier.
x1 = lerp (
grad (aaa, xf , yf , zf),
grad (baa, xf-1, yf , zf),
u);
x2 = lerp (
grad (aba, xf , yf-1, zf),
grad (bba, xf-1, yf-1, zf),
u);
y1 = lerp (x1, x2, v);
x1 = lerp (
grad (aab, xf , yf , zf-1),
grad (bab, xf-1, yf , zf-1),
u);
x2 = lerp (
grad (abb, xf , yf-1, zf-1),
grad (bbb, xf-1, yf-1, zf-1),
u);
y2 = lerp (x1, x2, v);
auto result = (lerp (y1, y2, w) + 1) / 2;
assert (0 <= result);
assert (result <= 1);
assert (false == std :: isnan (result));
return result;
}
I generate a 2D image by fixing z=0. This a frequency of 10 so x,y are in [0..10]:
My 2D version:
double grad (unsigned hash, double x, double y)
{
double u = (hash & 1) ? x : y;
double v = (hash & 2) ? x : y;
return ((hash & 4) ? u : -u) + (hash & 8) ? v : -v;
}
double
ImprovedNoise :: noise (double x, double y)
{
int xi = (int)x & 255;
int yi = (int)y & 255;
double xf = x - (int) x;
double yf = y - (int) y;
double u = fade (xf);
double v = fade (yf);
int aaa, aba,baa, bba;
auto & p = permutation;
aaa = p[p[ xi ] + yi ];
aba = p[p[ xi ] + inc(yi)];
baa = p[p[inc(xi)] + yi ];
bba = p[p[inc(xi)] + inc(yi)];
double x1, x2;
// The gradient function calculates the dot product between a
// pseudorandom gradient vector and the vector from the input
// coordinate to the 8 surrounding points in its unit cube.
// This is all then lerped together as a sort of weighted average
// based on the faded (u,v,w) values we made earlier.
x1 = lerp (
grad (aaa, xf , yf),
grad (baa, xf-1, yf),
u);
x2 = lerp (
grad (aba, xf , yf-1),
grad (bba, xf-1, yf-1),
u);
double result = (lerp (x1, x2, v) + 1) / 2;
assert (0 <= result);
assert (result <= 1);
assert (false == std :: isnan (result));
return result;
}
Here is the image it generates.
It's generated using this method:
int size=400;
int freq=10;
create_widget (size, size, [&] (int x, int y)
{
return noise (x*freq / float (size), y*freq / float (size));
});
What's causing those horizontal and vertical lines? I thought it might be an integer boundary issue, but that would predict freq artefacts across the whole image, so I guess it's something else.
Can you see what the mistake is?
There's probably a mistake in grad (the precedence of + is higher than ?:), which causes abrupt change of the (anyway incorrect) result on specific xf/yf/hash values.
return ((hash & 4) ? u : -u) + (hash & 8) ? v : -v;
( )

Distance between 2 hexagons on hexagon grid

I have a hexagon grid:
with template type coordinates T. How I can calculate distance between two hexagons?
For example:
dist((3,3), (5,5)) = 3
dist((1,2), (1,4)) = 2
First apply the transform (y, x) |-> (u, v) = (x, y + floor(x / 2)).
Now the facial adjacency looks like
0 1 2 3
0*-*-*-*
|\|\|\|
1*-*-*-*
|\|\|\|
2*-*-*-*
Let the points be (u1, v1) and (u2, v2). Let du = u2 - u1 and dv = v2 - v1. The distance is
if du and dv have the same sign: max(|du|, |dv|), by using the diagonals
if du and dv have different signs: |du| + |dv|, because the diagonals are unproductive
In Python:
def dist(p1, p2):
y1, x1 = p1
y2, x2 = p2
du = x2 - x1
dv = (y2 + x2 // 2) - (y1 + x1 // 2)
return max(abs(du), abs(dv)) if ((du >= 0 and dv >= 0) or (du < 0 and dv < 0)) else abs(du) + abs(dv)
Posting here after I saw a blog post of mine had gotten referral traffic from another answer here. It got voted down, rightly so, because it was incorrect; but it was a mischaracterization of the solution put forth in my post.
Your 'squiggly' axis - in terms of your x coordinate being displaced every other row - is going to cause you all sorts of headaches with trying to determine distances or doing pathfinding later on, if this is for a game of some sort. Hexagon grids lend themselves to three axes naturally, and a 'squared off' grid of hexagons will optimally have some negative coordinates, which allows for simpler math around distances.
Here's a grid with (x,y) mapped out, with x increasing to the lower right, and y increasing upwards.
By straightening things out, the third axis becomes obvious.
The neat thing about this, is that the three coordinates become interlinked - the sum of all three coordinates will always be 0.
With such a consistent coordinate system, the atomic distance between any two hexes is the largest change between the three coordinates, or:
d = max( abs(x1 - x2), abs(y1 -y2), abs( (-x1 + -y1) - (-x2 + -y2) )
Pretty straightforward. But you must fix your grid first!
The correct explicit formula for the distance, with your coordinate system, is given by:
d((x1,y1),(x2,y2)) = max( abs(x1 - x2),
abs((y1 + floor(x1/2)) - (y2 + floor(x2/2)))
)
Here is what a did:
Taking one cell as center (it is easy to see if you choose 0,0), cells at distance dY form a big hexagon (with “radius” dY). One vertices of this hexagon is (dY2,dY). If dX<=dY2 the path is a zig-zag to the ram of the big hexagon with a distance dY. If not, then the path is the “diagonal” to the vertices, plus an vertical path from the vertices to the second cell, with add dX-dY2 cells.
Maybe better to understand: led:
dX = abs(x1 - x2);
dY = abs(y1 - y2);
dY2= floor((abs(y1 - y2) + (y1+1)%2 ) / 2);
Then:
d = d((x1,y1),(x2,y2))
= dX < dY2 ? dY : dY + dX-dY2 + y1%2 * dY%2
First, you need to transform your coordinates to a "mathematical" coordinate system. Every two columns you shift your coordinates by 1 unit in the y-direction. The "mathamatical" coordinates (s, t) can be calculated from your coordinates (u,v) as follows:
s = u + floor(v/2)
t = v
If you call one side of your hexagons a, the basis vectors of your coordinate system are (0, -sqrt(3)a) and (3a/2, sqrt(3)a/2). To find the minimum distance between your points, you need to calculate the manhattan distance in your coordinate system, which is given by |s1-s2|+|t1-t2| where s and t are the coordinates in your system. The manhattan distance only covers walking in the direction of your basis vectors so it only covers walking like that: |/ but not walking like that: |\. You need to transform your vectors into another coordinate system with basis vectors (0, -sqrt(3)a) and (3a/2, -sqrt(3)a/2). The coordinates in this system are given by s'=s-t and t'=t so the manhattan distance in this coordinate system is given by |s1'-s2'|+|t1'-t2'|. The distance you are looking for is the minimum of the two calculated manhattan distances. Your code would look like this:
struct point
{
int u;
int v;
}
int dist(point const & p, point const & q)
{
int const ps = p.u + (p.v / 2); // integer division!
int const pt = p.v;
int const qs = q.u + (q.v / 2);
int const qt = q.v;
int const dist1 = abs(ps - qs) + abs(pt - qt);
int const dist2 = abs((ps - pt) - (qs - qt)) + abs(pt - qt);
return std::min(dist1, dist2);
}
(odd-r)(without z, only x,y)
I saw some problems with realizations above. Sorry, I didn't check it all but. But maybe my solution will be helpful for someone and maybe it's a bad and not optimized solution.
The main idea to go by diagonal and then by horizontal. But for that we need to note:
1) For example, we have 0;3 (x1=0;y1=3) and to go to the y2=6 we can handle within 6 steps to each point (0-6;6)
so: 0-left_border , 6-right_border
2)Calculate some offsets
#include <iostream>
#include <cmath>
int main()
{
//while(true){
int x1,y1,x2,y2;
std::cin>>x1>>y1;
std::cin>>x2>>y2;
int diff_y=y2-y1; //only up-> bottom no need abs
int left_x,right_x;
int path;
if( y1>y2 ) { // if Down->Up then swap
int temp_y=y1;
y1=y2;
y2=temp_y;
//
int temp_x=x1;
x1=x2;
x2=temp_x;
} // so now we have Up->Down
// Note that it's odd-r horizontal layout
//OF - Offset Line (y%2==1)
//NOF -Not Offset Line (y%2==0)
if( y1%2==1 && y2%2==0 ){ //OF ->NOF
left_x = x1 - ( (y2 - y1 + 1)/2 -1 ); //UP->DOWN no need abs
right_x = x1 + (y2 - y1 + 1)/2; //UP->DOWN no need abs
}
else if( y1%2==0 && y2%2==1 ){ // OF->NOF
left_x = x1 - (y2 - y1 + 1)/2; //UP->DOWN no need abs
right_x = x1 + ( (y2 - y1 + 1)/2 -1 ); //UP->DOWN no need abs
}
else{
left_x = x1 - (y2 - y1 + 1)/2; //UP->DOWN no need abs
right_x = x1 + (y2 - y1 + 1)/2; //UP->DOWN no need abs
}
/////////////////////////////////////////////////////////////
if( x2>=left_x && x2<=right_x ){
path = y2 - y1;
}
else {
int min_1 = std::abs( left_x - x2 );
int min_2 = std::abs( right_x - x2 );
path = y2 - y1 + std::min(min_1, min_2);
}
std::cout<<"Path: "<<path<<"\n\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n";
//}
return 0;
}
I believe the answer you seek is:
d((x1,y1),(x2,y2))=max(abs(x1-x2),abs(y1-y2));
You can find a good explanation on hexagonal grid coordinate-system/distances here:
http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/

Efficient way to get the angle between two vectors in a single plane?

If I know for a fact that the x and z values of the vectors will be identical,
therefore im only concerned in measuring the 'vertical' angle of from the differences in the y plane, is there a more efficient method to do this compared to computing the dot product?
My current code using the dot product method is as follows:
float a_mag = a.magnitude();
float b_mag = b.magnitude();
float ab_dot = a.dot(b);
float c = ab_dot / (a_mag * b_mag);
// clamp d to from going beyond +/- 1 as acos(+1/-1) results in infinity
if (c > 1.0f) {
c = 1.0;
} else if (c < -1.0) {
c = -1.0;
}
return acos(c);
I would love to be able to get rid of these square roots
Suppose that your two vectors live at u = (x, y1, z) and v = (x, y2, z), and you're interested in the planar angle between the two along the plane spanned by the two vectors. You'd have to compute the dot product and the magnitude, but you can save a few operations:
u.v = x.x + y1.y2 + z.z
u^2 = x.x + y1.y1 + z.z
v^2 = x.x + y2.y2 + z.z
So we should precompute:
float xz = x*x + z*z, y11 = y1*y1, y12 = y1*y2, y22 = y2*y2;
float cosangle = (xz + y12) / sqrt((xz + y11) * (xz + y22));
float angle = acos(cosangle);
If the values of x and z are unchanged, then the calculation is very easy: just use basic trigonometry.
Let the points be (x, y1, z) and (x, y2, z). You can find out the angle a vector makes with the ZX-plane. Let the angles be t1 and t2 respectively. Then:
w = sqrt(x^2 + z^2)
tan(t1) = y1 / w
So t1 = atan(y1 / w)
Similarly t2 = atan(y2 / w)
The angle is (t2 - t1)
There's one pitfall: When both x and z are zero, the tans are undefined... but such a trivial case can easily be handled separately.
Unfortunately, there seems to be no way to avoid the square root.

anti-aliased pixel rendering

I'm trying to implement anti-aliased pixel rendering. My basic idea is to render 4 pixels instead of 1, and give each "real" pixel a weight based on its distance to the "virtual" pixel:
void put_aa_pixel(double x, double y)
{
int x0 = int(x);
int x1 = x0 + 1;
int y0 = int(y);
int y1 = y0 + 1;
double weight_x1 = x - x0;
double weight_x0 = 1 - weight_x1;
double weight_y1 = y - y0;
double weight_y0 = 1 - weight_x1;
put_pixel(x0, y0, int((weight_x0 * weight_y0) * 255));
put_pixel(x1, y0, int((weight_x1 * weight_y0) * 255));
put_pixel(x0, y1, int((weight_x0 * weight_y1) * 255));
put_pixel(x1, y1, int((weight_x1 * weight_y1) * 255));
}
Multiplying the x and y weights gives me the overlapping area of the virtual pixel inside each real pixel. I naively assumed this would give me a perfect anti-aliasing effect, but the moving pixels inside my test program just display an aweful flicker. It looks much worse then simple pixels without any anti-aliasing.
However, when I switch from multiplication to addition, it looks much better:
put_pixel(x0, y0, int((weight_x0 + weight_y0) * 127.5));
put_pixel(x1, y0, int((weight_x1 + weight_y0) * 127.5));
put_pixel(x0, y1, int((weight_x0 + weight_y1) * 127.5));
put_pixel(x1, y1, int((weight_x1 + weight_y1) * 127.5));
Adding the weights doesn't seem to have any geometric significance. So why does this work better? What's wrong with the first version? And is there an even better approach?
As requested :)
Intuitively: your x and y weights express distance along axis from virtual to real pixel. So, the actual distance is sqrt(w_x^2 + w_y^2). Explains why sum works better - it's way closer to this form than multiplication.
There was a bug lurking in my code for half a year:
double weight_x1 = x - x0;
double weight_x0 = 1 - weight_x1;
double weight_y1 = y - y0;
double weight_y0 = 1 - weight_x1; // BUG
Can you see the bug? Yes, it's a classic copy and paste error:
double weight_y0 = 1 - weight_y1; // FIXED
After fixing the bug, the original multiplication approach looks very nice.

Creating a linear gradient in 2D array

I have a 2D bitmap-like array of let's say 500*500 values. I'm trying to create a linear gradient on the array, so the resulting bitmap would look something like this (in grayscale):
(source: showandtell-graphics.com)
The input would be the array to fill, two points (like the starting and ending point for the Gradient tool in Photoshop/GIMP) and the range of values which would be used.
My current best result is this:
alt text http://img222.imageshack.us/img222/1733/gradientfe3.png
...which is nowhere near what I would like to achieve. It looks more like a radial gradient.
What is the simplest way to create such a gradient? I'm going to implement it in C++, but I would like some general algorithm.
This is really a math question, so it might be debatable whether it really "belongs" on Stack Overflow, but anyway: you need to project the coordinates of each point in the image onto the axis of your gradient and use that coordinate to determine the color.
Mathematically, what I mean is:
Say your starting point is (x1, y1) and your ending point is (x2, y2)
Compute A = (x2 - x1) and B = (y2 - y1)
Calculate C1 = A * x1 + B * y1 for the starting point and C2 = A * x2 + B * y2 for the ending point (C2 should be larger than C1)
For each point in the image, calculate C = A * x + B * y
If C <= C1, use the starting color; if C >= C2, use the ending color; otherwise, use a weighted average:
(start_color * (C2 - C) + end_color * (C - C1))/(C2 - C1)
I did some quick tests to check that this basically worked.
In your example image, it looks like you have a radial gradient. Here's my impromtu math explanation for the steps you'll need. Sorry for the math, the other answers are better in terms of implementation.
Define a linear function (like y = x + 1) with the domain (i.e. x) being from the colour you want to start with to the colour your want to end with. You can think of this in terms of a range the within Ox0 to OxFFFFFF (for 24 bit colour). If you want to handle things like brightness, you'll have to do some tricks with the range (i.e. the y value).
Next you need to map a vector across the matrix you have, as this defines the direction that the colours will change in. Also, the colour values defined by your linear function will be assigned at each point along the vector. The start and end point of the vector also define the min and max of the domain in 1. You can think of the vector as one line of your gradient.
For each cell in the matrix, colours can be assigned a value from the vector where a perpendicular line from the cell intersects the vector. See the diagram below where c is the position of the cell and . is the the point of intersection. If you pretend that the colour at . is Red, then that's what you'll assign to the cell.
|
c
|
|
Vect:____.______________
|
|
I'll just post my solution.
int ColourAt( int x, int y )
{
float imageX = (float)x / (float)BUFFER_WIDTH;
float imageY = (float)y / (float)BUFFER_WIDTH;
float xS = xStart / (float)BUFFER_WIDTH;
float yS = yStart / (float)BUFFER_WIDTH;
float xE = xEnd / (float)BUFFER_WIDTH;
float yE = yEnd / (float)BUFFER_WIDTH;
float xD = xE - xS;
float yD = yE - yS;
float mod = 1.0f / ( xD * xD + yD * yD );
float gradPos = ( ( imageX - xS ) * xD + ( imageY - yS ) * yD ) * mod;
float mag = gradPos > 0 ? gradPos < 1.0f ? gradPos : 1.0f : 0.0f;
int colour = (int)( 255 * mag );
colour |= ( colour << 16 ) + ( colour << 8 );
return colour;
}
For speed ups, cache the derived "direction" values (hint: premultiply by the mag).
There are two parts to this problem.
Given two colors A and B and some percentage p, determine what color lies p 'percent of the way' from A to B.
Given a point on a plane, find the orthogonal projection of that point onto a given line.
The given line in part 2 is your gradient line. Given any point P, project it onto the gradient line. Let's say its projection is R. Then figure out how far R is from the starting point of your gradient segment, as a percentage of the length of the gradient segment. Use this percentage in your function from part 1 above. That's the color P should be.
Note that, contrary to what other people have said, you can't just view your colors as regular numbers in your function from part 1. That will almost certainly not do what you want. What you do depends on the color space you are using. If you want an RGB gradient, then you have to look at the red, green, and blue color components separately.
For example, if you want a color "halfway between" pure red and blue, then in hex notation you are dealing with
ff 00 00
and
00 00 ff
Probably the color you want is something like
80 00 80
which is a nice purple color. You have to average out each color component separately. If you try to just average the hex numbers 0xff0000 and 0x0000ff directly, you get 0x7F807F, which is a medium gray. I'm guessing this explains at least part of the problem with your picture above.
Alternatively if you are in the HSV color space, you may want to adjust the hue component only, and leave the others as they are.
void Image::fillGradient(const SColor& colorA, const SColor& colorB,
const Point2i& from, const Point2i& to)
{
Point2f dir = to - from;
if(to == from)
dir.x = width - 1; // horizontal gradient
dir *= 1.0f / dir.lengthQ2(); // 1.0 / (dir.x * dir.x + dir.y * dir.y)
float default_kx = float(-from.x) * dir.x;
float kx = default_kx;
float ky = float(-from.y) * dir.y;
uint8_t* cur_pixel = base; // array of rgba pixels
for(int32_t h = 0; h < height; h++)
{
for(int32_t w = 0; w < width; w++)
{
float k = std::clamp(kx + ky, 0.0f, 1.0f);
*(cur_pixel++) = colorA.r * (1.0 - k) + colorB.r * k;
*(cur_pixel++) = colorA.g * (1.0 - k) + colorB.g * k;
*(cur_pixel++) = colorA.b * (1.0 - k) + colorB.b * k;
*(cur_pixel++) = colorA.a * (1.0 - k) + colorB.a * k;
kx += dir.x;
}
kx = default_kx;
ky += dir.y;
}
}