Beispiel #1
0
def test_find_files_by_script_with_script_that_takes_multiple_files():
    with TempFile('.xcf') as with_white, TempFile('.xcf') as without_white:
        GimpFile(with_white)\
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8))\
            .add_layer_from_numpy('White', np.ones(shape=(1, 1), dtype=np.uint8)*255)

        GimpFile(without_white) \
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
            .add_layer_from_numpy('Black', np.zeros(shape=(1, 1), dtype=np.uint8))

        collection = GimpFileCollection([with_white, without_white])

        script = textwrap.dedent(
            """
            import gimp
            from pgimp.gimp.file import XcfFile
            from pgimp.gimp.parameter import return_json, get_json
            files = get_json('__files__')
            matches = []
            for file in files:
                with XcfFile(file) as image:
                    for layer in image.layers:
                        if layer.name == '{0:s}':
                            matches.append(file)
            return_json(matches)
            """
        )

        files = collection.find_files_by_script(script.format(escape_single_quotes('White')), timeout_in_seconds=3)
        assert len(files) == 1
        assert with_white == files[0]

        files = collection.find_files_by_script(script.format(escape_single_quotes('Not existing')), timeout_in_seconds=3)
        assert len(files) == 0
Beispiel #2
0
def test_find_files_by_script_with_script_that_takes_single_file():
    with TempFile('.xcf') as with_white, TempFile('.xcf') as without_white:
        GimpFile(with_white)\
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8))\
            .add_layer_from_numpy('White', np.ones(shape=(1, 1), dtype=np.uint8)*255)

        GimpFile(without_white) \
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
            .add_layer_from_numpy('Black', np.zeros(shape=(1, 1), dtype=np.uint8))

        collection = GimpFileCollection([with_white, without_white])

        script = textwrap.dedent(
            """
            from pgimp.gimp.file import open_xcf
            from pgimp.gimp.parameter import return_bool
            image = open_xcf('__file__')
            for layer in image.layers:
                if layer.name == '{0:s}':
                    return_bool(True)
            return_bool(False)
            """
        )

        files = collection.find_files_by_script(script.format(escape_single_quotes('White')), timeout_in_seconds=3)
        assert len(files) == 1
        assert with_white == files[0]

        files = collection.find_files_by_script(script.format(escape_single_quotes('Not existing')), timeout_in_seconds=3)
        assert len(files) == 0
Beispiel #3
0
    def create(
        self,
        layer_name: str,
        layer_content: np.ndarray,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Create a new gimp image with one layer from a numpy array.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f).create('Background', np.zeros(shape=(32, 32), dtype=np.uint8))
        ...     gimp_file.layer_names()
        ['Background']

        :param layer_name: Name of the layer to create.
        :param layer_content: Layer content, usually in the format of unsigned 8 bit integers.
        :param timeout: Execution timeout in seconds.
        :return: The newly created :py:class:`~pgimp.GimpFile.GimpFile`.
        """
        height, width, depth, image_type, layer_type = self._numpy_array_info(
            layer_content)

        tmpfile = tempfile.mktemp(suffix='.npy')
        np.save(tmpfile, layer_content)

        code = textwrap.dedent("""
            import gimp
            from pgimp.gimp.file import save_xcf
            from pgimp.gimp.layer import add_layer_from_numpy

            image = gimp.pdb.gimp_image_new({0:d}, {1:d}, {2:d})
            add_layer_from_numpy(image, '{6:s}', '{5:s}', image.width, image.height, {4:d})
            save_xcf(image, '{3:s}')
            """).format(width, height, image_type.value,
                        escape_single_quotes(self._file), layer_type,
                        escape_single_quotes(layer_name),
                        escape_single_quotes(tmpfile))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.long_running_timeout_in_seconds
            if timeout is None else timeout)

        os.remove(tmpfile)
        return self
Beispiel #4
0
    def merge_layer_from_file(
        self,
        other_file: 'GimpFile',
        name: str,
        clear_selection: bool = True,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Merges a layer from another file into the current file. The layer must exist in the current file.

        Example:

        >>> from pgimp.GimpFile import GimpFile, GimpFileType
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as other, TempFile('.xcf') as current:  # doctest:+ELLIPSIS
        ...     green_content = np.zeros(shape=(1, 1, 3), dtype=np.uint8)
        ...     green_content[:, :] = [0, 255, 0]
        ...     other_file = GimpFile(other).create('Green', green_content)
        ...     current_file = GimpFile(current).create('Green', np.zeros(shape=(1, 1, 3), dtype=np.uint8))
        ...     current_file.merge_layer_from_file(other_file, 'Green')
        ...     current_file.layer_names()
        ...     current_file.layer_to_numpy('Green')
        <...>
        ['Green']
        array([[[  0, 255,   0]]], dtype=uint8)

        :param other_file: The gimp file from which the layer contents are merged into the current file.
        :param name: Name of the layer to merge.
        :param clear_selection: Clear selection before merging to avoid only merging the selection.
        :param timeout: Execution timeout in seconds.
        :return: :py:class:`~pgimp.GimpFile.GimpFile`
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import XcfFile
            from pgimp.gimp.layer import merge_layer

            with XcfFile('{1:s}') as image_src, XcfFile('{0:s}', save=True) as image_dst:
                merge_layer(image_src, '{2:s}', image_dst, '{2:s}', 0, {3:s})
            """).format(escape_single_quotes(self._file),
                        escape_single_quotes(other_file._file),
                        escape_single_quotes(name), str(clear_selection))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.long_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #5
