Module concur.extra_widgets.pan_zoom
Zoomable, pannable widget with arbitrary content.
This widget is rarely used directly by user code. Most of the time, it's more convenient to use
higher-level widgets image()
, or frame()
.
Expand source code
""" Zoomable, pannable widget with arbitrary content.
This widget is rarely used directly by user code. Most of the time, it's more convenient to use
higher-level widgets `concur.extra_widgets.image.image`, or `concur.extra_widgets.frame.frame`.
"""
import copy
import numpy as np
import queue
import imgui
from concur.widgets import child, invisible_button
from concur.core import multi_orr, orr, forever, listen, optional, map as cmap
from concur.extra_widgets.draggable import draggable
def pan_zoom(name, state, width=None, height=None, content_gen=None, drag_tag=None, down_tag=None, hover_tag=None):
""" Create the Pan & Zoom widget, serving as a container for a thing given by `content_gen`.
This widget is mostly not used directly. Instead, a special-purpose wrapper can be used,
such as `concur.extra_widgets.image.image`, or `concur.extra_widgets.frame.frame`.
Args:
name: widget name. Also serves as an event identifier
state: `PanZoom` object representing state.
width: widget width in pixels. If `None`, it fills the available space.
height: widget height in pixels. If `None`, it fills the available space.
content_gen: Widget generator to display inside the pan-zoom widget. All events are passed
through as a return tuple member. This is a function that returns a Concur widget, and
takes two keyword arguments:
* **tf**: `concur.extra_widgets.pan_zoom.TF` object with transformation information
* **event_gen**: This is an event generator which returns the drag, down, and hover events when they occur.
It is up to the widget to do the necessary transformations
using the `TF` object, and optionally react to the events created by `event_gen`.
drag_tag: The event tag for LMB drag events. If `None`, no drag events are fired.
down_tag: The event tag for LMB down events. If `None`, no down events are fired.
Useful in combination with `drag_tag` to indicate when the dragging started.
hover_tag: The event tag for mouse hover when no mouse buttons are down.
If `None`, no hover events are fired. Events are fired only when the mouse is moving.
Returns:
To ease integration with other widgets, this widget returns events in a special format, `(name, state, event)`.
`state` or `event` may be `None` in case `state` didn't change, or there is no `event`.
If `drag_tag`, or `down_tag` arguments are used, these are fired in the `event` member of the return tuple.
"""
assert content_gen is not None
tf, content = None, None
event_queue = queue.Queue()
while True:
assert not state.keep_aspect or not state.fix_axis, \
"Can't fix axis and keep_aspect at the same time."
# Dynamically rescale width and height if they weren't specified
if width is None:
w = imgui.get_content_region_available()[0]
else:
w = width
if height is None:
h = imgui.get_content_region_available()[1]
else:
h = height
frame_w = max(1, w - state.margins[0] + state.margins[2])
frame_h = max(1, h - state.margins[1] + state.margins[3])
w = max(1, w)
h = max(1, h)
zoom_x = frame_w / (state.right - state.left)
zoom_y = frame_h / (state.bottom - state.top)
left, right = state.left, state.right
top, bottom = state.top, state.bottom
if state.keep_aspect:
aspect = float(state.keep_aspect)
assert state.keep_aspect > 0, "Negative aspect ratio is not supported."
if abs(zoom_x) > abs(zoom_y) * aspect:
zoom_x = np.sign(zoom_x) * abs(zoom_y) * aspect
center_x = (left + right) / 2
left = center_x - frame_w / zoom_x / 2
right = center_x + frame_w / zoom_x / 2
if abs(zoom_y) > abs(zoom_x) / aspect:
zoom_y = np.sign(zoom_y) * abs(zoom_x) / aspect
center_y = (top + bottom) / 2
top = center_y - frame_h / zoom_y / 2
bottom = center_y + frame_h / zoom_y / 2
origin = imgui.get_cursor_screen_pos()
# needs to be before is_window_hovered
imgui.begin_child(
"Pan-zoom", w, h, False,
flags=imgui.WINDOW_NO_SCROLLBAR | imgui.WINDOW_NO_SCROLL_WITH_MOUSE)
# Interaction
st = copy.deepcopy(state)
io = imgui.get_io()
is_window_hovered = imgui.is_window_hovered()
if (imgui.is_mouse_clicked(1) or imgui.is_mouse_clicked(2)) and is_window_hovered:
st.is_rmb_dragged = True
if not (io.mouse_down[1] or io.mouse_down[2]):
st.is_rmb_dragged = False
delta = io.mouse_delta
# Pan
if st.is_rmb_dragged and (delta[0] or delta[1]):
if st.fix_axis != 'x':
st.left -= delta[0] / zoom_x
st.right -= delta[0] / zoom_x
if st.fix_axis != 'y':
st.top -= delta[1] / zoom_y
st.bottom -= delta[1] / zoom_y
# Zoom
if (imgui.is_window_hovered() or st.is_rmb_dragged) and io.mouse_wheel:
factor = 1.3 ** io.mouse_wheel
if st.fix_axis != 'x':
mx_rel = (io.mouse_pos[0] - origin[0] - state.margins[0]) / frame_w * 2 - 1
mx = mx_rel * (right - left) / (st.right - st.left) / 2 + 0.5
wi = st.right - st.left
st.left = st.left + wi * mx - wi / factor * mx
st.right = st.right - wi * (1-mx) + wi / factor * (1-mx)
if st.fix_axis != 'y':
my_rel = (io.mouse_pos[1] - origin[1] - state.margins[1]) / frame_h * 2 - 1
my = my_rel * (bottom - top) / (st.bottom - st.top) / 2 + 0.5
hi = st.bottom - st.top
st.top = st.top + hi * my - hi / factor * my
st.bottom = st.bottom - hi * (1-my) + hi / factor * (1-my)
# Get screen-space bounds disregarding the margins
left_s = left - st.margins[0] / zoom_x
top_s = top - st.margins[1] / zoom_y
right_s = right - st.margins[2] / zoom_x
bottom_s = bottom - st.margins[3] / zoom_y
s2c = np.array([ # Screen to Content
[1 / zoom_x, 0, left_s - origin[0] / zoom_x],
[0, 1 / zoom_y, top_s - origin[1] / zoom_y]])
c2s = np.array([ # Content to Screen
[zoom_x, 0, origin[0] - left_s * zoom_x],
[0, zoom_y, origin[1] - top_s * zoom_y]])
view_s = [origin[0], origin[1], origin[0] + w, origin[1] + h]
view_c = [left_s, top_s, right_s, bottom_s]
content_value = None
content_returned = False
new_tf = TF(c2s, s2c, view_c, view_s)
if down_tag \
and not st.is_rmb_dragged \
and imgui.is_mouse_clicked() \
and is_window_hovered:
event_queue.put((down_tag, new_tf.inv_transform(np.array([[*io.mouse_pos]]))[0]))
if hover_tag \
and not st.is_rmb_dragged \
and not any(imgui.is_mouse_down(i) for i in [0,1,2]) \
and is_window_hovered:
new_tf.hovered = True
if (delta[0] or delta[1]):
event_queue.put((hover_tag, new_tf.inv_transform(np.array([[*io.mouse_pos]]))[0]))
if drag_tag \
and not st.is_rmb_dragged:
imgui.invisible_button("", w, h)
dx, dy = io.mouse_delta
if imgui.is_item_active() and (dx or dy):
event_queue.put((drag_tag, np.array([new_tf.s2c[0,0] * dx, new_tf.s2c[1,1] * dy])))
imgui.set_item_allow_overlap()
try:
from concur.core import lift
if tf is None or tf != new_tf:
tf = new_tf
w, h = tf.view_s[2] - tf.view_s[0], tf.view_s[3] - tf.view_s[1]
content = content_gen(tf=tf, event_gen=lambda: listen(event_queue))
next(content)
except StopIteration as e:
content_value = e.value
content_returned = True
finally:
imgui.end_child()
changed = st != state
if changed or content_returned:
return name, (st if changed else None, content_value)
yield
class PanZoom(object):
""" Pan & zoom state. """
def __init__(self, top_left, bottom_right, keep_aspect=True, fix_axis=None, margins=[0, 0, 0, 0]):
"""
Args:
top_left: Coordinates of the top left corner of the displayed content area.
bottom_right: Coordinates of the bottom right corner of the displayed content area.
keep_aspect: Keep aspect ratio (x/y) equal to a given constant and zoom proportionally.
If keep_aspect==True, it is equivalent to keep_aspect==1.
fix_axis: Do not zoom in a given axis (`'x'`, `'y'`, or `None`).
margins: Margins (left, top, right, bottom) of the view area in pixels.
If the view area should be inset by 5 px on each side, then use
margins=[5,5,-5,-5].
"""
self.reset_view(top_left, bottom_right)
# Include cases where cursor is outside the element, but was inside when the drag started.
# Exclude cases where cursor is inside the element, but was outside when the drag started.
self.is_rmb_dragged = False
self.is_lmb_dragged = False
self.margins = margins
self.keep_aspect = keep_aspect
self.fix_axis = fix_axis
def reset_view(self, top_left=None, bottom_right=None):
""" Reset view to default values. """
if top_left is not None:
self.default_left = top_left[0]
self.default_top = top_left[1]
if bottom_right is not None:
self.default_right = bottom_right[0]
self.default_bottom = bottom_right[1]
self.top = self.default_top
self.bottom = self.default_bottom
self.left = self.default_left
self.right = self.default_right
def __eq__(self, other):
return self.__dict__ == other.__dict__
class TF(object):
""" Transformation object containing information necessary for converting between screen-space and image-space.
Screen-space coordinates are in pixels with top-left window corner equal to (0, 0). Image space is
arbitrary, given by the respective PanZoom widget state. Transformations are expressed as NumPy
matrices in homogenous coordinates with two rows and three columns (shape: `(2, 3)`).
For example, a point `[px, py]` can be transformed to screen-space by left multiplication:
```python
q = np.matmul(c2s, [px, py, 1])
```
Mostly, the necessary conversions are performed by overlay widgets in `concur.draw`.
It may be useful to transform stuff by hand in cases when default behavior is not sufficient,
such as specifying the line width in image coordinates.
The `hovered` attribute is useful to highlight elements on mouse hover. Use `imgui.get_io().mouse_pos`
to get mouse position in screen-space and transform it using `TF.inv_transform` into
image coordinates.
Attributes:
c2s: Content-to-screen transformation matrix.
s2c: Screen-to-content transformation matrix.
view_s: Screen-space viewport coordinates as a list [left, top, right, bottom].
view_c: Image-space viewport coordinates as a list [left, top, right, bottom].
"""
def __init__(self, c2s, s2c, view_c, view_s):
self.c2s = c2s
self.s2c = s2c
self.view_c = view_c
self.view_s = view_s
self.hovered = False
def __eq__(self, other):
return \
np.array_equal(self.c2s, other.c2s) and \
np.array_equal(self.s2c, other.s2c) and \
self.view_c == other.view_c and \
self.view_s == other.view_s and \
True
def transform(self, points):
"""Transform a given NumPy array of `n` points of shape `(n, 2)` from content-space to screen-space."""
return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.c2s, (1, 1))
def inv_transform(self, points):
"""Transform a given NumPy array of `n` points of shape `(n, 2)` from screen-space to content-space."""
return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.s2c, (1, 1))
Functions
def pan_zoom(name, state, width=None, height=None, content_gen=None, drag_tag=None, down_tag=None, hover_tag=None)
-
Create the Pan & Zoom widget, serving as a container for a thing given by
content_gen
.This widget is mostly not used directly. Instead, a special-purpose wrapper can be used, such as
image()
, orframe()
.Args
name
- widget name. Also serves as an event identifier
state
PanZoom
object representing state.width
- widget width in pixels. If
None
, it fills the available space. height
- widget height in pixels. If
None
, it fills the available space. content_gen
-
Widget generator to display inside the pan-zoom widget. All events are passed through as a return tuple member. This is a function that returns a Concur widget, and takes two keyword arguments:
- tf:
TF
object with transformation information - event_gen: This is an event generator which returns the drag, down, and hover events when they occur.
It is up to the widget to do the necessary transformations using the
TF
object, and optionally react to the events created byevent_gen
. - tf:
drag_tag
- The event tag for LMB drag events. If
None
, no drag events are fired. down_tag
- The event tag for LMB down events. If
None
, no down events are fired. Useful in combination withdrag_tag
to indicate when the dragging started. hover_tag
- The event tag for mouse hover when no mouse buttons are down.
If
None
, no hover events are fired. Events are fired only when the mouse is moving.
Returns
To ease integration with other widgets, this widget returns events in a special format,
(name, state, event)
.state
orevent
may beNone
in casestate
didn't change, or there is noevent
.If
drag_tag
, ordown_tag
arguments are used, these are fired in theevent
member of the return tuple.Expand source code
def pan_zoom(name, state, width=None, height=None, content_gen=None, drag_tag=None, down_tag=None, hover_tag=None): """ Create the Pan & Zoom widget, serving as a container for a thing given by `content_gen`. This widget is mostly not used directly. Instead, a special-purpose wrapper can be used, such as `concur.extra_widgets.image.image`, or `concur.extra_widgets.frame.frame`. Args: name: widget name. Also serves as an event identifier state: `PanZoom` object representing state. width: widget width in pixels. If `None`, it fills the available space. height: widget height in pixels. If `None`, it fills the available space. content_gen: Widget generator to display inside the pan-zoom widget. All events are passed through as a return tuple member. This is a function that returns a Concur widget, and takes two keyword arguments: * **tf**: `concur.extra_widgets.pan_zoom.TF` object with transformation information * **event_gen**: This is an event generator which returns the drag, down, and hover events when they occur. It is up to the widget to do the necessary transformations using the `TF` object, and optionally react to the events created by `event_gen`. drag_tag: The event tag for LMB drag events. If `None`, no drag events are fired. down_tag: The event tag for LMB down events. If `None`, no down events are fired. Useful in combination with `drag_tag` to indicate when the dragging started. hover_tag: The event tag for mouse hover when no mouse buttons are down. If `None`, no hover events are fired. Events are fired only when the mouse is moving. Returns: To ease integration with other widgets, this widget returns events in a special format, `(name, state, event)`. `state` or `event` may be `None` in case `state` didn't change, or there is no `event`. If `drag_tag`, or `down_tag` arguments are used, these are fired in the `event` member of the return tuple. """ assert content_gen is not None tf, content = None, None event_queue = queue.Queue() while True: assert not state.keep_aspect or not state.fix_axis, \ "Can't fix axis and keep_aspect at the same time." # Dynamically rescale width and height if they weren't specified if width is None: w = imgui.get_content_region_available()[0] else: w = width if height is None: h = imgui.get_content_region_available()[1] else: h = height frame_w = max(1, w - state.margins[0] + state.margins[2]) frame_h = max(1, h - state.margins[1] + state.margins[3]) w = max(1, w) h = max(1, h) zoom_x = frame_w / (state.right - state.left) zoom_y = frame_h / (state.bottom - state.top) left, right = state.left, state.right top, bottom = state.top, state.bottom if state.keep_aspect: aspect = float(state.keep_aspect) assert state.keep_aspect > 0, "Negative aspect ratio is not supported." if abs(zoom_x) > abs(zoom_y) * aspect: zoom_x = np.sign(zoom_x) * abs(zoom_y) * aspect center_x = (left + right) / 2 left = center_x - frame_w / zoom_x / 2 right = center_x + frame_w / zoom_x / 2 if abs(zoom_y) > abs(zoom_x) / aspect: zoom_y = np.sign(zoom_y) * abs(zoom_x) / aspect center_y = (top + bottom) / 2 top = center_y - frame_h / zoom_y / 2 bottom = center_y + frame_h / zoom_y / 2 origin = imgui.get_cursor_screen_pos() # needs to be before is_window_hovered imgui.begin_child( "Pan-zoom", w, h, False, flags=imgui.WINDOW_NO_SCROLLBAR | imgui.WINDOW_NO_SCROLL_WITH_MOUSE) # Interaction st = copy.deepcopy(state) io = imgui.get_io() is_window_hovered = imgui.is_window_hovered() if (imgui.is_mouse_clicked(1) or imgui.is_mouse_clicked(2)) and is_window_hovered: st.is_rmb_dragged = True if not (io.mouse_down[1] or io.mouse_down[2]): st.is_rmb_dragged = False delta = io.mouse_delta # Pan if st.is_rmb_dragged and (delta[0] or delta[1]): if st.fix_axis != 'x': st.left -= delta[0] / zoom_x st.right -= delta[0] / zoom_x if st.fix_axis != 'y': st.top -= delta[1] / zoom_y st.bottom -= delta[1] / zoom_y # Zoom if (imgui.is_window_hovered() or st.is_rmb_dragged) and io.mouse_wheel: factor = 1.3 ** io.mouse_wheel if st.fix_axis != 'x': mx_rel = (io.mouse_pos[0] - origin[0] - state.margins[0]) / frame_w * 2 - 1 mx = mx_rel * (right - left) / (st.right - st.left) / 2 + 0.5 wi = st.right - st.left st.left = st.left + wi * mx - wi / factor * mx st.right = st.right - wi * (1-mx) + wi / factor * (1-mx) if st.fix_axis != 'y': my_rel = (io.mouse_pos[1] - origin[1] - state.margins[1]) / frame_h * 2 - 1 my = my_rel * (bottom - top) / (st.bottom - st.top) / 2 + 0.5 hi = st.bottom - st.top st.top = st.top + hi * my - hi / factor * my st.bottom = st.bottom - hi * (1-my) + hi / factor * (1-my) # Get screen-space bounds disregarding the margins left_s = left - st.margins[0] / zoom_x top_s = top - st.margins[1] / zoom_y right_s = right - st.margins[2] / zoom_x bottom_s = bottom - st.margins[3] / zoom_y s2c = np.array([ # Screen to Content [1 / zoom_x, 0, left_s - origin[0] / zoom_x], [0, 1 / zoom_y, top_s - origin[1] / zoom_y]]) c2s = np.array([ # Content to Screen [zoom_x, 0, origin[0] - left_s * zoom_x], [0, zoom_y, origin[1] - top_s * zoom_y]]) view_s = [origin[0], origin[1], origin[0] + w, origin[1] + h] view_c = [left_s, top_s, right_s, bottom_s] content_value = None content_returned = False new_tf = TF(c2s, s2c, view_c, view_s) if down_tag \ and not st.is_rmb_dragged \ and imgui.is_mouse_clicked() \ and is_window_hovered: event_queue.put((down_tag, new_tf.inv_transform(np.array([[*io.mouse_pos]]))[0])) if hover_tag \ and not st.is_rmb_dragged \ and not any(imgui.is_mouse_down(i) for i in [0,1,2]) \ and is_window_hovered: new_tf.hovered = True if (delta[0] or delta[1]): event_queue.put((hover_tag, new_tf.inv_transform(np.array([[*io.mouse_pos]]))[0])) if drag_tag \ and not st.is_rmb_dragged: imgui.invisible_button("", w, h) dx, dy = io.mouse_delta if imgui.is_item_active() and (dx or dy): event_queue.put((drag_tag, np.array([new_tf.s2c[0,0] * dx, new_tf.s2c[1,1] * dy]))) imgui.set_item_allow_overlap() try: from concur.core import lift if tf is None or tf != new_tf: tf = new_tf w, h = tf.view_s[2] - tf.view_s[0], tf.view_s[3] - tf.view_s[1] content = content_gen(tf=tf, event_gen=lambda: listen(event_queue)) next(content) except StopIteration as e: content_value = e.value content_returned = True finally: imgui.end_child() changed = st != state if changed or content_returned: return name, (st if changed else None, content_value) yield
Classes
class PanZoom (top_left, bottom_right, keep_aspect=True, fix_axis=None, margins=[0, 0, 0, 0])
-
Pan & zoom state.
Args
top_left
-
Coordinates of the top left corner of the displayed content area.
bottom_right
- Coordinates of the bottom right corner of the displayed content area.
keep_aspect
- Keep aspect ratio (x/y) equal to a given constant and zoom proportionally. If keep_aspect==True, it is equivalent to keep_aspect==1.
fix_axis
-
Do not zoom in a given axis (
'x'
,'y'
, orNone
). margins
-
Margins (left, top, right, bottom) of the view area in pixels. If the view area should be inset by 5 px on each side, then use margins=[5,5,-5,-5].
Expand source code
class PanZoom(object): """ Pan & zoom state. """ def __init__(self, top_left, bottom_right, keep_aspect=True, fix_axis=None, margins=[0, 0, 0, 0]): """ Args: top_left: Coordinates of the top left corner of the displayed content area. bottom_right: Coordinates of the bottom right corner of the displayed content area. keep_aspect: Keep aspect ratio (x/y) equal to a given constant and zoom proportionally. If keep_aspect==True, it is equivalent to keep_aspect==1. fix_axis: Do not zoom in a given axis (`'x'`, `'y'`, or `None`). margins: Margins (left, top, right, bottom) of the view area in pixels. If the view area should be inset by 5 px on each side, then use margins=[5,5,-5,-5]. """ self.reset_view(top_left, bottom_right) # Include cases where cursor is outside the element, but was inside when the drag started. # Exclude cases where cursor is inside the element, but was outside when the drag started. self.is_rmb_dragged = False self.is_lmb_dragged = False self.margins = margins self.keep_aspect = keep_aspect self.fix_axis = fix_axis def reset_view(self, top_left=None, bottom_right=None): """ Reset view to default values. """ if top_left is not None: self.default_left = top_left[0] self.default_top = top_left[1] if bottom_right is not None: self.default_right = bottom_right[0] self.default_bottom = bottom_right[1] self.top = self.default_top self.bottom = self.default_bottom self.left = self.default_left self.right = self.default_right def __eq__(self, other): return self.__dict__ == other.__dict__
Subclasses
Methods
def reset_view(self, top_left=None, bottom_right=None)
-
Reset view to default values.
Expand source code
def reset_view(self, top_left=None, bottom_right=None): """ Reset view to default values. """ if top_left is not None: self.default_left = top_left[0] self.default_top = top_left[1] if bottom_right is not None: self.default_right = bottom_right[0] self.default_bottom = bottom_right[1] self.top = self.default_top self.bottom = self.default_bottom self.left = self.default_left self.right = self.default_right
class TF (c2s, s2c, view_c, view_s)
-
Transformation object containing information necessary for converting between screen-space and image-space.
Screen-space coordinates are in pixels with top-left window corner equal to (0, 0). Image space is arbitrary, given by the respective PanZoom widget state. Transformations are expressed as NumPy matrices in homogenous coordinates with two rows and three columns (shape:
(2, 3)
).For example, a point
[px, py]
can be transformed to screen-space by left multiplication:q = np.matmul(c2s, [px, py, 1])
Mostly, the necessary conversions are performed by overlay widgets in
concur.draw
. It may be useful to transform stuff by hand in cases when default behavior is not sufficient, such as specifying the line width in image coordinates.The
hovered
attribute is useful to highlight elements on mouse hover. Useimgui.get_io().mouse_pos
to get mouse position in screen-space and transform it usingTF.inv_transform()
into image coordinates.Attributes
c2s
-
Content-to-screen transformation matrix.
s2c
-
Screen-to-content transformation matrix.
view_s
- Screen-space viewport coordinates as a list [left, top, right, bottom].
view_c
- Image-space viewport coordinates as a list [left, top, right, bottom].
Expand source code
class TF(object): """ Transformation object containing information necessary for converting between screen-space and image-space. Screen-space coordinates are in pixels with top-left window corner equal to (0, 0). Image space is arbitrary, given by the respective PanZoom widget state. Transformations are expressed as NumPy matrices in homogenous coordinates with two rows and three columns (shape: `(2, 3)`). For example, a point `[px, py]` can be transformed to screen-space by left multiplication: ```python q = np.matmul(c2s, [px, py, 1]) ``` Mostly, the necessary conversions are performed by overlay widgets in `concur.draw`. It may be useful to transform stuff by hand in cases when default behavior is not sufficient, such as specifying the line width in image coordinates. The `hovered` attribute is useful to highlight elements on mouse hover. Use `imgui.get_io().mouse_pos` to get mouse position in screen-space and transform it using `TF.inv_transform` into image coordinates. Attributes: c2s: Content-to-screen transformation matrix. s2c: Screen-to-content transformation matrix. view_s: Screen-space viewport coordinates as a list [left, top, right, bottom]. view_c: Image-space viewport coordinates as a list [left, top, right, bottom]. """ def __init__(self, c2s, s2c, view_c, view_s): self.c2s = c2s self.s2c = s2c self.view_c = view_c self.view_s = view_s self.hovered = False def __eq__(self, other): return \ np.array_equal(self.c2s, other.c2s) and \ np.array_equal(self.s2c, other.s2c) and \ self.view_c == other.view_c and \ self.view_s == other.view_s and \ True def transform(self, points): """Transform a given NumPy array of `n` points of shape `(n, 2)` from content-space to screen-space.""" return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.c2s, (1, 1)) def inv_transform(self, points): """Transform a given NumPy array of `n` points of shape `(n, 2)` from screen-space to content-space.""" return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.s2c, (1, 1))
Methods
def inv_transform(self, points)
-
Transform a given NumPy array of
n
points of shape(n, 2)
from screen-space to content-space.Expand source code
def inv_transform(self, points): """Transform a given NumPy array of `n` points of shape `(n, 2)` from screen-space to content-space.""" return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.s2c, (1, 1))
def transform(self, points)
-
Transform a given NumPy array of
n
points of shape(n, 2)
from content-space to screen-space.Expand source code
def transform(self, points): """Transform a given NumPy array of `n` points of shape `(n, 2)` from content-space to screen-space.""" return np.tensordot(np.hstack([points, np.ones((points.shape[0], 1))]), self.c2s, (1, 1))