Skip to content

Commit

Permalink
Merge pull request #2912 from mzivic7/fix_aalines_overlap
Browse files Browse the repository at this point in the history
Fix aalines overlap
  • Loading branch information
MyreMylar authored Oct 3, 2024
2 parents 3076513 + a94e88d commit 9ef9855
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 56 deletions.
197 changes: 141 additions & 56 deletions src_c/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ draw_line(SDL_Surface *surf, int x1, int y1, int x2, int y2, Uint32 color,
int *drawn_area);
static void
draw_aaline(SDL_Surface *surf, Uint32 color, float startx, float starty,
float endx, float endy, int *drawn_area);
float endx, float endy, int *drawn_area,
int disable_first_endpoint, int disable_second_endpoint,
int extra_pixel_for_aalines);
static void
draw_arc(SDL_Surface *surf, int x_center, int y_center, int radius1,
int radius2, int width, double angle_start, double angle_stop,
Expand Down Expand Up @@ -159,7 +161,7 @@ aaline(PyObject *self, PyObject *arg, PyObject *kwargs)
return RAISE(PyExc_RuntimeError, "error locking surface");
}

draw_aaline(surf, color, startx, starty, endx, endy, drawn_area);
draw_aaline(surf, color, startx, starty, endx, endy, drawn_area, 0, 0, 0);

if (!pgSurface_Unlock(surfobj)) {
return RAISE(PyExc_RuntimeError, "error unlocking surface");
Expand Down Expand Up @@ -255,9 +257,13 @@ aalines(PyObject *self, PyObject *arg, PyObject *kwargs)
SDL_Surface *surf = NULL;
Uint32 color;
float pts[4];
float pts_prev[4];
float *xlist, *ylist;
float x, y;
int l, t;
int extra_px;
int steep_prev;
int steep_curr;
PyObject *blend = NULL;
int drawn_area[4] = {INT_MAX, INT_MAX, INT_MIN,
INT_MIN}; /* Used to store bounding box values */
Expand Down Expand Up @@ -344,19 +350,84 @@ aalines(PyObject *self, PyObject *arg, PyObject *kwargs)
return RAISE(PyExc_RuntimeError, "error locking surface");
}

for (loop = 1; loop < length; ++loop) {
/* first line - if open, add endpoint pixels.*/
pts[0] = xlist[0];
pts[1] = ylist[0];
pts[2] = xlist[1];
pts[3] = ylist[1];

/* Previous points.
* Used to compare previous and current line.*/
pts_prev[0] = pts[0];
pts_prev[1] = pts[1];
pts_prev[2] = pts[2];
pts_prev[3] = pts[3];
steep_prev =
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
steep_curr = fabs(xlist[2] - pts[2]) < fabs(ylist[2] - pts[1]);
extra_px = steep_prev > steep_curr;
if (closed) {
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
1, extra_px);
}
else {
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 0,
1, extra_px);
}

for (loop = 2; loop < length - 1; ++loop) {
pts[0] = xlist[loop - 1];
pts[1] = ylist[loop - 1];
pts[2] = xlist[loop];
pts[3] = ylist[loop];
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area);

/* Comparing previous and current line.
* If one is steep and other is not, extra pixel must be drawn.*/
steep_prev =
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
extra_px = steep_prev != steep_curr;
pts_prev[0] = pts[0];
pts_prev[1] = pts[1];
pts_prev[2] = pts[2];
pts_prev[3] = pts[3];
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
1, extra_px);
}

/* Last line - if open, add endpoint pixels. */
pts[0] = xlist[length - 2];
pts[1] = ylist[length - 2];
pts[2] = xlist[length - 1];
pts[3] = ylist[length - 1];
steep_prev =
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
extra_px = steep_prev != steep_curr;
pts_prev[0] = pts[0];
pts_prev[1] = pts[1];
pts_prev[2] = pts[2];
pts_prev[3] = pts[3];
if (closed) {
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
1, extra_px);
}
else {
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
0, extra_px);
}

if (closed && length > 2) {
pts[0] = xlist[length - 1];
pts[1] = ylist[length - 1];
pts[2] = xlist[0];
pts[3] = ylist[0];
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area);
steep_prev =
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
extra_px = steep_prev != steep_curr;
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
1, extra_px);
}