0
def test_execute_script_and_return_json_with_script_that_takes_multiple_files_using_for_each():
    with TempFile('.xcf') as with_white, TempFile('.xcf') as without_white:
        GimpFile(with_white) \
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
            .add_layer_from_numpy('White', np.ones(shape=(1, 1), dtype=np.uint8) * 255)

        GimpFile(without_white) \
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
            .add_layer_from_numpy('Black', np.zeros(shape=(1, 1), dtype=np.uint8))

        collection = GimpFileCollection([with_white, without_white])

        script = textwrap.dedent(
            """
            from pgimp.gimp.file import for_each_file
            from pgimp.gimp.parameter import return_json, get_json

            matches = []

            def layer_matches(image, file):
                for layer in image.layers:
                    if layer.name == '{0:s}':
                        matches.append(file)

            for_each_file(layer_matches)
            return_json(matches)
            """
        )

        files = collection.execute_script_and_return_json(script.format(escape_single_quotes('White')),
                                                          timeout_in_seconds=3)
        assert len(files) == 1
        assert with_white == files[0]
Beispiel #6
0
    def dimensions(
        self,
        timeout: Optional[int] = None,
    ) -> Tuple[int, int]:
        """
        Return the image dimensions (width, height).

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f).create('Background', np.zeros(shape=(3, 2), dtype=np.uint8))
        ...     gimp_file.dimensions()
        (2, 3)

        :param timeout: Execution timeout in seconds.
        :return: Tuple of width and height.
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import open_xcf
            from pgimp.gimp.parameter import return_json

            image = open_xcf('{0:s}')
            return_json([image.width, image.height])
            """).format(escape_single_quotes(self._file))

        dimensions = self._gsr.execute_and_parse_json(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return tuple(dimensions)
Beispiel #7
0
    def find_files_containing_layer_by_name(self, layer_name: str, timeout_in_seconds: float = None) -> List[str]:
        """
        Find files that contain a layer that matching the given name.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     gfc.find_files_containing_layer_by_name('Foreground')
        ['..._fg.xcf']

        :param layer_name: Layer name to search for.
        :param timeout_in_seconds: Script execution timeout in seconds.
        :return: List of files containing the layer with the given name.
        """
        return self.find_files_by_script(textwrap.dedent(
            """
            from pgimp.gimp.parameter import return_json, get_json
            from pgimp.gimp.file import XcfFile
            files = get_json('__files__')
            matches = []
            for file in files:
                with XcfFile(file) as image:
                    for layer in image.layers:
                        if layer.name == '{0:s}':
                            matches.append(file)
            return_json(matches)
            """
        ).format(escape_single_quotes(layer_name)), timeout_in_seconds=timeout_in_seconds)
Beispiel #8
0
    def export(
        self,
        file: str,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Export a gimp file to another file format based on the file extension.

        Gimp will apply defaults for encoding to the desired format. E.g. png is saved including an alpha channel
        and jpg has no alpha channel but will use default compression settings.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as xcf, TempFile('.png') as png, TempFile('.xcf') as from_png:
        ...     gimp_file = GimpFile(xcf) \\
        ...         .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \\
        ...         .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=50.) \\
        ...         .export(png)  # saved as grayscale with alpha (identify -format '%[channels]' FILE)
        ...     GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
        array([[[127, 255]]], dtype=uint8)

        :param file: Filename including the desired extension to export to.
        :param timeout: Execution timeout in seconds.
        :return: :py:class:`~pgimp.GimpFile.GimpFile`
        """

        code = textwrap.dedent("""
            import gimp
            import gimpenums
            from pgimp.gimp.file import XcfFile
            with XcfFile('{0:s}') as image:
                merged = gimp.pdb.gimp_image_merge_visible_layers(image, gimpenums.CLIP_TO_IMAGE)
                gimp.pdb.gimp_file_save(image, merged, '{1:s}', '{1:s}')
            """).format(escape_single_quotes(self._file),
                        escape_single_quotes(file))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #9
0
    def layers_to_numpy(
        self,
        layer_names: List[str],
        use_temp_file=True,
        timeout: Optional[int] = None,
    ) -> np.ndarray:
        """
        Convert gimp layers to a numpy array of unsigned 8 bit integers.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f) \\
        ...         .create('Red', np.zeros(shape=(1, 2, 1), dtype=np.uint8)) \\
        ...         .add_layer_from_numpy('Green', np.ones(shape=(1, 2, 1), dtype=np.uint8)*127) \\
        ...         .add_layer_from_numpy('Blue', np.ones(shape=(1, 2, 1), dtype=np.uint8)*255)
        ...     gimp_file.layers_to_numpy(['Red', 'Green', 'Blue']).shape
        (1, 2, 3)

        :param layer_names: Names of the layers to convert.
        :param use_temp_file: Use a tempfile for data transmition instead of stdout. This is more robust in
                              a multiprocessing setting.
        :param timeout: Execution timeout in seconds.
        :return: Numpy array of unsigned 8 bit integers.
        """
        with TempFile('.npy') as tmpfile:
            bytes = self._gsr.execute_binary(
                textwrap.dedent(
                    """
                    import numpy as np
                    import sys
                    from pgimp.gimp.file import open_xcf
                    from pgimp.gimp.parameter import get_json, get_string
                    from pgimp.gimp.layer import convert_layers_to_numpy
    
                    np_buffer = convert_layers_to_numpy(open_xcf('{0:s}'), get_json('layer_names', '[]'))
                    temp_file = get_string('temp_file')
                    if temp_file:
                        np.save(temp_file, np_buffer)
                    else:
                        np.save(sys.stdout, np_buffer)
                    """, ).format(escape_single_quotes(self._file)),
                parameters={
                    'layer_names': layer_names,
                    'temp_file': tmpfile if use_temp_file else ''
                },
                timeout_in_seconds=self.long_running_timeout_in_seconds
                if timeout is None else timeout)
            if use_temp_file:
                return np.load(tmpfile)

        return np.load(io.BytesIO(bytes))
Beispiel #10
0
    def create_from_file(
        self,
        file: str,
        layer_name: str = 'Background',
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Create a new gimp file by importing an image from another format.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as xcf, TempFile('.png') as png, TempFile('.xcf') as from_png:
        ...     gimp_file = GimpFile(xcf) \\
        ...         .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \\
        ...         .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=50.) \\
        ...         .export(png)  # saved as grayscale with alpha (identify -format '%[channels]' FILE)
        ...     GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
        array([[[127, 255]]], dtype=uint8)

        :param file: File to import into gimp.
        :param layer_name: The layer name for the data to be imported.
        :param timeout: Execution timeout in seconds.
        :return:
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import save_xcf
            from pgimp.gimp.image import create_from_file
            image = create_from_file('{0:s}')
            image.layers[0].name = '{2:s}'
            save_xcf(image, '{1:s}')
            """).format(escape_single_quotes(file),
                        escape_single_quotes(self._file),
                        escape_single_quotes(layer_name))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #11
