#
##
## SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
## SPDX-License-Identifier: GPL-3.0-or-later
##
## This file is part of pyFormex 3.4 (Thu Nov 16 18:07:39 CET 2023)
## pyFormex is a tool for generating, manipulating and transforming 3D
## geometrical models by sequences of mathematical operations.
## Home page: https://pyformex.org
## Project page: https://savannah.nongnu.org/projects/pyformex/
## Development: https://gitlab.com/bverheg/pyformex
## Distributed under the GNU General Public License version 3 or later.
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see http://www.gnu.org/licenses/.
##
"""Saving OpenGL renderings to image files.
This module defines some functions that can be used to save the
OpenGL rendering and the pyFormex GUI to image files. There are even
functions for automatically saving multiple renderings to a series of
files, for creating a movie from these images and for recording the GUI
directly to a video file.
The most important functions in this module are
- :func:`saveImage`: save (parts of) the GUI to an image file
- :func:`recordSession`: record (parts of) the GUI to a video file
"""
from PIL import Image
import pyformex as pf
from pyformex import utils
from pyformex import Path
from pyformex.gui import QtGui
from pyformex.gui import qtutils
# global parameters for multisave mode
multisave = None
# imported module
gl2ps = None
# The image formats recognized by pyFormex
image_formats = {
'qt': None,
'pil': None,
'magick': None,
'gl2ps': None,
}
# TODO: should we keep this undocumented old functionality?
def convertEPS(epsfile, tofile, fmt):
epsfile = Path(epsfile)
if epsfile.exists():
cmd = f"pstopnm -portrait -stdout {epsfile}"
if fmt != 'ppm':
cmd += f" | pnmto{fmt} > {tofile}"
utils.command(cmd, shell=True)
######## LOW LEVEL FUNCTIONS ###############
[docs]def save_qt(filename, format, quality=-1, canvas=None, size=None,
crop=None, alpha=False):
"""Save an OpenGL canvas rendering as an image file.
Saves a :class:`~gui.viewport.QtCanvas` rendering using the Qt library
functions. This provides the 'qt' tool functionality of :func:`saveImage`,
which is the prefered user level function for saving pyFormex renderings
to image files.
Parameters
----------
filename: :term:`path_like`
The file path to which to save the image.
format: str
One of the supported image formats. Any format supported by QImage
for writing. The list can be got from ``imageFormats('qt', 'w')``.
quality: int, optional
The image quality for compressed formats.
canvas: :class:`~gui.viewport.QtCanvas`, optional
The canvas to be saved. If not specified, the current active canvas
is used.
size: int tuple, optional
A tuple (width, height) specifying the size of the output image.
The default is to use the canvas size.
crop: int 4-tuple, optional
A tuple (x, y, w, h) defining a rectangular area to crop from the
image. (x,y) is the top left corner and (w,h) are the width and
height in pixels. The stored image will thus have size (w,h).
Note that combining this parmeter with a canvas resizing using the
`size` parameter will make it difficult to predict what exactly will
end up in the cropped image. Therefore this parameter is commonly only
used with the original canvas size.
See Also
--------
saveImage: the recommended high level versatile image saving function
save_window: save the pyFormex window as image file
save_gl2ps: save the OpenGL rendering in a vector format
"""
pf.debug(f"save_qt", pf.DEBUG.IMAGE)
# make sure we have the current content displayed (on top)
canvas.makeCurrent()
canvas.raise_()
canvas.display()
pf.app.processEvents()
sta = 1 # Flag failure
filename = Path(filename)
if format is None:
format = filename.lsuffix.strip('.')
if format in imageFormats('qt', 'w'):
pf.debug("Image format can be saved by Qt", pf.DEBUG.IMAGE)
if size is None or size == canvas.getSize():
#
# Use direct grabbing from current buffer
# TODO: We could grab from the OpenGL buffers here!!
#
canvas.glFinish()
pf.debug(f"Saving image from opengl buffer with"
f"size {canvas.getSize()}", pf.DEBUG.IMAGE)
qim = canvas.grabFrameBuffer(withAlpha=alpha)
else:
# Render in an off-screen buffer and grab from there
# TODO: the offscreen buffer should be properly initialized
# according to the current canvas
#
wc, hc = canvas.getSize()
try:
w, h = size
except Exception:
w, h = wc, hc
pf.debug(f"Saving image from virtual buffer with size {w}x{h}",
pf.DEBUG.IMAGE)
qim = canvas.image(resize=(w, h), remove_alpha=not alpha)
pf.debug(f"Image has alpha channel: {qim.hasAlphaChannel()}",
pf.DEBUG.IMAGE)
if crop:
qim = qim.copy(*crop)
pf.debug(f"Saving canvas to {filename} in format {format}"
f" with size ({crop[2:4]}) and quality {quality}",
pf.DEBUG.IMAGE)
if qim.save(filename, format, quality):
sta = 0
return sta
# if we have gl2ps, or when building docs
if gl2ps or pf.sphinx :
[docs] def save_gl2ps(filename, format=None, canvas=None, title='', producer='',
# viewport=None
):
""" Export the OpenGL rendering to PostScript/PDF/TeX format.
Exporting OpenGL renderings to PostScript is based on the PS2GL
library by Christophe Geuzaine (http://geuz.org/gl2ps/), linked
to Python by Toby Whites's wrapper
(http://www.esc.cam.ac.uk/~twhi03/software/python-gl2ps-1.1.2.tar.gz)
This function is only defined if the gl2ps module is found.
It provides the 'gl2ps' tool functionality of :func:`saveImage`,
which is the prefered user level function for saving pyFormex
renderings to image files.
Parameters
----------
filename: :term:`path_like`
The file path to which to save the image.
format: str
One of the supported image formats. Any format supported by gl2ps.
The list can be got from ``imageFormats('gl2ps', 'w')``,
but at least includes 'ps', 'eps', 'pdf' and 'tex'.
In the case of 'tex', two files are actually written: one with
the .tex extension, and one with .eps extension.
If format is not specified, it is derived from the filename
extension.
canvas: :class:`~gui.viewport.QtCanvas`, optional
The canvas to be saved. If not specified, the current active canvas
is used.
title: str, optional
An optional title to be written into the image file.
producer: str, optional
A producer string to be written in the image file. Default
is a pyFormex identification.
See Also
--------
saveImage: the recommended high level versatile image saving function
save_qt: save the OpenGL rendering as a raster image
save_window: save the pyFormex window as image file
"""
from pyformex.opengl.gl import GL
pf.debug(f"save_window_gl2ps", pf.DEBUG.IMAGE)
# make sure we have the current content displayed (on top)
canvas.makeCurrent()
canvas.raise_()
canvas.display()
pf.app.processEvents()
filename = Path(filename)
if format is None:
format = filename.lsuffix.strip('.')
if format in imageFormats('gl2ps', 'w'):
pf.debug("Image format can be saved by gl2ps", pf.DEBUG.IMAGE)
filetype = _gl2ps_types[filetype]
fp = open(filename, "wb")
if not title:
title = filename
if not producer:
producer = pf.fullVersion()
# if not viewport:
# viewport = GL.glGetIntegerv(GL.GL_VIEWPORT)
bufsize = 0
state = gl2ps.GL2PS_OVERFLOW
opts = ( gl2ps.GL2PS_SILENT |
gl2ps.GL2PS_SIMPLE_LINE_OFFSET |
gl2ps.GL2PS_USE_CURRENT_VIEWPORT
)
##| gl2ps.GL2PS_NO_BLENDING | gl2ps.GL2PS_OCCLUSION_CULL | gl2ps.GL2PS_BEST_ROOT
##color = GL[[0.,0.,0.,0.]]
#print(f"VIEWPORT {viewport}")
#print(fp)
viewport=None
while state == gl2ps.GL2PS_OVERFLOW:
bufsize += 1024*1024
gl2ps.gl2psBeginPage(title, _producer, viewport, filetype,
gl2ps.GL2PS_BSP_SORT, opts, GL.GL_RGBA,
0, None, 0, 0, 0, bufsize, fp, '')
canvas.display()
canvas.glFinish()
state = gl2ps.gl2psEndPage()
fp.close()
return 0
[docs]def save_window(filename, format, quality=-1, windowname=None, crop=None):
"""Save (part of) a window as an image file.
Saves any window or part of it to an image file. This uses the 'import'
command from ImageMagick and can save in any format supported by it.
It provides the 'magick' tool functionality of :func:`saveImage`,
which is the prefered user level function for saving the pyFormex window
to image files.
Parameters
----------
filename: :term:`path_like`
The filename on which to save the image.
format: str
One of the supported image formats. See ImageMagick. The format needs
to be specified. It is currently not derived from the file name.
quality: int, optional
See :func:`save_window_rect`
windowname: str, optional
The name of the window to be saved. The windowid or name of the window
you want to save. To find the id/name of an open window, you can run
the command xwininfo and click on the window. If not specified, the
pyFormex main window is used. A value 'root' will save the full
desktop window.
crop: tuple|str, optional
Define a subregion of the window to be saved. A subregion can
be specified as a tuple (x,y,w,h) defining the rectangle to be saved:
(x,y) is the position of the upper left corner with respect to the
window origin; (w,h) are the width and height of the rectangle.
Some subregions can also be conveniently specified by a string:
'all' will save all viewports of the central widget, 'vp' will save
only the current viewport. The default None will
save the whole window (without border decorations).
Notes
-----
While this function can be used to save any window, it is primarily provided
to save pyFormex windows. Therefore, in all cases, the pyFormex GUI and
current canvas are raised and updated before saving.
See Also
--------
saveImage: the recommended high level versatile image saving function
save_qt: save the OpenGL rendering as a raster image
save_gl2ps: save the OpenGL rendering in a vector format
"""
pf.debug(f"save_window", pf.DEBUG.IMAGE)
pf.GUI.raise_()
pf.GUI.repaint()
pf.GUI.toolbar.repaint()
pf.GUI.update()
pf.canvas.makeCurrent()
pf.canvas.raise_()
pf.canvas.update()
pf.app.processEvents()
# Deprecated value 'canvas'
if crop == 'canvas':
utils.warn("crop='canvas' is deprecated. Use crop='all' instead")
crop = 'all'
if crop in ['all', 'vp']:
windowname = pf.GUI.windowTitle()
widget = pf.GUI.central if crop=='all' else pf.canvas
x, y = qtutils.relPos(widget)
w, h = qtutils.Size(widget)
crop = (x, y, w, h)
if windowname is None:
windowname = pf.GUI.windowTitle()
return save_window_rect(filename, format, quality, windowname, crop)
[docs]def save_window_rect(filename, format, quality=-1, window='root', crop=None):
"""Save a rectangular part of the screen to a an image file.
This uses the external program 'import' from Imagemagick to save a
rectangle from any window on the screen.
Parameters
----------
filename: :term:`path_like`
The name of the file on which to save the image.
format: str
The format of the image file. The available formats can be
obtained from `imageFormats('magick', 'w')`.
quality: int
For compressed images, this defines the quality in percent.
For PNG images, this is a value 0..9, where 0 is uncompressed
and 9 is maximal compression. For JPEG, this is a value 1..100
where 100 is maximum quality.
A value -1 selects the default quality, which is 0 for PNG and
90 for JPEG.
crop: tuple of int, optional
A tuple (x,y,w,h) specifying the rectangle to be saved from
the window. (x,y,) is the top left corner relative to the
window. (w,h) is the size of the rectangle. The default is
to save the whole window.
See Also
--------
saveImage: the recommended high level versatile image saving function
save_window: save the pyFormex window to an image file
record_rect: record a screen rectangle to a video file
"""
pf.debug(f"save_window_rect({filename!r}, {format!r},"
f" quality={quality}, window={window!r}, crop={crop})",
pf.DEBUG.IMAGE)
utils.External.require('imagemagick')
format = format.lower()
cmd = ['import', '-window', window]
if crop:
x, y, w, h = crop
cmd += ['-crop', f"{w}x{h}+{x}+{y}"]
if quality == -1:
if format == 'png':
quality = 0
elif format in ['jpg', 'jpeg']:
quality = 90
cmd += ['-quality', f"{quality}"]
cmd += [f"{format}:{filename}"]
P = utils.system(cmd)
return P.returncode
######## saveImage ######################
# For each saving tool, record the possible extents
_image_tools = {
'qt': ['canvas', 'rectangle', 'allvps'] # always available
}
_image_tools_detected = False
[docs]def extent_options(tool):
"""Return possible extents for a tool"""
return _image_tools[tool]
[docs]def image_extents():
"""Return a list with all image extents"""
if not _image_tools_detected:
detect_tools()
extents = []
for t in _image_tools:
extents.extend([e for e in _image_tools[t] if e not in extents])
return extents
[docs]def saveImage(filename=None,
extent='canvas', tool='qt',
format=None, quality=-1, size=None, alpha=True,
multi=False, hotkey=True, autosave=False,
verbose=False):
"""Save the pyFormex rendering or window to an image file.
This is the recommended high level function for saving a pyFormex
canvas rendering or even the whole pyFormex GUI window to an
image file in one of the many supported formats.
This function can also be used to start/stop multisave mode, which can
save a series of images.
Parameters
----------
filename: :term:`path_like` or :class:`~utils.NameSequence`
The path where the image is to be saved. If it is a NameSequence,
the actual filename will be ``next(filename)``.
If no filename is specified, nothing is saved and multisave mode
is turned off if it was on (see also ``multi``).
extent: str
Identifies which part of the GUI is to be saved. It should be
one of the following
- 'canvas': the current OpenGL viewport
- 'rectangle': let the user pick a rectangle from the canvas
- 'allvps': all the OpenGL viewports, each in their own file.
If there is more than one viewport, actual file names are derived
from the specified filename by adding '_vp#' (where # is the
viewport number) before the file extension
- 'central': the central widget of the GUI, including all the OpenGL
viewports, in a single image
- 'window': the pyFormex GUI main window, without the border
- 'border': the pyFormex GUI main window including border decorations
- 'screen': saves the full primary X11 screen
Depending on the installed tools on your system, some options may
not be available. The actually available options are given by
:func:`image_extents`.
tool: 'qt' | 'magick' | 'gl2ps'
The tool to be used to save the image. 'qt' is always available.
The others depend on your installation. The actually available
tools are given by :func:`image_tools`. The tool defines the possible
values of ``extent``:
- qt: 'canvas', 'rectangle, 'allvps'
- magick: 'canvas', 'central', 'window', 'border', 'screen'
- gl2ps: 'canvas'
format: str
One of the supported image formats. The list of supported
image formats depends on ``tool`` and can be found from
``imageFormats('qt', 'w')``.
If not specified, it is derived from the filename extension,
converted to lower case and without the leading dot.
quality: int, optional
See :func:`save_window_rect`.
size: tuple (w,h), optional
A tuple of width and height specifying the requested image size.
The default is to use the size of the current onscreen rendering
as this ensures the best quality.
If the output size is not equal to the actual rendering size,
the image is rendered offscreen and some OpenGL features might
not be shown correctly.
alpha: bool, optional
This is an experimental feature not ready for use.
multi: bool, optional
If True and a filename was specified, multisave mode is switched on.
In multisave mode, images will repeatedly be saved on demand or
automatically. The filename is turned into a
:class:`~utils.NameSequence`
to generate consecutive file names for the images. The images are saved
with the same format, quality and size. In multisave mode, each call
to :func:`saveNext` will save an image to the next generated file name.
The starting of multisave mode in itself does not save any image.
See also the ``hotkey`` and ``autosave`` options.
Multisave mode can be turned off from the GUI File menu or by calling
saveImage without a filename. Starting a new multisave mode
will also quietly stop a previous multisave.
hotkey: bool, optional
If True (default) when multisave mode is activated, a new image can
be saved by hitting a hotkey (configurable in the settings).
autosave: bool, optional
If True, a new image will be saved on each execution of the
:func:`~gui.draw` function.
verbose: True, optional
If True, additional error or warning messages may be displayed.
See Also
--------
save_window: save the pyFormex window to an image file
save_qt: save the OpenGL rendering as a raster image
save_gl2ps: save the OpenGL rendering in a vector format
"""
global multisave
# Leave multisave mode if no filename or starting new multisave mode
if multisave and (filename is None or multi):
print("Leave multisave mode")
if multisave['hotkey']:
pf.GUI.signals.SAVE.disconnect(saveNext)
multisave = None
if filename is None:
return
if isinstance(filename, utils.NameSequence) and not multi:
fileseq = filename
filename = next(fileseq)
else:
fileseq = None
filename = Path(filename)
# Check tool/extent
if tool not in image_tools() or extent not in extent_options(tool):
pf.warning("Invalid combination of tool/extent parameters")
return
# Get/Check format
if format is None:
format = filename.lsuffix.strip('.')
format = checkImageFormat('qt', 'w', format)
if not format:
pf.warning(f"Can not save image in format {format}")
return
crop = None
if extent == 'rectangle':
# Set crop from rectangle picked by user
x0, y0, x1, y1 = pf.canvas.getRectangle(yup=False)
crop = (x0 + 1, y0 + 1, x1 - x0 - 1, y1 - y0 - 1)
if multi: # Start multisave mode
fileseq = utils.NameSequence(filename)
while Path(fileseq.peek()).exists():
next(fileseq)
print(f"Start multisave mode to files: {fileseq.template} ({format})")
if hotkey:
pf.GUI.signals.SAVE.connect(saveNext)
if verbose:
pf.warning(
f"Each time you hit the {pf.cfg['keys/save']} key,"
f" the image will be saved to the next numbered file.")
multisave = {'filename': fileseq, 'tool': tool, 'extent': extent,
'format': format, 'quality': quality, 'size': size,
'crop': crop, 'alpha': alpha,
'hotkey': hotkey, 'autosave': autosave}
# verbosity 2
if pf.verbosity(2):
print(f"Multisave params: {multisave}")
return multisave is None
else:
# Save the image
sta = -1
if tool == 'magick':
# Grab from X server buffers (needs external ImageMagick)
utils.External.require('imagemagick')
windowname = None # use pyFormex window
if extent == 'canvas':
crop = 'vp'
elif extent == 'central':
crop = 'all'
elif extent == 'window':
crop = None # use whole window
elif extent == 'border':
windowname = 'root'
crop = pf.GUI.frameGeometry().getRect()
elif extent == 'screen':
windowname = 'root'
crop = pf.app.primaryScreen().geometry().getRect()
sta = save_window(filename, format, quality, windowname=windowname,
crop=crop)
elif tool == 'qt':
if extent in ['canvas', 'rectangle']:
sta = save_qt(filename, format, quality, canvas=pf.canvas,
size=size, crop=crop, alpha=alpha)
elif extent == 'allvps':
suffix = filename.lsuffix
fileseq = utils.NameSequence(
filename.with_suffix('_vp0'), suffix)
sta = 0
for vp in pf.GUI.viewports.all:
filename = next(fileseq)
sta += save_qt(filename, format, quality, canvas=vp,
size=size, alpha=alpha)
if sta == 0:
print(f"Image file {filename} written")
else:
break
elif tool == 'ps2gl':
sta = save_ps(pf.canvas, filename, filetype=format)
if sta == 0 and extent != 'allvps':
print(f"Image file {filename} written")
else:
pf.debug("Error while saving image {filename}", pf.DEBUG.IMAGE)
# REMOVED 2023-01
# @utils.deprecated_by('image.save', 'image.saveImage')
# def save(filename=None,
# grab=False, window=False, border=False,
# format=None, quality=-1, size=None, alpha=True,
# multi=False, hotkey=True, autosave=False,
# verbose=False):
# """Save the pyFormex rendering to an image file.
# This is a wrapper around :func:`saveImage`, using a slightly
# different (but deprecated) interface with reduced functionality.
# We describe here only the parameters that are different.
# The ``extent`` and ``tool`` parameters are missing and are
# replaced with ``window``, ``border`` and ``grab``.
# Parameters
# ----------
# window: bool
# If True, the full pyFormex window is saved. The default (False)
# only saves the central widget with the OpenGL rendering. If there
# are multiple viewports, only the current viewport is saved.
# border: bool
# If True, and also ``window`` is True, the image will also contain
# the window border decorations.
# grab: bool
# If True, the external 'import' program is used to grab the
# window from the screen buffers. This mode is also forced by
# the ``window=True`` option. The default (False) saves the
# image directly using the Qt GUI libraries.
# """
# if grab:
# tool = 'magick'
# if window:
# if border:
# extent = 'bordered'
# else:
# extent = 'window'
# else:
# extent = 'current viewport'
# else:
# tool = 'qt'
# extent = 'current viewport'
# saveImage(filename=filename, extent=extent, tool=tool,
# format=format, quality=quality, size=size, alpha=alpha,
# multi=multi, hotkey=hotkey, autosave=autosave,
# verbose=verbose)
[docs]def saveNext():
"""In multisave mode, save the next image.
This is a quiet function that does nothing if multisave was not activated.
It can thus safely be called on regular places in scripts where one would
like to have a saved image. In interactive use, the user then has to be
asked only once whether to activate the multisave mode or not.
"""
if multisave:
saveImage(multi=False, verbose=False, **multisave)
[docs]def autoSaveOn():
"""Returns True if autosave multisave mode is currently on.
Use this function instead of directly accessing the multisave variable.
"""
return multisave and multisave['autosave']
[docs]def createMovie(files, encoder='convert', outfn='output', **kargs):
"""Create a movie from a saved sequence of images.
Parameters
----------
files: list of str
A list of filenames, or a string with one or more filenames
separated by whitespace. The filenames can also contain wildcards
interpreted by the shell.
encoder: str
The external program to be used to create the movie.
This will also define the type of output file, and the extra parameters
that can be passed. The external program has to be installed on the
computer. The default is 'convert', which will create animated gif.
Other possible values are 'mencoder' and 'ffmeg', creating meg4
encode movies from jpeg input files.
outfn: str
output file name (not including the extension).
Default is 'output'.
**kargs:
Other parameters may be passed and may be needed, depending on the
converter program used. Thus, for the default 'convert' program,
each extra keyword parameter will be translated to an option
``-keyword value`` for the command. Example::
createMovie('images*.png',delay=1,colors=256)
will create an animated gif 'output.gif'.
"""
outfn = path(outfn)
if pf.verbosity(2):
print(f"Encoding {files}")
if isinstance(files, list):
files = ' '.join(files)
if encoder == 'convert':
outfile = outfn.with_suffix('.gif')
cmd= "convert " + " ".join(
[f"-{k} {kargs[k]}" for k in kargs]) + f" {files} {outfile}"
elif encoder == 'mencoder':
outfile = outfn.with_suffix('.avi')
cmd = (f"mencoder \"mf://{files}\" -o {outfile} -mf fps={kargs['fps']} "
f"-ovc lavc -lavcopts vcodec=msmpeg4v2:vbitrate={kargs['vbirate']}")
else:
outfile = outfn.with_suffix('.mp4')
cmd = f"ffmpeg -qscale 1 -r 1 -i {files} output.mp4"
pf.debug(cmd, pf.DEBUG.IMAGE)
P = utils.command(cmd)
if pf.verbosity(1):
print(f"Created video file {outfile.resolve()}")
return P.returncode
[docs]def changeBackgroundColorXPM(fn, color):
"""Change the background color of an .xpm image.
Parameters
----------
fn: :term:`path_like`
The file name of an .xpm image file.
color: str
The color to be set as background color. It is an X11 color name or
a web format hexadecimal color ('#FFF' or '#FFFFFF' is white).
A special value 'None' may be used to set a transparent background.
Notes
-----
The current background color is selected from the lower left pixel.
All pixels having that color will be changed.
This function is mainly intended for use by :func:`saveIcon`.
"""
with open(fn,'r') as fil:
t = fil.readlines()
c = ''
for l in t[::-1]:
if l.startswith('"'):
c = l[1]
if pf.verbosity(2):
print(f"Found '{c}' as background character")
break
if not c and pf.verbosity(1):
print(f"Can not change background color of '{fn}'")
return
for i, l in enumerate(t):
if l.startswith(f'"{c} c '):
t[i] = f'"{c} c None",\n'
break
with open(fn, 'w') as fil:
fil.writelines(t)
[docs]def saveIcon(fn, size=32, transparent=True):
"""Save the current rendering as an icon.
Saves the current rendering as an .xpm image file.
Parameters
----------
fn: :term:`path_like`
File name of the target image file. If it does not end with '.xpm',
this extension will be appended.
size: int
Pixel size of the output image.
transparent: bool
If True (default), the background will be set to transparent.
Notes
-----
This function is primarily intended for creating icons for the
pyFormex GUI.
"""
if not fn.endswith('.xpm'):
fn += '.xpm'
save_qt(fn, format='xpm', canvas=pf.canvas, size=(size, size))
if pf.verbosity(1):
print(f"Saved icon to file {fn} in {Path.cwd()}")
if transparent:
changeBackgroundColorXPM(fn, 'None')
_recording_P = None
_recording_button = None
record_extents = ['canvas', 'central', 'window', 'border', 'screen']
[docs]def recordSession(filename, extent='window', framerate=25):
"""Create a video from the pyFormex window or another zone on the screen.
This uses ffmpeg to record a rectangular area of the screen to a video file.
Parameters
----------
filename: :term:`path_like`
The path to the file to save. The filename should have an extension
.mp4 for the 'ffmpeg' tool, and '.ogv' for the 'recordmydesktop'
tool.
extent: str
The part
"""
global _recording_P, _recording_button
if _recording_P:
pf.warning("Another recorder is already running!")
return _recording_P
print(f"Recording your session to file {filename}")
pf.GUI.raise_()
pf.GUI.repaint()
pf.GUI.toolbar.repaint()
pf.GUI.update()
pf.canvas.makeCurrent()
pf.canvas.raise_()
pf.canvas.update()
pf.app.processEvents()
# we need screen offsets, so get the main window geometry first
if extent == 'canvas':
geom = qtutils.absRect(pf.canvas)
elif extent == 'central':
geom = qtutils.absRect(pf.GUI.central)
elif extent == 'window':
geom = pf.GUI.geometry().getRect()
elif extent == 'border':
geom = pf.GUI.frameGeometry().getRect()
elif extent == 'screen':
geom = pf.app.primaryScreen().geometry().getRect()
print(f"Geometry: geom")
x, y, w, h = geom
cmd = (f"ffmpeg -video_size {w}x{h} -framerate {framerate} -f x11grab"
f" -i :0.0+{x},{y} -y {filename}")
pf.debug(cmd, pf.DEBUG.IMAGE)
_recording_P = utils.command(cmd, wait=False)
if _recording_P:
pf.GUI.onExit(stopRecording)
_recording_button = pf.GUI.addStatusbarButtons(
':movie:recording', actions=[('REC', stopRecording)], spacing=0)
return _recording_P
def stopRecording():
global _recording_P, _recording_button
if _recording_P:
# Was recording: finish it
_recording_P.terminate()
returncode = _recording_P.wait()
pf.debug(f"Recording stopped with code {returncode}")
_recording_P = None
if _recording_button:
pf.GUI.statusbar.removeWidget(_recording_button)
_recording_button = None
return returncode
[docs]def record_rect(filename, size, pos, framerate=25):
"""Record a rectangular part of the screen to a a video file.
This uses the external program 'ffmpeg' to record a
rectangle from any window on the screen.
Parameters
----------
filename: :term:`path_like`
The name of the file on which to save the image. This should by
preference be a .mp4 file. If you want another format, one can
always convert afterwards.
size: tuple of int
A tuple (w,h) specifying the width and height of the rectangle to grab.
It is also the size of the output video. If you need another output
size, convert the video afterwards.
pos: tuple of int
A tuple (x,y) with the position of the top left corner of the rectangle
to grab. The values are relative to the primary screen origin.
framerate: int, optional
The number of frames to capture per second.
See Also
--------
recordSession: the recommended high level video recording function
save_window_rect: save a screen rectangle to an image file
"""
pf.debug(f"record_rect size={size} pos={pos} framerate={framerate}",
pf.DEBUG.IMAGE)
x, y, w, h = geom
cmd = (f"ffmpeg -video_size {w}x{h} -framerate {framerate} -f x11grab"
f" -i :0.0+{x},{y} -y {filename}")
cmd = f"import -window '{window}' {options} {format}:{filename}"
# We need to use shell=True because window name might contain spaces
# thus we need to add quotes, but these are not stripped off when
# splitting the command line.
# TODO: utils should probably be changed to strip quotes after splitting
P = utils.command(cmd, shell=True)
return P.returncode
### End