def draw_line(image: np.ndarray, line: Line2, color: Tuple[float, float, float], thickness: int = 1) -> None: image_extents = Rect2(pos=(0, 0), size=image.shape[1::-1]) start_point = line.eval_at(x=image_extents.x0) end_point = line.eval_at(x=image_extents.x1) if not image_extents.contains_point(start_point): if start_point.y < image_extents.y0: y_to_eval = image_extents.y0 else: y_to_eval = image_extents.y1 start_point = line.eval_at(y=y_to_eval) if not image_extents.contains_point(end_point): if end_point.y < image_extents.y0: y_to_eval = image_extents.y0 else: y_to_eval = image_extents.y1 end_point = line.eval_at(y=y_to_eval) cv2.line(image, pt1=tuple(start_point.as_type(int)), pt2=tuple(end_point.as_type(int)), color=color, thickness=thickness)
def _get_region_clip(self) -> Optional[Rect2]: image_size_hint = self._image_acquisition.get_image_size_hint() if image_size_hint is None: return None return Rect2( pos=(0, 0), size=image_size_hint, )
def set_background_image(self, image: Optional[np.ndarray]) -> None: if image is None: self._background_ro.props.pixbuf = None return self._background_ro.props.pixbuf = pixbuf_from_array(image) self._render.props.canvas_size = image.shape[1::-1] self._render.viewport_extents = Rect2(position=(0, 0), size=image.shape[1::-1])
def _update_dragging_indicator(self, current_cursor_pos: Vector2[float]) -> None: if not self._model.is_defining: self.view.bn_dragging.set(None) return self.view.bn_dragging.set( Rect2( pt0=self._model.begin_define_pos, pt1=current_cursor_pos, ))
def commit_define(self, end_pos: Vector2[float]) -> None: assert self.is_defining start_pos = self._begin_define_pos self._begin_define_pos = None region = Rect2( p0=start_pos, p1=end_pos, ).as_type(int) clip = self._bn_clip.get() if clip is not None: region = Rect2( x0=clamp(region.x0, clip.x0, clip.x1), y0=clamp(region.y0, clip.y0, clip.y1), x1=clamp(region.x1, clip.x0, clip.x1), y1=clamp(region.y1, clip.y0, clip.y1), ) if region.w == 0 or region.h == 0: return self.bn_region.set(region)
def draw_angle_marker(image: np.ndarray, vertex_pos: Vector2[float], start_angle: float, delta_angle: float, radius: float, color: Tuple[float, float, float]) -> None: if not Rect2(pos=(0, 0), size=image.shape[1::-1]).contains_point(vertex_pos): # Vertex is outside of the image, ignore. return end_angle = start_angle + delta_angle start_pos = vertex_pos delta_pos = radius * Vector2(math.cos(-end_angle), math.sin(-end_angle)) end_pos = start_pos + delta_pos cv2.line(image, pt1=tuple(start_pos.as_type(int)), pt2=tuple(end_pos.as_type(int)), color=color, thickness=1)
class Render(Gtk.DrawingArea, protocol.Render): _canvas_size = Vector2(1, 1) _viewport_extents = Rect2(position=(0, 0), size=_canvas_size) # type: Rect2[float] _viewport_stretch = protocol.Render.ViewportStretch.FIT STYLE = """ .render { border: 1px solid white; } """ _STYLE_PROV = Gtk.CssProvider() _STYLE_PROV.load_from_data(bytes(STYLE, 'utf-8')) class RenderObjectContainer: def __init__(self, render_object: protocol.RenderObject, handler_ids: Sequence[int]) -> None: self.render_object = render_object self.handler_ids = tuple(handler_ids) def __init__(self, *, can_focus=True, **options) -> None: super().__init__(focus_on_click=True, can_focus=can_focus, **options) self._ro_containers = [ ] # type: MutableSequence[Render.RenderObjectContainer] self.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.FOCUS_CHANGE_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.KEY_PRESS_MASK) self.get_style_context().add_class('render') self.get_style_context().add_provider(self._STYLE_PROV, Gtk.STYLE_PROVIDER_PRIORITY_USER) def do_draw(self, cr: cairo.Context) -> None: viewport_widget_extents = self.props.viewport_widget_extents with cairo_saved(cr): cr.rectangle(*viewport_widget_extents.position, *viewport_widget_extents.size) cr.clip() for ro in self._render_objects: ro.draw(cr) if self.has_focus(): # Draw focus indicator stroke_width = 1 rectangle_pos = viewport_widget_extents.position + ( stroke_width / 2, stroke_width / 2) rectangle_size = viewport_widget_extents.size - (stroke_width, stroke_width) cr.rectangle(*rectangle_pos, *rectangle_size) cr.set_source_rgb(70 / 255, 142 / 255, 220 / 255) cr.set_line_width(stroke_width) cr.stroke() @property def _render_objects(self) -> Sequence[protocol.RenderObject]: return sorted( (container.render_object for container in self._ro_containers), key=lambda ro: ro.props.z_index, ) def add_render_object(self, ro: protocol.RenderObject) -> None: handler_ids = (ro.connect('request-draw', lambda _: self.queue_draw()), ) self._ro_containers.append( self.RenderObjectContainer(render_object=ro, handler_ids=handler_ids)) ro.set_parent(self) self.queue_draw() def remove_render_object(self, ro: protocol.RenderObject) -> None: container = self._ro_container_from_ro(ro) for handler_id in container.handler_ids: ro.disconnect(handler_id) self._ro_containers.remove(container) self.queue_draw() def _ro_container_from_ro( self, ro: protocol.RenderObject) -> 'Render.RenderObjectContainer': for container in self._ro_containers: if container.render_object is ro: return container else: raise ValueError('No container found for {}.'.format(ro)) def do_button_press_event(self, event: Gdk.EventButton) -> None: self.emit('cursor-down-event', self._canvas_coord_from_widget(Vector2(event.x, event.y))) if self.props.can_focus: self.grab_focus() def do_button_release_event(self, event: Gdk.EventButton) -> None: self.emit('cursor-up-event', self._canvas_coord_from_widget(Vector2(event.x, event.y))) def do_motion_notify_event(self, event: Gdk.EventButton) -> None: self.emit('cursor-motion-event', self._canvas_coord_from_widget(Vector2(event.x, event.y))) def do_key_press_event(self, event: Gdk.EventKey) -> bool: if event.keyval == Gdk.KEY_Tab: # Allow user to use the tab key to cycle focus to another widget return False # Stop event propagation return True # Cursor signals @GObject.Signal(arg_types=(object, )) def cursor_down_event(self, pos: Vector2[float]) -> None: pass @GObject.Signal(arg_types=(object, )) def cursor_up_event(self, pos: Vector2[float]) -> None: pass @GObject.Signal(arg_types=(object, )) def cursor_motion_event(self, pos: Vector2[float]) -> None: pass # Coordinate transform functions def _widget_coord_from_canvas( self, coord_canvas: Tuple[float, float]) -> Vector2[float]: coord_canvas = Vector2(*coord_canvas) viewport_extents = self.props.viewport_extents viewport_widget_extents = self.props.viewport_widget_extents coord_viewport = coord_canvas - viewport_extents.position coord_viewport_pct = Vector2( coord_viewport.x / viewport_extents.size.x, coord_viewport.y / viewport_extents.size.y) coord_viewport_widget = Vector2( coord_viewport_pct.x * viewport_widget_extents.size.x, coord_viewport_pct.y * viewport_widget_extents.size.y) coord_widget = viewport_widget_extents.position + coord_viewport_widget return coord_widget def _canvas_coord_from_widget( self, coord_widget: Tuple[float, float]) -> Vector2[float]: coord_widget = Vector2(*coord_widget) viewport_extents = self.props.viewport_extents viewport_widget_extents = self.props.viewport_widget_extents coord_viewport_widget = coord_widget - viewport_widget_extents.position coord_viewport_pct = Vector2( coord_viewport_widget.x / viewport_widget_extents.size.x, coord_viewport_widget.y / viewport_widget_extents.size.y) coord_viewport = Vector2( coord_viewport_pct.x * viewport_extents.size.x, coord_viewport_pct.y * viewport_extents.size.y) coord_canvas = viewport_extents.position + coord_viewport return coord_canvas def _widget_dist_from_canvas( self, dist_canvas: Tuple[float, float]) -> Vector2[float]: dist_canvas = Vector2(*dist_canvas) viewport_size = self.props.viewport_extents.size viewport_widget_size = self.props.viewport_widget_extents.size scale = Vector2(viewport_widget_size.x / viewport_size.x, viewport_widget_size.y / viewport_size.y) dist_widget = Vector2(scale.x * dist_canvas.x, scale.y * dist_canvas.y) return dist_widget def _canvas_dist_from_widget( self, dist_widget: Tuple[float, float]) -> Vector2[float]: dist_widget = Vector2(*dist_widget) viewport_size = self.props.viewport_extents.size viewport_widget_size = self.props.viewport_widget_extents.size scale = Vector2(viewport_size.x / viewport_widget_size.x, viewport_size.y / viewport_widget_size.y) dist_canvas = Vector2(scale.x * dist_widget.x, scale.y * dist_widget.y) return dist_canvas # Canvas geometry properties @GObject.Property def canvas_size(self) -> Vector2[float]: return self._canvas_size @canvas_size.setter def canvas_size(self, new_size: Vector2[float]) -> None: self._canvas_size = new_size self.queue_draw() @GObject.Property def viewport_extents(self) -> Rect2[float]: return self._viewport_extents @viewport_extents.setter def viewport_extents(self, new_extents: Rect2[float]) -> None: self._viewport_extents = new_extents self.queue_draw() @GObject.Property def viewport_widget_extents(self) -> Rect2[float]: return Rect2(position=self._viewport_widget_pos, size=self._viewport_widget_size) @property def _viewport_widget_size(self) -> Vector2[float]: widget_size = Vector2(self.get_allocated_width(), self.get_allocated_height()) return self._calculate_stretch_size( stretch=self._viewport_stretch, reference_size=widget_size, child_size=self._viewport_extents.size) @property def _viewport_widget_pos(self) -> Vector2[float]: widget_size = Vector2(self.get_allocated_width(), self.get_allocated_height()) return self._calculate_offset_for_centred_rectangles( reference_size=widget_size, child_size=self._viewport_widget_size) @classmethod def _calculate_stretch_size(cls, stretch: protocol.Render.ViewportStretch, reference_size: Vector2[float], child_size: Vector2[float]) -> Vector2[float]: reference_aspect = reference_size[0] / reference_size[1] child_aspect = child_size[0] / child_size[1] if stretch is cls.ViewportStretch.FILL and (reference_aspect > child_aspect) \ or stretch is cls.ViewportStretch.FIT and (reference_aspect <= child_aspect): scale_factor = reference_size[0] / child_size[0] else: scale_factor = reference_size[1] / child_size[1] return child_size * scale_factor @staticmethod def _calculate_offset_for_centred_rectangles(reference_size: Vector2[float], child_size: Vector2[float]) \ -> Vector2[float]: offset = reference_size / 2 - child_size / 2 return offset
def viewport_widget_extents(self) -> Rect2[float]: return Rect2(position=self._viewport_widget_pos, size=self._viewport_widget_size)