0
    def remove_layer(
        self,
        layer_name: str,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Removes a layer from the gimp file.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as file:
        ...     gimp_file = GimpFile(file) \\
        ...         .create('Background', np.zeros(shape=(2, 2), dtype=np.uint8)) \\
        ...         .add_layer_from_numpy('Black', np.zeros(shape=(2, 2), dtype=np.uint8)) \\
        ...         .remove_layer('Background')
        ...     gimp_file.layer_names()
        ['Black']

        :param layer_name: Name of the layer to remove.
        :param timeout: Execution timeout in seconds.
        :return: :py:class:`~pgimp.GimpFile.GimpFile`
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import XcfFile
            from pgimp.gimp.layer import remove_layer

            with XcfFile('{0:s}', save=True) as image:
                remove_layer(image, '{1:s}')
            """).format(escape_single_quotes(self._file),
                        escape_single_quotes(layer_name))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #12
0
    def create_from_template(
        self,
        other_file: 'GimpFile',
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Create a new gimp file without any layers from a template containing the dimensions (width, height)
        and the image type.

        Example:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> with TempFile('.xcf') as original, TempFile('.xcf') as created:
        ...     original_file = GimpFile(original).create('Background', np.zeros(shape=(3, 2), dtype=np.uint8))
        ...     created_file = GimpFile(created).create_from_template(original_file)
        ...     created_file.layer_names()
        []

        :param other_file: The template file.
        :param timeout: Execution timeout in seconds.
        :return: The newly created :py:class:`~pgimp.GimpFile.GimpFile`.
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import save_xcf
            from pgimp.gimp.image import create_from_template_file
            image = create_from_template_file('{0:s}')
            save_xcf(image, '{1:s}')
            """).format(escape_single_quotes(other_file._file),
                        escape_single_quotes(self._file))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #13
