OpenSesame icon indicating copy to clipboard operation
OpenSesame copied to clipboard

Ability to save screenshot of canvas in inline script

Open dschreij opened this issue 11 years ago • 5 comments

(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')

dschreij avatar Apr 16 '15 13:04 dschreij

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 buffer keyword is applicable to all backends--it's just that not all backends can save the front buffer, so they should raise an osexception when buffer=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 from libopensesame.py3compat to make sure it's a unicode object.

Cheers! Sebastiaan

smathot avatar Apr 17 '15 08:04 smathot

Ok, thanks for the explanation. I'll see what I can do in the near future!

dschreij avatar Apr 24 '15 11:04 dschreij

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?

dschreij avatar Apr 22 '19 11:04 dschreij

Has this been implemented? It does not show up in the doc.

mariansauter avatar Apr 07 '21 09:04 mariansauter

@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?

smathot avatar Apr 08 '21 08:04 smathot