Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Gradient circles

Credits go to Edgar Reynaldo and SiegeLord for helping out!
This thread is locked; no one can reply to it. rss feed Print
Gradient circles
Kikaru
Member #7,616
August 2006
avatar

I am working on a project that would be greatly enhanced by a gradient circle effect. Something like:

void grad_circle(BITMAP *bmp, int x, int y, int rad, int col1, int col2)

Gradation would go from center to edges, a filled circle.

Can anyone recommend either a pre-made function or a way to write a very fast, effective one? I am using plain Allegro, and it's kinda late to change libraries.

It needs to be very fast, ideally.

Thanks in advance.

SiegeLord
Member #7,827
October 2006
avatar

Here's a little thing I hacked from allegro's do_circle routine... It is far from perfect (it has a lot of overdraw - about 28%) but it manages not to use any square roots (since in effect we are just calling allegro's do_circle routine a bunch of times). (note that it is incomplete, I did not put the actual color blending function since you did not specify whether you wanted this to be palette based or not).

void grad_circle(BITMAP *bmp, int x, int y, int rad, int col1, int col2)
{
    for(int ii = 0; ii < rad; ii++)
    {
        int color = color_blend(ii, rad, col1, col2);//provide your own blending function here
        gapless_do_circle(screen, 200, 200, ii, color, putpixel);
    }
}

And here is the actual worker function. I do not understand yet how allegro's do_cirle works, so this hack is sort of primitive. As such it is both ugly and it has a lot of overdraw (as I said above).

1void gapless_do_circle(BITMAP *bmp, int x, int y, int radius, int d, void (*proc)(BITMAP *, int, int, int))
2{
3 int cx = 0;
4 int cy = radius;
5 int df = 1 - radius;
6 int d_e = 3;
7 int d_se = -2 * radius + 5;
8 
9 bool needed = false;
10
11 bool fix_diag = false;
12 
13 do
14 {
15 if(!fix_diag)
16 {
17 proc(bmp, x + cx, y + cy, d);
18 
19 if (cx)
20 proc(bmp, x - cx, y + cy, d);
21 
22 if (cy)
23 proc(bmp, x + cx, y - cy, d);
24 
25 if ((cx) && (cy))
26 proc(bmp, x - cx, y - cy, d);
27 
28 if (cx != cy)
29 {
30 proc(bmp, x + cy, y + cx, d);
31 
32 if (cx)
33 proc(bmp, x + cy, y - cx, d);
34 
35 if (cy)
36 proc(bmp, x - cy, y + cx, d);
37 
38 if (cx && cy)
39 proc(bmp, x - cy, y - cx, d);
40 }
41 }
42 
43 if(needed)
44 {
45 proc(bmp, x + cx - 1, y + cy, d);
46
47 if (cx)
48 proc(bmp, x - cx + 1, y + cy, d);
49 
50 if (cy)
51 proc(bmp, x + cx - 1, y - cy, d);
52 
53 if ((cx) && (cy))
54 proc(bmp, x - cx + 1, y - cy, d);
55 
56 if (cx != cy)
57 {
58 proc(bmp, x + cy, y + cx - 1, d);
59 
60 if (cx)
61 proc(bmp, x + cy, y - cx + 1, d);
62 
63 if (cy)
64 proc(bmp, x - cy, y + cx - 1, d);
65 
66 if (cx && cy)
67 proc(bmp, x - cy, y - cx + 1, d);
68 }
69 }
70 
71 if (df < 0)
72 {
73 df += d_e;
74 d_e += 2;
75 d_se += 2;
76 needed = false;
77 }
78 else
79 {
80 df += d_se;
81 d_e += 2;
82 d_se += 4;
83 cy--;
84 needed = true;
85 }
86 cx++;
87 if(!(cx <= cy))
88 fix_diag = true;
89 }
90 while (cx <= cy + 2);
91}

Don't ask me how it works. :-[

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I came up with another way. It doesn't draw quite as far out as allegro's circle function , but it's always within one pixel of it. Not sure where the difference is.

Basically , the function traverses the y-values of the circle and the x-values of the horizontal chord of the circle at that y-value and draws a pixel with the color scaled from the inner color to the outer color according to the distance/radius factor.

Here's the function code and a short example that draws a gradient circle using purple and yellow.

1 
2#include <allegro.h>
3 
4#include <cmath>
5 
6void gradient_circle(BITMAP *bmp, int cx, int cy, int rad, int outer_color, int inner_color) {
7 int rel_xpos = 0;
8 int rel_ypos = 0;
9 int hxcl = 0;//half of horizontal chord length;
10 
11 int abs_ypos_top = 0;
12 int abs_ypos_bottom = 0;
13 int abs_xpos_left = 0;
14 int abs_xpos_right = 0;
15
16 int oc_red = getr(outer_color);
17 int oc_green = getg(outer_color);
18 int oc_blue = getb(outer_color);
19 int ic_red = getr(inner_color);
20 int ic_green = getg(inner_color);
21 int ic_blue = getb(inner_color);
22
23 int gc_red = 0;
24 int gc_green = 0;
25 int gc_blue = 0;
26 int gradient_color = 0;
27
28 double oc_to_ic_red_diff = static_cast<double>(oc_red - ic_red);
29 double oc_to_ic_green_diff = static_cast<double>(oc_green - ic_green);
30 double oc_to_ic_blue_diff = static_cast<double>(oc_blue - ic_blue);
31
32 double d_radius = static_cast<double>(rad);
33 double d_radial_distance = 0.0;
34 double d_gradient_factor = 0.0;
35
36 for (rel_ypos = 0 ; rel_ypos <= rad ; rel_ypos++) {
37 abs_ypos_top = cy - rel_ypos;
38 abs_ypos_bottom = cy + rel_ypos;
39 hxcl = static_cast<int>(sqrt(static_cast<double>(rad*rad - rel_ypos*rel_ypos)) + 0.5);// Use this for a rounding method for the x position at the edge of the circle
40// hxcl = static_cast<int>(sqrt(static_cast<double>(rad*rad - rel_ypos*rel_ypos)));
41 for (rel_xpos = 0 ; rel_xpos <= hxcl ; rel_xpos++) {
42 abs_xpos_left = cx - rel_xpos;
43 abs_xpos_right = cx + rel_xpos;
44 d_radial_distance = sqrt(static_cast<double>(rel_xpos*rel_xpos + rel_ypos*rel_ypos));
45 d_gradient_factor = d_radial_distance / d_radius;
46 gc_red = ic_red + static_cast<int>(d_gradient_factor*oc_to_ic_red_diff);
47 gc_green = ic_green + static_cast<int>(d_gradient_factor*oc_to_ic_green_diff);
48 gc_blue = ic_blue + static_cast<int>(d_gradient_factor*oc_to_ic_blue_diff);
49 gradient_color = makecol(gc_red , gc_green , gc_blue);
50 putpixel(bmp , abs_xpos_left , abs_ypos_top , gradient_color);
51 putpixel(bmp , abs_xpos_left , abs_ypos_bottom , gradient_color);
52 putpixel(bmp , abs_xpos_right , abs_ypos_top , gradient_color);
53 putpixel(bmp , abs_xpos_right , abs_ypos_bottom , gradient_color);
54 }
55 }
56}
57 
58 
59 
60int main(int argc , char** argv) {
61 
62 
63 if (allegro_init() != 0) {return 0;}
64 if (install_keyboard() != 0) {return 0;}
65
66 int dcd = desktop_color_depth();
67 if (dcd != 0) {
68 set_color_depth(dcd);
69 } else {
70 set_color_depth(32);
71 }
72 int dw = 0;
73 int dh = 0;
74 if (get_desktop_resolution(&dw , &dh) != 0) {
75 dw = 800;
76 dh = 600;
77 }
78 if (set_gfx_mode(GFX_AUTODETECT_FULLSCREEN , dw , dh , 0 , 0) != 0) {return 0;}
79
80 BITMAP* buf = create_bitmap(dw , dh);
81 if (!buf) {return 0;}
82
83 clear_to_color(buf , makecol(0,0,0));
84
85 int circ_rad = (dw + dh)/16;
86 int outer_color = makecol(255,255,0);
87 int inner_color = makecol(255,0,255);
88
89
90 circle(buf , dw/2 , dh/2 , circ_rad , makecol(255,255,255));
91 gradient_circle(buf , dw/2 , dh/2 , circ_rad , outer_color , inner_color);
92
93 blit(buf , screen , 0 , 0 , 0 , 0 , SCREEN_W , SCREEN_H);
94
95 clear_keybuf();
96 readkey();
97 
98 return 0;
99}
100END_OF_MAIN()

What it produces (image scaled down 50%) :
http://www.allegro.cc/files/attachment/594368

You can see there are a few white pixels near the edges , those are an allegro circle drawn underneath with the same radius as the one passed to the function.

Kikaru
Member #7,616
August 2006
avatar

I will be drawing some of these circles with radii of 1500+, so it needs to be pretty darn fast, and accuracy doesn't matter quite so much. Basically, the fewer doubles, floats, sqrt()s, and sin()/cos()s the better. I will look into these later, as I don't have a compiler right now.

The color is either 32 or 16 bit, so no palletes.

The second function looks nice, but it shows up really weird on my computor.

Arthur Kalliokoski
Second in Command
February 2005
avatar

I've tried stuff like this, and always got untouched pixels within the filled circle???

They all watch too much MSNBC... they get ideas.

Kikaru
Member #7,616
August 2006
avatar

Ok, Edgar's circle works, but if I try to copy and paste the function, it looks really weird. Still figuring it out.

Thanks for the input!

[EDIT] it was defaulting to 8-bit color mode. :P

Fladimir da Gorf
Member #1,565
October 2001
avatar

You'll get a huge performance increase if you can ensure that pixels outside of the screen won't be filled in the routine and replacing putpixel with a _putpixelXX.

OpenLayer has reached a random SVN version number ;) | Online manual | Installation video!| MSVC projects now possible with cmake | Now alvailable as a Dev-C++ Devpack! (Thanks to Kotori)