PyMem_Free(xlist);
Expand Down Expand Up @@ -1276,7 +1347,9 @@ set_and_check_rect(SDL_Surface *surf, int x, int y, Uint32 color,

static void
draw_aaline(SDL_Surface *surf, Uint32 color, float from_x, float from_y,
float to_x, float to_y, int *drawn_area)
float to_x, float to_y, int *drawn_area,
int disable_first_endpoint, int disable_second_endpoint,
int extra_pixel_for_aalines)
{
float gradient, dx, dy, intersect_y, brightness;
int x, x_pixel_start, x_pixel_end;
Expand Down Expand Up @@ -1379,68 +1452,80 @@ draw_aaline(SDL_Surface *surf, Uint32 color, float from_x, float from_y,

/* Handle endpoints separately.
* The line is not a mathematical line of thickness zero. The same
* goes for the endpoints. The have a height and width of one pixel. */
* goes for the endpoints. The have a height and width of one pixel.
* Extra pixel drawing is requested externally from aalines.
* It is drawn only when one line is steep and other is not.*/
/* First endpoint */
x_pixel_start = (int)from_x;
y_endpoint = intersect_y = from_y + gradient * (x_pixel_start - from_x);
if (to_x > clip_left + 1.0f) {
x_gap = 1 + x_pixel_start - from_x;
brightness = y_endpoint - (int)y_endpoint;
if (steep) {
x = (int)y_endpoint;
y = x_pixel_start;
}
else {
x = x_pixel_start;
y = (int)y_endpoint;
}
if ((int)y_endpoint < y_endpoint) {
if (!disable_first_endpoint || extra_pixel_for_aalines) {
x_pixel_start = (int)from_x;
y_endpoint = intersect_y =
from_y + gradient * (x_pixel_start - from_x);
if (to_x > clip_left + 1.0f) {
x_gap = 1 + x_pixel_start - from_x;
brightness = y_endpoint - (int)y_endpoint;
if (steep) {
x = (int)y_endpoint;
y = x_pixel_start;
}
else {
x = x_pixel_start;
y = (int)y_endpoint;
}
if ((int)y_endpoint < y_endpoint) {
pixel_color = get_antialiased_color(surf, x, y, color,
brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
}
if (steep) {
x--;
}
else {
y--;
}
brightness = 1 - brightness;
pixel_color =
get_antialiased_color(surf, x, y, color, brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
intersect_y += gradient;
x_pixel_start++;
}
if (steep) {
x--;
}
else {
y--;
}
brightness = 1 - brightness;
pixel_color =
get_antialiased_color(surf, x, y, color, brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
intersect_y += gradient;
x_pixel_start++;
}
/* To be sure main loop skips first endpoint.*/
if (disable_first_endpoint) {
x_pixel_start = (int)ceil(from_x);
intersect_y = from_y + gradient * (x_pixel_start - from_x);
}
/* Second endpoint */
x_pixel_end = (int)ceil(to_x);
if (from_x < clip_right - 1.0f) {
y_endpoint = to_y + gradient * (x_pixel_end - to_x);
x_gap = 1 - x_pixel_end + to_x;
brightness = y_endpoint - (int)y_endpoint;
if (steep) {
x = (int)y_endpoint;
y = x_pixel_end;
}
else {
x = x_pixel_end;
y = (int)y_endpoint;
}
if ((int)y_endpoint < y_endpoint) {
if (!disable_second_endpoint || extra_pixel_for_aalines) {
if (from_x < clip_right - 1.0f) {
y_endpoint = to_y + gradient * (x_pixel_end - to_x);
x_gap = 1 - x_pixel_end + to_x;
brightness = y_endpoint - (int)y_endpoint;
if (steep) {
x = (int)y_endpoint;
y = x_pixel_end;
}
else {
x = x_pixel_end;
y = (int)y_endpoint;
}
if ((int)y_endpoint < y_endpoint) {
pixel_color = get_antialiased_color(surf, x, y, color,
brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
}
if (steep) {
x--;
}
else {
y--;
}
brightness = 1 - brightness;
pixel_color =
get_antialiased_color(surf, x, y, color, brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
}
if (steep) {
x--;
}
else {
y--;
}
brightness = 1 - brightness;
pixel_color =
get_antialiased_color(surf, x, y, color, brightness * x_gap);
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
}

/* main line drawing loop */
Expand Down
54 changes: 54 additions & 0 deletions test/draw_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3646,6 +3646,60 @@ class DrawAALinesTest(AALinesMixin, DrawTestCase):
class to add any draw.aalines specific tests to.
"""

def test_aalines__overlap(self):
"""Ensures that two adjacent antialiased lines are not overlapping.
Draws two lines, and checks if 2 pixels, at shared point between those
two lines, are too bright.
See: https://github.com/pygame-community/pygame-ce/pull/2912
"""
line_color = (150, 150, 150)
max_expected_colors = ((70, 70, 70), (100, 100, 100))
test_points = [[20.1, 25.4], [25.1, 25.6], [30.1, 25.8]]

surface = pygame.display.set_mode((50, 50))
self.draw_aalines(surface, line_color, False, test_points)

for i, y in enumerate((25, 26)):
check_color = tuple(surface.get_at((25, y)))
self.assertLess(
check_color,
max_expected_colors[i],
f"aalines are overlapping, pos={(25, y)}",
)

def test_aalines__steep_missing_pixel(self):
"""Ensures there are no missing pixels between steep and non-steep lines.
Draws two adjacent lines: first is not steep and second is, then
checks if there is missing pixel at shared point between those two lines.
See: https://github.com/pygame-community/pygame-ce/pull/2912
"""
line_color = (150, 150, 150)
min_expected_color = (40, 40, 40)
test_points = [[11.2, 8.5], [17.1, 25.7], [35.8, 25.5], [47.6, 41.8]]

surface = pygame.display.set_mode((50, 50))
self.draw_aalines(surface, line_color, False, test_points)

# First line is steep, and other line is not steep
check_color = tuple(surface.get_at((17, 26)))
self.assertGreater(
check_color,
min_expected_color,
"Pixel is missing between steep and non-steep line, pos=(17, 26)",
)

# First line is not steep, and other line is steep
check_color = tuple(surface.get_at((36, 25)))
self.assertGreater(
check_color,
min_expected_color,
"Pixel is missing between non-steep and steep line, pos=(26, 25)",
)


### Polygon Testing ###########################################################

Expand Down

0 comments on commit 9ef9855

Please sign in to comment.