0
    def create_empty(
        self,
        width: int,
        height: int,
        type: GimpFileType = GimpFileType.RGB,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Creates an empty image without any layers.

        Example:

        >>> from pgimp.GimpFile import GimpFile, GimpFileType
        >>> from pgimp.util.TempFile import TempFile
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f).create_empty(3, 2, GimpFileType.RGB)
        ...     gimp_file.layer_names()
        []

        :param width: Image width.
        :param height: Image height.
        :param type: Image type, e.g. rgb or gray.
        :param timeout: Execution timeout in seconds.
        :return: The newly created :py:class:`~pgimp.GimpFile.GimpFile`.
        """
        code = textwrap.dedent("""
            import gimp
            from pgimp.gimp.file import save_xcf
            image = gimp.pdb.gimp_image_new({0:d}, {1:d}, {2:d})
            save_xcf(image, '{3:s}')
            """).format(width, height, type.value,
                        escape_single_quotes(self._file))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        return self
Beispiel #14
0
    def layers(
        self,
        timeout: Optional[int] = None,
    ) -> List[Layer]:
        """
        Returns the image layers. The topmost layer is the first element, the bottommost the last element.
        
        :param timeout: Execution timeout in seconds.
        :return: List of :py:class:`~pgimp.layers.Layer`.
        """
        code = textwrap.dedent("""
            from pgimp.gimp.file import open_xcf
            from pgimp.gimp.parameter import return_json

            image = open_xcf('{0:s}')

            result = []
            for layer in image.layers:
                properties = dict()
                properties['name'] = layer.name
                properties['visible'] = layer.visible
                properties['opacity'] = layer.opacity
                result.append(properties)

            return_json(result)
            """.format(escape_single_quotes(self._file)))

        result = self._gsr.execute_and_parse_json(
            code,
            timeout_in_seconds=self.short_running_timeout_in_seconds
            if timeout is None else timeout)
        layers = []
        for idx, layer_properties in enumerate(result):
            layer_properties['position'] = idx
            layers.append(Layer(layer_properties))

        return layers
Beispiel #15
0
    def execute_script_and_return_json(
            self,
            script: str,
            parameters: dict = None,
            timeout_in_seconds: float = None
    ) -> Union[JsonType, Dict[str, JsonType]]:
        """
        Execute a gimp script on the collection.

        If the script opens a file with **open_xcf('__file__')**, then the script is executed for each file
        and a result returned by **return_json(value)** is expected. The results will be returned as a
        dictionary containing the filenames as keys and the results as values.

        If the script retrieves the whole list of files with **get_json('__files__')**, then the script is
        only executed once and passed the whole list of files as a parameter. A result returned by
        **return_json(value)** is expected. This solution has better performance
        but you need to make sure that memory is cleaned up between opening files, e.g. by invoking
        **gimp_image_delete(image)**.

        Example with script that is executed per file:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.util.string import escape_single_quotes
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     script = textwrap.dedent(
        ...         '''
        ...         from pgimp.gimp.file import open_xcf
        ...         from pgimp.gimp.parameter import return_json
        ...         image = open_xcf('__file__')
        ...         for layer in image.layers:
        ...             if layer.name == '{0:s}':
        ...                 return_json(True)
        ...         return_json(False)
        ...         '''
        ...     ).format(escape_single_quotes('Foreground'))
        ...     gfc.execute_script_and_return_json(script)
        {'..._bg.xcf': False, '..._fg.xcf': True}

        Example with script that is executed once on all files using open_xcf():

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.util.string import escape_single_quotes
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     script = textwrap.dedent(
        ...         '''
        ...         import gimp
        ...         from pgimp.gimp.file import XcfFile
        ...         from pgimp.gimp.parameter import return_json, get_json
        ...         files = get_json('__files__')
        ...         matches = []
        ...         for file in files:
        ...             with XcfFile(file) as image:
        ...                 for layer in image.layers:
        ...                     if layer.name == '{0:s}':
        ...                         matches.append(file)
        ...         return_json(matches)
        ...         '''
        ...     ).format(escape_single_quotes('Foreground'))
        ...     gfc.execute_script_and_return_json(script)
        ['..._fg.xcf']

        Example with script that is executed once on all files using for_each_file():

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.util.string import escape_single_quotes
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     script = textwrap.dedent(
        ...         '''
        ...         from pgimp.gimp.file import for_each_file
        ...         from pgimp.gimp.parameter import return_json, get_json
        ...
        ...         matches = []
        ...
        ...         def layer_matches(image, file):
        ...             for layer in image.layers:
        ...                 if layer.name == '{0:s}':
        ...                     matches.append(file)
        ...
        ...         for_each_file(layer_matches)
        ...         return_json(matches)
        ...         '''
        ...     ).format(escape_single_quotes('Foreground'))
        ...     gfc.execute_script_and_return_json(script)
        ['..._fg.xcf']

        :param script: Script to be executed on the files.
        :param parameters: Parameters to pass to the script.
        :param timeout_in_seconds:  Script execution timeout in seconds.
        :return: Dictionary of filenames and results if the script reads a single file.
                 Json if the script takes the whole list of files.
        """
        parameters = parameters or {}
        if "open_xcf('__file__')" in script and "return_json(" in script:
            return {file: self._gsr.execute_and_parse_json(
                script.replace('__file__', escape_single_quotes(file)),
                parameters=parameters,
                timeout_in_seconds=timeout_in_seconds
            ) for file in self._files}
        elif ("get_json('__files__')" in script or "for_each_file(" in script) and "return_json(" in script:
            return self._gsr.execute_and_parse_json(
                script,
                parameters={**parameters, '__files__': self._files},
                timeout_in_seconds=timeout_in_seconds
            )
        else:
            raise GimpMissingRequiredParameterException(
                'Either an image file must be opened with open_xcf(\'__file__\') ' +
                'and the result is returned with return_json() ' +
                'or a list of files must be retrieved by get_json(\'__files__\') or for_each_file() ' +
                'and the result is returned with return_json().'
            )
Beispiel #16
0
def test_escape_single_quotes():
    input = "'abc'"
    expected = "\\'abc\\'"

    assert expected == escape_single_quotes(input)
Beispiel #17
0
    def add_layer_from_file(
        self,
        other_file: 'GimpFile',
        name: str,
        new_name: str = None,
        new_type: GimpFileType = GimpFileType.RGB,
        new_position: int = 0,
        new_visibility: Optional[bool] = None,
        new_opacity: Optional[float] = None,
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Adds a new layer to the gimp file from another gimp file.

        Example:

        >>> from pgimp.GimpFile import GimpFile, GimpFileType
        >>> from pgimp.util.TempFile import TempFile
        >>> import numpy as np
        >>> with TempFile('.xcf') as other, TempFile('.xcf') as current:  # doctest:+ELLIPSIS
        ...     green_content = np.zeros(shape=(1, 1, 3), dtype=np.uint8)
        ...     green_content[:, :] = [0, 255, 0]
        ...     other_file = GimpFile(other).create('Green', green_content)
        ...     current_file = GimpFile(current).create('Background', np.zeros(shape=(1, 1, 3), dtype=np.uint8))
        ...     current_file.add_layer_from_file(
        ...         other_file,
        ...         'Green',
        ...         new_name='Green (copied)',
        ...         new_type=GimpFileType.RGB, new_position=1
        ...     )
        ...     current_file.layer_names()
        ...     current_file.layer_to_numpy('Green (copied)')
        <...>
        ['Background', 'Green (copied)']
        array([[[  0, 255,   0]]], dtype=uint8)

        :param other_file: The gimp file from which to copy the layer into the current image.
        :param name: The layer name in the other file to copy over to the current file. Also the layer name
                     in the current file if no new name is set.
        :param new_name: The new layer name in the current image. Same as the layer name in the other file if not set.
        :param new_type: The layer type to create in the current image. E.g. rgb or grayscale.
        :param new_position: Position in the stack of layers. On top = 0, bottom = number of layers.
        :param new_visibility: Visibility of the layer if it should be changed.
        :param new_opacity: Opacity for the layer if it should be changed.
        :param timeout: Execution timeout in seconds.
        :return: :py:class:`~pgimp.GimpFile.GimpFile`
        """
        code = textwrap.dedent("""
            from pgimp.gimp.parameter import get_json
            from pgimp.gimp.file import XcfFile
            from pgimp.gimp.layer import copy_layer
            
            params = get_json('params')
            new_position = params['new_position']
            new_visibility = params['new_visibility']
            new_opacity = params['new_opacity']

            with XcfFile('{1:s}') as image_src, XcfFile('{0:s}', save=True) as image_dst:
                copy_layer(image_src, '{3:s}', image_dst, '{2:s}', new_position)
                if new_visibility is not None:
                    image_dst.layers[new_position].visible = new_visibility
                if new_opacity is not None:
                    image_dst.layers[new_position].opacity = float(new_opacity)
            """).format(
            escape_single_quotes(self._file),
            escape_single_quotes(other_file._file),
            escape_single_quotes(new_name or name),
            escape_single_quotes(name),
            new_type.value,
        )

        self._gsr.execute(
            code,
            timeout_in_seconds=self.long_running_timeout_in_seconds
            if timeout is None else timeout,
            parameters={
                'params': {
                    'new_visibility': new_visibility,
                    'new_position': new_position,
                    'new_opacity': new_opacity,
                }
            })
        return self
