Skip to content

Commit 9ef9855

Browse files
authored
Merge pull request #2912 from mzivic7/fix_aalines_overlap
Fix aalines overlap
2 parents 3076513 + a94e88d commit 9ef9855

File tree

2 files changed

+195
-56
lines changed

2 files changed

+195
-56
lines changed

src_c/draw.c

Lines changed: 141 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ draw_line(SDL_Surface *surf, int x1, int y1, int x2, int y2, Uint32 color,
4646
int *drawn_area);
4747
static void
4848
draw_aaline(SDL_Surface *surf, Uint32 color, float startx, float starty,
49-
float endx, float endy, int *drawn_area);
49+
float endx, float endy, int *drawn_area,
50+
int disable_first_endpoint, int disable_second_endpoint,
51+
int extra_pixel_for_aalines);
5052
static void
5153
draw_arc(SDL_Surface *surf, int x_center, int y_center, int radius1,
5254
int radius2, int width, double angle_start, double angle_stop,
@@ -159,7 +161,7 @@ aaline(PyObject *self, PyObject *arg, PyObject *kwargs)
159161
return RAISE(PyExc_RuntimeError, "error locking surface");
160162
}
161163

162-
draw_aaline(surf, color, startx, starty, endx, endy, drawn_area);
164+
draw_aaline(surf, color, startx, starty, endx, endy, drawn_area, 0, 0, 0);
163165

164166
if (!pgSurface_Unlock(surfobj)) {
165167
return RAISE(PyExc_RuntimeError, "error unlocking surface");
@@ -255,9 +257,13 @@ aalines(PyObject *self, PyObject *arg, PyObject *kwargs)
255257
SDL_Surface *surf = NULL;
256258
Uint32 color;
257259
float pts[4];
260+
float pts_prev[4];
258261
float *xlist, *ylist;
259262
float x, y;
260263
int l, t;
264+
int extra_px;
265+
int steep_prev;
266+
int steep_curr;
261267
PyObject *blend = NULL;
262268
int drawn_area[4] = {INT_MAX, INT_MAX, INT_MIN,
263269
INT_MIN}; /* Used to store bounding box values */
@@ -344,19 +350,84 @@ aalines(PyObject *self, PyObject *arg, PyObject *kwargs)
344350
return RAISE(PyExc_RuntimeError, "error locking surface");
345351
}
346352

347-
for (loop = 1; loop < length; ++loop) {
353+
/* first line - if open, add endpoint pixels.*/
354+
pts[0] = xlist[0];
355+
pts[1] = ylist[0];
356+
pts[2] = xlist[1];
357+
pts[3] = ylist[1];
358+
359+
/* Previous points.
360+
* Used to compare previous and current line.*/
361+
pts_prev[0] = pts[0];
362+
pts_prev[1] = pts[1];
363+
pts_prev[2] = pts[2];
364+
pts_prev[3] = pts[3];
365+
steep_prev =
366+
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
367+
steep_curr = fabs(xlist[2] - pts[2]) < fabs(ylist[2] - pts[1]);
368+
extra_px = steep_prev > steep_curr;
369+
if (closed) {
370+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
371+
1, extra_px);
372+
}
373+
else {
374+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 0,
375+
1, extra_px);
376+
}
377+
378+
for (loop = 2; loop < length - 1; ++loop) {
348379
pts[0] = xlist[loop - 1];
349380
pts[1] = ylist[loop - 1];
350381
pts[2] = xlist[loop];
351382
pts[3] = ylist[loop];
352-
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area);
383+
384+
/* Comparing previous and current line.
385+
* If one is steep and other is not, extra pixel must be drawn.*/
386+
steep_prev =
387+
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
388+
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
389+
extra_px = steep_prev != steep_curr;
390+
pts_prev[0] = pts[0];
391+
pts_prev[1] = pts[1];
392+
pts_prev[2] = pts[2];
393+
pts_prev[3] = pts[3];
394+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
395+
1, extra_px);
396+
}
397+
398+
/* Last line - if open, add endpoint pixels. */
399+
pts[0] = xlist[length - 2];
400+
pts[1] = ylist[length - 2];
401+
pts[2] = xlist[length - 1];
402+
pts[3] = ylist[length - 1];
403+
steep_prev =
404+
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
405+
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
406+
extra_px = steep_prev != steep_curr;
407+
pts_prev[0] = pts[0];
408+
pts_prev[1] = pts[1];
409+
pts_prev[2] = pts[2];
410+
pts_prev[3] = pts[3];
411+
if (closed) {
412+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
413+
1, extra_px);
353414
}
415+
else {
416+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
417+
0, extra_px);
418+
}
419+
354420
if (closed && length > 2) {
355421
pts[0] = xlist[length - 1];
356422
pts[1] = ylist[length - 1];
357423
pts[2] = xlist[0];
358424
pts[3] = ylist[0];
359-
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area);
425+
steep_prev =
426+
fabs(pts_prev[2] - pts_prev[0]) < fabs(pts_prev[3] - pts_prev[1]);
427+
steep_curr = fabs(pts[2] - pts[0]) < fabs(pts[3] - pts[1]);
428+
extra_px = steep_prev != steep_curr;
429+
draw_aaline(surf, color, pts[0], pts[1], pts[2], pts[3], drawn_area, 1,
430+
1, extra_px);
360431
}
361432

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

