why BitBlt() is slower in python compared to C++? - python

I am trying to use BitBlt() function to copy the bitmap data.
When I use the python win32ui/win32gui API the average time it takes is coming around 30 ms, while the same API when I call using C++, the average time comes around 10-15 ms.
So I am wondering, what could be the reason for the this behavior?
Thanks
EDIT : Here is the snippet :
python :
hdesktop = win32gui.GetDesktopWindow()
# create a device context
desktop_dc = win32gui.GetWindowDC(hdesktop)
img_dc = win32ui.CreateDCFromHandle(desktop_dc)
# create a memory based device context
mem_dc = img_dc.CreateCompatibleDC()
# create a bitmap object
screenshot = win32ui.CreateBitmap()
screenshot.CreateCompatibleBitmap(img_dc, width, height)
oldbmp = mem_dc.SelectObject(screenshot)
# copy the screen into our memory device context
mem_dc.BitBlt((destUpLeftX, destUpLeftY), (width, height), img_dc, (srcUpLeftX, srcUpLeftY),win32con.SRCCOPY)
mem_dc.SelectObject(oldbmp)
win32gui.DeleteObject(screenshot.GetHandle())
img_dc.DeleteDC()
win32gui.ReleaseDC(hdesktop, desktop_dc)
mem_dc.DeleteDC()
C++ :
HDC hwindowDC=GetDC(GetDesktopWindow());
HDC hwindowCompatibleDC=CreateCompatibleDC(hwindowDC);
// create a bitmap
HBITMAP hbwindow = CreateCompatibleBitmap( hwindowDC, width, height);
LOG(LOG_DBG, "before SelectObject");
// SAVE OLD BITMAP
HGDIOBJ hOldBmp = SelectObject(hwindowCompatibleDC, hbwindow); //copy from hwindowCompatibleDC to hbwindow
BitBlt(hwindowCompatibleDC, destUpLeftX, destUpLeftY, width, height, hwindowDC, srcUpLeftX, srcUpLeftY, SRCCOPY);
SelectObject(hwindowCompatibleDC, hOldBmp);
DeleteDC(hwindowCompatibleDC);
DeleteObject(hbwindow);
// RELEASE WINDOW DC
ReleaseDC(GetDesktopWindow(), hwindowDC);

Related

python thread creating in callback, not finishing