Thomas Harte
Member #33
April 2000
avatar

Quote:

I will be drawing some of these circles with radii of 1500+, so it needs to be pretty darn fast, and accuracy doesn't matter quite so much. Basically, the fewer doubles, floats, sqrt()s, and sin()/cos()s the better. I will look into these later, as I don't have a compiler right now.

I think the fastest method would be to prefill a texture with a scaled version of the fill, then circle with drawing_mode set to DRAW_MODE_COPY_PATTERN. So predraw a circle with at least 256 colour steps in it, using sqrt, sin, cos, whatever. The draw it to an offscreen BITMAP as though you were using stretch_blit instead of circlefill. So if you drew it to the screen you'd get something a lot like the circle you want, but with a jaggedy edge.

The trick is that instead of drawing it to the screen, just set the BITMAP you drew to as the fill pattern and then use circlefill. So you get the outline calculated in the normal way and the fill taken from your scaled BITMAP.

Kikaru
Member #7,616
August 2006
avatar

Well, I changed stuff around a bit, so speed doesn't matter so much anymore. :)

Thanks for the functions!

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

No problem , it was fun and now I have a gradient circle function to add to my collection of useful code. It may be possible to make it even faster. Right now it's drawn using 4 mirror images but that could be doubled to 8 mirror images by splitting the quarter into an eighth and flipping rel_xpos and rel_ypos to give four more draws per pixel calculation. Maybe I'll mess around with it some more later.

Go to: