Skip to content

Display a numpy image and draw pixel-coordinate overlays

Keywords: image · ImageCanvas · rectangle · Rectangle2D · shader · uniform · overlay · pixels

Shows a synthetic RGB gradient in an ~pytanga.viz.ImageCanvas (a dedicated 2D scene with a y-down pixel frame, 1 unit = 1 pixel), draws a ~pytanga.geometry.Rectangle2D overlay (via ~pytanga.geometry.Rectangle2D.between) in pixel coordinates, and binds a ctrl+left-drag handler that maps the cursor position to the image's brightness/contrast uniforms.

The ctrl+left-drag is registered as a ~pytanga.viz.DragBinding (a mouse-button + modifier combination) via the canvas's drag_handlers list.

Run

uv run python py/examples/viz/image/image_canvas.py

Source

viz/image/image_canvas.py

Code

# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 Christian Perwass

"""image_canvas.py — Display a numpy image and draw pixel-coordinate overlays.

Shows a synthetic RGB gradient in an :class:`~pytanga.viz.ImageCanvas` (a
dedicated 2D scene with a y-down pixel frame, 1 unit = 1 pixel), draws a
:class:`~pytanga.geometry.Rectangle2D` overlay (via
:meth:`~pytanga.geometry.Rectangle2D.between`) in pixel coordinates, and binds
a ctrl+left-drag handler that maps the cursor position to the image's
brightness/contrast uniforms.

The ctrl+left-drag is registered as a :class:`~pytanga.viz.DragBinding` (a
mouse-button + modifier combination) via the canvas's ``drag_handlers`` list.

Run with:  uv run python py/examples/viz/image/image_canvas.py

Keywords: image, ImageCanvas, rectangle, Rectangle2D, shader, uniform, overlay, pixels
"""

import numpy as np

from pytanga.geometry import Point, Rectangle2D
from pytanga.viz import (
    DragBinding,
    DragEvent,
    ImageCanvas,
    ImageData,
    ModifierKey,
    MouseButton,
    Visualizer,
)


def _gradient(width: int, height: int) -> np.ndarray:
    """A 3-channel RGB gradient of shape (H, W, 3), dtype uint8."""
    ys, xs = np.mgrid[0:height, 0:width]
    r = (xs / max(width - 1, 1) * 255).astype(np.uint8)
    g = (ys / max(height - 1, 1) * 255).astype(np.uint8)
    b = np.full_like(r, 128)
    return np.stack([r, g, b], axis=-1)


def _rectangle(canvas: ImageCanvas, x0: int, y0: int, x1: int, y1: int) -> None:
    """Draw a rectangle outline in pixel coordinates (y down)."""
    canvas.add(
        Rectangle2D.between(
            Point(float(x0), float(y0), 0.0),
            Point(float(x1), float(y1), 0.0),
        ),
        color="#ff4444",
    )


def main() -> None:
    width, height = 320, 200
    viz = Visualizer(add_default_axes=False, add_default_grid=False, space_dim=2)

    async def on_drag(event: DragEvent, canvas: ImageCanvas) -> bool:
        # ctrl+left drag: horizontal position → contrast, vertical → brightness.
        px, py = event.world_position.x, event.world_position.y
        canvas.set_uniform("u_contrast", 0.5 + 1.5 * px / width)
        canvas.set_uniform("u_brightness", (py / height - 0.5) * 2.0)
        return True

    canvas = ImageCanvas(
        viz,
        drag_handlers=[DragBinding(MouseButton.LEFT, on_drag, ModifierKey.CTRL)],
    )
    canvas.set_image(ImageData("gradient", data=_gradient(width, height)))
    _rectangle(canvas, 40, 30, 160, 120)

    viz.show(layout=canvas.scene_view())
    viz.wait()


if __name__ == "__main__":
    main()