I'm trying to save image buffers as .pngs with blender's python API (but this is mainly just a python question)
I'm trying to speed things up by making a thread to save the image, and the function that creates the thread is being called from a callback-handler that is activated whenever the 3D screen is refreshed, here is the full code (its a little messy):
import base64, io, os, bgl, gpu, bpy, threading, time, sys
import numpy as np
from gpu_extras.presets import draw_texture_2d
from PIL import Image
import multiprocessing.pool as mpool
finalPath = bpy.context.scene.render.filepath + "hithere.png"
WIDTH = 1920
HEIGHT = 1080
offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)
def draw2():
global finalPath
global array
global WIDTH
global HEIGHT
global needsSaving
context = bpy.context
scene = context.scene
view_matrix = scene.camera.matrix_world.inverted()
projection_matrix = scene.camera.calc_matrix_camera(
context.depsgraph, x=WIDTH, y=HEIGHT)
offscreen.draw_view3d(
scene,
context.view_layer,
context.space_data,
context.region,
view_matrix,
projection_matrix)
bgl.glDisable(bgl.GL_DEPTH_TEST)
draw_texture_2d(offscreen.color_texture, (0, -125), WIDTH, HEIGHT)
buffer = bgl.Buffer(bgl.GL_BYTE, WIDTH * HEIGHT * 4)
bgl.glReadBuffer(bgl.GL_BACK)
bgl.glReadPixels(0, -125, WIDTH, HEIGHT, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
needle = threading.Thread(target=saveIt,args=[buffer, finalPath, WIDTH, HEIGHT])
needle.daemon = True
needle.start()
#### thread.start_new_thread(saveIt,(buffer, finalPath, WIDTH, HEIGHT))
def coby(scene):
frame = scene.frame_current
folder = scene.render.filepath
myFormat = "png"#scene.render.image_settings.renderformat.lower()
outputPath = os.path.join(folder, "%05d.%s" % (frame, myFormat))
global finalPath
finalPath = outputPath
h = bpy.types.SpaceView3D.draw_handler_add(draw2, (), 'WINDOW', 'POST_PIXEL')
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(coby)
def saveIt(buffer, path, width, height):
array = np.asarray(buffer, dtype=np.uint8)
myBytes = array.tobytes()
im = Image.frombytes("RGBA",(width, height), myBytes)
rawBytes = io.BytesIO()
im.save(rawBytes, "PNG")
rawBytes.seek(0)
base64Encoded = base64.b64encode(rawBytes.read())
txt = "data:image/png;base64," + base64Encoded.decode()
f = open(finalPath, "wb")
f.write(base64.decodebytes(base64Encoded))
f.close()
it actually does work, except when I play the timeline in blender (which calls the frame_pre callback and also the 3D view-refresh callback (not sure in which order though), most of my images are replaced, except some are not, as can be seen in this screenshot:
[![enter image description here][1]][1]
I originally had all blue-planed images, then I ran the script through the thread, and it replaced almost all of them, except some of the blue-planed images still remain (seemingly at a random interval). This worked fine if I call .join() right after I make the thread, or don't use the thread at all, but seemingly the thread is the only way to make it work a lot faster.
I've been looking around for how to use threads with queues and pooling (How to use python multiprocessing Pool.map within loop, What happened to thread.start_new_thread in python 3,
How can I make a background, non-blocking input loop in python?, Creating Threads in python),
SO: Why aren't all of the threads finishing?
[1]: https://i.stack.imgur.com/nJCwH.png .................

Load image on python CheckListCtrl with transparency

I'm starting to use WX GUI on python, and on my "Hello Word" project I'm trying to create a program with the ability to read any image and show it as icon into a CheckListCtrl column. I've done the first part (read the image and draw it into the CheckListCtrl), but I'm not able to load a PNG image and keep the transparency on that icon.
My code is the following:
'''
17 June 2018
#autor: Daniel Carrasco
'''
import wx
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
import sys
from pathlib import Path
BACKGROUNDCOLOR = (240, 240, 240, 255);
class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
def __init__(self, parent):
wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER,
size=wx.Size(395, 467), pos=wx.Point(10, 20));
CheckListCtrlMixin.__init__(self);
ListCtrlAutoWidthMixin.__init__(self);
#====================================================================
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
self.dataFolder = {
"images": Path("images/")
}
wx.Frame.__init__(self, *args, **kwargs);
icon = wx.Icon("icons.ico", wx.BITMAP_TYPE_ICO)
self.SetIcon(icon);
self.createWidgets();
self.createButtons();
self.Show();
#----------------------------------------------------------
def exitGUI(self, event): # callback
self.Destroy();
#----------------------------------------------------------
def createWidgets(self):
self.CreateStatusBar(); # wxPython built-in method
self.createMenu();
# Creamos el panel
boxSizer = wx.BoxSizer();
panel = wx.Panel(self);
panel.SetBackgroundColour(BACKGROUNDCOLOR);
panel.SetSizerAndFit(boxSizer);
staticBox = wx.StaticBox( panel, -1, "Listado de Saves", size=(415, 500),
pos=wx.Point(5, 0) )
self.statBoxSizerV = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
# Lista de items
self.itemList = CheckListCtrl(staticBox);
self.itemList.InsertColumn(0, '', width=32);
self.itemList.InsertColumn(1, 'Icono', width=52);
self.itemList.InsertColumn(2, 'Título', width=140);
self.il_Small = self.itemList.GetImageList(wx.IMAGE_LIST_SMALL);
self.il = wx.ImageList(48, 48, wx.IMAGE_LIST_SMALL);
self.itemList.SetImageList(self.il, wx.IMAGE_LIST_SMALL);
image = wx.Image(str(self.dataFolder["images"] / "tick_1.png"), wx.BITMAP_TYPE_ANY);
self.il.Add(wx.Bitmap(image));
image = wx.Image(str(self.dataFolder["images"] / 'tick_2.png'), wx.BITMAP_TYPE_ANY);
self.il.Add(wx.Bitmap(image));
image = wx.Image(str(self.dataFolder["images"] / 'exit.png'), wx.BITMAP_TYPE_ANY );
for x in range(0, 4):
for y in range(0, 4):
image.SetAlpha(x, y, 0);
image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
# image.ClearAlpha();
self.il.Add(wx.Bitmap(image));
image = wx.Image(str(self.dataFolder["images"] / 'test.png'), wx.BITMAP_TYPE_ANY );
image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
self.il.Add(image.ConvertToBitmap());
index = self.itemList.InsertItem(sys.maxsize, "test");
self.itemList.SetItemColumnImage(0, 1, 3)
#self.itemList.Append("Prueba");
#----------------------------------------------------------
def createButtons(self):
pass
#----------------------------------------------------------
def createMenu(self):
# Menú Archivo
APP_EXIT = 1;
mArchivo = wx.Menu();
qmi = wx.MenuItem(mArchivo, APP_EXIT, '&Salir\tCtrl+Q');
image = wx.Image(str(self.dataFolder["images"] / 'exit.png'),wx.BITMAP_TYPE_PNG);
image = image.Scale(16, 16, wx.IMAGE_QUALITY_HIGH);
qmi.SetBitmap(image.ConvertToBitmap());
mArchivo.Append(qmi);
self.Bind(wx.EVT_MENU, self.exitGUI, id=APP_EXIT);
# Barra de menús
menuBar = wx.MenuBar();
menuBar.Append(mArchivo, "&Archivo");
# Seteamos la barra de menús
self.SetMenuBar(menuBar);
#======================
# Start GUI
#======================
app = wx.App()
MainFrame(None, style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX, title="Savegame Linker", size=(485,587))
app.MainLoop()
This code just read the image into and Image object, then scale the image and add a border resizing. The problem is that PNG transparency is not kept and only the border is transparent:
If I remove the image transparency with Photoshop (adding white background), then the image is showed with the transparency I want:
Is there any way to keep the PNG transparency on CheckListCtrl, or at least add a white background to the image (that looks like an alternative solution). I want to do it if posible using only WX, because I think that use pillow module for example, just for remove transparency, is not an optimal solution.
Thanks!!
I don't know how you run that code but try as I might, I have been unable to do so and thus cannot be sure of the answer below.
A wx.Image has a range of "image handlers", of which, only the BMPHandler is loaded by default. I suspect that you need to load the PNGHandler before attempting SetAlpha and you should probably check the image with HasAlpha beforehand.
See: https://docs.wxpython.org/wx.Image.html
Alpha channel support
Starting from wxWidgets 2.5.0 wx.Image supports alpha channel data, that is in addition to a byte for the red, green and blue colour components for each pixel it also stores a byte representing the pixel opacity. An alpha value of 0 corresponds to a transparent pixel (null opacity) while a value of 255 means that the pixel is 100% opaque. The constants IMAGE_ALPHA_TRANSPARENT and IMAGE_ALPHA_OPAQUE can be used to indicate those values in a more readable form. While all images have RGB data, not all images have an alpha channel. Before using wx.Image.GetAlpha you should check if this image contains an alpha channel with wx.Image.HasAlpha . Currently the BMP, PNG, TGA, and TIFF format handlers have full alpha channel support for loading so if you want to use alpha you have to use one of these formats. If you initialize the image alpha channel yourself using wx.Image.SetAlpha , you should save it in either PNG, TGA, or TIFF format to avoid losing it as these are the only handlers that currently support saving with alpha.
Available image handlers The following image handlers are
available. BMPHandler is always installed by default. To use other
image formats, install the appropriate handler with
wx.Image.AddHandler or call wx.InitAllImageHandlers .
BMPHandler: For loading (including alpha support) and saving, always
installed.
wx.PNGHandler: For loading and saving. Includes alpha
support.
wx.JPEGHandler: For loading and saving.
wx.GIFHandler: For loading and saving (see below).
wx.PCXHandler: For loading and saving (see below).
wx.PNMHandler: For loading and saving (see below).
wx.TIFFHandler: For loading and saving. Includes alpha support.
wx.TGAHandler: For loading and saving. Includes alpha support.
wx.IFFHandler: For loading only. wx.XPMHandler: For loading and saving.
ICOHandler: For loading and saving. CURHandler: For loading and saving.
ANIHandler: For loading only.
When saving in PCX format, wx.PCXHandler will count the number of different colours in the image; if there are 256 or less colours, it will save as 8 bit, else it will> save as 24 bit. Loading PNMs only works for ASCII or raw RGB images.
When saving in PNM format, wx.PNMHandler will always save as raw RGB.
Saving GIFs requires images of maximum 8 bpp (see Quantize ), and the
alpha channel converted to a mask (see wx.Image.ConvertAlphaToMask ).
Saving an animated GIF requires images of the same size (see
wx.GIFHandler.SaveAnimation )
See also wx.Bitmap, wx.InitAllImageHandlers , PixelData
I think that the right way is converting the transparency into mask. I've already tested it, but looks like I've used the function after other that made it fail.
image = wx.Image(str(self.dataFolder["images"] / 'test.png'), wx.BITMAP_TYPE_ANY );
image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
image.ConvertAlphaToMask(threshold=50);
image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
self.il.Add(image.ConvertToBitmap());
The last time I'd tried the function was after the image.Size function, and then it fails (maybe the Size function removes the transparency), but if is done before then works.
Thanks again and greetings!!
EDIT:
A few months later I've continued the project and I've found another way to make the background transparent: Remove the transparency converting it to solid white:
def remove_transparency(im, bg_colour=(255, 255, 255)):
# Only process if image has transparency (http://stackoverflow.com/a/1963146)
if im.mode in ('RGBA', 'LA') or (im.mode == 'P' and 'transparency' in im.info):
# Need to convert to RGBA if LA format due to a bug in PIL (http://stackoverflow.com/a/1963146)
alpha = im.convert('RGBA').split()[-1]
# Create a new background image of our matt color.
# Must be RGBA because paste requires both images have the same format
# (http://stackoverflow.com/a/8720632 and http://stackoverflow.com/a/9459208)
bg = Image.new("RGBA", im.size, bg_colour + (255,))
bg.paste(im, mask=alpha)
return bg
else:
return im
# Open the image
sbuf = BytesIO(campo[4])
im = Image.open(sbuf)
# Remove transparency (white background will be transparent on ImageList)
im2 = remove_transparency(im).convert("RGB")
im.close()
# Create an wx.Image from image
width, height = im2.size
image = wx.Image(width, height, im2.tobytes())
image = image.Size(wx.Size(48,48), wx.Point(2,2), 255, 255, 255)
# Convert it to Bitmap and add it to ImageList
image = image.ConvertToBitmap()
icon_image = self.il.Add(image)
sbuf.close()
I've changed slightly the way I store the images, and now they comes from a PNG stored on an SQLite DB BLOB (campo[4]).
Greetings!!

