diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 680a68c302b..5aedd47fa95 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -625,18 +625,6 @@ def test_point(points: Coords) -> None: assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") -def test_point_I16() -> None: - # Arrange - im = Image.new("I;16", (1, 1)) - draw = ImageDraw.Draw(im) - - # Act - draw.point((0, 0), fill=0x1234) - - # Assert - assert im.getpixel((0, 0)) == 0x1234 - - @pytest.mark.parametrize("points", POINTS) def test_polygon(points: Coords) -> None: # Arrange @@ -650,19 +638,6 @@ def test_polygon(points: Coords) -> None: assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -@pytest.mark.parametrize("points", POINTS) -def test_polygon_width_I16(points: Coords) -> None: - # Arrange - im = Image.new("I;16", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.polygon(points, outline=0xFFFF, width=2) - - # Assert - assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon_width_I.tiff") - - @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("kite_points", KITE_POINTS) def test_polygon_kite( @@ -791,20 +766,6 @@ def test_rectangle_zero_width(bbox: Coords) -> None: assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png") -@pytest.mark.parametrize("bbox", BBOX) -def test_rectangle_I16(bbox: Coords) -> None: - # Arrange - im = Image.new("I;16", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(bbox, outline=0xCDEF) - - # Assert - assert im.getpixel((X0, Y0)) == 0xCDEF - assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff") - - @pytest.mark.parametrize("bbox", BBOX) def test_rectangle_translucent_outline(bbox: Coords) -> None: # Arrange @@ -1169,6 +1130,29 @@ def test_line_horizontal() -> None: ) +def test_line_horizontal_w1px_direction() -> None: + # Drawing right-to-left must paint the same pixels as left-to-right. + im_lr = Image.new("L", (20, 3)) + ImageDraw.Draw(im_lr).line((2, 1, 17, 1), fill=255) + assert [im_lr.getpixel((x, 1)) for x in range(20)] == [ + 255 if 2 <= x <= 17 else 0 for x in range(20) + ] + + im_rl = Image.new("L", (20, 3)) + ImageDraw.Draw(im_rl).line((17, 1, 2, 1), fill=255) + assert_image_equal(im_rl, im_lr) + + +def test_line_joints_blend_once() -> None: + # Pixels shared by consecutive polyline segments + # (and the final endpoint, which is drawn separately) + # must be blended exactly once. + im = Image.new("RGB", (20, 3)) + draw = ImageDraw.Draw(im, "RGBA") + draw.line([(0, 1), (10, 1), (19, 1)], fill=(255, 255, 255, 128)) + assert {im.getpixel((x, 1)) for x in range(20)} == {(128, 128, 128)} + + @pytest.mark.xfail(reason="failing test") def test_line_h_s1_w2() -> None: img, draw = create_base_image_draw((20, 20)) diff --git a/Tests/test_imagedraw_i16.py b/Tests/test_imagedraw_i16.py new file mode 100644 index 00000000000..b99d0589a30 --- /dev/null +++ b/Tests/test_imagedraw_i16.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +import pytest + +from PIL import Image, ImageDraw +from Tests.helper import assert_image_equal_tofile +from Tests.test_imagedraw import BBOX, POINTS, X0, Y0, H, W + +TYPE_CHECKING = False + +if TYPE_CHECKING: + from PIL._typing import Coords + +I16_MODES = ("I;16", "I;16L", "I;16B", "I;16N") +I16_INK = 0x1234 + + +def create_I16_image_draw(mode: str) -> tuple[Image.Image, ImageDraw.ImageDraw]: + img = Image.new(mode, (8, 8)) + return img, ImageDraw.Draw(img) + + +@pytest.mark.parametrize("points", POINTS) +def test_polygon_width_I16(points: Coords) -> None: + # Arrange + im = Image.new("I;16", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.polygon(points, outline=0xFFFF, width=2) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon_width_I.tiff") + + +@pytest.mark.parametrize("bbox", BBOX) +def test_rectangle_I16(bbox: Coords) -> None: + # Arrange + im = Image.new("I;16", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(bbox, outline=0xCDEF) + + # Assert + assert im.getpixel((X0, Y0)) == 0xCDEF + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff") + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_point_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.point((4, 4), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_horizontal_line_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.line((0, 4, 7, 4), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_vertical_line_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.line((4, 0, 4, 7), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_diagonal_line_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.line((0, 0, 7, 7), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_rectangle_fill_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.rectangle((0, 0, 7, 7), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_polygon_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.polygon([(0, 0), (7, 0), (7, 7), (0, 7)], fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_masked_polygon_I16(mode: str) -> None: + # A polygon outline wider than one pixel is drawn through a mask. + img, draw = create_I16_image_draw(mode) + draw.polygon([(0, 0), (7, 0), (7, 7), (0, 7)], outline=I16_INK, width=8) + assert img.getpixel((4, 4)) == I16_INK + + +@pytest.mark.parametrize("mode", I16_MODES) +def test_ellipse_I16(mode: str) -> None: + img, draw = create_I16_image_draw(mode) + draw.ellipse((0, 0, 7, 7), fill=I16_INK) + assert img.getpixel((4, 4)) == I16_INK diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 6962851c20f..bfd1ef179ce 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -119,6 +119,12 @@ Two new filters are available for :py:meth:`~PIL.Image.Image.resize` and Other changes ============= +Drawing on I;16B images +^^^^^^^^^^^^^^^^^^^^^^^ + +When drawing on an ``I;16B`` image, some drawing methods wrote in the wrong byte order. +All drawing operations now use the byte order of the image mode. + Python 3.15 ^^^^^^^^^^^ diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 3217953a3e8..fd35eee0907 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -41,7 +41,36 @@ #define FLOOR(v) ((v) >= 0.0 ? (int)(v) : (int)floor(v)) #define INK8(ink) (*(UINT8 *)ink) -#define INK16(ink) (*(UINT16 *)ink) + +// True when the given I;16 mode stores its pixels most significant byte first. +static inline int +isModeI16BigEndian(const ModeID mode) { + return mode == IMAGING_MODE_I_16B +#ifdef WORDS_BIGENDIAN + || mode == IMAGING_MODE_I_16N +#endif + ; +} + +// Convert getink()'s ink value into the image's storage order +// and return in the native order so the drawing functions don't +// need to care about it. +static inline INT32 +ink16(Imaging im, const void *ink_) { + const UINT8 *in = ink_; + UINT8 out[2]; + UINT16 ink; + + if (isModeI16BigEndian(im->mode)) { + out[0] = in[1]; + out[1] = in[0]; + } else { + out[0] = in[0]; + out[1] = in[1]; + } + memcpy(&ink, out, sizeof(ink)); + return ink; +} /* * Rounds around zero (up=away from zero, down=towards zero) @@ -68,17 +97,14 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging); static inline void point8(Imaging im, int x, int y, int ink) { if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { - if (isModeI16(im->mode)) { -#ifdef WORDS_BIGENDIAN - im->image8[y][x * 2] = (UINT8)(ink >> 8); - im->image8[y][x * 2 + 1] = (UINT8)ink; -#else - im->image8[y][x * 2] = (UINT8)ink; - im->image8[y][x * 2 + 1] = (UINT8)(ink >> 8); -#endif - } else { - im->image8[y][x] = (UINT8)ink; - } + im->image8[y][x] = (UINT8)ink; + } +} + +static inline void +point16(Imaging im, int x, int y, int ink) { + if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { + ((UINT16 *)im->image8[y])[x] = (UINT16)ink; } } @@ -92,10 +118,22 @@ point32(Imaging im, int x, int y, int ink) { static inline void point32rgba(Imaging im, int x, int y, int ink) { unsigned int tmp; + const UINT8 *in = (UINT8 *)&ink; + if (in[3] == 0) { + // Transparent ink. Nothing to paint. + return; + } + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { + // Painting outside the canvas. + return; + } - if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { - UINT8 *out = (UINT8 *)im->image[y] + x * 4; - UINT8 *in = (UINT8 *)&ink; + UINT8 *out = (UINT8 *)im->image[y] + x * 4; + if (in[3] == 255) { // Solid ink, no need to blend. + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + } else { out[0] = BLEND(in[3], out[0], in[0], tmp); out[1] = BLEND(in[3], out[1], in[1], tmp); out[2] = BLEND(in[3], out[2], in[2], tmp); @@ -116,33 +154,15 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { x1 = im->xsize - 1; } if (x0 <= x1) { - int bigendian = -1; - if (isModeI16(im->mode)) { - bigendian = - ( -#ifdef WORDS_BIGENDIAN - im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16L -#else - im->mode == IMAGING_MODE_I_16B -#endif - ) - ? 1 - : 0; - } - if (mask == NULL && bigendian == -1) { - memset(im->image8[y0] + x0, (UINT8)ink, (x1 - x0 + 1)); + UINT8 *p = im->image8[y0]; + if (mask == NULL) { + memset(p + x0, (UINT8)ink, (x1 - x0 + 1)); } else { - UINT8 *p = im->image8[y0]; - while (x0 <= x1) { - if (mask == NULL || mask->image8[y0][x0]) { - if (bigendian == -1) { - p[x0] = ink; - } else { - p[x0 * 2 + (bigendian ? 1 : 0)] = ink; - p[x0 * 2 + (bigendian ? 0 : 1)] = ink >> 8; - } + UINT8 *mask_row = mask->image8[y0]; + for (; x0 <= x1; x0++) { + if (mask_row[x0]) { + p[x0] = (UINT8)ink; } - x0++; } } } @@ -150,9 +170,7 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { } static inline void -hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { - INT32 *p; - +hline16(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { if (y0 >= 0 && y0 < im->ysize) { if (x0 < 0) { x0 = 0; @@ -164,19 +182,27 @@ hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { } else if (x1 >= im->xsize) { x1 = im->xsize - 1; } - p = im->image32[y0]; - while (x0 <= x1) { - if (mask == NULL || mask->image8[y0][x0]) { - p[x0] = ink; + if (x0 <= x1) { + UINT16 *p = (UINT16 *)im->image8[y0]; + if (mask == NULL) { + for (; x0 <= x1; x0++) { + p[x0] = (UINT16)ink; + } + } else { + UINT8 *mask_row = mask->image8[y0]; + for (; x0 <= x1; x0++) { + if (mask_row[x0]) { + p[x0] = (UINT16)ink; + } + } } - x0++; } } } static inline void -hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { - unsigned int tmp; +hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { + INT32 *p; if (y0 >= 0 && y0 < im->ysize) { if (x0 < 0) { @@ -189,237 +215,265 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { } else if (x1 >= im->xsize) { x1 = im->xsize - 1; } - if (x0 <= x1) { - UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4; - UINT8 *in = (UINT8 *)&ink; - while (x0 <= x1) { - if (mask == NULL || mask->image8[y0][x0]) { - out[0] = BLEND(in[3], out[0], in[0], tmp); - out[1] = BLEND(in[3], out[1], in[1], tmp); - out[2] = BLEND(in[3], out[2], in[2], tmp); + p = im->image32[y0]; + if (mask == NULL) { + for (; x0 <= x1; x0++) { + p[x0] = ink; + } + } else { + UINT8 *mask_row = mask->image8[y0]; + for (; x0 <= x1; x0++) { + if (mask_row[x0]) { + p[x0] = ink; } - x0++; - out += 4; } } } } +// On arm64/NEON, doing no-op work for the unused band allows for better vectorization +#if defined(__aarch64__) || defined(__ARM_NEON) +#define KEEP_UNUSED_BAND(out, a, tmp) (out)[3] = BLEND(a, (out)[3], (out)[3], tmp) +#else +#define KEEP_UNUSED_BAND(out, a, tmp) ((void)0) +#endif + static inline void -line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { - int i, n, e; - int dx, dy; - int xs, ys; +hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { + unsigned int tmp; + UINT8 r = ((UINT8 *)&ink)[0]; + UINT8 g = ((UINT8 *)&ink)[1]; + UINT8 b = ((UINT8 *)&ink)[2]; + UINT8 a = ((UINT8 *)&ink)[3]; - /* normalize coordinates */ - dx = x1 - x0; - if (dx < 0) { - dx = -dx, xs = -1; - } else { - xs = 1; - } - dy = y1 - y0; - if (dy < 0) { - dy = -dy, ys = -1; - } else { - ys = 1; + if (a == 0) { // Transparent ink. Nothing to paint. + return; } - n = (dx > dy) ? dx : dy; + int xsize = im->xsize, ysize = im->ysize; + if (y0 < 0 || y0 >= ysize || x0 >= xsize || x1 < 0) { + // Painting outside the canvas. + return; + } - if (dx == 0) { - /* vertical */ - for (i = 0; i < dy; i++) { - point8(im, x0, y0, ink); - y0 += ys; - } + x0 = x0 < 0 ? 0 : x0; + x1 = x1 >= xsize ? xsize - 1 : x1; - } else if (dy == 0) { - /* horizontal */ - for (i = 0; i < dx; i++) { - point8(im, x0, y0, ink); - x0 += xs; - } + UINT8 *restrict out = (UINT8 *)im->image[y0] + x0 * 4; - } else if (dx > dy) { - /* bresenham, horizontal slope */ - n = dx; - dy += dy; - e = dy - dx; - dx += dx; - - for (i = 0; i < n; i++) { - point8(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; + if (mask == NULL) { + if (a == 255) { // Solid ink, no need to blend. + for (; x0 <= x1; x0++, out += 4) { + out[0] = r; + out[1] = g; + out[2] = b; + KEEP_UNUSED_BAND(out, 255, tmp); + } + } else { + for (; x0 <= x1; x0++, out += 4) { + out[0] = BLEND(a, out[0], r, tmp); + out[1] = BLEND(a, out[1], g, tmp); + out[2] = BLEND(a, out[2], b, tmp); + KEEP_UNUSED_BAND(out, a, tmp); } - e += dy; - x0 += xs; } - } else { - /* bresenham, vertical slope */ - n = dy; - dx += dx; - e = dx - dy; - dy += dy; - - for (i = 0; i < n; i++) { - point8(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; + UINT8 *mask_row = mask->image8[y0]; + if (a == 255) { // Solid paint, no need to blend. + for (; x0 <= x1; x0++, out += 4) { + if (mask_row[x0]) { + out[0] = r; + out[1] = g; + out[2] = b; + } + } + } else { + for (; x0 <= x1; x0++, out += 4) { + if (mask_row[x0]) { + out[0] = BLEND(a, out[0], r, tmp); + out[1] = BLEND(a, out[1], g, tmp); + out[2] = BLEND(a, out[2], b, tmp); + } } - e += dx; - y0 += ys; } } } static inline void -line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { - int i, n, e; - int dx, dy; - int xs, ys; +vline8(Imaging im, int x0, int y0, int y1, int ink) { + int ysize = im->ysize; - /* normalize coordinates */ - dx = x1 - x0; - if (dx < 0) { - dx = -dx, xs = -1; - } else { - xs = 1; + if (x0 < 0 || x0 >= im->xsize || y0 >= ysize || y1 < 0) { + return; } - dy = y1 - y0; - if (dy < 0) { - dy = -dy, ys = -1; - } else { - ys = 1; + y0 = y0 < 0 ? 0 : y0; + y1 = y1 >= ysize ? ysize - 1 : y1; + + UINT8 **rows = im->image8; + for (; y0 <= y1; y0++) { + rows[y0][x0] = (UINT8)ink; } +} - n = (dx > dy) ? dx : dy; +static inline void +vline16(Imaging im, int x0, int y0, int y1, int ink) { + int ysize = im->ysize; - if (dx == 0) { - /* vertical */ - for (i = 0; i < dy; i++) { - point32(im, x0, y0, ink); - y0 += ys; - } + if (x0 < 0 || x0 >= im->xsize || y0 >= ysize || y1 < 0) { + return; + } + y0 = y0 < 0 ? 0 : y0; + y1 = y1 >= ysize ? ysize - 1 : y1; - } else if (dy == 0) { - /* horizontal */ - for (i = 0; i < dx; i++) { - point32(im, x0, y0, ink); - x0 += xs; - } + UINT8 **rows = im->image8; + for (; y0 <= y1; y0++) { + ((UINT16 *)rows[y0])[x0] = (UINT16)ink; + } +} - } else if (dx > dy) { - /* bresenham, horizontal slope */ - n = dx; - dy += dy; - e = dy - dx; - dx += dx; - - for (i = 0; i < n; i++) { - point32(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; - } - e += dy; - x0 += xs; - } +static inline void +vline32(Imaging im, int x0, int y0, int y1, int ink) { + int ysize = im->ysize; - } else { - /* bresenham, vertical slope */ - n = dy; - dx += dx; - e = dx - dy; - dy += dy; - - for (i = 0; i < n; i++) { - point32(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; - } - e += dx; - y0 += ys; - } + if (x0 < 0 || x0 >= im->xsize || y0 >= ysize || y1 < 0) { + return; + } + y0 = y0 < 0 ? 0 : y0; + y1 = y1 >= ysize ? ysize - 1 : y1; + + INT32 **rows = im->image32; + for (; y0 <= y1; y0++) { + rows[y0][x0] = ink; } } static inline void -line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { - int i, n, e; - int dx, dy; - int xs, ys; +vline32rgba(Imaging im, int x0, int y0, int y1, int ink) { + unsigned int tmp; + UINT8 *in = (UINT8 *)&ink; + UINT8 r = in[0], g = in[1], b = in[2], a = in[3]; + int ysize = im->ysize; - /* normalize coordinates */ - dx = x1 - x0; - if (dx < 0) { - dx = -dx, xs = -1; - } else { - xs = 1; + if (a == 0) { // Transparent ink. Nothing to paint. + return; } - dy = y1 - y0; - if (dy < 0) { - dy = -dy, ys = -1; + if (x0 < 0 || x0 >= im->xsize || y0 >= ysize || y1 < 0) { + return; + } + y0 = y0 < 0 ? 0 : y0; + y1 = y1 >= ysize ? ysize - 1 : y1; + + char **rows = im->image; + if (a == 255) { // Solid ink, no need to blend. + for (; y0 <= y1; y0++) { + UINT8 *out = (UINT8 *)rows[y0] + x0 * 4; + out[0] = r; + out[1] = g; + out[2] = b; + } } else { - ys = 1; + for (; y0 <= y1; y0++) { + UINT8 *out = (UINT8 *)rows[y0] + x0 * 4; + out[0] = BLEND(a, out[0], r, tmp); + out[1] = BLEND(a, out[1], g, tmp); + out[2] = BLEND(a, out[2], b, tmp); + } } +} - n = (dx > dy) ? dx : dy; +#define GEN_LINE(point, hline, vline) \ + { \ + int i, n, e, dx, dy, xs, ys; \ + /* normalize coordinates */ \ + dy = y1 - y0; \ + if (dy < 0) { \ + dy = -dy, ys = -1; \ + } else { \ + ys = 1; \ + } \ + if (dy == 0) { /* horizontal, exclude endpoint */ \ + if (x1 > x0) { \ + hline(im, x0, y0, x1 - 1, ink, NULL); \ + } else if (x0 > x1) { \ + hline(im, x1 + 1, y0, x0, ink, NULL); \ + } \ + return; \ + } \ + dx = x1 - x0; \ + if (dx < 0) { \ + dx = -dx, xs = -1; \ + } else { \ + xs = 1; \ + } \ + \ + n = (dx > dy) ? dx : dy; \ + \ + if (dx == 0) { /* vertical, exclude endpoint */ \ + if (ys > 0) { \ + vline(im, x0, y0, y0 + dy - 1, ink); \ + } else { \ + vline(im, x0, y0 - dy + 1, y0, ink); \ + } \ + return; \ + } else if (dx > dy) { /* bresenham, horizontal slope */ \ + n = dx; \ + dy += dy; \ + e = dy - dx; \ + dx += dx; \ + \ + for (i = 0; i < n; i++) { \ + point(im, x0, y0, ink); \ + if (e >= 0) { \ + y0 += ys; \ + e -= dx; \ + } \ + e += dy; \ + x0 += xs; \ + } \ + } else { /* bresenham, vertical slope */ \ + n = dy; \ + dx += dx; \ + e = dx - dy; \ + dy += dy; \ + \ + for (i = 0; i < n; i++) { \ + point(im, x0, y0, ink); \ + if (e >= 0) { \ + x0 += xs; \ + e -= dy; \ + } \ + e += dx; \ + y0 += ys; \ + } \ + } \ + } - if (dx == 0) { - /* vertical */ - for (i = 0; i < dy; i++) { - point32rgba(im, x0, y0, ink); - y0 += ys; - } +static inline void +line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { + GEN_LINE(point8, hline8, vline8); +} - } else if (dy == 0) { - /* horizontal */ - for (i = 0; i < dx; i++) { - point32rgba(im, x0, y0, ink); - x0 += xs; - } +static inline void +line16(Imaging im, int x0, int y0, int x1, int y1, int ink) { + GEN_LINE(point16, hline16, vline16); +} - } else if (dx > dy) { - /* bresenham, horizontal slope */ - n = dx; - dy += dy; - e = dy - dx; - dx += dx; - - for (i = 0; i < n; i++) { - point32rgba(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; - } - e += dy; - x0 += xs; - } +static inline void +line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { + GEN_LINE(point32, hline32, vline32); +} - } else { - /* bresenham, vertical slope */ - n = dy; - dx += dx; - e = dx - dy; - dy += dy; - - for (i = 0; i < n; i++) { - point32rgba(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; - } - e += dx; - y0 += ys; - } +static inline void +line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { + UINT8 *in = (UINT8 *)&ink; + int a = in[3]; + if (a == 0) { // Transparent ink. Nothing to paint. + return; } + + GEN_LINE(point32rgba, hline32rgba, vline32rgba); } +#undef GEN_LINE static int x_cmp(const void *x0, const void *x1) { @@ -664,12 +718,14 @@ add_edge(Edge *e, int x0, int y0, int x1, int y1) { typedef struct { void (*point)(Imaging im, int x, int y, int ink); void (*hline)(Imaging im, int x0, int y0, int x1, int ink, Imaging mask); + void (*vline)(Imaging im, int x0, int y0, int y1, int ink); void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink); } DRAW; -DRAW draw8 = {point8, hline8, line8}; -DRAW draw32 = {point32, hline32, line32}; -DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba}; +DRAW draw8 = {point8, hline8, vline8, line8}; +DRAW draw16 = {point16, hline16, vline16, line16}; +DRAW draw32 = {point32, hline32, vline32, line32}; +DRAW draw32rgba = {point32rgba, hline32rgba, vline32rgba, line32rgba}; /* -------------------------------------------------------------------- */ /* Interface */ @@ -677,10 +733,11 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba}; #define DRAWINIT() \ if (im->image8) { \ - draw = &draw8; \ if (isModeI16(im->mode)) { \ - ink = INK16(ink_); \ + draw = &draw16; \ + ink = ink16(im, ink_); \ } else { \ + draw = &draw8; \ ink = INK8(ink_); \ } \ } else { \ @@ -779,8 +836,6 @@ ImagingDrawRectangle( int width, int op ) { - int i; - int y; int tmp; DRAW *draw; INT32 ink; @@ -804,7 +859,7 @@ ImagingDrawRectangle( y1 = im->ysize; } - for (y = y0; y <= y1; y++) { + for (int y = y0; y <= y1; y++) { draw->hline(im, x0, y, x1, ink, NULL); } @@ -813,11 +868,18 @@ ImagingDrawRectangle( if (width == 0) { width = 1; } - for (i = 0; i < width; i++) { + // Adjust side edges to avoid overlapping (matters with blends) + int ya = y0 + width, yb = y1 - width + 1, va = 0, vb = -1; + if (ya < yb) { + va = ya, vb = yb - 1; + } else if (ya > yb) { + va = yb + 1, vb = ya; + } + for (int i = 0; i < width; i++) { draw->hline(im, x0, y0 + i, x1, ink, NULL); draw->hline(im, x0, y1 - i, x1, ink, NULL); - draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink); - draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); + draw->vline(im, x1 - i, va, vb, ink); + draw->vline(im, x0 + i, va, vb, ink); } } diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index fc29e9baeed..79d6b4f257f 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -249,9 +249,3 @@ getRawModeData(const RawModeID id) { } return &RAWMODES[id]; } - -int -isModeI16(const ModeID mode) { - return mode == IMAGING_MODE_I_16 || mode == IMAGING_MODE_I_16L || - mode == IMAGING_MODE_I_16B || mode == IMAGING_MODE_I_16N; -} diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 39c0eb91994..f691bcd4fbe 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -223,7 +223,10 @@ findRawModeID(const char *const name); const RawModeData *const getRawModeData(const RawModeID id); -int -isModeI16(const ModeID mode); +static inline int +isModeI16(const ModeID mode) { + return mode == IMAGING_MODE_I_16 || mode == IMAGING_MODE_I_16L || + mode == IMAGING_MODE_I_16B || mode == IMAGING_MODE_I_16N; +} #endif // __MODE_H__