Beispiel #18
0
    def find_files_by_script(self, script_predicate: str, timeout_in_seconds: float = None) -> List[str]:
        """
        Find files matching certain criteria by executing a gimp script.

        If the script opens a file with **open_xcf('__file__')**, then the script is executed for each file
        and a boolean result returned by **return_bool(value)** is expected.

        If the script retrieves the whole list of files with **get_json('__files__')**, then the script is
        only executed once and passed the whole list of files as a parameter. A result returned by
        **return_json(value)** in the form of a list is expected. This solution has better performance
        but you need to make sure that memory is cleaned up between opening files, e.g. by invoking
        **gimp_image_delete(image)**.

        Example with script that is executed per file:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.util.string import escape_single_quotes
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     script = textwrap.dedent(
        ...         '''
        ...         from pgimp.gimp.file import open_xcf
        ...         from pgimp.gimp.parameter import return_bool
        ...         image = open_xcf('__file__')
        ...         for layer in image.layers:
        ...             if layer.name == '{0:s}':
        ...                 return_bool(True)
        ...         return_bool(False)
        ...         '''
        ...     ).format(escape_single_quotes('Foreground'))
        ...     gfc.find_files_by_script(script)
        ['..._fg.xcf']

        Example with script that is executed once on all files:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.GimpFileCollection import GimpFileCollection
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.util.string import escape_single_quotes
        >>> import numpy as np
        >>> with TempFile('_bg.xcf') as f1, TempFile('_fg.xcf') as f2:  # doctest: +ELLIPSIS
        ...     gf1 = GimpFile(f1).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gf2 = GimpFile(f2).create('Foreground', np.zeros(shape=(2, 2), dtype=np.uint8))
        ...     gfc = GimpFileCollection.create_from_gimp_files([gf1, gf2])
        ...     script = textwrap.dedent(
        ...         '''
        ...         import gimp
        ...         from pgimp.gimp.file import XcfFile
        ...         from pgimp.gimp.parameter import return_json, get_json
        ...         files = get_json('__files__')
        ...         matches = []
        ...         for file in files:
        ...             with XcfFile(file) as image:
        ...                 for layer in image.layers:
        ...                     if layer.name == '{0:s}':
        ...                         matches.append(file)
        ...         return_json(matches)
        ...         '''
        ...     ).format(escape_single_quotes('Foreground'))
        ...     gfc.find_files_by_script(script)
        ['..._fg.xcf']

        :param script_predicate: Script to be executed.
        :param timeout_in_seconds: Script execution timeout in seconds.
        :return: List of files matching the criteria.
        """
        if "open_xcf('__file__')" in script_predicate and "return_bool(" in script_predicate:
            return list(filter(lambda file: self._gsr.execute_and_parse_bool(
                script_predicate.replace('__file__', escape_single_quotes(file)),
                timeout_in_seconds=timeout_in_seconds
            ), self._files))
        if "get_json('__files__')" in script_predicate and "return_json(" in script_predicate:
            return self._gsr.execute_and_parse_json(
                script_predicate,
                parameters={'__files__': self._files},
                timeout_in_seconds=timeout_in_seconds
            )
        raise GimpMissingRequiredParameterException(
            'Either an image file must be opened with open_xcf(\'__file__\') ' +
            'and the result is returned with return_bool() ' +
            'or a list of files must be retrieved by get_json(\'__files__\') ' +
            'and the result is returned with return_json().'
        )