How to take snapshot from given region of the screen with python?

Here is my thoughts:
1) snap shot the given region from the screen like (100,100,80,60), save the result as image
2) process the image with OpenCV python interface
Just be first to python and wonder if this is good solution,be specific,wonder how to snapshot with python.
Thanks,
It's fairly simple using CGRectMake in Apple's CoreGraphics api:
CG.CGRectMake(x, y, w, h)
This allows you to define the horizontal/vertical positions, and width/height respectively.
Code:
#!/usr/bin/python
import Quartz
import LaunchServices
from Cocoa import NSURL
import Quartz.CoreGraphics as CG
def screenshot(path, dpi, region = None):
if region is None:
region = CG.CGRectInfinite
image = CG.CGWindowListCreateImage(
region,
CG.kCGWindowListOptionOnScreenOnly,
CG.kCGNullWindowID,
CG.kCGWindowImageDefault)
url = NSURL.fileURLWithPath_(path)
dest = Quartz.CGImageDestinationCreateWithURL(
url,
LaunchServices.kUTTypePNG, 1, None
)
prop = {
Quartz.kCGImagePropertyDPIWidth: dpi,
Quartz.kCGImagePropertyDPIHeight: dpi,
}
Quartz.CGImageDestinationAddImage(dest, image, prop)
Quartz.CGImageDestinationFinalize(dest)
spec = (0, 0, 800, 600) # x, y, w, h
path = '/path/to/screnshot_region.png' # save path
area = CG.CGRectMake(*spec) # set area to cgrect
screenshot(path, dpi=72, region=area) # call function
To use, just call the function:
screenshot(path, dpi=72, region=area)
*dpi will set the image output resolution; leave out the region argument for fullscreen capture. In regards to the OpenCV portion - I don't use it often enough to provide anything at this time.

