I am new to PYopenGL,
Actually, I’m also not sure if PYopenGL is the right approach for my task.
I have a 3D model in a Wavefront obj file format. I need to take a “printscreen” of the model from a given view. In other words, I would need to render the model and instead of display the model save it as an image (jpg)
My idea was to use PYopenGL for this task. However, googling I could find no suggestion or example how to do this. Therefore, I start to have doubts, if PYopenGL is the right tool for my task.
Did somebody of you already something like this or know an example that I can use to learn about?
Thanks in advance.
Michi
GLUT hidden window method is much simpler, and platform independent, but it leads to window blinking.
To setup it for Django i.e. you can implement renderer as separate web server, which will blinks by window only once on start, and then returning rendered images by http response.
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image
from PIL import ImageOps
import sys
width, height = 300, 300
def init():
glClearColor(0.5, 0.5, 0.5, 1.0)
glColor(0.0, 1.0, 0.0)
gluOrtho2D(-1.0, 1.0, -1.0, 1.0)
glViewport(0, 0, width, height)
def render():
glClear(GL_COLOR_BUFFER_BIT)
# draw xy axis with arrows
glBegin(GL_LINES)
# x
glVertex2d(-1, 0)
glVertex2d(1, 0)
glVertex2d(1, 0)
glVertex2d(0.95, 0.05)
glVertex2d(1, 0)
glVertex2d(0.95, -0.05)
# y
glVertex2d(0, -1)
glVertex2d(0, 1)
glVertex2d(0, 1)
glVertex2d(0.05, 0.95)
glVertex2d(0, 1)
glVertex2d(-0.05, 0.95)
glEnd()
glFlush()
def draw():
render()
glutSwapBuffers()
def main():
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(300, 300)
glutCreateWindow(b"OpenGL Offscreen")
glutHideWindow()
init()
render()
glPixelStorei(GL_PACK_ALIGNMENT, 1)
data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
image = Image.frombytes("RGBA", (width, height), data)
image = ImageOps.flip(image) # in my case image is flipped top-bottom for some reason
image.save('glutout.png', 'PNG')
#glutDisplayFunc(draw)
#glutMainLoop()
main()
My answer (partially based on CodeSurgeon's answer) is for the second part of the question.
Off-screen rendering (means render something to internal buffer instead of visible window and save rendered image to file or transfer as http response to display on web page) in PyOpenGL (so as in OpenGL itself) is little tricky because everything done by GLUT so far (create window, init opengl context etc) you need to do by hands now, because you don't need the standard GLUT window to pop up or even blink.
So there are 3 methods for off-screen rendering in OpenGL:
1) Use GLUT for initialization, but hide glut window and render to it. This method is totally platform independent, but GLUT window appears for short time during initialization, so it's not so suitable for web server i.e. But you still can setup it as separate web server, which do initialization only on startup and use some interface to communicate with it.
2) Manually create everything: hidden window, OpenGL context, and Framebuffer Object to render to. This method is good, cause you control everything and no window appears, but creation of context is platform specific (below is example for Win64)
3) 3rd method is like method 2, but use default Framebuffer created by WGL, instead of creating FBO by hands. Same effects as method 2, but simpler. If you don't need FBO for some other reason, may be the preferable one.
Now I'll describe method 2, hardcore one. More samples are in my GitHub repository.
So off-screen rendering algorithm consists of following steps:
Create hidden window, because you need the window, even hidden to
create OpenGL context
Create OpenGL context
Create Framebuffer object (FBO)
Create Rendering buffers (Color and Depth) and attach them to FBO (see FBO manual for details)
Bind FBO to OpenGL context for rendering
Render something. In this example I use only 2D primitives for simplification, but buffers are ready for 3D rendering with depth-test
Setup buffers for reading, in our case there is only one FBO, so no need to choose one to read from
Read rendered data from Color render buffer with glReadPixels()
Do whatever you want with received data, i.e. create PIL image from it and save it to file. Also you can render with double resolution and resize PIL image for anti-aliasing effect.
So there is full example below.
Important! 3.1.1 PyOpenGL implementation have a BUG! When you just import WGL, glReadPixels() starts to crash with
ctypes.ArgumentError: argument 7: : wrong type
To avoid this go to your packages dir\OpenGL\raw\WGL_types.py , find the following lines
HANDLE = POINTER(None) # /home/mcfletch/pylive/OpenGL-ctypes/src/wgl.h:60
# TODO: figure out how to make the handle not appear as a void_p within the code...
HANDLE.final = True
and replace it with (for x64 of course, for x86 UINT32 suppose)
HANDLE = UINT64
HANDLE.final = True
So there is example
from win32api import *
from win32con import *
from win32gui import *
from OpenGL.WGL import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image
from PIL import ImageOps
import uuid
# =========================================
# I left here only necessary constants, it's easy to search for the rest
PFD_TYPE_RGBA = 0
PFD_MAIN_PLANE = 0
PFD_DOUBLEBUFFER = 0x00000001
PFD_DRAW_TO_WINDOW = 0x00000004
PFD_SUPPORT_OPENGL = 0x00000020
# =========================================
# OpenGL context creation helpers
def mywglCreateContext(hWnd):
pfd = PIXELFORMATDESCRIPTOR()
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
pfd.iPixelType = PFD_TYPE_RGBA
pfd.cColorBits = 32
pfd.cDepthBits = 24
pfd.iLayerType = PFD_MAIN_PLANE
hdc = GetDC(hWnd)
pixelformat = ChoosePixelFormat(hdc, pfd)
SetPixelFormat(hdc, pixelformat, pfd)
oglrc = wglCreateContext(hdc)
wglMakeCurrent(hdc, oglrc)
# check is context created succesfully
# print "OpenGL version:", glGetString(GL_VERSION)
def mywglDeleteContext():
hrc = wglGetCurrentContext()
wglMakeCurrent(0, 0)
if hrc: wglDeleteContext(hrc)
# =========================================
# OpenGL Framebuffer Objects helpers
def myglCreateBuffers(width, height):
fbo = glGenFramebuffers(1)
color_buf = glGenRenderbuffers(1)
depth_buf = glGenRenderbuffers(1)
# binds created FBO to context both for read and draw
glBindFramebuffer(GL_FRAMEBUFFER, fbo)
# bind color render buffer
glBindRenderbuffer(GL_RENDERBUFFER, color_buf)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buf)
# bind depth render buffer - no need for 2D, but necessary for real 3D rendering
glBindRenderbuffer(GL_RENDERBUFFER, depth_buf)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf)
return fbo, color_buf, depth_buf, width, height
def myglDeleteBuffers(buffers):
fbo, color_buf, depth_buf, width, height = buffers
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glDeleteRenderbuffers(1, color_buf)
glDeleteRenderbuffers(1, depth_buf)
glDeleteFramebuffers(1, fbo)
def myglReadColorBuffer(buffers):
fbo, color_buf, depth_buf, width, height = buffers
glPixelStorei(GL_PACK_ALIGNMENT, 1)
glReadBuffer(GL_COLOR_ATTACHMENT0)
data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
return data, width, height
# =========================================
# Scene rendering
def renderInit(width, height):
glClearColor(0.5, 0.5, 0.5, 1.0)
glColor(0.0, 1.0, 0.0)
gluOrtho2D(-1.0, 1.0, -1.0, 1.0)
glViewport(0, 0, width, height)
def render():
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# draw xy axis with arrows
glBegin(GL_LINES)
# x
glVertex2d(-1, 0)
glVertex2d(1, 0)
glVertex2d(1, 0)
glVertex2d(0.95, 0.05)
glVertex2d(1, 0)
glVertex2d(0.95, -0.05)
# y
glVertex2d(0, -1)
glVertex2d(0, 1)
glVertex2d(0, 1)
glVertex2d(0.05, 0.95)
glVertex2d(0, 1)
glVertex2d(-0.05, 0.95)
glEnd()
glFlush()
# =========================================
# Windows stuff and main steps
def main():
# Create window first with Win32 API
hInstance = GetModuleHandle(None)
wndClass = WNDCLASS()
wndClass.lpfnWndProc = DefWindowProc
wndClass.hInstance = hInstance
wndClass.hbrBackground = GetStockObject(WHITE_BRUSH)
wndClass.hCursor = LoadCursor(0, IDC_ARROW)
wndClass.lpszClassName = str(uuid.uuid4())
wndClass.style = CS_OWNDC
wndClassAtom = RegisterClass(wndClass)
# don't care about window size, couse we will create independent buffers
hWnd = CreateWindow(wndClassAtom, '', WS_POPUP, 0, 0, 1, 1, 0, 0, hInstance, None)
# Ok, window created, now we can create OpenGL context
mywglCreateContext(hWnd)
# In OpenGL context create Framebuffer Object (FBO) and attach Color and Depth render buffers to it
width, height = 300, 300
buffers = myglCreateBuffers(width, height)
# Init our renderer
renderInit(width, height)
# Now everything is ready for job to be done!
# Render something and save it to file
render()
data, width, height = myglReadColorBuffer(buffers)
image = Image.frombytes("RGBA", (width, height), data)
image = ImageOps.flip(image) # in my case image is flipped top-bottom for some reason
# it's easy to achive antialiasing effect by resizing rendered image
# don't forget to increase initial rendered image resolution and line thikness for 2D
#image = image.resize((width/2, height/2), Image.ANTIALIAS)
image.save("fbo.png", "PNG")
# Shutdown everything
myglDeleteBuffers(buffers)
mywglDeleteContext()
main()
In additional to glutHideWindow(), if you need do it on server, you can use virtual display.
requirements.txt: pyopengl, pillow, pyvirtualdisplay.
packages: freeglut3-dev, xvfb.
from pyvirtualdisplay import Display
# before glutInit create virtual display
display = Display(visible=0, size=(HEIGHT, WIDTH))
display.start()
PyOpenGL can be used for your purposes if all you are interested in is rendering your 3d model/scene from a specific angle to an image as long as you do not mind having an OpenGL window open and running.
There are many sources online that discuss how to write parsers for the .obj format. In addition to looking at the wikipedia article on the format here, I believe you can find an implementation of a fixed function obj loader on the pygame website. If you are making the .obj models yourself, it will be easier as the spec is pretty loose and it can be tough to write a robust parser. Alternatively, you can use libraries like Assimp to load your models and extract their data, which has the python pyAssimp bindings.
As for saving a screenshot, you need to render the 3D scene to a texture. I would strongly recommend taking a look at this answer. You would need to learn about how to use glReadPixels as well as how to use FBOs (Frame Buffer Objects) if you want to do offscreen rendering.
Related
I'm trying to use skia-python with glfw to draw various shapes and text onto a transparent floating overlay. I have a small demo working for my purposes, but it behaves differently if the window is created with full screen resolution versus created with anything smaller.
The code here alternates between drawing some red text or a green circle, and attempts to clear the canvas in between using canvas.clear(skia.ColorTRANSPARENT). This behaves exactly as I want if the window is created with dimensions anything less than 1920x1080 (the resolution of my screen). If I create the window with these full screen dimensions, instead of clearing the canvas this call fills the screen with black (and then the rest of the code still works, alternating between text and circle).
import contextlib, glfw, skia
from OpenGL import GL
import time
WIDTH, HEIGHT = 1920, 1080
#WIDTH, HEIGHT = 1919, 1079
#contextlib.contextmanager
def glfw_window():
if not glfw.init():
raise RuntimeError('glfw.init() failed')
glfw.window_hint(glfw.STENCIL_BITS, 8) # ?
glfw.window_hint(glfw.SAMPLES, 14) # ?
glfw.window_hint(glfw.DECORATED, 0)
glfw.window_hint(glfw.TRANSPARENT_FRAMEBUFFER, 1)
glfw.window_hint(glfw.FLOATING, 1)
window = glfw.create_window(WIDTH, HEIGHT, '', None, None)
glfw.make_context_current(window)
yield window
glfw.terminate()
#contextlib.contextmanager
def skia_surface(window):
context = skia.GrDirectContext.MakeGL()
(fb_width, fb_height) = glfw.get_framebuffer_size(window)
backend_render_target = skia.GrBackendRenderTarget(
fb_width,
fb_height,
0, # sampleCnt
0, # stencilBits
skia.GrGLFramebufferInfo(0, GL.GL_RGBA8))
surface = skia.Surface.MakeFromBackendRenderTarget(
context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin,
skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB())
assert surface is not None
yield surface
context.abandonContext()
def drawString(canvas):
paint = skia.Paint(AntiAlias=True, Color=skia.ColorRED)
font = skia.Font(skia.Typeface('meiryo'), 36)
canvas.drawString('あかさたな', 100, 100, font, paint)
def drawCircle(canvas):
paint = skia.Paint(Color=skia.ColorGREEN)
canvas.drawCircle(100, 100, 40, paint)
with glfw_window() as window:
GL.glClear(GL.GL_COLOR_BUFFER_BIT)
with skia_surface(window) as surface:
with surface as canvas:
while (glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)):
canvas.clear(skia.ColorTRANSPARENT)
if int(time.time()) % 2 == 0:
drawString(canvas)
else:
drawCircle(canvas)
surface.flushAndSubmit()
glfw.swap_buffers(window)
glfw.poll_events()
#glfw.wait_events()
For the purpose of demonstration, this is the entire working demo. Most of it comes from this skia-python documentation. I think the only packages it needs are glfw (2.5.3), PyOpenGl (3.1.6), and skia-python (87.4)
My actual use case will use a slightly different loop and doesn't need to draw to the screen as frequently as this demo, but it does need to periodically clear the canvas. I don't actually need it to be perfectly full screen, I can use 1919x1079, mostly I'm just curious what's going on here. This is being tested on Windows 10 with Python 3.10 by the way
I am using PyQt 5.15.2 (Qt 5.15.2) on macOs 10.15.7.
I am trying to produce a PDF from a QGraphicsScene.
I am drawing some paths with a brush that has a pixmap texture, and a scale transformation applied (with brush.setTransform).
When I display the scene in a QGraphicsView I get exactly the desired output.
When I render the same scene to a QPrinter (set to produce a pdf) the texture is applied but ignoring the transformation.
Is this a known limitation? Is there a way around it?
Below is a minimal example showing the problem in a context similar to my use case.
Here I am creating a texture on the fly for illustration purposes.
The rendering artifacts due to antialiasing are irrelevant for my question.
The issue is the discrepancy between the scale (set at .5) of the texture in the rendered brush and the scale (always 1) in the pdf output.
[To generate the output just double click on the view, you'll see a test.pdf in the current path.]
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtPrintSupport import *
class Test(QGraphicsView):
def __init__(self):
QGraphicsView.__init__(self)
# Here I am creating a texture for testing
# it is a 50x50 black square
s = 50
img = QImage(s, s, QImage.Format_ARGB32)
img.fill(Qt.transparent)
for x in range(s):
img.setPixelColor(x,0,Qt.black)
img.setPixelColor(x,s-1,Qt.black)
img.setPixelColor(s-1,x,Qt.black)
img.setPixelColor(0,x,Qt.black)
self.scene = QGraphicsScene()
self.setScene(self.scene)
r = self.scene.addRect(0, 0, 1404, 1872)
pen = QPen()
pen.setWidth(150)
pen.setCapStyle(Qt.RoundCap)
pen.setJoinStyle(Qt.RoundJoin)
b=QBrush(img)
# Here I am transforming the brush so that the texture should be scaled 50% in both height and width
tr = QTransform()
tr.scale(.5,.5)
b.setTransform(tr)
pen.setBrush(b)
# A random path for testing, drawn using the textured brush
path = QPainterPath(QPointF(200, 200))
path.lineTo(300,300)
path.lineTo(300,1000)
path.lineTo(700,700)
self.pathItem = QGraphicsPathItem(path, r)
self.pathItem.setPen(pen)
def resizeEvent(self, event):
self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
def mouseDoubleClickEvent(self, event):
printer = QPrinter(QPrinter.HighResolution)
printer.setOutputFormat(QPrinter.PdfFormat)
# printer.setPageSize(QPrinter.A4)
printer.setOutputFileName("test.pdf")
printer.setPaperSize(QSizeF(1404,1872), QPrinter.Millimeter)
printer.setPageMargins(0,0,0,0, QPrinter.Millimeter)
p=QPainter()
p.begin(printer)
self.scene.render(p)
p.end()
if __name__ == '__main__':
app = QApplication([])
print(QT_VERSION_STR)
print(PYQT_VERSION_STR)
viewer = Test()
viewer.show()
app.exec_()
The desired output is on the left, as correctly displayed by the QGraphicsView.
On the right is the PDF rendering of the same scene, which incorrectly ignores the scaling of the brush.
I'm using Python and OpenGL to make some calculations related to a physical system and then display them and be able to manipulate the image rotating it, translating it, ...
I'm not a professional programmer and I have little experience using OpenGL and I'm having some issues trying to get what I want. I would like to be able to save the window that I get as an output in a video file and to do so I've seen the possibility of using glReadPixels to capture each frame and then put them all together.
I am using right now something that looks like the following code and I want to be able to save the frames to images but, although I've been able to save to save the pixels to an array using glReadPixels, I don't know where nor how to call the function that I've defined as captureScreen. What should I do to get the output that I'm looking for?
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image
from scipy import *
import numpy as np
import random as rnd
def captureScreen(file_):
glPixelStorei(GL_PACK_ALIGNMENT, 1)
data = glReadPixels(0, 0, 800, 800, GL_RGBA, GL_UNSIGNED_BYTE)
image = Image.fromstring("RGBA", (800, 800), data)
image.save(file_, 'png')
def main():
glutInit(sys.argv)
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(800, 800)
glutCreateWindow ("Test")
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE)
glEnable(GL_BLEND)
glEnable(GL_TEXTURE_2D)
glEnable(GL_COLOR_MATERIAL)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPointSize( 6.0 )
glLineWidth( 2.0 )
glEnable(GL_POINT_SMOOTH)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
glEnable(GL_POLYGON_SMOOTH)
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glClearColor(0.0, 0.0, 0.0, 0.0)
glutFullScreen()
glutKeyboardFunc(keyboard)
glutIdleFunc(dynamics)
glutDisplayFunc(display)
glutMouseFunc(mouse)
glutMainLoop()
if __name__ == '__main__':
main()
keyboard, display, dynamics and mouse are functions previously defined.
You can call glReadPixels() just after glutSwapBuffers():
glutSwapBuffers()
glReadBuffer(GL_FRONT)
glReadPixels(...)
Or just before:
glReadBuffer(GL_BACK)
glReadPixels(...)
glutSwapBuffers()
So captureScreen() should be called from your "display" function which is probably the one which calls glutSwapBuffers()
I'm trying to get 48x48 or 256x256 icons from files in Windows and have come across what seems like a dead-end. At the moment I have a HICON handle(since PySides QFileIconProvider only returns 32x32 icons) in python which I would like to show in a pyside window but functions like QPixmap.fromHICON/HBITMAP are not implemented and also seems to have been removed from the source since Qt 4.8(?). Also, I'm trying to avoid having to save the icon to a file.
So, is there any way to get a HICON or possibly any other things you can turn it into, to any kind of PySide object?
EDIT:
I've been trying to simply rewrite the old function fromWinHBITMAP function in python but it isn't going great. I'm uncertain how I should translate the src line into python and I don't either have any idea how I change the value of the memory buffer returned by QImage.scanLine()
for (int y=0; y<h; ++y) {
QRgb *dest = (QRgb *) image.scanLine(y);
const QRgb *src = (const QRgb *) (data + y * bytes_per_line);
for (int x=0; x<w; ++x) {
dest[x] = src[x] | mask;
}
}
At the moment I create a PyCBITMAP from the HICON with the win32api and retrieves the list of bits.
for y in range(0, hIcon.height):
dest = i.scanLine(y)
src = bitmapbits[y*hIcon.widthBytes:(y*hIcon.widthBytes)+hIcon.widthBytes]
for x in range(0, hIcon.width):
dest[x] = bytes(ctypes.c_uint32(src[x] | 0))
This results in "ValueError: cannot modify size of memoryview object"
The source for the function be found here: http://www.qtcentre.org/threads/19188-Converting-from-HBitmap-to-a-QPixmap?p=94747#post94747
Fixed it!
def iconToQImage(hIcon):
hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
hbmp = win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc, hIcon.width, hIcon.height)
hdc = hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
win32gui.DrawIconEx(hdc.GetHandleOutput(), 0, 0, hIcon.hIcon, hIcon.width, hIcon.height, 0, None, 0x0003)
bitmapbits = hbmp.GetBitmapBits(True)
image = QtGui.QImage(bitmapbits, hIcon.width, hIcon.height, QtGui.QImage.Format_ARGB32_Premultiplied)
return image
It's a bit hard to get this sort of setup going but from I read around Python Imaging Library (PIL) supports bitmap and ICO files and has downloads for Windows. Assuming you can get a filename of the icon, you can load it up with PIL and then transfer the raw data to a QImage:
from PIL import Image
from PySide.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication
im = Image.open("my_image.png")
data = im.tostring('raw', 'RGBA')
app = QApplication([])
image = QImage(data, im.size[0], im.size[1], QImage.Format_ARGB32)
pix = QPixmap.fromImage(image)
lbl = QLabel()
lbl.setPixmap(pix)
lbl.show()
app.exec_()
Then work with whatever QImage operation you need to do from there.
While #egs0's answer is accurate, trying to display the output may cause problems because QLabel doesn't handle bitmap very well. To solve these problems, convert the result to another image format.
import win32ui
import win32gui
# Doesn't matter which library. Qt5 should work just as well.
from PySide6 import QtGui, QtCore
def iconToQImage(hIcon, width, height, im_format="PNG"):
hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
hbmp = win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc, width, height)
hdc = hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
win32gui.DrawIconEx(hdc.GetHandleOutput(), 0, 0, hIcon, width, height, 0, None, 0x0003)
bitmapbits = hbmp.GetBitmapBits(True)
image = QtGui.QImage(bitmapbits, width, height, QtGui.QImage.Format_ARGB32_Premultiplied)
# Write to and then load from a buffer to convert to PNG.
buffer = QtCore.QBuffer()
buffer.setOpenMode(QtCore.QIODevice.ReadWrite)
image.save(buffer, im_format)
image.loadFromData(buffer.data(), im_format)
# Use QtGui.Pixmap.fromImage() to get a pixmap instead.
return image
It's also possible to get the size of an icon using the following function, adapted from here:
def getIconSize(HIcon):
info = win32gui.GetIconInfo(HIcon)
if info[4]: # Icon has color plane.
bmp = win32gui.GetObject(info[4])
width = bmp.bmWidth
height = bmp.bmHeight
else: # Icon has no colour plane, image data stored in mask.
bmp = win32gui.GetObject(info[3])
width = bmp.width
height = bmp.height // 2 # A monochrome icon contains image and XOR mask in the hbmMask.
info[3].close()
info[4].close()
return width, height
I've isolated the cause of the problem to be the image, since the code seems to work with other png images with transparency. However, it doesn't seem to work with the one image I need it to. This would be of great help seeing as I'm trying to make a nice shaped window.
The image:
The code:
import wx
class PictureWindow(wx.Frame):
def __init__(self, parent, id, title, pic_location):
# For PNGs. Must be PNG-8 for transparency...
self.bmp = wx.Image(pic_location, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
framesize = (self.bmp.GetWidth(), self.bmp.GetHeight())
# Launch a frame the size of our image. Note the position and style stuff...
# (Set pos to (-1, -1) to let the OS place it.
# This style wx.FRAME_SHAPED is a frameless plain window.
wx.Frame.__init__(self, parent, id, title, size=framesize, pos = (50, 50), style = wx.FRAME_SHAPED)
r = wx.RegionFromBitmap(self.bmp)
self.SetShape(r)
# Define the panel and place the pic
panel = wx.Panel(self, -1)
self.mainPic = wx.StaticBitmap(panel, -1, self.bmp)
# Set an icon for the window if we'd like
#icon1 = wx.Icon("icon.ico", wx.BITMAP_TYPE_ICO)
#self.SetIcon(icon1)
self.Show()
# The paint stuff is only necessary if doing a shaped window
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Main()
def OnPaint(self, event):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0, 0, True)
def Main(self):
sizer = wx.GridBagSizer()
button = wx.Button(self,-1,label="Click me !")
sizer.Add(button, (0,1))
# What pic are we opening?
pic_location = r"C:\Users\user\Pictures\CPUBAR\A4.png" # PNG must be png-8 for transparency...
app = wx.App(redirect=0) # the redirect parameter keeps stdout from opening a wx gui window
PictureWindow(None, -1, 'Picture Viewer', pic_location)
app.MainLoop()
This is in Windows 7, btw.
wx.RegionFromBitmap uses the bitmap's mask to set the shape. If your source image has an alpha channel then it won't have a mask and so wx.RegionFromBitmap will not be able to determine what shape to use. You can convert the source image such that all pixels are either fully opaque or fully transparent, and then wx.Image will load it with a mask instead of an alpha channel. Or you can convert it at runtime using wx.Image's ConvertAlphaToMask method before converting it to a wx.Bitmap.
Your code says that it needs to be in png-8 format in order for transparency to work.
First of all, is the image in png-8 format?
second, why is this a requisite for transparency???
You're converting your image to a bitmap - bitmaps do not support transparency.
As a workaround you could use a .gif (if you can stand the limited color set).
They only support a 1-bit alpha channel.