Edit: I no longer need this, as I found an entirely different way to skin the particular cat I was skinning. Still, I'm leaving it up as an exercise. I'd be interested in knowing a good algorithm for this. Mine has a number of problems, and it just doesn't work.
Firstly, I don't need to know how to blend between colors. My library can already give you any point in a range between two colors in many different color models, so that's not an issue.
auto color3 = color1.blend(color2,floatRatioBetweenZeroAndOne)
. That's not an issue.
What I'm having trouble with is the actual drawing.
I take a bounding rectangle, a center/starting point, and an angle. I'd like the gradient to be either along or perpendicular to (whichever makes most sense) an imaginary line starting at the center and at the angle specified, and clipped to the rect, so like
________
\ |
|\ |
| \ |
| \ |
| \ |
| \ |
| \ |
| * |
| \|
|________\
and furthermore, scaled to that rect to ensure that both the start color and end color are represented somewhere on that plane.
* = the starting point - at this point the color will be fully the start color, the rotation angle is the angle of that line, gradually transitioning the end color as they move away from that line at the given angle (moving perpendicularly away)
That means in some case, there will be "dead space" of all one color at certain angles in certain extents of the rectangle, and that is okay.
But I'm just having trouble computing it.
What I have tried:
double coldiff = start_color.difference(end_color);
if(coldiff==0) {
return filled_rectangle_impl(destination,rect,start_color,clip,async);
}
double rads = rotation * (3.1415926536 / 180.0);
const double ctheta = cos(rads);
const double stheta = sin(rads);
const double ld_start = sqrt(pow(start_location.x-rect.x1,2)+pow((double)start_location.y-rect.y1,2.0));
double ld_end = sqrt(pow((double)start_location.x-rect.x2,2)+pow((double)start_location.y-rect.y2,2.0));
double ld = abs(ld_start-ld_end+1);
double ldr = coldiff/ld;
Serial.printf("coldiff = %f\n",coldiff);
Serial.printf("ldr = %f\n",ldr);
srect16 cl;
if(clip==nullptr) {
cl = rect;
} else {
cl = clip->crop(rect);
}
int xd = rect.x1<=rect.x2?1:-1;
int yd = rect.y1<=rect.y2?1:-1;
for(int y = rect.y1;y<=rect.y2;y+=yd) {
for(int x = rect.x1;x<=rect.x2;x+=xd) {
double rx = (ctheta * (x - (double)start_location.x) - stheta * (y - (double)start_location.y) + (double)start_location.x);
double ry = (stheta * (x - (double)start_location.x) + ctheta * (y - (double)start_location.y) + (double)start_location.y);
ld = sqrt(pow((double)start_location.x-rx,2.0)+pow((double)start_location.y-ry,2.0));
float r = ld*coldiff;
r=helpers::clamp(r,(float)0.0,(float)1.0);
auto px = start_color.blend(end_color,1.0-r);
spoint16 pt(x,y);
draw::point(destination,pt,px,cl);
}
}
return gfx_result::success;
I'm getting all start color except for the very top left point which is white (end color i think - it's in the hsv model).
Here's the basic idea of what I'm doing.
I'm getting a cartesian distance between two colors. This works, because I use it all over the place in my code for the past year.
Then I take that difference and attempt to create a scaling factor based basically on the maximum distance a color can be from the start within the bounding rectangle. I am pretty sure I'm doing this wrong.
And then basically what I'm doing is going through the rect (x1,y1), to (x2,y2). For each point, I'm applying my rotation coefficients, and then computing the distance from that to my center point, which I'm also doing wrong, because it needs to be along the line. I'm overthinking this part.
And then finally I'm scaling and clamping the value to between 1.0 and 0.0 and inverting it to properly give a ratio to blend. My ratio isn't working. My scaling seems to off by orders of magnitude but I'm having trouble working out where it is going wrong.