-
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
TransparentPaintWindow #1839
Comments
That is not a transparent window/frame. The background if fully opaque. This is a transparent frameimport 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() |
Basically what was trying to be achieved here was in the creation of the shapedbitmapbutton widget I was looking for ways to make it cross platform and just use pure wxpy without ctypes/etc. Like I said above, I'm not sure if this might work on mac/linux since wxPy4.1 changes a bit of things. Ex: maybe the docs didn't get updated... The main thing to note with this sample is that it just uses wxpy, but also it points out that the matrix transform method wasn't working fully(specifically |
Updated collapsed sample code in OP to reflect these small changes regarding the edges not drawing up to speed than the painting was occuring. # Draw BORDER_SIMPLE like rectangle for sanity check.
# When animating this visually seems to be a bit slow drawing the edges
# with TRANSPARENT_WINDOW.
# Freeze/Thaw fixes this when modding the matrix values and refreshing.
.... in slider and timer event wrapped SetPosition call with Freeze/Thaw ...
self.panel.Freeze()
self.panel.transwin.SetPosition((x+inc, y+inc))
self.panel.Thaw() |
Updated OP with apng so as to show what this widget is actually supposed to do. |
GTK is how to di it. and it can be done. I was looking into it. There are Python libraries for GTK that you should be able to leverage to do what is called alpha blending so do a search for "GTK alpha blending" |
Obviously, If I can paint on a wxWindow "Live" from my Krita plugin in wxPython/OpenGL/Vulkan, then there is at least a way to do this in QT by default with no hassles. This "thing", I'm trying to get closed, is the cross-compatable code part in pure wxPy. @vadz Maybe the 'Flying V' can help solve the OS compat issue more accurately, since he works on the C family side of things.... Is this widget feature possible yet cross-platform? |
OK I now remember what the issue is. If you render inside of the wxFrame paint event using GraphicsContext in the frame you will get what appears to be at first a transparent frame. But when you move the window that is behind that frame you then see that the frame is not actually transparent. the reason this is working for you is because you are using a window and that window is limited to the bounds of the frame. so you will not have this problem, because you are redrawing each time a move takes place the data that is supposed to be an alpha channel gets updated. If you did this as a test you would see what I am talking about. Modift you code so that when you move the slider it updates the window first and then changes all of the button labels to something different. You will see that the labels for the buttons that are "behind" the window do not change. |
Seems to work fine for me. It updates when transparent paint window is over the button. mod- add child change at end of timer handler. children = self.panel.GetChildren()
children[7].SetLabel('%s' % x)
## self.panel.transwin.Refresh()
## self.panel.Refresh()
## self.Refresh() The order of operations is important oftentimes when playing around with this stuff to get it to look right. This would be handled on the programmers end, not the widget. Edit: Added this sanity check to collapsible code in OP to show that it is rendering thru the transwin. |
@kdschlosser Oftentimes, the smearing or draw over effect isn't desirable, but take for example when you win a game of solitaire on windows. The cards start jumping off the stacks and draw over themselves. This is a good example of how this undesirable way of doing things sometimes turn it into "Art". |
what funny is the wx.TRANSPARENT_WINDOW style I believe is supposed to map to the Windows API WS_EX_TRANSPARENT. And this is only supposed to allow click through meaning that mouse clicks will pass through the window to the window underneath it. It is not supposed to have anything to do with alpha blending. Yet it does to some extent in wxWidgets but only for a wxWindow instance and not a wxFrame instance. Now I wanted to show you what I was talking about earlier as far as the background data not getting updated properly The other thing is that using a PaintDC solves the issue with the font not having an alpha channel. You will also notice that I am not using the wx.TRANSPARENT_WINDOW style. If this was fixed so that it did a per pixel alpha blend and updated when things change behind it then it would be cross platform. This has got to be a bug in wxWidgets Example code (MSW only)import wx
import math
class AlphaFrame(wx.Frame):
_xml = None
_vehicle = None
def __init__(self, parent=None, size=(800, 800)):
wx.Frame.__init__(
self,
parent,
-1,
style=(
wx.NO_BORDER |
wx.FRAME_NO_TASKBAR
)
)
self.SetSize(size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
def Draw(self):
self.Refresh()
self.Update()
def OnPaint(self, _):
width, height = self.GetClientSize()
dc = wx.PaintDC(self)
gc = wx.GraphicsContext.Create(dc)
gc.SetPen(gc.CreatePen(wx.Pen(wx.Colour(255, 0, 0, 175), 10)))
gc.DrawRoundedRectangle(20, 20, width - 40, height / 2, 5)
text = 'Is this what you are trying to do?'
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 - 180.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 = 180.0 + (angle_spacing / 2)
font = wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_BOLD, False, "")
graphicsFont = gc.CreateFont(font, wx.Colour(0, 0, 0, 50))
gc.SetFont(graphicsFont)
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 = 180.0 + (angle_spacing / 2)
graphicsFont = gc.CreateFont(font, wx.Colour(0, 255, 0, 50))
gc.SetFont(graphicsFont)
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)
# gc.DrawText(char, x, y, -text_radians)
angle += spacing
gc.Destroy()
del gc
app = wx.App()
frame = AlphaFrame()
frame.Show()
frame.Draw()
app.MainLoop() |
I wouldnt exactly call that a bug. probably more or less a detail of the frame implementation. I've tried that long time ago and got the same results. It does have a couple uses if you need to capture a screenie on frame creation. but yes, the frame has to have something, so basically it bakes the texture to become the frame. weird but whatever... With the frame transparency stuff that is usually got to do with the OS or the desktop shell. In this case your mswalpha.py with the ctypes stuff is basically what needed to be implemented at the wxWidgets level and tweaked to deal with the antialiasing and everything not going BLACK if a single pixel had alpha in 1-254 range and also being able to place widgets on top of it without a float frame like in my other sample. |
on OSX to render a transparent frame like in my examples a call need to be made to wx.Frame.SetBackgroundStyle(wx.BG_STYLE_TRANSPARENT) That is all that should need to be done. I am going to dig into Linux next. |
As I was saying about there being a bug. If setting the style when using osx works then wxWidgets is not properly rendering on MSW. This would be a bug. |
on linux passing wx.BG_STYLE_TRANSPARENT to the constructor should enable per pixel alpha blending. again if both this and the mac version work properly then there is 100% a bug. so that would be the cross platform support. it is fairly easy enough to do a check for windows and use the ctypes code. |
It throws this error with wx.BG_STYLE_TRANSPARENT. I think what that means is since msw isn't double buffered by default you may need to try and do a 2 step creation, tho I'm not sure. It works fine on wxPy4.0 and wxPy4.1 the way it is now, so unless there is any sort of benefit to complicating things, then it works fine for windows.
The only problem I ran into so far was the gcdc matrix wasnt working on wxPy4.0.7 And chances are that I will use wx.Control when I get to updating the shapedbitmapbutton code and refactoring it since it makes doing a few things easier on the users end. |
@kdschlosser If your really interested in how to easily swap it to solitaire mode, def OnLeftDown(self, event):
self.ToggleWindowStyle(wx.TRANSPARENT_WINDOW)
self.Refresh() And if you are so concerned about the ctypes stuff, As I said before, this isn't intended to be a ShapedFrame or TopLevelWindow. If you want to take the other samples I whipped up that uses the mswalpha.py that was working fine and try to attempt to put one of these on it, it may actually work somehow if you try hard enough, tho I'm not interested in reinventing the wheel with the shaped frame alpha and to try and get every single event thing working atm. Plus multiplying alpha with alpha will probably result in the transpaintwindow not working properly or could be black and x3DoubleBuffering would only complicate things even more. As far as clickthru, shapedbitmapbutton already handles all that in pure wxpy event fashion, so if you like to toy around with layers something along these lines would get you close to handling whatever you would need in a class dict. I think you said at one time, there was a way to set clickthru with ctypes..., chances are if so, that may be faster if desired and no widgets reside upon the window could make an option for it... def OnButton(self, event):
print('OnButton')
evtObj = event.GetEventObject()
evtObj = self.transwin
# for child in self.GetChildren():
# if child == evtObj:
# continue
# self.RemoveChild(evtObj)
# self.AddChild(evtObj)
# evtObj.Raise()
evtObj.Lower()
def OnRaise(self, event):
evtObj = event.GetEventObject()
evtObj.Raise()
def OnLower(self, event):
evtObj = event.GetEventObject()
evtObj.Lower() As far as the mswalpha.py, I'll want to have you at least review the doc stuff I've done on it and have a few samples that use it, before sending a pullreqz. As if any of my widgets rely on it, then it will need to be in wx.lib, before I pullreqz any MCOW widgets that might use it if running on msw. |
I never stated anything about setting wx.BG_STYLE_TRANSPARENT for MSW. that style would need to be set into the style parameter when calling the constructor. I know you were looking for a cross platform solution for rendering transparent frames/windows and you have all of the pieces needed to successfully do that. OSX: wx.Frame.SetBackgroundStyle(wx.BG_STYLE_TRANSPARENT) |
Ok, so if I do a wx.Platform check right before the constructor that might work also.... That seems to be the answer you managed to dig up? Is that correct? I couldn't manage to dig up a test sample anywhere that had any lines written that way. It would appear that the wx.TRANSPARENT_WINDOW flag should have been enough by looking at the docs, but needed to be double buffered. Everything here has been trial and error to get it working to this point correctly without using a python matrix also. Also, when I trimmed trailing whitespace this came up, but I cannot get it to work. |
@kdschlosser Ok. It seems that that snippet is close. This worked also. But needed to keep wx.TRANSPARENT_WINDOW in style and not set the frame to BG_STYLE_PAINT(it complained when I did this after using the BG_STYLE_TRANSPARENT) Seems to work the same. both ways. will need to test further. Also Double Buffering seems to still be needed. class TransparentPaintWindow(wx.Window):
""""""
def __init__(self, parent, id=wx.ID_ANY,
pos=wx.DefaultPosition, size=wx.DefaultSize,
style=wx.TRANSPARENT_WINDOW, name='window'):
""""""
wx.Window.__init__(self)
# Do this first:
self.SetBackgroundStyle(wx.BG_STYLE_TRANSPARENT)
# And really create the window afterwards:
self.Create(parent, id, pos, size, style, name)
# self.SetupWindowProperties() |
@RobinD42 This stuff appears to need updated in the docs to be more accurately explained for each platform as all the method docs referencing this are confusingly worded. @kdschlosser I'll update the OP sample after a bit more testing and sanity tests. I'll try to see if the 2 stage creation might work on Frame also, tho I won't cross my fingers on that one. I do believe that the ctypes mswalpha.py for it appears to be the correct answer for it antialiasing wise. |
I am only going by what I see in the source code for wxWidgets. on MSW when the background gets erased a check is done to see if the style wx.BG_STYLE_TRANSPARENT has been set. If it has then it will not render the background with a comment line stating that it will be handled in the paint event. This does not actually make the background transparent by doing this. I am 100% certain that on MSW the style wx.TRANSPARENT_WINDOW is only supposed to change the "click through" of the mouse. why you are ending up with a black background with this flag is not set I am not sure. This is not supposed to be like that. you should have to set wx.TRANSPARENT_WINDOW and wx.BG_STYLE_TRANSPARENT if you wanted to have a transparent background and click through. when I test the wx.TRANSPARENT_WINDOW style using the ctypes method of rendering the only thing that changes is the click through of the mouse. on Windows you cannot set the wx.BG_STYLE_TRANSPARENT this way |
I managed to dig thru some of my zillion alpha test samples and came across one that I had abandoned at the time for unknown reasons(probably this or I forgot why). This one is back from/updated from late 2013/early 2014 when Jon was toying with EmbroiderModder Stitchify on his Arduino. https://hackaday.com/2014/02/15/generating-embroidery-with-an-arduino/
Anyhow, around that time I was also working on my ShapedBitmapButton for my MCOW library which had intentions of supporting vector and alpha images. Needless to say, I had to put that on hold until wxPy4.1.x because of various hiccups and trying to get it to work a zillion ways with various libraries, but each library had its own limitations/problems.
This is probably the 3rd sample I want to try and get added to the demo by years end, dealing with wrapping up the lingering msw alpha issues. Other alpha threads of note are these two.
#1544
#1463
@kdschlosser Yep, it can be done in pure wxpy... This sample does not rely on your mswalpha.py . It may not be cross-platform tho... ...remember baby steps... :/
And here is the sample to be tested.
As a zip with images
TransparentPaintWindow.zip
And here is just the code.
TransparentPaintWindow.py Sample Code - Click to expand
@tatarize This may be of interest to you also if you was having nightmares with the alpha embroidery matrix stuff on wxPy4.0
@RobinD42 I really didn't realize that the matrix transforms wasn't working completely correctly on gcdc wxPy4.0 until I revisited this sample. The gc stuff works but the gcdc stuff didn't. This just goes to show the amount oh headaches and rage quits I was having knowing that I had somehow on one occasion got it to sorta work somehow and then probably broke it by changing one line.
Chances are I might have used a slow pure-python matrix to hack the drawing to work or somesuch or used a different lib implementation but that was real slow because I was dealing with attempting to translating light reflections from 3D into 2D similar to Jon's pseudo rendering which would look alot better, but alas was python based speed wise(and would take around 20 sec to do calculations so it def wasn't realtime by any means)
If you manage to make 1 last Py2 build by the year end(pretty please), call it 4.0.8 or whatever and be the last Py2 build for 4.0, this would be an extremely important bug/fix(maybe it wasn't finished/wipz). I would request that this be looked into and included if at all possible, as without this, it creates a bit of trial/error issues.
Also the Phoenix docs say that
wx.TRANSPARENT_WINDOW
is windows only... Is this still true with wxPy4.1? I don't have access to a mac to test. Maybe you can give a suggestion of what would work similarly so as to make a cross platform file if possible. Also I don't have a linux box handy atm that has wxPy built for it, so test/ideas/translations of code on that end are welcome also.Another question I had(tho while a bit off subject) was if there is a way to convert a path to points(for instance GraphicsPath or somesuch) similar to this image. https://github.com/Shinao/PathToPoints . I recall I tried before but don't recall I ever asked or got an answer if it could be done in pure wxpy. The whole goal was to be able to convert for example a path/mathematica curve to x amount of points from length which then could be like
connect-the-dots
and pseudo rendered upon a ShapedBitmapButton/etc...The text was updated successfully, but these errors were encountered: