Open kdschlosser opened 4 years ago
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 https://github.com/wxWidgets/Phoenix/issues/1544#issuecomment-661862752 That I think should allow you to use a PaintEvent and might work like you are expecting... Tho antialiasing may or may not work properly on the edges...
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.
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+ SolidBrush
, and that is then held by the wxGDIPlusFontData
class along with the gdi+ Font
object. The Font
and Brush
are then used when calling the gdi+ DrawString
API. There really isn't a lot of complexity there, just a lot of little things working together to tie the two APIs together.
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.
@RobinD42
I have also tried to wrap MemoryDC with GCDC and GraphicsContext with GCDC and the results are the same. Now.. If I create a GraphicsContext instance from a PaintDC it does render properly.
I am guessing I am probably going to have to learn more about Cairo
The GCDC
is still going to use the default renderer backend, which on Windows is GDI+, so I wouldn't expect any difference when using the GCDC
vs what you were doing before. To use a non-default you need to explicitly create the renderer and then use it to create the GraphicsContext
and related objects. For example, something like this:
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 wx.lib.Graphics
. If you want to use the Cairo API directly then the wx.lib.wxcairo
package provides some code to help you use Cairo on wx.DCs
, wx.Bitmaps
, etc.
@RobinD42
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
extern "C" __declspec(dllexport) int SetTextRenderingHint(void* v, int renderingHint) {
Gdiplus::Graphics* g = reinterpret_cast<Gdiplus::Graphics*>(v);
return g->SetTextRenderingHint(static_cast<Gdiplus::TextRenderingHint>(renderingHint));
}
and ctypes in python
stdc = ctypes.cdll.LoadLibrary(R"PyWxGdiPlus.dll")
TextRenderingHintAntiAliasGridFit = 3
r = stdc.SetTextRenderingHint(ctypes.c_void_p(gc.GetNativeContext().__int__()), TextRenderingHintAntiAliasGridFit)
edit: and I don't believe this is a wx bug, same problem happens using gdiplus directly.
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:
GdipSetTextRenderingHint(ctypes.c_void_p(gc.GetNativeContext().__int__()), TextRenderingHintAntiAliasGridFit)
however I'm getting a InvalidParameter result.
This:
GdipSetTextRenderingHint(gc.GetNativeContext(), TextRenderingHintAntiAliasGridFit)
results in:
[ctypes.ArgumentError: argument 1: <class 'TypeError'>: Don't know how to convert parameter 1]
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)
```python import ctypes.wintypes import ctypes import wx HANDLE = ctypes.wintypes.HANDLE LPWSTR = ctypes.wintypes.LPWSTR HRESULT = ctypes.HRESULT LONG = ctypes.wintypes.LONG HWND = ctypes.wintypes.HWND INT = ctypes.wintypes.INT HDC = ctypes.wintypes.HDC HGDIOBJ = ctypes.wintypes.HGDIOBJ BOOL = ctypes.wintypes.BOOL DWORD = ctypes.wintypes.DWORD UBYTE = ctypes.c_ubyte COLORREF = DWORD GWL_EXSTYLE = -20 WS_EX_LAYERED = 0x00080000 ULW_ALPHA = 0x00000002 AC_SRC_OVER = 0x00000000 AC_SRC_ALPHA = 0x00000001 def RGB(r, g, b): return COLORREF(r | (g << 8) | (b << 16)) class POINT(ctypes.Structure): _fields_ = [ ('x', LONG), ('y', LONG) ] class SIZE(ctypes.Structure): _fields_ = [ ('cx', LONG), ('cy', LONG) ] class BLENDFUNCTION(ctypes.Structure): _fields_ = [ ('BlendOp', UBYTE), ('BlendFlags', UBYTE), ('SourceConstantAlpha', UBYTE), ('AlphaFormat', UBYTE) ] byref = ctypes.byref kernel32 = ctypes.windll.Kernel32 GetTempPathW = kernel32.GetTempPathW GetTempPathW.restype = DWORD GetTempPathW.argtypes = [DWORD, LPWSTR] gdi32 = ctypes.windll.Gdi32 # HDC CreateCompatibleDC( # HDC hdc # ); CreateCompatibleDC = gdi32.CreateCompatibleDC CreateCompatibleDC.restype = HDC # HGDIOBJ SelectObject( # HDC hdc, # HGDIOBJ h # ); SelectObject = gdi32.SelectObject SelectObject.restype = HGDIOBJ # BOOL DeleteDC( # HDC hdc # ); DeleteDC = gdi32.DeleteDC DeleteDC.restype = BOOL shell32 = ctypes.windll.Shell32 SHGetFolderPathW = shell32.SHGetFolderPathW SHGetFolderPathW.restype = HRESULT SHGetFolderPathW.argtypes = [HWND, INT, HANDLE, DWORD, LPWSTR] user32 = ctypes.windll.User32 # LONG GetWindowLongW( # HWND hWnd, # int nIndex # ) GetWindowLongW = user32.GetWindowLongW GetWindowLongW.restype = LONG # LONG SetWindowLongW( # HWND hWnd, # int nIndex, # LONG dwNewLong # ); SetWindowLongW = user32.SetWindowLongW SetWindowLongW.restype = LONG # HDC GetDC( # HWND hWnd # ); GetDC = user32.GetDC GetDC.restype = HDC # HWND GetDesktopWindow(); GetDesktopWindow = user32.GetDesktopWindow GetDesktopWindow.restype = HWND # BOOL UpdateLayeredWindow( # HWND hWnd, # HDC hdcDst, # POINT *pptDst, # SIZE *psize, # HDC hdcSrc, # POINT *pptSrc, # COLORREF crKey, # BLENDFUNCTION *pblend, # DWORD dwFlags # ); UpdateLayeredWindow = user32.UpdateLayeredWindow UpdateLayeredWindow.restype = BOOL import math class AlphaFrame(wx.Frame): _xml = None _vehicle = None def __init__(self, parent=None, size=(800, 800), style=wx.TRANSPARENT_WINDOW): wx.Frame.__init__( self, parent, -1, style=( wx.NO_BORDER | wx.FRAME_NO_TASKBAR | style ) ) self.SetSize(size) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) def Draw(self): width, height = self.GetClientSize() bmp = wx.Bitmap.FromRGBA(width, height) dc = wx.MemoryDC() dc.SelectObject(bmp) gc = wx.GraphicsContext.Create(dc) gc.SetPen(gc.CreatePen(wx.Pen(wx.Colour(255, 0, 0, 175), 10))) # gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(0, 0, 255, 100)))) gc.DrawRoundedRectangle(20, 20, width - 40, height / 2, 5) text = 'This is a transparent frame' text_len = len(text) text_width, char_height = self.GetFullTextExtent(text)[:2] radius = (min(width, height) * 0.90) / 2 circumference = radius * math.pi * 2 angle_range = 320.0 - 190.0 avg_char_width = text_width / text_len angle_ratio = angle_range / 360.0 arc_length = circumference * angle_ratio num_steps = arc_length / avg_char_width angle_spacing = angle_range / num_steps pixels_per_degree = circumference / 360.0 angle = 190.0 + (angle_spacing / 2) font = self.GetFont() font.SetStyle(wx.FONTSTYLE_ITALIC) font.MakeBold() font.SetFractionalPointSize(font.GetFractionalPointSize() * 3) gc.SetFont(font, wx.Colour(0, 0, 0, 1)) center_x = width / 2.0 center_y = height / 2.0 for char in list(text): char_width = gc.GetFullTextExtent(char)[0] angle_offset = (char_width / 2) / pixels_per_degree spacing = (angle_range / text_len) + angle_offset for _ in range(2): radians = math.radians(angle - angle_offset) cos = math.cos(radians) sin = math.sin(radians) x = center_x + (radius * cos) y = center_y + (radius * sin) text_radians = math.radians(angle - angle_offset + 90.0) gc.DrawText(char, x, y, -text_radians) angle_offset -= 0.3 angle += spacing angle = 190.0 + (angle_spacing / 2) gc.SetFont(font, wx.Colour(0, 255, 0, 200)) for char in list(text): char_width = gc.GetFullTextExtent(char)[0] angle_offset = (char_width / 2) / pixels_per_degree spacing = (angle_range / text_len) + angle_offset radians = math.radians(angle - angle_offset - 1) cos = math.cos(radians) sin = math.sin(radians) x = center_x + (radius * cos) y = center_y + (radius * sin) text_radians = math.radians(angle - angle_offset + 90.0) gc.DrawText(char, x, y, -text_radians) angle += spacing dc.SelectObject(wx.NullBitmap) gc.Destroy() del gc dc.Destroy() del dc self.Render(bmp) def Render(self, bmp, transparency=255): x, y = self.GetPosition() hndl = self.GetHandle() style = GetWindowLongW(HWND(hndl), INT(GWL_EXSTYLE)) SetWindowLongW(HWND(hndl), INT(GWL_EXSTYLE), LONG(style | WS_EX_LAYERED)) hdcDst = GetDC(GetDesktopWindow()) hdcSrc = CreateCompatibleDC(HDC(hdcDst)) pptDst = POINT(int(x), int(y)) psize = SIZE(bmp.GetWidth(), bmp.GetHeight()) pptSrc = POINT(0, 0) crKey = RGB(0, 0, 0) pblend = BLENDFUNCTION(AC_SRC_OVER, 0, transparency, AC_SRC_ALPHA) SelectObject(HDC(hdcSrc), HGDIOBJ(bmp.GetHandle())) UpdateLayeredWindow( HWND(hndl), HDC(hdcDst), byref(pptDst), byref(psize), HDC(hdcSrc), byref(pptSrc), crKey, byref(pblend), DWORD(ULW_ALPHA) ) DeleteDC(HDC(hdcDst)) DeleteDC(HDC(hdcSrc)) app = wx.App() frame = AlphaFrame() frame.Show() frame.Draw() app.MainLoop() ```