Source code for tangram.image

# Copyright 2017 The Tangram Developers. See the AUTHORS file at the
# top-level directory of this distribution and at
# https://github.com/renatoGarcia/tangram/blob/master/AUTHORS.
#
# This file is part of Tangram.
#
# Tangram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tangram 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Tangram in the COPYING and COPYING.LESSER files.
# If not, see <http://www.gnu.org/licenses/>.

from collections import abc
import weakref
from typing import Iterable, Optional, Any, Union

from gi.repository import Clutter
import cairo

from ._base import Base
from ._gui_thread import on_gtk_thread
from ._scene import Scene
from ._utility import ndarray_to_surface, Signal
from .widget import InputState, MouseButton
from ._typing import NpArray


class _Image(Clutter.Actor):
    def __init__(self, image):
        super().__init__(request_mode=Clutter.RequestMode.CONTENT_SIZE,
                         reactive=True)

        self._ndarray = image.copy()
        self._ndarray.flags.writeable = False
        self._img = Clutter.Canvas()
        self._img = Clutter.Canvas(width=image.shape[1], height=image.shape[0])

        self.set_content(self._img)
        self.set_content_scaling_filters(Clutter.ScalingFilter.NEAREST,
                                         Clutter.ScalingFilter.NEAREST)

        self._img.connect('draw', self._on_draw)
        self._img.invalidate()

    def _on_draw(self, canvas, ctx, width, height):
        # This handler call will be automatically protected by cairo_save() and
        # cairo_restore()
        ctx.save()
        ctx.set_operator(cairo.Operator.CLEAR)
        ctx.paint()
        ctx.restore()

        source = ndarray_to_surface(self._ndarray)
        ctx.set_source_surface(source)
        ctx.paint()

        return True


[docs]class Image(Base): """Class holding an Image. This class holds the pixels data and a list like attribute with all the image layers. The image pixel indexing is similar to that of a matrix items. The image coordinate system has the origin at the top-left pixel, and any pixel is indexed by an ordinate pair *(r, c)*, where *r* is the pixel row and *c* is the pixel column. Both rows and columns start the counting at zero. .. attention:: The Tangram Image adopts the same `coordinate system conventions of scikit-image <http://scikit-image.org/docs/dev/user_guide/numpy_images.html#coordinate-conventions>`_, using underlying the `indexing machinery of NumPy ndarrays <https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html>`_. Once created, the image pixels data will be copied to an internal buffer and marked immutable, changes on the original data will not reflect on the Image content. Its content however can be read-only accessed using the :code:`[]` operator. .. rubric:: Signals Attributes: mouse_click: Emitted by a mouse click on the image. :param pos: The mouse position (r, c) :type pos: Tuple[float, float] :param button: The MouseButton pressed. :type button: tangram.widget.MouseButton :param input_state: The mouse + keyboard state. :type input_state: tangram.widget.InputState .. rubric:: Attributes Attributes: layers: A list like object with the image :ref:`layers <sec-tutorial-layers>`. shape: A tuple with the image shape (rows, columns[, channels]). On greyscale images the *channels* item will not be present. """ class Layers(abc.MutableSequence): def __init__(self, clutter_image): self.__image = weakref.ref(clutter_image) self.__list = [] def __getitem__(self, key): return self.__list[key] @on_gtk_thread def __delitem__(self, key): old_value = self.__list[key] self.__image().remove_child(old_value._clutter_obj) del self.__list[key] @on_gtk_thread def __setitem__(self, key, value): old_value = self.__list[key] self.__image().replace_child(old_value._clutter_obj, value._clutter_obj) self.__list[key] = value def __len__(self): return len(self.__list) @on_gtk_thread def insert(self, index, value): if index < 0: index = max(index, -len(self)) index = index % len(self) else: index = min(index, len(self)) self.__image().insert_child_at_index(value._clutter_obj, index) self.__list.insert(index, value)
[docs] @on_gtk_thread def __init__(self, array: NpArray, *, layers: Iterable[Any] = [], name: Optional[str] = None) -> None: """ The NumPy ndarray *array* data will be copied to an internal buffer, and any change on the original data will not be visible. Args: array: A NumPy ndarray with the image data. layers: The initial image :ref:`layers <sec-tutorial-layers>`. name: The :ref:`tan name <sec-tutorial-tans-access>`. """ super().__init__(name) self._clutter_obj = _Image(array) self.layers = Image.Layers(self._clutter_obj) self._root_actor = Scene() self._root_actor.add_child(self._clutter_obj) self._clutter_obj.connect('button-press-event', self.__on_button_press) self.mouse_click = Signal() for layer in layers: self.layers.append(layer)
def __on_button_press(self, _actor, event): _, *pos = self._clutter_obj.transform_stage_point(event.x, event.y) button = MouseButton(button=event.button, n_clicks=event.click_count) input_state = InputState(Base._pressed_keys, event.modifier_state) self.mouse_click.emit(pos, button, input_state) return True @property def shape(self): return self._clutter_obj._ndarray.shape def __getitem__(self, key): return self._clutter_obj._ndarray[key]