How to properly do centered zooming? - c

My problem is more generic than programming, however it involves some basic C codes, I hope this won't be closed in here.
I have a rounded target display, which will display an image, first centered and fitted:
Circle's radius is 360, that's fixed.
I need to add do zoom-in and out functionality (in case image is larger than target). In this example the above image is 1282x720, so it's well above the circle's size. (To fit into the circle, now it's roughly 313x176)
I would like to do a properly aligned "center-fixed zoom", i.e.: whatever is currently centered shall remain centered after the zoom operation.
Image is put into a component called scroller which has an option to set its offset, i.e. how many pixels shall it skip from top and left of its content. This scroller component is by default aligns its content to top-left corner.
I've put a red dot into the middle of the image, to be easier to follow.
So upon zooming in this happens (image is starting to be left-aligned):
Please note it is still in the middle vertically, as it's stills smaller in height than its container.
However on the next zooming-in step, the red centerpoint will slightly go downwards, as the image in this case has more height than container, hence it's also started being top-aligned:
Now, making it to stay always in center is easy:
I need to ask the scroller to scroll to
image_width/2 - 180, //horizontal skip
image_height/2 - 180 //vertical skip
In this case, if I zoom-in in 5 steps from fitted size to full size, scroller's skip numbers are these:
Step0 (fit): 0, 0
Step1: 73, 0
Step2: 170, 16
Step3: 267, 71
Step4: 364, 125
Step5 (original size): 461, 180
But I don't want the image to stay in center constantly, I'd rather do something similar what image editors are doing, i.e.: center point shall remain in center during zoom operation, than user can pan, and next zoom operation will keep the new center point in center.
How shall I do this?
Target language is C, and there is no additional 3rd party library which is usable, I'll need to do this manually.
Scroller is actually an elm_scroller.

You need to modify all four positions points, not only x2 and y2, think of them as a the sides of a rectangle, so to keep a centered zoom every side of the square needs to "grow" to de absolute center of the image.
X1 > Left , Y1 > Top
X2 > Right , Y2 > Bottom
#include <stdint.h>
#include <stdio.h>
typedef struct {
int32_t x;
int32_t y;
int32_t width;
int32_t heigth;
uint32_t o_width;
uint32_t o_heigth;
} IMG_C_POS;
void set_img_c_pos(IMG_C_POS * co, int32_t w, int32_t h){
co->o_heigth = h;
co->o_width = w;
co->heigth = h;
co->width = w;
co->x = 0;
co->y = 0;
}
void add_img_zoom(IMG_C_POS * co, uint16_t zoom){
uint32_t zoom_y = (co->o_heigth / 100) * (zoom / 2);
uint32_t zoom_x = (co->o_width / 100) * (zoom / 2);
co->heigth -= zoom_y;
co->width -= zoom_x;
co->x += zoom_x;
co->y += zoom_y;
}
void sub_img_zoom(IMG_C_POS * co, uint16_t zoom){
uint32_t zoom_y = (co->o_heigth / 100) * (zoom / 2);
uint32_t zoom_x = (co->o_width / 100) * (zoom / 2);
co->heigth += zoom_y;
co->width += zoom_x;
co->x -= zoom_x;
co->y -= zoom_y;
}
void img_new_center(IMG_C_POS * co, int16_t nx, int16_t ny){
int32_t oy = co->o_heigth / 2;
if(oy <= ny){
co->heigth += oy - ny;
co->y += oy - ny;
} else {
co->heigth -= oy - ny;
co->y -= oy - ny;
}
int32_t ox = co->o_width / 2;
if(ox <= nx){
co->width += ox - nx;
co->x += ox - nx;
} else {
co->width -= ox - nx;
co->x -= ox - nx;
}
}
void offset_img_center(IMG_C_POS * co, int16_t x_offset, int16_t y_offset){
if (y_offset != 0){
int32_t y_m_size = (co->o_heigth / 100) * y_offset;
co->heigth += y_m_size;
co->y += y_m_size;
}
if (x_offset != 0){
int32_t x_m_size = (co->o_width / 100) * x_offset;
co->width += x_m_size;
co->x += x_m_size;
}
}
int main(void) {
IMG_C_POS position;
set_img_c_pos(&position, 1282, 720);
sub_img_zoom(&position, 50);
img_new_center(&position, (1282 / 2) - 300, (720 / 2) + 100);
for (int i = 0; i < 4; i++){
printf("X1 -> %-5i Y1 -> %-5i X2 -> %-5i Y2 -> %-5i \n",
position.x, position.y, position.width, position.heigth
);
offset_img_center(&position, 4, -2);
add_img_zoom(&position, 20);
}
return 0;
}

Related

How to zoom/move into the mandelbrot set

I'm doing the Mandelbrot set in c as a school project.<br>
This is my code:
int mandelbrot(int x, int y, t_data *data)
{
double complex c;
double complex z;
double cx;
double cy;
int i;
cx = map(x, 0, WIDTH, data->min, data->max);
cy = map(y, 0, HEIGHT, data->min , data->max);
c = cx + cy * I;
z = 0;
i = 0;
while (i < data->max_iteration)
{
z = z * z + c;
if (creal(z) * creal(z) + cimag(z) * cimag(z) > 4)
break;
i++;
}
return (get_color(i, *data));
}
I used the map() function to convert the window coordinates to the complex coordinates
and I used it to get the cursor coordinates in the complex space too:
double map(double value, double screen_min, double screen_max
, double image_min, double image_max)
{
return ((value - screen_min ) \
* (image_max - image_min) \
/ (screen_max - screen_min) \
+ image_min);
}
the data is a struct that has the variables I need for the mand:
typedef struct mouse
{
double cursor_cx; // cursor x coordinates in the complex plane
double cursor_cy; // cursor x coordinates in the complex plane
double min; // the minimum edge of the mand initialized to -2
double max; // initialized to 2
int max_iteration; // initialized to 100
mlx_t *mlx; // the window I guess
mlx_image_t *g_img; // the img I'll draw my pixels to
} t_data;
My problem now is moving the mandelbrot around and zooming in and out.<br>
1st: I managed to zoom in and out by changing the min and max:
void zoom_hook(double xdelta, double ydelta, void *param)
{
int32_t cursor_x = get_cursor_x();
int32_t cursor_y = get_cursor_x();
float step;
step = 0.1;
t_data *data = param;
(void)xdelta;
if (ydelta > 0)
{
data->min += step;
data->max -= step;
}
else if (ydelta < 0)
{
data->min -= step;
data->max += step;
}
}
but when I zoom in a few times the mand get's inverted and starts zooming out even tho I'm zooming in<br>
that's because min is moving from -2 to 0 then if I zoom more then that from 0 to 2
and vise versa for max
so I need a better way of zooming in, I'm expecting to be able to zoom in forever as long as it is within the limits of the computer. but right now my mand get's inverted before reaching that limit.
and more than that I can get the cursor x and y positions so I want to zoom in into those coordinates.
2nd: I need to move my `mand` and this is what I tried but it doesn't work It's zooming in and out but in diagonal way:
void key_hook(void *param)
{
t_data *data;
float step;
step = 0.1;
data = param;
if (mlx_is_key_down(data->mlx, MLX_KEY_ESCAPE))
mlx_close_window(data->mlx);
if (mlx_is_key_down(data->mlx, MLX_KEY_RIGHT))
data->min -= step;
if (mlx_is_key_down(data->mlx, MLX_KEY_LEFT))
data->min += step;
if (mlx_is_key_down(data->mlx, MLX_KEY_UP))
data->max += step;
if (mlx_is_key_down(data->mlx, MLX_KEY_DOWN))
data->max -= step;
}
I'm expecting it to move to the directions right, left, up and down.<br>
So I need some helpt to move and zoom my mandelbrot set.
Thanks in advance.

Inconsistent performance when writing pixels to custom buffer (X11 / C)

I'm writing my own library based on X11 which uses only CPU rendering (GPU usage is not allowed!).Today I stumbled upon strange discovery. When I draw pixels like this:
const int ratio = 1;
for(int y = 0; y < XF_GetWindowHeight() / ratio; ++y)
for(int x = 0; x < XF_GetWindowWidth() / ratio; ++x)
XF_DrawPoint(x * ratio, y * ratio, 0xff0000); // function gets x, y and color
It completes after ~1.5ms every frame. But when I draw the same region but using fewer loop calls and drawing bigger rectangles instead of points (pixels) I get result of ~0.8ms.
const int ratio = 32;
for(int y = 0; y < XF_GetWindowHeight() / ratio; ++y)
for(int x = 0; x < XF_GetWindowWidth() / ratio; ++x)
// function gets x, y, w, h, color and if only draw outline (doesn't matter in this case)
XF_DrawRect(x * ratio, y * ratio, ratio, ratio, 0xff0000, false);
It seems strange to me, that even if the XF_DrawRect function is more complicated and both loops in the end are drawing the same amount of pixels, calling fewer loops seems to impact the performance greatly.
void XF_DrawPoint(int x, int y, uint32_t color)
*(h_lines[y] + x) = color; // h_lines is array with pointers to each row
void XF_DrawRect(int x, int y, int w, int h, uint32_t color, XF_Bool outline)
uint32_t *s = h_lines[y] + x;
int hz_count = 0;
while(h--) {
hz_count = w;
while(hz_count--) {
*s++ = color;
}
s += WINDOW_WIDTH - w;
}
So, as you can see, implementation of XF_DrawRect is more complex (there are some range checks at the beginning to trim the rectangle if it goes out of bounds, but it doesn't matter) then the XF_DrawPoint and still, there is ~2x improvement in speed, when drawing the same area.My question is: why?

Having trouble with Box Blur Edges

I am a newbie hobbyist trying to program a box blur and I am having trouble with respect to edges. I am hoping that someone can spot the error.
The edges are black and I am assuming that it's because the borders are not being reflected properly. I am sure this has been discussed with a fix size kernel however I am using a variable sized kernel.
I am using the code found on another post --
Optimized float Blur variations
However I just do not understand the reflected borders portion.
I do not really care if optimized or not nor do I care about other kernel shapes, box shape will be just fine.
The code is
{// code from https://stackoverflow.com/questions/7860575/optimized-float-blur-variations
//--------------------------------------
int image_width ;
int image_height ;
int scale = 0;
int weight = (radius * 2) + 1;
int kernel_X = 0;
int kernel_Y = 0;
//--------------------------------------
float sum = 0.0;
int kernel_width = radius;//set both to the same to make the kernel square
int kernel_height = radius;//set both to the same to make the kernel square
// HORIZONTAL
for(iy = 0; iy < image_height ;iy++)
{
sum = 0.0;
// Process entire window for first pixel (including wrap-around edge)
for (kernel_X = 0; kernel_X <= kernel_width; kernel_X++)
{
if (kernel_X >= 0 && kernel_X < image_width)
//sum += src[iy * image_width ];
sum += src[iy * image_width + kernel_X];
}
//>-------------- border code does not reflect edges HELP!!
// Wrap watch for left side of image & resulting black bar
for (kernel_X = (image_width - kernel_width); kernel_X < image_width; kernel_X++)
{
// if (kernel_X >= 0 && kernel_X < image_width)// HORIZONTAL width = horizontal = X
// sum += src[iy * kernel_width + image_width ];//<-------------------enter tester formula here
// sum += src[iy + ix * image_width + kernel_X];//<-------------------FAIL
// sum += src[iy * kernel_width + image_width ];//<-------------------streaky
}
// Store first window
tmp[iy * image_width] = (sum / weight );
for(ix = 1; ix < image_width; ix++)
{
// Subtract pixel leaving window
if (ix - kernel_width - 1 >= 0)
sum -= src[iy * image_width + ix - kernel_width - 1];
// Add pixel entering window
if (ix + kernel_width < image_width)
sum += src[iy * image_width + ix + kernel_width];
else
sum += src[iy * image_width + ix + kernel_width - image_width];
tmp[iy * image_width + ix] = (sum / weight);//just for testing
}
}
// VERTICAL
for(ix = 0; ix < image_width; ix++)
{
sum = 0.0;
// Process entire window for first pixel
for (kernel_Y = 0; kernel_Y <= kernel_height; kernel_Y++)
{
if (kernel_Y >= 0 && kernel_Y < image_height)
sum += tmp[kernel_Y * image_width + ix];
}
//>-------------- border code does not reflect edges HELP!!
// Wrap watch for top side of image & resulting black bar
for (kernel_Y = image_height-kernel_height; kernel_Y < kernel_height; kernel_Y++)
{
//if (kernel_Y >= 0 && kernel_Y < image_height)
// sum += tmp[(iy + kernel_height - image_height) * image_width + ix];
}
for(iy=1;iy< image_height ;iy++)
{
// Subtract pixel leaving window
if (iy-kernel_height-1 >= 0)
sum -= tmp[(iy - kernel_height-1) * image_width + ix];
// Add pixel entering window
if (iy + kernel_height < image_height)
sum += tmp[(iy + kernel_height) * image_width + ix];
else
sum += tmp[(iy + kernel_height - image_height) * image_width + ix];
dst[ (scale * image_width * image_height) + (iy * image_width + ix) ] = (sum / weight);
}
}
}
I appreciate any help on this.
Thanks
John
edit here are some links of image examples of the edges.
image with proper box blur
http://img687.imageshack.us/img687/931/standardboxblur.jpg
Image with improper edges using the above code (notice dark bar on Top and Left edges, bottom and right are not quite right either)
http://img202.imageshack.us/img202/5137/boxblurbadedges.jpg
It might be easiest if you put your sampling into a separate routine which, given an x and y coordinate, returns the pixel value. You can then do some checks and clamp the x and y values to be between 0 and width and 0 and height, respectively. Then you can safely pass in negative values or values greater than width or height. It also allows you to more easily try other schemes like reflection, clamping to a color, extrapolation, etc. Simply swap out the sampling function that clamps with one that does some other behavior.

fast algorithm for drawing filled circles?

I am using Bresenham's circle algorithm for fast circle drawing. However, I also want to (at the request of the user) draw a filled circle.
Is there a fast and efficient way of doing this? Something along the same lines of Bresenham?
The language I am using is C.
Having read the Wikipedia page on Bresenham's (also 'Midpoint') circle algorithm, it would appear that the easiest thing to do would be to modify its actions, such that instead of
setPixel(x0 + x, y0 + y);
setPixel(x0 - x, y0 + y);
and similar, each time you instead do
lineFrom(x0 - x, y0 + y, x0 + x, y0 + y);
That is, for each pair of points (with the same y) that Bresenham would you have you plot, you instead connect with a line.
Just use brute force. This method iterates over a few too many pixels, but it only uses integer multiplications and additions. You completely avoid the complexity of Bresenham and the possible bottleneck of sqrt.
for(int y=-radius; y<=radius; y++)
for(int x=-radius; x<=radius; x++)
if(x*x+y*y <= radius*radius)
setpixel(origin.x+x, origin.y+y);
Here's a C# rough guide (shouldn't be that hard to get the right idea for C) - this is the "raw" form without using Bresenham to eliminate repeated square-roots.
Bitmap bmp = new Bitmap(200, 200);
int r = 50; // radius
int ox = 100, oy = 100; // origin
for (int x = -r; x < r ; x++)
{
int height = (int)Math.Sqrt(r * r - x * x);
for (int y = -height; y < height; y++)
bmp.SetPixel(x + ox, y + oy, Color.Red);
}
bmp.Save(#"c:\users\dearwicker\Desktop\circle.bmp");
You can use this:
void DrawFilledCircle(int x0, int y0, int radius)
{
int x = radius;
int y = 0;
int xChange = 1 - (radius << 1);
int yChange = 0;
int radiusError = 0;
while (x >= y)
{
for (int i = x0 - x; i <= x0 + x; i++)
{
SetPixel(i, y0 + y);
SetPixel(i, y0 - y);
}
for (int i = x0 - y; i <= x0 + y; i++)
{
SetPixel(i, y0 + x);
SetPixel(i, y0 - x);
}
y++;
radiusError += yChange;
yChange += 2;
if (((radiusError << 1) + xChange) > 0)
{
x--;
radiusError += xChange;
xChange += 2;
}
}
}
Great ideas here!
Since I'm at a project that requires many thousands of circles to be drawn, I have evaluated all suggestions here (and improved a few by precomputing the square of the radius):
http://quick-bench.com/mwTOodNOI81k1ddaTCGH_Cmn_Ag
The Rev variants just have x and y swapped because consecutive access along the y axis are faster with the way my grid/canvas structure works.
The clear winner is Daniel Earwicker's method ( DrawCircleBruteforcePrecalc ) that precomputes the Y value to avoid unnecessary radius checks. Somewhat surprisingly that negates the additional computation caused by the sqrt call.
Some comments suggest that kmillen's variant (DrawCircleSingleLoop) that works with a single loop should be very fast, but it's the slowest here. I assume that is because of all the divisions. But perhaps I have adapted it wrong to the global variables in that code. Would be great if someone takes a look.
EDIT: After looking for the first time since college years at some assembler code, I managed find that the final additions of the circle's origin are a culprit.
Precomputing those, I improved the fastest method by a factor of another 3.7-3.9 according to the bench!
http://quick-bench.com/7ZYitwJIUgF_OkDUgnyMJY4lGlA
Amazing.
This being my code:
for (int x = -radius; x < radius ; x++)
{
int hh = (int)std::sqrt(radius_sqr - x * x);
int rx = center_x + x;
int ph = center_y + hh;
for (int y = center_y-hh; y < ph; y++)
canvas[rx][y] = 1;
}
I like palm3D's answer. For being brute force, this is an amazingly fast solution. There are no square root or trigonometric functions to slow it down. Its one weakness is the nested loop.
Converting this to a single loop makes this function almost twice as fast.
int r2 = r * r;
int area = r2 << 2;
int rr = r << 1;
for (int i = 0; i < area; i++)
{
int tx = (i % rr) - r;
int ty = (i / rr) - r;
if (tx * tx + ty * ty <= r2)
SetPixel(x + tx, y + ty, c);
}
This single loop solution rivals the efficiency of a line drawing solution.
int r2 = r * r;
for (int cy = -r; cy <= r; cy++)
{
int cx = (int)(Math.Sqrt(r2 - cy * cy) + 0.5);
int cyy = cy + y;
lineDDA(x - cx, cyy, x + cx, cyy, c);
}
palm3D's brute-force algorithm I found to be a good starting point. This method uses the same premise, however it includes a couple of ways to skip checking most of the pixels.
First, here's the code:
int largestX = circle.radius;
for (int y = 0; y <= radius; ++y) {
for (int x = largestX; x >= 0; --x) {
if ((x * x) + (y * y) <= (circle.radius * circle.radius)) {
drawLine(circle.center.x - x, circle.center.x + x, circle.center.y + y);
drawLine(circle.center.x - x, circle.center.x + x, circle.center.y - y);
largestX = x;
break; // go to next y coordinate
}
}
}
Next, the explanation.
The first thing to note is that if you find the minimum x coordinate that is within the circle for a given horizontal line, you immediately know the maximum x coordinate.
This is due to the symmetry of the circle. If the minimum x coordinate is 10 pixels ahead of the left of the bounding box of the circle, then the maximum x is 10 pixels behind the right of the bounding box of the circle.
The reason to iterate from high x values to low x values, is that the minimum x value will be found with less iterations. This is because the minimum x value is closer to the left of the bounding box than the centre x coordinate of the circle for most lines, due to the circle being curved outwards, as seen on this image
The next thing to note is that since the circle is also symmetric vertically, each line you find gives you a free second line to draw, each time you find a line in the top half of the circle, you get one on the bottom half at the radius-y y coordinate. Therefore, when any line is found, two can be drawn and only the top half of the y values needs to be iterated over.
The last thing to note is that is that if you start from a y value that is at the centre of the circle and then move towards the top for y, then the minimum x value for each next line must be closer to the centre x coordinate of the circle than the last line. This is also due to the circle curving closer towards the centre x value as you go up the circle. Here is a visual on how that is the case.
In summary:
If you find the minimum x coordinate of a line, you get the maximum x coordinate for free.
Every line you find to draw on the top half of the circle gives you a line on the bottom half of the circle for free.
Every minimum x coordinate has to be closer to the centre of the circle than the previous x coordinate for each line when iterating from the centre y coordinate to the top.
You can also store the value of (radius * radius), and also (y * y) instead of calculating them
multiple times.
Here's how I'm doing it:
I'm using fixed point values with two bits precision (we have to manage half points and square values of half points)
As mentionned in a previous answer, I'm also using square values instead of square roots.
First, I'm detecting border limit of my circle in a 1/8th portion of the circle. I'm using symetric of these points to draw the 4 "borders" of the circle. Then I'm drawing the square inside the circle.
Unlike the midpoint circle algorith, this one will work with even diameters (and with real numbers diameters too, with some little changes).
Please forgive me if my explanations were not clear, I'm french ;)
void DrawFilledCircle(int circleDiameter, int circlePosX, int circlePosY)
{
const int FULL = (1 << 2);
const int HALF = (FULL >> 1);
int size = (circleDiameter << 2);// fixed point value for size
int ray = (size >> 1);
int dY2;
int ray2 = ray * ray;
int posmin,posmax;
int Y,X;
int x = ((circleDiameter&1)==1) ? ray : ray - HALF;
int y = HALF;
circlePosX -= (circleDiameter>>1);
circlePosY -= (circleDiameter>>1);
for (;; y+=FULL)
{
dY2 = (ray - y) * (ray - y);
for (;; x-=FULL)
{
if (dY2 + (ray - x) * (ray - x) <= ray2) continue;
if (x < y)
{
Y = (y >> 2);
posmin = Y;
posmax = circleDiameter - Y;
// Draw inside square and leave
while (Y < posmax)
{
for (X = posmin; X < posmax; X++)
setPixel(circlePosX+X, circlePosY+Y);
Y++;
}
// Just for a better understanding, the while loop does the same thing as:
// DrawSquare(circlePosX+Y, circlePosY+Y, circleDiameter - 2*Y);
return;
}
// Draw the 4 borders
X = (x >> 2) + 1;
Y = y >> 2;
posmax = circleDiameter - X;
int mirrorY = circleDiameter - Y - 1;
while (X < posmax)
{
setPixel(circlePosX+X, circlePosY+Y);
setPixel(circlePosX+X, circlePosY+mirrorY);
setPixel(circlePosX+Y, circlePosY+X);
setPixel(circlePosX+mirrorY, circlePosY+X);
X++;
}
// Just for a better understanding, the while loop does the same thing as:
// int lineSize = circleDiameter - X*2;
// Upper border:
// DrawHorizontalLine(circlePosX+X, circlePosY+Y, lineSize);
// Lower border:
// DrawHorizontalLine(circlePosX+X, circlePosY+mirrorY, lineSize);
// Left border:
// DrawVerticalLine(circlePosX+Y, circlePosY+X, lineSize);
// Right border:
// DrawVerticalLine(circlePosX+mirrorY, circlePosY+X, lineSize);
break;
}
}
}
void DrawSquare(int x, int y, int size)
{
for( int i=0 ; i<size ; i++ )
DrawHorizontalLine(x, y+i, size);
}
void DrawHorizontalLine(int x, int y, int width)
{
for(int i=0 ; i<width ; i++ )
SetPixel(x+i, y);
}
void DrawVerticalLine(int x, int y, int height)
{
for(int i=0 ; i<height ; i++ )
SetPixel(x, y+i);
}
To use non-integer diameter, you can increase precision of fixed point or use double values.
It should even be possible to make a sort of anti-alias depending on the difference between dY2 + (ray - x) * (ray - x) and ray2 (dx² + dy² and r²)
If you want a fast algorithm, consider drawing a polygon with N sides, the higher is N, the more precise will be the circle.
I would just generate a list of points and then use a polygon draw function for the rendering.
It may not be the algorithm yo are looking for and not the most performant one,
but I always do something like this:
void fillCircle(int x, int y, int radius){
// fill a circle
for(int rad = radius; rad >= 0; rad--){
// stroke a circle
for(double i = 0; i <= PI * 2; i+=0.01){
int pX = x + rad * cos(i);
int pY = y + rad * sin(i);
drawPoint(pX, pY);
}
}
}
The following two methods avoid the repeated square root calculation by drawing multiple parts of the circle at once and should therefore be quite fast:
void circleFill(const size_t centerX, const size_t centerY, const size_t radius, color fill) {
if (centerX < radius || centerY < radius || centerX + radius > width || centerY + radius > height)
return;
const size_t signedRadius = radius * radius;
for (size_t y = 0; y < radius; y++) {
const size_t up = (centerY - y) * width;
const size_t down = (centerY + y) * width;
const size_t halfWidth = roundf(sqrtf(signedRadius - y * y));
for (size_t x = 0; x < halfWidth; x++) {
const size_t left = centerX - x;
const size_t right = centerX + x;
pixels[left + up] = fill;
pixels[right + up] = fill;
pixels[left + down] = fill;
pixels[right + down] = fill;
}
}
}
void circleContour(const size_t centerX, const size_t centerY, const size_t radius, color stroke) {
if (centerX < radius || centerY < radius || centerX + radius > width || centerY + radius > height)
return;
const size_t signedRadius = radius * radius;
const size_t maxSlopePoint = ceilf(radius * 0.707106781f); //ceilf(radius * cosf(TWO_PI/8));
for (size_t i = 0; i < maxSlopePoint; i++) {
const size_t depth = roundf(sqrtf(signedRadius - i * i));
size_t left = centerX - depth;
size_t right = centerX + depth;
size_t up = (centerY - i) * width;
size_t down = (centerY + i) * width;
pixels[left + up] = stroke;
pixels[right + up] = stroke;
pixels[left + down] = stroke;
pixels[right + down] = stroke;
left = centerX - i;
right = centerX + i;
up = (centerY - depth) * width;
down = (centerY + depth) * width;
pixels[left + up] = stroke;
pixels[right + up] = stroke;
pixels[left + down] = stroke;
pixels[right + down] = stroke;
}
}
This was used in my new 3D printer Firmware, and it is proven the
fastest way for filled circle of a diameter from 1 to 43 pixel. If
larger is needed, the following memory block(or array) should be
extended following a structure I wont waste my time explaining...
If you have questions, or need larger diameter than 43, contact me, I
will help you drawing the fastest and perfect filled circles... or
Bresenham's circle drawing algorithm can be used above those
diameters, but having to fill the circle after, or incorporating the
fill into Bresenham's circle drawing algorithm, will only result in
slower fill circle than my code. I already benchmarked the different
codes, my solution is 4 to 5 times faster. As a test I have been
able to draw hundreds of filled circles of different size and colors
on a BigTreeTech tft24 1.1 running on a 1-core 72 Mhz cortex-m4
https://www.youtube.com/watch?v=7_Wp5yn3ADI
// this must be declared anywhere, as static or global
// as long as the function can access it !
uint8_t Rset[252]={
0,1,1,2,2,1,2,3,3,1,3,3,4,4,2,3,4,5,5,5,2,4,5,5,
6,6,6,2,4,5,6,6,7,7,7,2,4,5,6,7,7,8,8,8,2,5,6,7,
8,8,8,9,9,9,3,5,6,7,8,9,9,10,10,10,10,3,5,7,8,9,
9,10,10,11,11,11,11,3,5,7,8,9,10,10,11,11,12,12,
12,12,3,6,7,9,10,10,11,12,12,12,13,13,13,13,3,6,
8,9,10,11,12,12,13,13,13,14,14,14,14,3,6,8,9,10,
11,12,13,13,14,14,14,15,15,15,15,3,6,8,10,11,12,
13,13,14,14,15,15,15,16,16,16,16,4,7,8,10,11,12,
13,14,14,15,16,16,16,17,17,17,17,17,4,7,9,10,12,
13,14,14,15,16,16,17,17,17,18,18,18,18,18,4,7,9,
11,12,13,14,15,16,16,17,17,18,18,18,19,19,19,19,
19,7,9,11,12,13,15,15,16,17,18,18,19,19,20,20,20,
20,20,20,20,20,7,9,11,12,14,15,16,17,17,18,19,19
20,20,21,21,21,21,21,21,21,21};
// SOLUTION 1: (the fastest)
void FillCircle_v1(uint16_t x, uint16_t y, uint16_t r)
{
// all needed variables are created and set to their value...
uint16_t radius=(r<1) ? 1 : r ;
if (radius>21 ) {radius=21; }
uint16_t diam=(radius*2)+1;
uint16_t ymir=0, cur_y=0;
radius--; uint16_t target=(radius*radius+3*radius)/2; radius++;
// this part draws directly into the ILI94xx TFT buffer mem.
// using pointers..2 versions where you can draw
// pixels and lines with coordinates will follow
for (uint16_t yy=0; yy<diam; yy++)
{ ymir= (yy<=radius) ? yy+target : target+diam-(yy+1);
cur_y=y-radius+yy;
uint16_t *pixel=buffer_start_addr+x-Rset[ymir]+cur_y*buffer_width;
for (uint16_t xx= 0; xx<=(2*Rset[ymir]); xx++)
{ *pixel++ = CANVAS::draw_color; }}}
// SOLUTION 2: adaptable to any system that can
// add a pixel at a time: (drawpixel or add_pixel,etc_)
void FillCircle_v2(uint16_t x, uint16_t y, uint16_t r)
{
// all needed variables are created and set to their value...
uint16_t radius=(r<1) ? 1 : r ;
if (radius>21 ) {radius=21; }
uint16_t diam=(radius*2)+1;
uint16_t ymir=0, cur_y=0;
radius--; uint16_t target=(radius*radius+3*radius)/2; radius++;
for (uint16_t yy=0; yy<diam; yy++)
{ ymir= (yy<=radius) ? yy+target : target+diam-(yy+1);
cur_y=y-radius+yy;
uint16_t Pixel_x=x-Rset[ymir];
for (uint16_t xx= 0; xx<=(2*Rset[ymir]); xx++)
{ //use your add_pixel or draw_pixel here
// using those coordinates:
// X position will be... (Pixel_x+xx)
// Y position will be... (cur_y)
// and add those 3 brackets at the end
}}}
// SOLUTION 3: adaptable to any system that can draw fast
// horizontal lines
void FillCircle_v3(uint16_t x, uint16_t y, uint16_t r)
{
// all needed variables are created and set to their value...
uint16_t radius=(r<1) ? 1 : r ;
if (radius>21 ) {radius=21; }
uint16_t diam=(radius*2)+1;
uint16_t ymir=0, cur_y=0;
radius--; uint16_t target=(radius*radius+3*radius)/2; radius++;
for (uint16_t yy=0; yy<diam; yy++)
{ ymir= (yy<=radius) ? yy+target : target+diam-(yy+1);
cur_y=y-radius+yy;
uint16_t start_x=x-Rset[ymir];
uint16_t width_x=2*Rset[ymir];
// ... then use your best drawline function using those values:
// start_x: position X of the start of the line
// cur_y: position Y of the current line
// width_x: length of the line
// if you need a 2nd coordinate then :end_x=start_x+width_x
// and add those 2 brackets after !!!
}}
I did pretty much what AlegGeorge did but I changed three lines. I thought that this is faster but these are the results am I doing anything wrong? my function is called DrawBruteforcePrecalcV4. here's the code:
for (int x = 0; x < radius ; x++) // Instead of looping from -radius to radius I loop from 0 to radius
{
int hh = (int)std::sqrt(radius_sqr - x * x);
int rx = center_x + x;
int cmx = center_x - x;
int ph = center_y+hh;
for (int y = center_y-hh; y < ph; y++)
{
canvas[rx][y] = 1;
canvas[cmx][y] = 1;
}
}

Filling a polygon

I created this function that draws a simple polygon with n number of vertexes:
void polygon (int n)
{
double pI = 3.141592653589;
double area = min(width / 2, height / 2);
int X = 0, Y = area - 1;
double offset = Y;
int lastx, lasty;
double radius = sqrt(X * X + Y * Y);
double quadrant = atan2(Y, X);
int i;
for (i = 1; i <= n; i++)
{
lastx = X; lasty = Y;
quadrant = quadrant + pI * 2.0 / n;
X = round((double)radius * cos(quadrant));
Y = round((double)radius * sin(quadrant));
setpen((i * 255) / n, 0, 0, 0.0, 1); // r(interval) g b, a, size
moveto(offset + lastx, offset + lasty); // Moves line offset
lineto(offset + X, offset + Y); // Draws a line from offset
}
}
How can I fill it with a solid color?
I have no idea how can I modify my code in order to draw it filled.
The common approach to fill shapes is to find where the edges of the polygon cross either each x or each y coordinate. Usually, y coordinates are used, so that the filling can be done using horizontal lines. (On framebuffer devices like VGA, horizontal lines are faster than vertical lines, because they use consecutive memory/framebuffer addresses.)
In that vein,
void fill_regular_polygon(int center_x, int center_y, int vertices, int radius)
{
const double a = 2.0 * 3.14159265358979323846 / (double)vertices;
int i = 1;
int y, px, py, nx, ny;
if (vertices < 3 || radius < 1)
return;
px = 0;
py = -radius;
nx = (int)(0.5 + radius * sin(a));
ny = (int)(0.5 - radius * cos(a));
y = -radius;
while (y <= ny || ny > py) {
const int x = px + (nx - px) * (y - py) / (ny - py);
if (center_y + y >= 0 && center_y + y < height) {
if (center_x - x >= 0)
moveto(center_x - x, center_y + y);
else
moveto(0, center_y + y);
if (center_x + x < width)
lineto(center_x + x, center_y + y);
else
lineto(width - 1, center_y + y);
}
y++;
while (y > ny) {
if (nx < 0)
return;
i++;
px = nx;
py = ny;
nx = (int)(0.5 + radius * sin(a * (double)i));
ny = (int)(0.5 - radius * cos(a * (double)i));
}
}
}
Note that I only tested the above with a simple SVG generator, and compared the drawn lines to the polygon. Seems to work correctly, but use at your own risk; no guarantees.
For general shapes, use your favourite search engine to look for "polygon filling" algorithms. For example, this, this, this, and this.
There are 2 different ways to implement a solution:
Scan-line
Starting at the coordinate that is at the top (smallest y value), continue to scan down line by line (incrementing y) and see which edges intersect the line.
For convex polygons you find 2 points, (x1,y) and (x2,y). Simply draw a line between those on each scan-line.
For concave polygons this can also be a multiple of 2. Simply draw lines between each pair. After one pair, go to the next 2 coordinates. This will create a filled/unfilled/filled/unfilled pattern on that scan line which resolves to the correct overall solution.
In case you have self-intersecting polygons, you would also find coordinates that are equal to some of the polygon points, and you have to filter them out. After that, you should be in one of the cases above.
If you filtered out the polygon points during scan-lining, don't forget to draw them as well.
Flood-fill
The other option is to use flood-filling. It has to perform more work evaluating the border cases at every step per pixel, so this tends to turn out as a slower version. The idea is to pick a seed point within the polygon, and basically recursively extend up/down/left/right pixel by pixel until you hit a border.
The algorithm has to read and write the entire surface of the polygon, and does not cross self-intersection points. There can be considerable stack-buildup (for naive implementations at least) for large surfaces, and the reduced flexibility you have for the border condition is pixel-based (e.g. flooding into gaps when other things are drawn on top of the polygon). In this sense, this is not a mathematically correct solution, but it works well for many applications.
The most efficient solution is by decomposing the regular polygon in trapezoids (and one or two triangles).
By symmetry, the vertexes are vertically aligned and it is an easy matter to find the limiting abscissas (X + R cos(2πn/N) and X + R cos(2π(+1)N)).
You also have the ordinates (Y + R sin(2πn/N) and Y + R sin(2π(+1)N)) and it suffices to interpolate linearly between two vertexes by Y = Y0 + (Y1 - Y0) (X - X0) / (X1 - X0).
Filling in horizontal runs is a little more complex, as the vertices may not be aligned horizontally and there are more trapezoids.
Anyway, it seems that I / solved / this myself again, when not relying on assistance (or any attempt for it)
void polygon (int n)
{
double pI = 3.141592653589;
double area = min(width / 2, height / 2);
int X = 0, Y = area - 1;
double offset = Y;
int lastx, lasty;
while(Y-->0) {
double radius = sqrt(X * X + Y * Y);
double quadrant = atan2(Y, X);
int i;
for (i = 1; i <= n; i++)
{
lastx = X; lasty = Y;
quadrant = quadrant + pI * 2.0 / n;
X = round((double)radius * cos(quadrant));
Y = round((double)radius * sin(quadrant));
//setpen((i * 255) / n, 0, 0, 0.0, 1);
setpen(255, 0, 0, 0.0, 1); // just red
moveto(offset + lastx, offset + lasty);
lineto(offset + X, offset + Y);
} }
}
As you can see, it isn't very complex, which means it might not be the most efficient solution either.. but it is close enough.
It decrements radius and fills it by virtue of its smaller version with smaller radius.
On that way, precision plays an important role and the higher n is the less accuracy it will be filled with.

Resources