Skip to content

Add draw.aaline width argument #3140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildconfig/stubs/pygame/draw.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def aaline(
color: ColorLike,
start_pos: Point,
end_pos: Point,
width: int = 1,
) -> Rect: ...
def aalines(
surface: Surface,
Expand Down
Binary file modified docs/reST/ref/code_examples/draw_module_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/reST/ref/code_examples/draw_module_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
# 5 pixels wide. Uses (r, g, b) color - medium sea green.
pygame.draw.line(screen, (60, 179, 113), [0, 0], [50, 30], 5)

# Draw on the screen a green antialiased line from (0, 25) to (50, 55)
# 5 pixels wide. Uses (r, g, b) color - medium sea green.
pygame.draw.aaline(screen, (60, 179, 113), [0, 25], [50, 55], 5)

# Draw on the screen a green line from (0, 50) to (50, 80)
# Because it is an antialiased line, it is 1 pixel wide.
# Uses (r, g, b) color - medium sea green.
Expand Down
74 changes: 9 additions & 65 deletions docs/reST/ref/draw.rst
Original file line number Diff line number Diff line change
Expand Up @@ -456,72 +456,10 @@ object around the draw calls (see :func:`pygame.Surface.lock` and

| :sl:`draw a straight antialiased line`
| :sg:`aaline(surface, color, start_pos, end_pos) -> Rect`
:sg:`aaline(surface, color, start_pos, end_pos, width=1) -> Rect`

Draws a straight antialiased line on the given surface.

The line has a thickness of one pixel and the endpoints have a height and
width of one pixel each.

The way a line and its endpoints are drawn:
If both endpoints are equal, only a single pixel is drawn (after
rounding floats to nearest integer).

Otherwise if the line is not steep (i.e. if the length along the x-axis
is greater than the height along the y-axis):

For each endpoint:

If ``x``, the endpoint's x-coordinate, is a whole number find
which pixels would be covered by it and draw them.

Otherwise:

Calculate the position of the nearest point with a whole number
for its x-coordinate, when extending the line past the
endpoint.

Find which pixels would be covered and how much by that point.

If the endpoint is the left one, multiply the coverage by (1 -
the decimal part of ``x``).

Otherwise multiply the coverage by the decimal part of ``x``.

Then draw those pixels.

*e.g.:*
| The left endpoint of the line ``((1, 1.3), (5, 3))`` would
cover 70% of the pixel ``(1, 1)`` and 30% of the pixel
``(1, 2)`` while the right one would cover 100% of the
pixel ``(5, 3)``.
| The left endpoint of the line ``((1.2, 1.4), (4.6, 3.1))``
would cover 56% *(i.e. 0.8 * 70%)* of the pixel ``(1, 1)``
and 24% *(i.e. 0.8 * 30%)* of the pixel ``(1, 2)`` while
the right one would cover 42% *(i.e. 0.6 * 70%)* of the
pixel ``(5, 3)`` and 18% *(i.e. 0.6 * 30%)* of the pixel
``(5, 4)`` while the right

Then for each point between the endpoints, along the line, whose
x-coordinate is a whole number:

Find which pixels would be covered and how much by that point and
draw them.

*e.g.:*
| The points along the line ``((1, 1), (4, 2.5))`` would be
``(2, 1.5)`` and ``(3, 2)`` and would cover 50% of the pixel
``(2, 1)``, 50% of the pixel ``(2, 2)`` and 100% of the pixel
``(3, 2)``.
| The points along the line ``((1.2, 1.4), (4.6, 3.1))`` would
be ``(2, 1.8)`` (covering 20% of the pixel ``(2, 1)`` and 80%
of the pixel ``(2, 2)``), ``(3, 2.3)`` (covering 70% of the
pixel ``(3, 2)`` and 30% of the pixel ``(3, 3)``) and ``(4,
2.8)`` (covering 20% of the pixel ``(2, 1)`` and 80% of the
pixel ``(2, 2)``)

Otherwise do the same for steep lines as for non-steep lines except
along the y-axis instead of the x-axis (using ``y`` instead of ``x``,
top instead of left and bottom instead of right).
Draws a straight antialiased line on the given surface. There are no endcaps.
For thick lines the ends are squared off.

.. note::
Regarding float values for coordinates, a point with coordinate
Expand All @@ -543,6 +481,11 @@ object around the draw calls (see :func:`pygame.Surface.lock` and
:param end_pos: end position of the line, (x, y)
:type end_pos: tuple(int or float, int or float) or
list(int or float, int or float) or Vector2(int or float, int or float)
:param int width: (optional) used for line thickness

| if width >= 1, used for line thickness (default is 1)
| if width < 1, nothing will be drawn
|

:returns: a rect bounding the changed pixels, if nothing is drawn the
bounding rect's position will be the ``start_pos`` parameter value (float
Expand All @@ -555,6 +498,7 @@ object around the draw calls (see :func:`pygame.Surface.lock` and
.. versionchangedold:: 2.0.0 Added support for keyword arguments.
.. versionchanged:: 2.4.0 Removed deprecated 'blend' argument
.. versionchanged:: 2.5.0 ``blend`` argument readded for backcompat, but will always raise a deprecation exception when used
.. versionchanged:: 2.5.2 Added line width

.. ## pygame.draw.aaline ##

Expand Down
2 changes: 1 addition & 1 deletion src_c/doc/draw_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
#define DOC_DRAW_ARC "arc(surface, color, rect, start_angle, stop_angle) -> Rect\narc(surface, color, rect, start_angle, stop_angle, width=1) -> Rect\ndraw an elliptical arc"
#define DOC_DRAW_LINE "line(surface, color, start_pos, end_pos) -> Rect\nline(surface, color, start_pos, end_pos, width=1) -> Rect\ndraw a straight line"
#define DOC_DRAW_LINES "lines(surface, color, closed, points) -> Rect\nlines(surface, color, closed, points, width=1) -> Rect\ndraw multiple contiguous straight line segments"
#define DOC_DRAW_AALINE "aaline(surface, color, start_pos, end_pos) -> Rect\ndraw a straight antialiased line"
#define DOC_DRAW_AALINE "aaline(surface, color, start_pos, end_pos) -> Rect\naaline(surface, color, start_pos, end_pos, width=1) -> Rect\ndraw a straight antialiased line"
#define DOC_DRAW_AALINES "aalines(surface, color, closed, points) -> Rect\ndraw multiple contiguous straight antialiased line segments"
64 changes: 59 additions & 5 deletions src_c/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ draw_line_width(SDL_Surface *surf, Uint32 color, int x1, int y1, int x2,
static void
draw_line(SDL_Surface *surf, int x1, int y1, int x2, int y2, Uint32 color,
int *drawn_area);
void
line_width_corners(float from_x, float from_y, float to_x, float to_y,
int width, float *x1, float *y1, float *x2, float *y2,
float *x3, float *y3, float *x4, float *y4);
static void
draw_aaline(SDL_Surface *surf, Uint32 color, float startx, float starty,
float endx, float endy, int *drawn_area,
Expand Down Expand Up @@ -115,16 +119,17 @@ aaline(PyObject *self, PyObject *arg, PyObject *kwargs)
PyObject *colorobj, *start, *end;
SDL_Surface *surf = NULL;
float startx, starty, endx, endy;
int width = 1; /* Default width. */
PyObject *blend = NULL;
int drawn_area[4] = {INT_MAX, INT_MAX, INT_MIN,
INT_MIN}; /* Used to store bounding box values */
Uint32 color;
static char *keywords[] = {"surface", "color", "start_pos",
"end_pos", "blend", NULL};
static char *keywords[] = {"surface", "color", "start_pos", "end_pos",
"width", "blend", NULL};

if (!PyArg_ParseTupleAndKeywords(arg, kwargs, "O!OOO|O", keywords,
if (!PyArg_ParseTupleAndKeywords(arg, kwargs, "O!OOO|iO", keywords,
&pgSurface_Type, &surfobj, &colorobj,
&start, &end, &blend)) {
&start, &end, &width, &blend)) {
return NULL; /* Exception already set. */
}

Expand Down Expand Up @@ -157,11 +162,27 @@ aaline(PyObject *self, PyObject *arg, PyObject *kwargs)
return RAISE(PyExc_TypeError, "invalid end_pos argument");
}

if (width < 1) {
return pgRect_New4((int)startx, (int)starty, 0, 0);
}

if (!pgSurface_Lock(surfobj)) {
return RAISE(PyExc_RuntimeError, "error locking surface");
}

draw_aaline(surf, color, startx, starty, endx, endy, drawn_area, 0, 0, 0);
if (width > 1) {
float x1, y1, x2, y2, x3, y3, x4, y4;
line_width_corners(startx, starty, endx, endy, width, &x1, &y1, &x2,
&y2, &x3, &y3, &x4, &y4);
draw_line_width(surf, color, (int)startx, (int)starty, (int)endx,
(int)endy, width, drawn_area);
draw_aaline(surf, color, x1, y1, x2, y2, drawn_area, 0, 0, 0);
draw_aaline(surf, color, x3, y3, x4, y4, drawn_area, 0, 0, 0);
}
else {
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 @@ -1836,6 +1857,39 @@ draw_line_width(SDL_Surface *surf, Uint32 color, int x1, int y1, int x2,
}
}

// Calculates 4 points, representing corners of draw_line_width()
// first two points assemble left line and second two - right line
void
line_width_corners(float from_x, float from_y, float to_x, float to_y,
int width, float *x1, float *y1, float *x2, float *y2,
float *x3, float *y3, float *x4, float *y4)
{
float aa_width = (float)width / 2;
float extra_width = (1.0f - (width % 2)) / 2;
int steep = fabs(to_x - from_x) <= fabs(to_y - from_y);

if (steep) {
*x1 = from_x + extra_width + aa_width;
*y1 = from_y;
*x2 = to_x + extra_width + aa_width;
*y2 = to_y;
*x3 = from_x + extra_width - aa_width;
*y3 = from_y;
*x4 = to_x + extra_width - aa_width;
*y4 = to_y;
}
else {
*x1 = from_x;
*y1 = from_y + extra_width + aa_width;
*x2 = to_x;
*y2 = to_y + extra_width + aa_width;
*x3 = from_x;
*y3 = from_y + extra_width - aa_width;
*x4 = to_x;
*y4 = to_y + extra_width - aa_width;
}
}

/* Algorithm modified from
* https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm
*/
Expand Down
Loading
Loading