Beispiel #19
0
    def create_indexed(
        self,
        layer_name: str,
        layer_content: np.ndarray,
        colormap: Union[np.ndarray, ColorMap],
        timeout: Optional[int] = None,
    ) -> 'GimpFile':
        """
        Create a new indexed gimp image with one layer from a numpy array. An indexed image has a single channel
        and displays the values using a colormap.

        Example using a predefined colormap:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.GimpFile import ColorMap
        >>> import numpy as np
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f).create_indexed(
        ...         'Background',
        ...         np.arange(0, 256, dtype=np.uint8).reshape((1, 256)),
        ...         ColorMap.JET
        ...     )

        Example using a custom colormap:

        >>> from pgimp.GimpFile import GimpFile
        >>> from pgimp.util.TempFile import TempFile
        >>> from pgimp.GimpFile import ColorMap
        >>> import numpy as np
        >>> with TempFile('.xcf') as f:
        ...     gimp_file = GimpFile(f).create_indexed(
        ...         'Background',
        ...         np.arange(0, 256, dtype=np.uint8).reshape((1, 256)),
        ...         np.array(
        ...             [[255, 0, 0], [0, 255, 0], [0, 0, 255], *[[i, i, i] for i in range(3, 256)]],
        ...             dtype=np.uint8
        ...         )
        ...     )

        :param layer_name: Name of the layer to create.
        :param layer_content: Layer content, usually in the format of unsigned 8 bit integers.
        :param timeout: Execution timeout in seconds.
        :return: The newly created :py:class:`~pgimp.GimpFile.GimpFile`.
        """
        if isinstance(colormap, np.ndarray):
            if not len(layer_content.shape) == 2 and not (len(
                    layer_content.shape) == 3 and layer_content.shape[2] == 1):
                raise DataFormatException(
                    'Indexed images can only contain one channel')
            colormap = 'np.frombuffer({0}, dtype=np.uint8).reshape((256, 3))'.format(
                colormap.tobytes())
        if isinstance(colormap, ColorMap):
            colormap = colormap.value

        tmpfile = tempfile.mktemp(suffix='.npy')
        np.save(tmpfile, layer_content)

        code = textwrap.dedent("""
            import gimp
            import gimpenums
            from pgimp.gimp.file import save_xcf
            from pgimp.gimp.colormap import *  # necessary for predefined colormaps
            from pgimp.gimp.layer import add_layer_from_numpy

            cmap = {0:s}
            image = gimp.pdb.gimp_image_new({1:d}, {2:d}, gimpenums.GRAY)
            palette_name = gimp.pdb.gimp_palette_new('colormap')
            for i in range(0, cmap.shape[0]):
                gimp.pdb.gimp_palette_add_entry(palette_name, str(i), (int(cmap[i][0]), int(cmap[i][1]), int(cmap[i][2])))
            gimp.pdb.gimp_convert_indexed(image, gimpenums.NO_DITHER, gimpenums.CUSTOM_PALETTE, 256, False, False, palette_name)

            add_layer_from_numpy(image, '{5:s}', '{4:s}', image.width, image.height, gimpenums.INDEXED_IMAGE)
            save_xcf(image, '{3:s}')
            """).format(colormap,
                        layer_content.shape[1], layer_content.shape[0],
                        escape_single_quotes(self._file),
                        escape_single_quotes(layer_name),
                        escape_single_quotes(tmpfile))

        self._gsr.execute(
            code,
            timeout_in_seconds=self.long_running_timeout_in_seconds
            if timeout is None else timeout)

        os.remove(tmpfile)
        return self