diff --git a/src_c/draw.c b/src_c/draw.c index d24f84fffd..67febe7a98 100644 --- a/src_c/draw.c +++ b/src_c/draw.c @@ -1042,6 +1042,19 @@ add_line_to_drawn_list(int x1, int y1, int x2, int y2, int *pts) } } +// Prevents double evaluation +inline static int +intmin(int a, int b) +{ + return ((a) < (b) ? (a) : (b)); +} + +inline static int +intmax(int a, int b) +{ + return ((a) > (b) ? (a) : (b)); +} + static int clip_line(SDL_Surface *surf, int *x1, int *y1, int *x2, int *y2, int width, int xinc) @@ -1059,13 +1072,9 @@ clip_line(SDL_Surface *surf, int *x1, int *y1, int *x2, int *y2, int width, top = MIN(*y1, *y2) - width; bottom = MAX(*y1, *y2) + width; } - if (surf->clip_rect.x > right || surf->clip_rect.y > bottom || - surf->clip_rect.x + surf->clip_rect.w <= left || - surf->clip_rect.y + surf->clip_rect.h <= top) { - return 0; - } - - return 1; + return ((surf->clip_rect.x > right) || (surf->clip_rect.y > bottom) || + (surf->clip_rect.x + surf->clip_rect.w <= left) || + (surf->clip_rect.y + surf->clip_rect.h <= top)); } static int @@ -1504,12 +1513,6 @@ static void draw_line_width(SDL_Surface *surf, Uint32 color, int x1, int y1, int x2, int y2, int width, int *drawn_area) { - int dx, dy, err, e2, sx, sy, start_draw, end_draw; - int end_x = surf->clip_rect.x + surf->clip_rect.w - 1; - int end_y = surf->clip_rect.y + surf->clip_rect.h - 1; - int xinc = 0; - int extra_width = 1 - (width % 2); - if (width < 1) return; if (width == 1) { @@ -1517,74 +1520,134 @@ draw_line_width(SDL_Surface *surf, Uint32 color, int x1, int y1, int x2, return; } - width = (width / 2); + int dx, dy, err, e2, sx, sy, start_draw, end_draw, diff, exit; + int end_x = surf->clip_rect.x + surf->clip_rect.w - 1; + int end_y = surf->clip_rect.y + surf->clip_rect.h - 1; + int xinc = 0; + int extra_width = 1 - (width % 2); + width = (width / 2); /* Decide which direction to grow (width/thickness). */ if (abs(x1 - x2) <= abs(y1 - y2)) { /* The line's thickness will be in the x direction. The top/bottom * ends of the line will be flat. */ xinc = 1; } - - if (!clip_line(surf, &x1, &y1, &x2, &y2, width, xinc)) + if (clip_line(surf, &x1, &y1, &x2, &y2, width, xinc)) return; if (x1 == x2 && y1 == y2) { /* Single point */ - start_draw = MAX((x1 - width) + extra_width, surf->clip_rect.x); - end_draw = MIN(end_x, x1 + width); + start_draw = intmax((x1 - width) + extra_width, surf->clip_rect.x); + end_draw = intmin(end_x, x1 + width); if (start_draw <= end_draw) { drawhorzline(surf, color, start_draw, y1, end_draw); add_line_to_drawn_list(start_draw, y1, end_draw, y1, drawn_area); } return; } + // Bresenham's line algorithm dx = abs(x2 - x1); - dy = abs(y2 - y1); + dy = -abs(y2 - y1); sx = x2 > x1 ? 1 : -1; sy = y2 > y1 ? 1 : -1; - err = (dx > dy ? dx : -dy) / 2; + err = dx + dy; + // If line is more vertical than horizontal if (xinc) { - while (y1 != (y2 + sy)) { - if (surf->clip_rect.y <= y1 && y1 <= end_y) { - start_draw = - MAX((x1 - width) + extra_width, surf->clip_rect.x); - end_draw = MIN(end_x, x1 + width); - if (start_draw <= end_draw) { - drawhorzline(surf, color, start_draw, y1, end_draw); - add_line_to_drawn_list(start_draw, y1, end_draw, y1, - drawn_area); - } + drawn_area[0] = + intmax((intmin(x1, x2) - width) + extra_width, surf->clip_rect.x); + drawn_area[1] = intmax(intmin(y1, y2), surf->clip_rect.y); + drawn_area[2] = intmin(intmax(x1, x2) + width, end_x); + drawn_area[3] = intmin(intmax(y1, y2), end_y); + // Set exit to y value of where line will leave surface + // Set diff to difference between starting y coordinate and the y value + // of the line's entry point to the surface + + if (y2 > y1) { + exit = intmin(end_y + 1, y2 + sy); + diff = surf->clip_rect.y - y1; + } + else { + exit = intmax(surf->clip_rect.y - 1, y2 + sy); + diff = y1 - end_y; + } + // If line starts outside of surface + if (diff > 0) { + // Set y1 to entry y point + y1 += diff * sy; + // Adjust err by dx for the change in the y axis + err += diff * dx; + // Calculate change in x value (x = y/m), uses ceil for consistency + // between positive/negative values + diff = (int)ceil((diff * dx) / (float)-dy); + x1 += diff * sx; + // Adjust err value to correct for change in x axis + err += diff * dy; + } + // Continue through normal Bresenham's line algorithm iteration + while (y1 != exit) { + start_draw = intmax((x1 - width) + extra_width, surf->clip_rect.x); + end_draw = intmin(end_x, x1 + width); + if (start_draw <= end_draw) { + drawhorzline(surf, color, start_draw, y1, end_draw); } - e2 = err; - if (e2 > -dx) { - err -= dy; + + e2 = err * 2; + if (e2 >= dy) { + err += dy; x1 += sx; } - if (e2 < dy) { + if (e2 <= dx) { err += dx; y1 += sy; } } } else { - while (x1 != (x2 + sx)) { - if (surf->clip_rect.x <= x1 && x1 <= end_x) { - start_draw = - MAX((y1 - width) + extra_width, surf->clip_rect.y); - end_draw = MIN(end_y, y1 + width); - if (start_draw <= end_draw) { - drawvertline(surf, color, start_draw, x1, end_draw); - add_line_to_drawn_list(x1, start_draw, x1, end_draw, - drawn_area); - } + drawn_area[0] = intmax(intmin(x1, x2), surf->clip_rect.x); + drawn_area[1] = + intmax((intmin(y1, y2) - width) + extra_width, surf->clip_rect.y); + drawn_area[2] = intmin(MAX(x1, x2), end_x); + drawn_area[3] = intmin(intmax(y1, y2) + width, end_y); + // Set exit to x value of where line will leave surface + // Set diff to difference between starting x coordinate and the x value + // of the line's entry point to the surface + if (x2 > x1) { + diff = surf->clip_rect.x - x1; + exit = intmin(end_x + 1, x2 + sx); + } + else { + diff = x1 - end_x; + exit = intmax(surf->clip_rect.x - 1, x2 + sx); + } + // If line starts outside of surface + if (diff > 0) { + // Set x1 to entry x point + x1 += diff * sx; + // Adjust err by dy for the change in the x axis + err += diff * dy; + // Calculate change in y value (y = mx), uses ceil for consistency + // between positive/negative values + diff = (int)ceil((diff * -dy) / (float)dx); + y1 += diff * sy; + // Adjust err value to correct for change in y axis + err += diff * dx; + } + + // Continue through normal Bresenham's line algorithm iteration + while (x1 != exit) { + start_draw = intmax((y1 - width) + extra_width, surf->clip_rect.y); + end_draw = intmin(end_y, y1 + width); + if (start_draw <= end_draw) { + drawvertline(surf, color, start_draw, x1, end_draw); } - e2 = err; - if (e2 > -dx) { - err -= dy; + + e2 = err * 2; + if (e2 >= dy) { + err += dy; x1 += sx; } - if (e2 < dy) { + if (e2 <= dx) { err += dx; y1 += sy; } @@ -1599,41 +1662,165 @@ static void draw_line(SDL_Surface *surf, int x1, int y1, int x2, int y2, Uint32 color, int *drawn_area) { - int dx, dy, err, e2, sx, sy; + int dx, dy, err, e2, sx, sy, diff, exit, end; + int xinc = 0; if (x1 == x2 && y1 == y2) { /* Single point */ set_and_check_rect(surf, x1, y1, color, drawn_area); return; } + // Determine if the line is more vertical or horizontal for clipping + // purposes + if (abs(x1 - x2) <= abs(y1 - y2)) { + xinc = 1; + } + + if (clip_line(surf, &x1, &y1, &x2, &y2, 0, xinc)) + return; + if (y1 == y2) { /* Horizontal line */ - dx = (x1 < x2) ? 1 : -1; - for (sx = 0; sx <= abs(x1 - x2); sx++) { - set_and_check_rect(surf, x1 + dx * sx, y1, color, drawn_area); + if (x1 < x2) { + drawhorzline( + surf, color, intmax(x1, surf->clip_rect.x), y1, + intmin(x2, surf->clip_rect.x + surf->clip_rect.w - 1)); + add_line_to_drawn_list( + intmax(x1, surf->clip_rect.x), y1, + intmin(x2, surf->clip_rect.x + surf->clip_rect.w - 1), y1, + drawn_area); + } + else { + drawhorzline( + surf, color, intmax(x2, surf->clip_rect.x), y1, + intmin(x1, surf->clip_rect.x + surf->clip_rect.w - 1)); + add_line_to_drawn_list( + intmax(x2, surf->clip_rect.x), y1, + intmin(x1, surf->clip_rect.x + surf->clip_rect.w - 1), y1, + drawn_area); } - return; } if (x1 == x2) { /* Vertical line */ - dy = (y1 < y2) ? 1 : -1; - for (sy = 0; sy <= abs(y1 - y2); sy++) - set_and_check_rect(surf, x1, y1 + dy * sy, color, drawn_area); + if (y1 < y2) { + drawvertline( + surf, color, intmax(y1, surf->clip_rect.y), x1, + intmin(y2, surf->clip_rect.y + surf->clip_rect.h - 1)); + add_line_to_drawn_list( + x1, intmax(y1, surf->clip_rect.y), x1, + intmin(y2, surf->clip_rect.y + surf->clip_rect.h - 1), + drawn_area); + } + else { + drawvertline( + surf, color, intmax(y2, surf->clip_rect.y), x1, + intmin(y1, surf->clip_rect.y + surf->clip_rect.h - 1)); + add_line_to_drawn_list( + x1, intmax(y2, surf->clip_rect.y), x1, + intmin(y1, surf->clip_rect.y + surf->clip_rect.h - 1), + drawn_area); + } return; } - dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; - dy = abs(y2 - y1), sy = y1 < y2 ? 1 : -1; - err = (dx > dy ? dx : -dy) / 2; - while (x1 != x2 || y1 != y2) { - set_and_check_rect(surf, x1, y1, color, drawn_area); - e2 = err; - if (e2 > -dx) { - err -= dy; - x1 += sx; + drawn_area[0] = intmax(intmin(x1, x2), surf->clip_rect.x); + drawn_area[1] = intmax(intmin(y1, y2), surf->clip_rect.y); + drawn_area[2] = + intmin(intmax(x1, x2), surf->clip_rect.x + surf->clip_rect.w - 1); + drawn_area[3] = + intmin(intmax(y1, y2), surf->clip_rect.y + surf->clip_rect.h - 1); + dx = abs(x2 - x1); + dy = -abs(y2 - y1); + sx = x2 > x1 ? 1 : -1; + sy = y2 > y1 ? 1 : -1; + err = dx + dy; + // If line is more vertical than horizontal + if (xinc) { + end = y2 + sy; + // Set exit to y value of where line will leave surface + // Set diff to difference between starting y coordinate and the y value + // of the line's entry point to the surface + if (y2 > y1) { + exit = surf->clip_rect.y + surf->clip_rect.h; + diff = surf->clip_rect.y - y1; + } + else { + exit = surf->clip_rect.y - 1; + diff = y1 - (surf->clip_rect.y + surf->clip_rect.h - 1); + } + // If line starts outside of surface + if (diff > 0) { + // Set y1 to entry y point + y1 += diff * sy; + // Adjust err by dx for the change in the y axis + err += diff * dx; + // Calculate change in x value (x = y/m), uses ceil for consistency + // between positive/negative values + diff = (int)ceil(((float)diff * dx) / (float)-dy); + x1 += diff * sx; + // Adjust err value to correct for change in x axis + err += diff * dy; + } + + // Continue through normal Bresenham's line algorithm iteration + while (y1 != end) { + if (y1 != exit) { + set_at(surf, x1, y1, color); + } + else + break; + e2 = err * 2; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } + } + } + else { + end = x2 + sx; + // Set exit to x value of where line will leave surface + // Set diff to difference between starting x coordinate and the x value + // of the line's entry point to the surface + if (x2 > x1) { + diff = surf->clip_rect.x - x1; + exit = surf->clip_rect.x + surf->clip_rect.w; } - if (e2 < dy) { - err += dx; - y1 += sy; + else { + diff = x1 - (surf->clip_rect.x + surf->clip_rect.w - 1); + exit = surf->clip_rect.x - 1; + } + // If line starts outside of surface + if (diff > 0) { + // Set x1 to entry x point + x1 += diff * sx; + // Adjust err by dy for the change in the x axis + err += diff * dy; + // Calculate change in y value (y = mx), uses ceil for consistency + // between positive/negative values + diff = (int)ceil(((float)diff * -dy) / (float)dx); + y1 += diff * sy; + // Adjust err value to correct for change in y axis + err += diff * dx; + } + + // Continue through normal Bresenham's line algorithm iteration + while (x1 != end) { + if (x1 != exit) { + set_at(surf, x1, y1, color); + } + else + break; + e2 = err * 2; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } } } - set_and_check_rect(surf, x2, y2, color, drawn_area); } static int @@ -2633,7 +2820,8 @@ draw_fillpoly(SDL_Surface *surf, int *point_x, int *point_y, * 3. each two x-coordinates in x_intersect are then inside the polygon * (draw line for a pair of two such points) */ - for (y = miny; (y <= maxy); y++) { + for (y = MAX(miny, surf->clip_rect.y); + (y <= MIN(maxy, surf->clip_rect.y + surf->clip_rect.h)); y++) { // n_intersections is the number of intersections with the polygon int n_intersections = 0; for (i = 0; (i < num_points); i++) { diff --git a/test/draw_test.py b/test/draw_test.py index 4429740b28..f6873f8344 100644 --- a/test/draw_test.py +++ b/test/draw_test.py @@ -1548,57 +1548,6 @@ def test_line__gaps_with_thickness(self): pos = (x, y + ((x + 2) // 5)) self.assertEqual(surface.get_at(pos), expected_color, f"pos={pos}") - def test_line__bounding_rect(self): - """Ensures draw line returns the correct bounding rect. - - Test lines with endpoints on and off the surface and a range of - width/thickness values. - """ - - line_color = pygame.Color("red") - surf_color = pygame.Color("black") - width = height = 30 - # Using a rect to help manage where the lines are drawn. - helper_rect = pygame.Rect((0, 0), (width, height)) - - # Testing surfaces of different sizes. One larger than the helper_rect - # and one smaller (to test lines that span the surface). - for size in ((width + 5, height + 5), (width - 5, height - 5)): - surface = pygame.Surface(size, 0, 32) - surf_rect = surface.get_rect() - - # Move the helper rect to different positions to test line - # endpoints on and off the surface. - for pos in rect_corners_mids_and_center(surf_rect): - helper_rect.center = pos - - # Draw using different thicknesses. - for thickness in range(-1, 5): - for start, end in self._rect_lines(helper_rect): - surface.fill(surf_color) # Clear for each test. - - bounding_rect = self.draw_line( - surface, line_color, start, end, thickness - ) - - if 0 < thickness: - # Calculating the expected_rect after the line is - # drawn (it uses what is actually drawn). - expected_rect = create_bounding_rect( - surface, surf_color, start - ) - else: - # Nothing drawn. - expected_rect = pygame.Rect(start, (0, 0)) - - self.assertEqual( - bounding_rect, - expected_rect, - "start={}, end={}, size={}, thickness={}".format( - start, end, size, thickness - ), - ) - def test_line__surface_clip(self): """Ensures draw line respects a surface's clip area.""" surfw = surfh = 30 @@ -2324,53 +2273,6 @@ def test_lines__gaps_with_thickness(self): pos = (x_right + t, y) self.assertEqual(surface.get_at(pos), expected_color, f"pos={pos}") - def test_lines__bounding_rect(self): - """Ensures draw lines returns the correct bounding rect. - - Test lines with endpoints on and off the surface and a range of - width/thickness values. - """ - line_color = pygame.Color("red") - surf_color = pygame.Color("black") - width = height = 30 - # Using a rect to help manage where the lines are drawn. - pos_rect = pygame.Rect((0, 0), (width, height)) - - # Testing surfaces of different sizes. One larger than the pos_rect - # and one smaller (to test lines that span the surface). - for size in ((width + 5, height + 5), (width - 5, height - 5)): - surface = pygame.Surface(size, 0, 32) - surf_rect = surface.get_rect() - - # Move pos_rect to different positions to test line endpoints on - # and off the surface. - for pos in rect_corners_mids_and_center(surf_rect): - pos_rect.center = pos - # Shape: Triangle (if closed), ^ caret (if not closed). - pts = (pos_rect.midleft, pos_rect.midtop, pos_rect.midright) - pos = pts[0] # Rect position if nothing drawn. - - # Draw using different thickness and closed values. - for thickness in range(-1, 5): - for closed in (True, False): - surface.fill(surf_color) # Clear for each test. - - bounding_rect = self.draw_lines( - surface, line_color, closed, pts, thickness - ) - - if 0 < thickness: - # Calculating the expected_rect after the lines are - # drawn (it uses what is actually drawn). - expected_rect = create_bounding_rect( - surface, surf_color, pos - ) - else: - # Nothing drawn. - expected_rect = pygame.Rect(pos, (0, 0)) - - self.assertEqual(bounding_rect, expected_rect) - def test_lines__surface_clip(self): """Ensures draw lines respects a surface's clip area.""" surfw = surfh = 30 @@ -4111,61 +4013,6 @@ def test_invalid_points(self): ), ) - def test_polygon__bounding_rect(self): - """Ensures draw polygon returns the correct bounding rect. - - Tests polygons on and off the surface and a range of width/thickness - values. - """ - polygon_color = pygame.Color("red") - surf_color = pygame.Color("black") - min_width = min_height = 5 - max_width = max_height = 7 - sizes = ((min_width, min_height), (max_width, max_height)) - surface = pygame.Surface((20, 20), 0, 32) - surf_rect = surface.get_rect() - # Make a rect that is bigger than the surface to help test drawing - # polygons off and partially off the surface. - big_rect = surf_rect.inflate(min_width * 2 + 1, min_height * 2 + 1) - - for pos in rect_corners_mids_and_center( - surf_rect - ) + rect_corners_mids_and_center(big_rect): - # A rect (pos_rect) is used to help create and position the - # polygon. Each of this rect's position attributes will be set to - # the pos value. - for attr in RECT_POSITION_ATTRIBUTES: - # Test using different rect sizes and thickness values. - for width, height in sizes: - pos_rect = pygame.Rect((0, 0), (width, height)) - setattr(pos_rect, attr, pos) - # Points form a triangle with no fully - # horizontal/vertical lines. - vertices = ( - pos_rect.midleft, - pos_rect.midtop, - pos_rect.bottomright, - ) - - for thickness in range(4): - surface.fill(surf_color) # Clear for each test. - - bounding_rect = self.draw_polygon( - surface, polygon_color, vertices, thickness - ) - - # Calculating the expected_rect after the polygon - # is drawn (it uses what is actually drawn). - expected_rect = create_bounding_rect( - surface, surf_color, vertices[0] - ) - - self.assertEqual( - bounding_rect, - expected_rect, - f"thickness={thickness}", - ) - def test_polygon__surface_clip(self): """Ensures draw polygon respects a surface's clip area. diff --git a/testingfile.py b/testingfile.py new file mode 100644 index 0000000000..02bf8cf25e --- /dev/null +++ b/testingfile.py @@ -0,0 +1,118 @@ +from sys import stdout +from pstats import Stats +from cProfile import Profile + + +import pygame + +pygame.init() + + +def draw_line_short_inside(): + lines = [ + ((256, 256), (0, 0)), + ((128, 256), (128, 0)), + ((50, 256), (128, 0)), + ((0, 240), (12, 56)), + ((50, 40), (60, 11)), + ] + widths = [1, 2, 4, 6, 20] + surf = pygame.Surface((256, 256), pygame.SRCALPHA, 32) + surf.fill((255, 255, 255, 255)) + for iterations in range(0, 10000): + for line in lines: + for width in widths: + pygame.draw.line( + surf, pygame.Color("red"), line[0], line[1], width=width + ) + + +def draw_line_long_inside(): + lines = [ + ((1256, 256), (0, 0)), + ((1128, 256), (128, 0)), + ((150, 1256), (128, 0)), + ((0, 240), (1200, 56)), + ((50, 40), (1060, 11)), + ] + widths = [1, 2, 4, 6, 20] + surf = pygame.Surface((2048, 2048), pygame.SRCALPHA, 32) + surf.fill((255, 255, 255, 255)) + for iterations in range(0, 10000): + for line in lines: + for width in widths: + pygame.draw.line( + surf, pygame.Color("red"), line[0], line[1], width=width + ) + + +def draw_line_short_outside(): + lines = [ + ((256, 256), (0, 0)), + ((128, 256), (128, 0)), + ((50, 256), (128, 0)), + ((0, 240), (12, 56)), + ((50, 40), (80, 11)), + ] + widths = [1, 2, 4, 6, 20] + surf = pygame.Surface((64, 64), pygame.SRCALPHA, 32) + surf.fill((255, 255, 255, 255)) + for iterations in range(0, 10000): + for line in lines: + for width in widths: + pygame.draw.line( + surf, pygame.Color("red"), line[0], line[1], width=width + ) + + +def draw_line_long_outside(): + lines = [ + ((2256, 256), (0, 0)), + ((2128, 256), (128, 0)), + ((150, 4256), (128, 0)), + ((0, 240), (2200, 56)), + ((50, 40), (4060, 11)), + ] + widths = [1, 2, 4, 6, 20] + surf = pygame.Surface((1024, 1024), pygame.SRCALPHA, 32) + surf.fill((255, 255, 255, 255)) + for iterations in range(0, 10000): + for line in lines: + for width in widths: + pygame.draw.line( + surf, pygame.Color("red"), line[0], line[1], width=width + ) + + +if __name__ == "__main__": + print("Draw Line - short, inside surface") + profiler = Profile() + profiler.runcall(draw_line_short_inside) + stats = Stats(profiler, stream=stdout) + stats.strip_dirs() + stats.sort_stats("cumulative") + stats.print_stats() + + print("\nDraw Line - long, inside surface") + profiler = Profile() + profiler.runcall(draw_line_long_inside) + stats = Stats(profiler, stream=stdout) + stats.strip_dirs() + stats.sort_stats("cumulative") + stats.print_stats() + + print("\nDraw Line - short, edges outside surface") + profiler = Profile() + profiler.runcall(draw_line_short_outside) + stats = Stats(profiler, stream=stdout) + stats.strip_dirs() + stats.sort_stats("cumulative") + stats.print_stats() + + print("\nDraw Line - long, edges a long way outside surface") + profiler = Profile() + profiler.runcall(draw_line_long_outside) + stats = Stats(profiler, stream=stdout) + stats.strip_dirs() + stats.sort_stats("cumulative") + stats.print_stats()