CreateCompatibleDC fails after calling it exactly 4,984 times

I've encountered a strange bug in my program. It's a little odd, as it occurs on exactly the 4984th call to the function. I've been tweaking this all day, and without fail, that's the number at which it fails.
The code in question is a small convenience function which creates and returns a DC and Bitmap. The context of this little function is that it's a piece in my stab at a screen recorder, so it's getting called tons and tons of times.
When I first noticed the error, after some sleuthing around, I found this very similar Stackoverflow question, so the code below is modeled after the answer in that thread. However, even after following the suggested deletion and releasing pattern, the problem remains for me right on that 4984th iteration.
This is the specific failure point of the program:
def _createDcAndBitmap(self, size, input_bitmap=None):
hwnd = win32gui.GetDesktopWindow()
zhwndDevice = win32gui.GetWindowDC(hwnd)
zmfcDC = win32ui.CreateDCFromHandle(zhwndDevice)
zsaveDC = zmfcDC.CreateCompatibleDC()
zsaveBitMap = win32ui.CreateBitmap()
zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)
hOldBmp = zsaveDC.SelectObject(zsaveBitMap)
return zsaveDC, zsaveBitMap, hOldBmp, hwnd
The error is always throw from the line:
zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)
With the error reported by Python as:
error: CreateCompatibleDC failed
Calling FormatMessage from the win32api gives further information:
Invalid device context (DC) handle.
The Full Code:
class Bitmap(object):
_sourceDC, _sourceBitmap, hOldBmp, hwnd = self._bytesToDcAndBitmap(bytestring, sourceSize)
_bytes, _size = self._scaleBitmap(_sourceDC, _sourceBitmap, hOldBmp, hwnd, sourceSize)
def _scaleBitmap(self, sourceDC, sourceBitmap, sourceHOldBmp, sourceHwnd, sourceSize):
'''
Resizes the current bitmap down to a target size
of (X, 540), where the X is varied depending on the
aspect ratio of the input bitmap
'''
target_size = self._getTargetSize(sourceSize)
destDC, destBitmap, hOldBmp, hwnd = self._createDcAndBitmap(target_size)
win32gui.SetStretchBltMode(destDC.GetHandleAttrib(), 4)
win32gui.StretchBlt(pywintypes.HANDLE(destDC.GetHandleAttrib()), 0,0,target_size[0], target_size[1], # #UndefinedVariable HANDLE -- PyDev is dumb
sourceDC.GetHandleAttrib(), 0,0, sourceSize[0], sourceSize[1], win32con.SRCCOPY)
new_bytestring = destBitmap.GetBitmapBits(True)
new_size = self._bitmapSize(destBitmap)
self._deleteDCBitmapOldBmpAndHwmn(sourceDC, sourceBitmap, sourceHOldBmp, sourceHwnd)
self._deleteDCBitmapOldBmpAndHwmn(destDC, destBitmap, hOldBmp, hwnd)
def _bytesToDcAndBitmap(self, bytestring, sourceSize):
a = (ctypes.c_int * (sourceSize[0]*sourceSize[1]))()
ctypes.memmove(a, bytestring, len(bytestring))
hwnd = win32gui.GetDesktopWindow()
zhwndDevice = win32gui.GetWindowDC(hwnd)
zmfcDC = win32ui.CreateDCFromHandle(zhwndDevice)
zsaveDC = zmfcDC.CreateCompatibleDC()
zsaveBitMap = win32ui.CreateBitmap()
zsaveBitMap.CreateCompatibleBitmap(zmfcDC, sourceSize[0], sourceSize[1])
hOldBmp = zsaveDC.SelectObject(zsaveBitMap)
ctypes.windll.gdi32.SetBitmapBits(zsaveBitMap.GetHandle(), len(bytestring), ctypes.byref(a))
return zsaveDC, zsaveBitMap, hOldBmp, hwnd
def _createDcAndBitmap(self, size, input_bitmap=None):
hwnd = win32gui.GetDesktopWindow()
zhwndDevice = win32gui.GetWindowDC(hwnd)
zmfcDC = win32ui.CreateDCFromHandle(zhwndDevice)
zsaveDC = zmfcDC.CreateCompatibleDC()
zsaveBitMap = win32ui.CreateBitmap()
zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)
hOldBmp = zsaveDC.SelectObject(zsaveBitMap)
return zsaveDC, zsaveBitMap, hOldBmp, hwnd
def _deleteDCBitmapOldBmpAndHwmn(self, dc, bitmap, old_bitmap, hwnd):
win32gui.SelectObject(dc.GetHandleAttrib(), old_bitmap.GetHandle())
win32gui.DeleteDC(dc.GetHandleAttrib())
win32gui.DeleteObject(bitmap.GetHandle())
win32gui.ReleaseDC(win32gui.GetDesktopWindow(), hwnd)
The code is a little peculiar, as it's running on the 'exit' end of a pipe. So it's job is reconstructing a serialized byte string (gotten from GetBitmapBits()) back into a Bitmap, scaling it, then going back to a byte string. Doing it this way is about a solid order of magnitude faster than using higher level Python libraries :)
So, I'm guessing this is due to a memory leak somewhere, but as far as I can tell, I'm closing everything down correctly. And yet, it still fails right around the 5000th call.
Am I missing a leak somewhere?

