-
-
Notifications
You must be signed in to change notification settings - Fork 183
Add outline option for rendering fonts #2828
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have just a couple comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The challenge of #2452 is to design a good API for this, and this PR doesn’t accomplish that in my opinion.
- Outline color isn’t supported
- Looking at my notes from before (haven’t run this yet), doesn’t this PR mean that people have to call font.render twice? Once for the text, then once for the outline. That seems inconvenient for users.
It actually is, you need to call it in the font.render() method.
Yep, that's how SDL2 implemented it. |
Perhaps |
Maybe perhaps
for the font constructor and
for the render call? But I'm not sure what to do if |
Well then how do you do the color? Another kwarg? Kwargs are expensive to parse in functions that get called all the time, too. I think my second proposal in the issue would work well (minus the render outline only thing, I think that’s unnecessary now)
|
You can do the outline color by specifying it in the render() method with no modifications to the existing code. I'm personally against the outline_color parameter as existing draw methods have the |
@Starbuck5, sorry I meant the |
I agree with @Starbuck that this should be implemented as a styling attribute to Font - similar to the current styling attributes bold, italic & underline. The current Proposed test program: import pygame
from pygame import Color, Font, QUIT
pygame.init()
window_surf = pygame.display.set_mode((400, 200))
impact_font = Font(filename="fonts/impact.ttf", size=64)
impact_font.outline_width = 2 # defaults to 0
impact_font.outline_color = Color("red") # defaults to black
text_with_outline = impact_font.render(
text="Hello World", antialias=True, color=Color("white")
)
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
window_surf.fill(Color("black"))
window_surf.blit(text_with_outline, (50, 50))
pygame.display.update()
font.render can just check if outline_width is greater than 0 or not - and if so draw the outline before or after the main font rendering code - whichever looks better. As an aside maybe we could make color and antialias into arguments with default values. We almost certainly would have already converted 'antialias' if it was on the other side of 'color' in the argument list. At least that way you could do:
Which isn't a typing saving over:
But probably clearer to read. Might also open up:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think 80% of the time when people use this they are going to want to draw an outline around actual visible text.
A possible upgrade to font.render()
that might be nice in concert with this is to allow it to set alpha pixels on the produced surface. I believe at the minute text surfaces come out of render in a slightly odd format that looks like normal 32 bit 'ARGB' - but is subtly not that. You can see this by doing:
text_surf = font.render("Hello World", True, Color("white"))
# text_surf = text_surf.convert_alpha()
text_surf = text_surf.premul_alpha()
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
window_surf.fill(Color("grey"))
window_surf.blit(text_surf, (50, 50), special_flags=BLEND_PREMULTIPLIED)
pygame.display.update()
and seeing the difference after uncommenting the .convert_alpha()
line. I expect at some point it was marginally cheaper to blit whatever, not-quite-full-transparency format comes out of font.render() but I doubt it still is. Anyway, if we could render semi- and full transparency text then you would get the benefits of outline only text without having to change the API.
Okay... I'll get to work on this. |
docs/reST/ref/font.rst
Outdated
| :sl:`Gets or sets the font's outline color` | ||
| :sg:`outline_size -> RGB` | ||
|
||
Returns the color of the outline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto this doesn't just return it, it can be set here too. What does this default to?
Plus one of these attributes should have a small example of how to use this, with an image of what the output looks like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example as in the examples directory? Or an example as an example image in the documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example as in a code block in the docs showing how it works, with a picture of the rendered output.
src_c/font.c
Outdated
if (!PyUnicode_Check(text) && !PyBytes_Check(text) && text != Py_None) { | ||
return RAISE_TEXT_TYPE_ERROR(); | ||
PyErr_Format(PyExc_TypeError, "text must be a unicode or bytes"); | ||
return 0; | ||
} | ||
|
||
if (wraplength < 0) { | ||
return RAISE(PyExc_ValueError, | ||
"wraplength parameter must be positive"); | ||
PyErr_Format(PyExc_ValueError, | ||
"The wraplength parameter must be positive."); | ||
return 0; | ||
} | ||
|
||
if (PyUnicode_Check(text)) { | ||
Py_ssize_t _size = -1; | ||
astring = PyUnicode_AsUTF8AndSize(text, &_size); | ||
if (astring == NULL) { /* exception already set */ | ||
return NULL; | ||
if (astring == NULL) { | ||
return 0; // exception already set. | ||
} | ||
if (strlen(astring) != (size_t)_size) { | ||
return RAISE(PyExc_ValueError, | ||
"A null character was found in the text"); | ||
PyErr_Format(PyExc_ValueError, | ||
"A null character was found in the text."); | ||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do these changes exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return type is no longer a PyObject *
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RAISE doesn't return a PyObject, it returns NULL. And NULL is 0, so this is the same code?
self.f = pygame_font.Font(None, 32) | ||
self.f = pygame_font.Font(None, 96) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to see the outline when the font is small.
} | ||
|
||
SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; | ||
SDL_Color backg = {0, 0, 0, SDL_ALPHA_OPAQUE}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we warn here when outline colour is the same as background or foreground?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that'll be necessary as printing content to the console is already a controversial topic.
src_c/font.c
Outdated
static char *kwlist[] = {"text", "antialias", "color", | ||
"bgcolor", "wraplength", NULL}; | ||
|
||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OpO|Oi", kwlist, &text, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
having foreground colour and background colour as params, but outline colour as a property feels inconsistent. I would like to request some feedback from the maintainers on this. Maybe we should fix this now, maybe we need to make an issue for this after merge.
return 0; // exception already set | ||
} | ||
|
||
SDL_Color foreg = {rgba[0], rgba[1], rgba[2], SDL_ALPHA_OPAQUE}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the API here, shouldn't the background be transparent by default? I know this matches the old behaviour, but this probably warrants a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I learned from my professor that you cannot assign NULL
to typedef struct
s. Correct me if she's wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I meant I don't understand why the alpha isn't 0. It was like that before, but this part of the code is unclear to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was originally SDL_ALPHA_OPAQUE
before. https://github.com/pygame-community/pygame-ce/pull/2828/files#diff-75538c02a9bfe6511e8b72324ec5736b4d22aae6c5a99c52a83cef0dc70df300L575
Overall, this is promising. |
00660ec
to
0a2094a
Compare
} | ||
|
||
static int | ||
font_setter_outline_width(PyFontObject *self, PyObject *value, void *closure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I try to set a float it gives me this:
C:\Users\charl\Desktop\testr.py:8: DeprecationWarning: an integer is required (got type float). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
arial.outline_width = 1.5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not happen on my machine.
======================================================================
ERROR: test_set_outline_width_as_float (__main__.FontTypeTest.test_set_outline_width_as_float)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/nari/Documents/pygame-ce/test/font_test.py", line 392, in test_set_outline_width_as_float
f.outline_width = 20.06
^^^^^^^^^^^^^^^
TypeError: 'float' object cannot be interpreted as an integer
----------------------------------------------------------------------
Ran 72 tests in 13.958s
@@ -306,6 +306,30 @@ solves no longer exists, it will likely be removed in the future. | |||
|
|||
.. ## Font.point_size ## | |||
|
|||
.. attribute:: outline_width |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outline_radius
is a better name if you're already going to mention radius in the docs description.
.. attribute:: outline_color | ||
|
||
| :sl:`Gets or sets the font's outline color` | ||
| :sg:`outline_width -> RGB` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You say (0, 0, 0, 255), but that's RGBA.
Hello! I tried doing #2452. Let me know if there are any issues.