From 8d9279dd7329cc8dd330f483df2867fd51ca8dcf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Jan 2025 21:58:04 +1100 Subject: [PATCH 1/2] Only use outside border of stroke in text() --- Tests/test_imagedraw.py | 22 ++++++++++++++++++++++ Tests/test_imagefont.py | 14 ++++++++++++++ src/PIL/ImageDraw.py | 1 + src/PIL/ImageFont.py | 1 + src/PIL/_imagingft.pyi | 1 + src/_imagingft.c | 7 +++++-- 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 5fc1c2766..28d7ed725 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1396,6 +1396,28 @@ def test_stroke_descender() -> None: assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) +@skip_unless_feature("freetype2") +def test_stroke_inside_gap() -> None: + # Arrange + im = Image.new("RGB", (120, 130)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) + + # Act + draw.text((12, 12), "i", "#f00", font, stroke_width=20) + + # Assert + for y in range(im.height): + glyph = "" + for x in range(im.width): + if im.getpixel((x, y)) == (0, 0, 0): + if glyph == "started": + glyph = "ended" + else: + assert glyph != "ended", "Gap inside stroked glyph" + glyph = "started" + + @skip_unless_feature("freetype2") def test_split_word() -> None: # Arrange diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6a0a940b9..f110cc1d0 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -461,6 +461,20 @@ def test_free_type_font_get_mask(font: ImageFont.FreeTypeFont) -> None: assert mask.size == (108, 13) +def test_stroke_mask() -> None: + # Arrange + text = "i" + + # Act + font = ImageFont.truetype(FONT_PATH, 128) + mask = font.getmask(text, stroke_width=2) + + # Assert + assert mask.getpixel((34, 5)) == 255 + assert mask.getpixel((38, 5)) == 0 + assert mask.getpixel((42, 5)) == 255 + + def test_load_when_image_not_found() -> None: with tempfile.NamedTemporaryFile(delete=False) as tmp: pass diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index d8e4c0c60..d0f6c5b7d 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -643,6 +643,7 @@ class ImageDraw: features=features, language=language, stroke_width=stroke_width, + stroke_filled=True, anchor=anchor, ink=ink, start=start, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index d8c265560..a4986aa8c 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -644,6 +644,7 @@ class FreeTypeFont: features, language, stroke_width, + kwargs.get("stroke_filled", False), anchor, ink, start[0], diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 9cc9822f5..813294747 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -28,6 +28,7 @@ class Font: features: list[str] | None, lang: str | None, stroke_width: float, + stroke_filled: bool, anchor: str | None, foreground_ink_long: int, x_start: float, diff --git a/src/_imagingft.c b/src/_imagingft.c index 3a65007a5..4281e0b5e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -834,6 +834,7 @@ font_render(FontObject *self, PyObject *args) { int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */ float stroke_width = 0; + int stroke_filled = 0; PY_LONG_LONG foreground_ink_long = 0; unsigned int foreground_ink; const char *mode = NULL; @@ -853,7 +854,7 @@ font_render(FontObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "OO|zzOzfzLffO:render", + "OO|zzOzfpzLffO:render", &string, &fill, &mode, @@ -861,6 +862,7 @@ font_render(FontObject *self, PyObject *args) { &features, &lang, &stroke_width, + &stroke_filled, &anchor, &foreground_ink_long, &x_start, @@ -1005,7 +1007,8 @@ font_render(FontObject *self, PyObject *args) { if (stroker != NULL) { error = FT_Get_Glyph(glyph_slot, &glyph); if (!error) { - error = FT_Glyph_Stroke(&glyph, stroker, 1); + error = stroke_filled ? FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1) + : FT_Glyph_Stroke(&glyph, stroker, 1); } if (!error) { FT_Vector origin = {0, 0}; From 0318304f9ae63fe81e513bdb32c1497539c66176 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Jan 2025 17:27:25 +1100 Subject: [PATCH 2/2] Do not draw normal text onto stroke text if they are the same color --- src/PIL/ImageDraw.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index d0f6c5b7d..81f8fbce0 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -693,7 +693,8 @@ class ImageDraw: draw_text(stroke_ink, stroke_width) # Draw normal text - draw_text(ink, 0) + if ink != stroke_ink: + draw_text(ink) else: # Only draw normal text draw_text(ink)