Python + GStreamer: scale video to window

I'm having some trouble rescaling video output of GStreamer to the dimension of the window the video is displayed in (retaining aspect ratio of the video). The problem is that I first need to preroll the video to be able to determine its dimensions by retrieving the negotiated caps, and then calculate the dimensions it needs to be displayed in to fit the window. Once I have prerolled the video and got the dimension caps, I cannot change the video's dimension anymore. Setting the new caps still results in the video being output in its original size. What must I do to solve this?
Just to be complete. In the current implementation I cannot render to an OpenGL texture which would have easily solved this problem because you could simply render output to the texture and scale it to fit the screen. I have to draw the output on a pygame surface, which needs to have the correct dimensions. pygame does offer functionality to scale its surfaces, but I think such an implementation (as I have now) is slower than retrieving the frames in their correct size directly from GStreamer (am I right?)
This is my code for loading and displaying the video (I omitted the main loop stuff):
def calcScaledRes(self, screen_res, image_res):
"""Calculate image size so it fits the screen
Args
screen_res (tuple) - Display window size/Resolution
image_res (tuple) - Image width and height
Returns
tuple - width and height of image scaled to window/screen
"""
rs = screen_res[0]/float(screen_res[1])
ri = image_res[0]/float(image_res[1])
if rs > ri:
return (int(image_res[0] * screen_res[1]/image_res[1]), screen_res[1])
else:
return (screen_res[0], int(image_res[1]*screen_res[0]/image_res[0]))
def load(self, vfile):
"""
Loads a videofile and makes it ready for playback
Arguments:
vfile -- the uri to the file to be played
"""
# Info required for color space conversion (YUV->RGB)
# masks are necessary for correct display on unix systems
_VIDEO_CAPS = ','.join([
'video/x-raw-rgb',
'red_mask=(int)0xff0000',
'green_mask=(int)0x00ff00',
'blue_mask=(int)0x0000ff'
])
self.caps = gst.Caps(_VIDEO_CAPS)
# Create videoplayer and load URI
self.player = gst.element_factory_make("playbin2", "player")
self.player.set_property("uri", vfile)
# Enable deinterlacing of video if necessary
self.player.props.flags |= (1 << 9)
# Reroute frame output to Python
self._videosink = gst.element_factory_make('appsink', 'videosink')
self._videosink.set_property('caps', self.caps)
self._videosink.set_property('async', True)
self._videosink.set_property('drop', True)
self._videosink.set_property('emit-signals', True)
self._videosink.connect('new-buffer', self.__handle_videoframe)
self.player.set_property('video-sink', self._videosink)
# Preroll movie to get dimension data
self.player.set_state(gst.STATE_PAUSED)
# If movie is loaded correctly, info about the clip should be available
if self.player.get_state(gst.CLOCK_TIME_NONE)[0] == gst.STATE_CHANGE_SUCCESS:
pads = self._videosink.pads()
for pad in pads:
caps = pad.get_negotiated_caps()[0]
self.vidsize = caps['width'], caps['height']
else:
raise exceptions.runtime_error("Failed to retrieve video size")
# Calculate size of video when fit to screen
self.scaledVideoSize = self.calcScaledRes((self.screen_width,self.screen_height), self.vidsize)
# Calculate the top left corner of the video (to later center it vertically on screen)
self.vidPos = ((self.screen_width - self.scaledVideoSize [0]) / 2, (self.screen_height - self.scaledVideoSize [1]) / 2)
# Add width and height info to video caps and reload caps
_VIDEO_CAPS += ", width={0}, heigh={1}".format(self.scaledVideoSize[0], self.scaledVideoSize[1])
self.caps = gst.Caps(_VIDEO_CAPS)
self._videosink.set_property('caps', self.caps) #??? not working, video still displayed in original size
def __handle_videoframe(self, appsink):
"""
Callback method for handling a video frame
Arguments:
appsink -- the sink to which gst supplies the frame (not used)
"""
buffer = self._videosink.emit('pull-buffer')
img = pygame.image.frombuffer(buffer.data, self.vidsize, "RGB")
# Upscale image to new surfuace if presented fullscreen
# Create the surface if it doesn't exist yet and keep rendering to this surface
# for future frames (should be faster)
if not hasattr(self,"destSurf"):
self.destSurf = pygame.transform.scale(img, self.destsize)
else:
pygame.transform.scale(img, self.destsize, self.destSurf)
self.screen.blit(self.destSurf, self.vidPos)
# Swap the buffers
pygame.display.flip()
# Increase frame counter
self.frameNo += 1
I'm pretty sure that your issue was (has it is very long time since you asked this question) that you never hooked up the bus to watch for messages that were emitted.
The code for this is usually something like this:
def some_function(self):
#code defining Gplayer (the pipeline)
#
# here
Gplayer.set_property('flags', self.GST_VIDEO|self.GST_AUDIO|self.GST_TEXT|self.GST_SOFT_VOLUME|self.GST_DEINTERLACE)
# more code
#
# finally
# Create the bus to listen for messages
bus = Gplayer.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect('message', self.OnBusMessage)
bus.connect('sync-message::element', self.OnSyncMessage)
# Listen for gstreamer bus messages
def OnBusMessage(self, bus, message):
t = message.type
if t == Gst.MessageType.ERROR:
pass
elif t == Gst.MessageType.EOS:
print ("End of Audio")
return True
def OnSyncMessage(self, bus, msg):
if msg.get_structure() is None:
return True
if message_name == 'prepare-window-handle':
imagesink = msg.src
imagesink.set_property('force-aspect-ratio', True)
imagesink.set_window_handle(self.panel1.GetHandle())
The key bit for your issue is setting up a call back for the sync-message and in that call-back, setting the property force-aspect-ratio to True.
This property ensures that the video fits the window it is being displayed in at all times.
Note the self.panel1.GetHandle() function names the panel in which you are displaying the video.
I appreciate that you will have moved on but hopefully this will help someone else trawling through the archives.

Categories