Click here to Skip to main content
15,868,153 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
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:

C++
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.
Posted
Updated 19-Apr-22 15:05pm
v4
Comments
0x01AA 19-Apr-22 14:48pm    
Not that I understand the problem, but for me double ld = abs(ld_start-ld_end+1);looks suspecious. This because it looks for me to a mixup of vector distances vs. absolute values differences.
honey the codewitch 19-Apr-22 18:05pm    
I wouldn't be surprised if it's off. I was actually trying a number of things. CPallini caught a show stopper for me that explains why i got nothing.
CPallini 19-Apr-22 14:50pm    
I don't understand how do you want the transition to happen. I understood the starting point position and color (that's easy).
All the rest is a bit confused. For instance you said that top-left of the rectangle is expected to be the full 'end color', but, in this assumption "gradually transitioning the end color as they move away from that line at the given angle (moving perpendicularly away)" does NOT fit well, in my opinion.
honey the codewitch 19-Apr-22 18:04pm    
I shouldn't said the thing about the top left. I want the colors to start at the start color and radiate away from the center point where the start color is, toward the extents of the rectangle, where at least somewhere, end_color should be represented. This is actually easier to do spherically, but I want to do it so it is along an imaginary line instead of radiating away from a point, if that makes sense.
CPallini 20-Apr-22 1:48am    
Yes, that makes sense.

Quote:
int yd = rect.y1<=rect.y2?1:-1;
for(int y = rect.y1;y<=rect.y2;y+=yd) {
Does it make sense?
I mean, if (rect.y1>rect.y2) then the loop is never executed.
 
Share this answer
 
Comments
honey the codewitch 19-Apr-22 18:01pm    
If rect.y1>rect.y2 xd will be -1, oooooh thank you. I just saw it.
CPallini 20-Apr-22 1:47am    
You are welcome.
Shao Voon Wong 19-Apr-22 21:03pm    
Brilliant! Good work at spotting the bug! Got my 5!
CPallini 20-Apr-22 1:47am    
Thank you very much.
I wrote this article twenty years ago : Windows Fortune Application[^], and it uses a gradient to fill the window background. It computes the gradient by doing a linear interpolation from start to end color for each shade independently, meaning the red values, green values, and blue values are handled separately. You can see what the results look like.

Regarding your code, calling the pow function to square a value is about the least efficient way you could possibly do it. Internally, it takes a logarithm and an exponential to do its computation. I think it is much, much better to write yourself a template function to do squares and cubes but that's just me.
 
Share this answer
 
Comments
honey the codewitch 19-Apr-22 15:49pm    
I learned a long time ago to first make it work, then make it work fast. It serves me well, except if there are design issues tied up with performance. The blast radius of any design changes are limited pretty much to this routine, so if i have to rewrite it for performance I will. I wrote this to try things out, and I didn't want it too laden with premature optimizations.

Edit: Looking at your solution, it's too simple for my application, as it only does a gradient in one direction, not at arbitrary angles with arbitrary starting points.

Pixel blending by channel is something my library already does when you call blend.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900