Only use outside border of stroke in text()

This commit is contained in:
Andrew Murray 2025-01-18 21:58:04 +11:00
parent cf7dd2f0e9
commit 8d9279dd73
6 changed files with 44 additions and 2 deletions

View File

@ -1396,6 +1396,28 @@ def test_stroke_descender() -> None:
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) 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") @skip_unless_feature("freetype2")
def test_split_word() -> None: def test_split_word() -> None:
# Arrange # Arrange

View File

@ -461,6 +461,20 @@ def test_free_type_font_get_mask(font: ImageFont.FreeTypeFont) -> None:
assert mask.size == (108, 13) 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: def test_load_when_image_not_found() -> None:
with tempfile.NamedTemporaryFile(delete=False) as tmp: with tempfile.NamedTemporaryFile(delete=False) as tmp:
pass pass

View File

@ -643,6 +643,7 @@ class ImageDraw:
features=features, features=features,
language=language, language=language,
stroke_width=stroke_width, stroke_width=stroke_width,
stroke_filled=True,
anchor=anchor, anchor=anchor,
ink=ink, ink=ink,
start=start, start=start,

View File

@ -644,6 +644,7 @@ class FreeTypeFont:
features, features,
language, language,
stroke_width, stroke_width,
kwargs.get("stroke_filled", False),
anchor, anchor,
ink, ink,
start[0], start[0],

View File

@ -28,6 +28,7 @@ class Font:
features: list[str] | None, features: list[str] | None,
lang: str | None, lang: str | None,
stroke_width: float, stroke_width: float,
stroke_filled: bool,
anchor: str | None, anchor: str | None,
foreground_ink_long: int, foreground_ink_long: int,
x_start: float, x_start: float,

View File

@ -834,6 +834,7 @@ font_render(FontObject *self, PyObject *args) {
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */
float stroke_width = 0; float stroke_width = 0;
int stroke_filled = 0;
PY_LONG_LONG foreground_ink_long = 0; PY_LONG_LONG foreground_ink_long = 0;
unsigned int foreground_ink; unsigned int foreground_ink;
const char *mode = NULL; const char *mode = NULL;
@ -853,7 +854,7 @@ font_render(FontObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"OO|zzOzfzLffO:render", "OO|zzOzfpzLffO:render",
&string, &string,
&fill, &fill,
&mode, &mode,
@ -861,6 +862,7 @@ font_render(FontObject *self, PyObject *args) {
&features, &features,
&lang, &lang,
&stroke_width, &stroke_width,
&stroke_filled,
&anchor, &anchor,
&foreground_ink_long, &foreground_ink_long,
&x_start, &x_start,
@ -1005,7 +1007,8 @@ font_render(FontObject *self, PyObject *args) {
if (stroker != NULL) { if (stroker != NULL) {
error = FT_Get_Glyph(glyph_slot, &glyph); error = FT_Get_Glyph(glyph_slot, &glyph);
if (!error) { 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) { if (!error) {
FT_Vector origin = {0, 0}; FT_Vector origin = {0, 0};