Ability to save screenshot of canvas in inline script
(Feature request)
Several times in the past I have found it necessary to save the contents of a canvas to an image file (i.e. make a screenshot). I wrote a function for this, that I copy paste each time, but I think it might be of added value to integrate this function in the OpenSesame canvas object, especially since all backends offer this functionality natively and it is just a matter of passing a few commands (and do some checks if saving the file is possible of course). Here is the code that does the saving:
def save_as_file(canvas, filename, buffer='front'):
""" Save the contents of the current screenbuffer to an image file.
Parameters
----------
canvas : openexp.Canvas
The canvas object to save the image from
filename : string
The location and filenam to save the image to. If an absolute path is
provided, the file will be saved there. If a relative path is provided,
the file will be saved to that folder relative to the location of the
experiment file
buffer : string, default 'front'
Only relevant when using the psychopy backend. Indicates whether the
front buffer or back buffer should be saved, so this value should be
'front' or 'back'
Returns
-------
string : The full path to the file that has just been saved
Raises
------
libopensesame.exceptions.osexception if an incorrect file format or buffer
value have been provided
"""
import os
from libopensesame.exceptions import osexception
# Check if supplied filename has a valid image extension
if not os.path.splitext(filename)[1] in ['.jpg', '.jpeg', '.png', '.bmp', '.tga']:
raise osexception("The filename does not have a valid image extension. Use .jpg, .jpeg, .png, .bmp or .tga")
if not buffer in ['front','back']:
raise osexception("Invalid buffer value; the only options are 'front' or 'back'.")
# Check if image file path is absolute, if not, find relative path to current experiment file
if not os.path.isabs(filename):
filename = os.path.abspath(os.path.join(self.experiment.experiment_path, filename))
# Create any subfolders if necessary
file_dir = os.path.dirname(filename)
if not os.path.isdir(file_dir):
os.makedirs(file_dir)
# Backend specific saving of canvas to image file
if self.experiment.get("canvas_backend") == "legacy":
import pygame
pygame.image.save(canvas.surface, filename)
elif self.experiment.get("canvas_backend") == "xpyriment":
canvas._canvas.save(filename)
elif self.experiment.get("canvas_backend") == "psycho":
win.getMovieFrame(buffer)
win.saveMovieFrames(filename)
# Return the abspath of the saved file (to easily reference it later)
return filename
My idea would be that this function is contained in the canvas object (so this does not need to be passed as the first parameter). To call this function, one could simply do:
canvas = self.offline_canvas()
canvas.text("Dit wordt een gave screenshot")
canvas.show()
canvas.save_as_file("screenshot.png")
What do you think? (maybe you can think of a more appropriate name than 'save_af_file')
Hi Daniel,
Yes, that seems like a useful addition. As you say, it should be included in the canvas back-end, perhaps as a screenshot() or screen_capture() method. To me, save_as_file() is a bit ambiguous, because it could also mean that you're pickling the object.
If you want to add this, please use the ising branch, which is where all the new functionality should go. Or, if this is still too unstable to be useful, you can work from heisenberg, but I will still merge it into ising.
You know what the routine is for modifying the backends?
- First add a documented dummy method to
openexp._canvas.canvas.canvas, and then override this method in all backends that support it. - Docstrings must be yamldoc-style, so that it can automatically parsed by the documentation site.
- Make sure that the functionality is unambiguous. For example, the
bufferkeyword is applicable to all backends--it's just that not all backends can save the front buffer, so they should raise anosexceptionwhenbuffer=u'front'(I'm not sure, actually, but that's what your implementation suggests). - Path-handling should be unicode safe and Python 2 and 3 compatible. You can use
safe_decode(), imported fromlibopensesame.py3compatto make sure it's a unicode object.
Cheers! Sebastiaan
Ok, thanks for the explanation. I'll see what I can do in the near future!
I needed this function again recently and came across this post, which I had completely forgot. Is this still relevant enough to implement in the OS Canvases themselves?? Has anything changed regarding your instructions on how to integrate this with the OpenSesame code base?
Has this been implemented? It does not show up in the doc.
@mariansauter This hasn't been implemented yet. There's still a PR open (#658 ) but it has gone off the radar. I don't think @dschreij has time for this currently, but if someone (you?) feels like picking this up and properly testing it against the loewenfeld branch then perhaps we can still merge it?