12771348
static void
12781349
draw_aaline(SDL_Surface *surf, Uint32 color, float from_x, float from_y,
1279-
float to_x, float to_y, int *drawn_area)
1350+
float to_x, float to_y, int *drawn_area,
1351+
int disable_first_endpoint, int disable_second_endpoint,
1352+
int extra_pixel_for_aalines)
12801353
{
12811354
float gradient, dx, dy, intersect_y, brightness;
12821355
int x, x_pixel_start, x_pixel_end;
@@ -1379,68 +1452,80 @@ draw_aaline(SDL_Surface *surf, Uint32 color, float from_x, float from_y,
13791452

13801453
/* Handle endpoints separately.
13811454
* The line is not a mathematical line of thickness zero. The same
1382-
* goes for the endpoints. The have a height and width of one pixel. */
1455+
* goes for the endpoints. The have a height and width of one pixel.
1456+
* Extra pixel drawing is requested externally from aalines.
1457+
* It is drawn only when one line is steep and other is not.*/
13831458
/* First endpoint */
1384-
x_pixel_start = (int)from_x;
1385-
y_endpoint = intersect_y = from_y + gradient * (x_pixel_start - from_x);
1386-
if (to_x > clip_left + 1.0f) {
1387-
x_gap = 1 + x_pixel_start - from_x;
1388-
brightness = y_endpoint - (int)y_endpoint;
1389-
if (steep) {
1390-
x = (int)y_endpoint;
1391-
y = x_pixel_start;
1392-
}
1393-
else {
1394-
x = x_pixel_start;
1395-
y = (int)y_endpoint;
1396-
}
1397-
if ((int)y_endpoint < y_endpoint) {
1459+
if (!disable_first_endpoint || extra_pixel_for_aalines) {
1460+
x_pixel_start = (int)from_x;
1461+
y_endpoint = intersect_y =
1462+
from_y + gradient * (x_pixel_start - from_x);
1463+
if (to_x > clip_left + 1.0f) {
1464+
x_gap = 1 + x_pixel_start - from_x;
1465+
brightness = y_endpoint - (int)y_endpoint;
1466+
if (steep) {
1467+
x = (int)y_endpoint;
1468+
y = x_pixel_start;
1469+
}
1470+
else {
1471+
x = x_pixel_start;
1472+
y = (int)y_endpoint;
1473+
}
1474+
if ((int)y_endpoint < y_endpoint) {
1475+
pixel_color = get_antialiased_color(surf, x, y, color,
1476+
brightness * x_gap);
1477+
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
1478+
}
1479+
if (steep) {
1480+
x--;
1481+
}
1482+
else {
1483+
y--;
1484+
}
1485+
brightness = 1 - brightness;
13981486
pixel_color =
13991487
get_antialiased_color(surf, x, y, color, brightness * x_gap);
14001488
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
1489+
intersect_y += gradient;
1490+
x_pixel_start++;
14011491
}
1402-
if (steep) {
1403-
x--;
1404-
}
1405-
else {
1406-
y--;
1407-
}
1408-
brightness = 1 - brightness;
1409-
pixel_color =
1410-
get_antialiased_color(surf, x, y, color, brightness * x_gap);
1411-
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
1412-
intersect_y += gradient;
1413-
x_pixel_start++;
1492+
}
1493+
/* To be sure main loop skips first endpoint.*/
1494+
if (disable_first_endpoint) {
1495+
x_pixel_start = (int)ceil(from_x);
1496+
intersect_y = from_y + gradient * (x_pixel_start - from_x);
14141497
}
14151498
/* Second endpoint */
14161499
x_pixel_end = (int)ceil(to_x);
1417-
if (from_x < clip_right - 1.0f) {
1418-
y_endpoint = to_y + gradient * (x_pixel_end - to_x);
1419-
x_gap = 1 - x_pixel_end + to_x;
1420-
brightness = y_endpoint - (int)y_endpoint;
1421-
if (steep) {
1422-
x = (int)y_endpoint;
1423-
y = x_pixel_end;
1424-
}
1425-
else {
1426-
x = x_pixel_end;
1427-
y = (int)y_endpoint;
1428-
}
1429-
if ((int)y_endpoint < y_endpoint) {
1500+
if (!disable_second_endpoint || extra_pixel_for_aalines) {
1501+
if (from_x < clip_right - 1.0f) {
1502+
y_endpoint = to_y + gradient * (x_pixel_end - to_x);
1503+
x_gap = 1 - x_pixel_end + to_x;
1504+
brightness = y_endpoint - (int)y_endpoint;
1505+
if (steep) {
1506+
x = (int)y_endpoint;
1507+
y = x_pixel_end;
1508+
}
1509+
else {
1510+
x = x_pixel_end;
1511+
y = (int)y_endpoint;
1512+
}
1513+
if ((int)y_endpoint < y_endpoint) {
1514+
pixel_color = get_antialiased_color(surf, x, y, color,
1515+
brightness * x_gap);
1516+
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
1517+
}
1518+
if (steep) {
1519+
x--;
1520+
}
1521+
else {
1522+
y--;
1523+
}
1524+
brightness = 1 - brightness;
14301525
pixel_color =
14311526
get_antialiased_color(surf, x, y, color, brightness * x_gap);
14321527
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
14331528
}
1434-
if (steep) {
1435-
x--;
1436-
}
1437-
else {
1438-
y--;
1439-
}
1440-
brightness = 1 - brightness;
1441-
pixel_color =
1442-
get_antialiased_color(surf, x, y, color, brightness * x_gap);
1443-
set_and_check_rect(surf, x, y, pixel_color, drawn_area);
14441529
}
14451530

14461531
/* main line drawing loop */

test/draw_test.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3646,6 +3646,60 @@ class DrawAALinesTest(AALinesMixin, DrawTestCase):
36463646
class to add any draw.aalines specific tests to.
36473647
"""
36483648

3649+
def test_aalines__overlap(self):
3650+
"""Ensures that two adjacent antialiased lines are not overlapping.
3651+
3652+
Draws two lines, and checks if 2 pixels, at shared point between those
3653+
two lines, are too bright.
3654+
3655+
See: https://github.com/pygame-community/pygame-ce/pull/2912
3656+
"""
3657+
line_color = (150, 150, 150)
3658+
max_expected_colors = ((70, 70, 70), (100, 100, 100))
3659+
test_points = [[20.1, 25.4], [25.1, 25.6], [30.1, 25.8]]
3660+
3661+
surface = pygame.display.set_mode((50, 50))
3662+
self.draw_aalines(surface, line_color, False, test_points)
3663+
3664+
for i, y in enumerate((25, 26)):
3665+
check_color = tuple(surface.get_at((25, y)))
3666+
self.assertLess(
3667+
check_color,
3668+
max_expected_colors[i],
3669+
f"aalines are overlapping, pos={(25, y)}",
3670+
)
3671+
3672+
def test_aalines__steep_missing_pixel(self):
3673+
"""Ensures there are no missing pixels between steep and non-steep lines.
3674+
3675+
Draws two adjacent lines: first is not steep and second is, then
3676+
checks if there is missing pixel at shared point between those two lines.
3677+
3678+
See: https://github.com/pygame-community/pygame-ce/pull/2912
3679+
"""
3680+
line_color = (150, 150, 150)
3681+
min_expected_color = (40, 40, 40)
3682+
test_points = [[11.2, 8.5], [17.1, 25.7], [35.8, 25.5], [47.6, 41.8]]
3683+
3684+
surface = pygame.display.set_mode((50, 50))
3685+
self.draw_aalines(surface, line_color, False, test_points)
3686+
3687+
# First line is steep, and other line is not steep
3688+
check_color = tuple(surface.get_at((17, 26)))
3689+
self.assertGreater(
3690+
check_color,
3691+
min_expected_color,
3692+
"Pixel is missing between steep and non-steep line, pos=(17, 26)",
3693+
)
3694+
3695+
# First line is not steep, and other line is steep
3696+
check_color = tuple(surface.get_at((36, 25)))
3697+
self.assertGreater(
3698+
check_color,
3699+
min_expected_color,
3700+
"Pixel is missing between non-steep and steep line, pos=(26, 25)",
3701+
)
3702+
36493703

36503704
### Polygon Testing ###########################################################
36513705

0 commit comments

Comments
 (0)