-
Notifications
You must be signed in to change notification settings - Fork 509
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
GraphicsContext does not use alpha channel properly when rendering fonts/text #1840
Comments
I'm not exactly sure why it is giving me errors about sending a wx.GraphicsFont. It appears you got something screwed up since it is accepting a wx.Font.
... so not exactly sure what the real problem is. If you modify my TransparentPaintWindow Sample you can indeed see that the GraphicsFont alpha is working correctly. I think somehow something got screwed up, since you are using your ctypes stuff... |
Depending on the results you want, you can do like I did in the previous sample where I used your mswalpha for the transparent frame, and then create a 2nd float on parent frame for the mswalpha affected one and use a region for the text and then set the transparency on the frame. See this sample #1544 (comment) |
You have to use FromRGBA otherwise the bitmap will not have an alpha channel... the MemoryDC is only being used for the purposes of selecting the bitmap. I am creating a GraphicsContext instance from the MemoryDC instance. This allows me to render to the bitmap using the Graphics context. As you stated, The GraphicsContext.SetFont method should not be allowing a wxFont instance to be passed to it. The only thing the ctypes stuff does is it draws the bitmap to the screen. That is it. The font is being rendered to the bitmap without an alpha channel and this is how it gets drawn on the screen by the ctypes stuff. The problem is NOT with that portion of the code. There is a GraphicsContext.CreateFont method and I have tried using this as well but the results are exactly the same. gc.SetFont(gc.CreateFont(font, wx.Colour(0, 255, 0, 100))) I would have thought that this would utilize the alpha channel properly but it does not. |
This old mailing list sample converted to phoenix might help. Shaped Text Frame - Click to expandimport wx
class ShapedText(wx.Frame):
def __init__(self, text="Scrolling text!"):
wx.Frame.__init__(self, None, style=
wx.FRAME_SHAPED | wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP)
if not self.IsDoubleBuffered():
self.SetDoubleBuffered(True) # Reduce flicker.
# Set up the timer which will move the text and paint the screen.
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, source=self.timer)
self.timer.Start(10)
# Make sure we are using our custom paint handler.
self.Bind(wx.EVT_PAINT, self.OnPaint)
screenwidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
framewidth = int(screenheight * .75)
frameheight = 100
framex = int(screenwidth/2 - framewidth/2)
framey = screenheight - 150
# Create the bitmap.
self.textFont = wx.Font(36, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
self.bmp = wx.Bitmap(framewidth, frameheight)
self.SetScrolledString(text)
self.SetWindowShape()
self.SetPosition((framex, framey))
self.SetSize((framewidth, self.textHeight+5))
self.SetTransparent(100)
def SetScrolledString(self, txt):
self.scrolledStr = txt
self.textOffset = 0
# Calculate the text width.
memDC = wx.MemoryDC()
memDC.SetFont(self.textFont)
self.textWidth, self.textHeight, heightLine = memDC.GetFullMultiLineTextExtent(self.scrolledStr)
memDC.Destroy()
def SetWindowShape(self):
# Use the bitmap's mask to determine the region.
r = wx.Region(self.bmp)
self.SetShape(r)
def OnPaint(self, event):
# Draw the bitmap on the screen.
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0, 0, useMask=True)
def OnTimer(self, event):
# Draw the text on the bitmap, set a mask, and paint.
x, y = self.Size
memDC = wx.MemoryDC(self.bmp)
memDC.SetPen(wx.WHITE_PEN)
memDC.DrawRectangle(0, 0, x, y)
memDC.SetFont(self.textFont)
memDC.DrawText(self.scrolledStr, x-self.textOffset, 0)
memDC.Destroy()
mask = wx.Mask(self.bmp, wx.WHITE)
self.bmp.SetMask(mask) # Comment this line to disable transparency.
self.textOffset += 1
# Stop the timer when the text has scrolled the entire way across.
# print(self.textOffset-self.Size[0], self.textWidth)
if self.textOffset - x == self.textWidth:
# print(self.textOffset-self.Size[0], textWidth)
self.timer.Stop()
self.Destroy()
self.SetWindowShape()
self.Refresh()
if __name__ == "__main__":
app = wx.App()
frame = ShapedText()
frame.Show(True)
app.MainLoop() |
nope that is not going to support alpha channels |
Some implementation details that may help, if you haven't already dug into the relevant C++ code: In the GDI+ GC backend the colour value is used to create a gdi+ The alpha value is used when creating the brush, but I don't know why it wouldn't be using it. I do know that GDI+ has some holes in its functionality, perhaps drawing text with a partially transparent brush is one of them? Have you tried the Direct2D GC backend? If you're just interested in getting a bitmap out of this then using the Cairo backend would probably be an option as well. |
I have also tried to wrap MemoryDC with GCDC and GraphicsContext with GCDC and the results are the same. I am guessing I am probably going to have to learn more about Cairo |
The if 'wxMSW' in wx.PlatformInfo:
renderer = wx.GraphicsRenderer.GetDirect2DRenderer() # or GetCairoRenderer
else:
renderer = wx.GraphicsRenderer.GetDefaultRenderer()
ctx = renderer.CreateContext(dc) If wanted, you can create the renderer once and reuse the same one throughout the application's lifetime. There is also a module implementing a GraphicsContext-like in Python using Cairo, in |
If I use the Direct2D renderer as you have shown above. Nothing gets drawn. |
I have the same issue (plus the text looks quite bad). This is what it looks like (text is drawn on a WS_EX_LAYERED window, with a white window behind it) To fix it, we have the use SetTextRenderingHint to TextRenderingHintAntiAliasGridFit. This fixes both the alpha channel not applying and the the text rendering. Unfortunately I couldn't find a way to call SetTextRenderingHint in wx, so had to use a .dll with
and ctypes in python
edit: and I don't believe this is a wx bug, same problem happens using gdiplus directly. |
This is good to know. You should be able to make those function calls without having to make a dll. something along these lines. import ctypes
GpStatus = ctypes.HRESULT
gdiplus = ctypes.windll.gdiplus
GdipSetTextRenderingHint = gdiplus.GdipSetTextRenderingHint
GdipSetTextRenderingHint.restype = GpStatus and then in a paint event TextRenderingHintAntiAliasGridFit = 3
def OnPaint(self, event):
# Create paint DC
dc = wx.PaintDC(self)
# Create graphics context from it
gc = wx.GraphicsContext.Create(dc)
if gc:
GdipSetTextRenderingHint(ctypes.byref(gc.GetNativeContext()), TextRenderingHintAntiAliasGridFit) something like that. I have not tested the above but it would be something really similar I would imagine. If you look at the Windows SDK header file um\gdiplusgraphics.h and at line 199 is the method SetTextRenderingHint Status SetTextRenderingHint(IN TextRenderingHint newMode)
{
return SetStatus(DllExports::GdipSetTextRenderingHint(nativeGraphics,
newMode));
} and all the method is doing is calling the exported GdipSetTextRenderingHint function. So it is a matter of passing the native context to the exported function. If you look at um\gdiplusflat.h at line 1615 you have the following code. GpStatus WINGDIPAPI
GdipSetTextRenderingHint(GpGraphics *graphics, TextRenderingHint mode); In you code example you are getting the handle of the native context and creating the GpGraphics instance in c code using that handle then calling the method which in turn calls the exported function. Since there is already an instance of the native context that can be gotten on the python side of things there should be a way to pass that native context to the exported function directly without having to compile a dll to do it for us, |
code correction. TextRenderingHintAntiAliasGridFit = 3
def OnPaint(self, event):
# Create paint DC
dc = wx.PaintDC(self)
# Create graphics context from it
gc = wx.GraphicsContext.Create(dc)
if gc:
GdipSetTextRenderingHint(gc.GetNativeContext(), TextRenderingHintAntiAliasGridFit) I think that should work. GetNativeContext returns a pointer to GpGraphics I believe. |
Thanks! Didn't realize there was a wrapper api for gdiplus. I can't seem to get the parameters right for the call however. GetNativeContext() gives a <class 'sip.voidptr'> so this should work: however I'm getting a InvalidParameter result. This: results in: |
OK try wrapping the NativeContex in ctypes.byref() |
You can also try doing this p = ctypes.c_void_p(gc.GetNativeContext().__int__())
GdipSetTextRenderingHint(ctypes.byref(p), TextRenderingHintAntiAliasGridFit) |
I have another. Here are 4 ways you can try it p = gc.GetNativeContext()
GdipSetTextRenderingHint(ctypes.byref(p), TextRenderingHintAntiAliasGridFit) p = ctypes.c_void_p(gc.GetNativeContext().__int__())
GdipSetTextRenderingHint(ctypes.byref(p), TextRenderingHintAntiAliasGridFit) p = ctypes.cast(gc.GetNativeContext(), ctypes.POINTER(ctypes.c_void_p))
GdipSetTextRenderingHint(p, TextRenderingHintAntiAliasGridFit) p = ctypes.cast(gc.GetNativeContext(), ctypes.POINTER(ctypes.c_void_p))
GdipSetTextRenderingHint(ctypes.byref(p), TextRenderingHintAntiAliasGridFit) |
No, it doesn't work... GdipSetTextRenderingHint expects a Gdiplus::GpGraphics* and what we have is Gdiplus::Graphics*, and I'm not sure how to convert between the two |
not sure then. |
Operating system: MSW 7 x64
wxPython version & source: 4.1.0 msw (phoenix) wxWidgets 3.1.4, pypi
Python version & source: 3.7.5 Stackless 3.7 (tags/v3.7.5-slp:f7925f2a02, Oct 20 2019, 15:28:53) [MSC v.1916 64 bit (AMD64)]
Description of the problem: when rendering text using a GraphicsContext instance the alpha channel causes a change in the brightness but not a change in opacity
Code Example (MSW only)
The text was updated successfully, but these errors were encountered: