Skip to content

Commit

Permalink
Create an independent renderer for draw_marker_at_points (#724)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwiggins authored Mar 18, 2021
1 parent 2ce5387 commit 9f201e2
Show file tree
Hide file tree
Showing 28 changed files with 11,967 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ build/
dist/

# SWIG & Cython intermediate files
kiva/_marker_renderer.cpp
kiva/agg/agg.py
kiva/agg/agg_wrap.cpp
kiva/agg/plat_support.py
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ include docs/Makefile
include docs/kiva/agg/notes
include kiva/_cython_speedups.*
include kiva/_hit_test.*
include kiva/_marker_renderer.*
recursive-include docs *.py *.rst *.txt *.css *.png *.ico *.doc
recursive-include enable/examples *.py *.svg *.jpg *.enaml
recursive-include kiva/examples *.py *.txt *.gif *.jpg
Expand All @@ -19,4 +20,5 @@ recursive-include kiva/agg/LICENSES *
recursive-include kiva/fonttools/tests/data *.ttc *.ttf *.afm
recursive-include kiva/fonttools/LICENSES *
recursive-include kiva/gl *.h *.cpp *.i LICENSE_*
recursive-include kiva/markers *.h LICENSE_*
recursive-include kiva/quartz *.pyx *.pxi *.pxd mac_context*.*
44 changes: 44 additions & 0 deletions kiva/_marker_renderer.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
from libcpp cimport bool

cdef extern from "marker_renderer.h" namespace "agg24markers":
cdef cppclass pixfmt_abgr32:
pass
cdef cppclass pixfmt_argb32:
pass
cdef cppclass pixfmt_bgra32:
pass
cdef cppclass pixfmt_rgba32:
pass
cdef cppclass pixfmt_bgr24:
pass
cdef cppclass pixfmt_rgb24:
pass


cdef extern from "marker_renderer.h" namespace "kiva_markers":
# This is just here for the type signature
cdef enum marker_type:
pass

# Abstract base class
cdef cppclass marker_renderer_base:
bool draw_markers(double* pts, unsigned Npts,
unsigned size, marker_type marker,
double* fill, double* stroke)
void transform(double sx, double sy,
double shx, double shy,
double tx, double ty)

# Template class
cdef cppclass marker_renderer[pixfmt_T]:
marker_renderer(unsigned char* buf, unsigned width, unsigned height,
int stride, bool bottom_up)
138 changes: 138 additions & 0 deletions kiva/_marker_renderer.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import cython
import numpy as np
from numpy cimport uint8_t

cimport _marker_renderer

ctypedef _marker_renderer.marker_renderer_base renderer_base_t

@cython.internal
cdef class MarkerRendererBase:
cdef renderer_base_t* _this
cdef object py_array

def __dealloc__(self):
del self._this

cdef int base_init(self, image) except -1:
if image is None:
raise ValueError('image argument must not be None.')

# Retain a reference to the memory view supplied to the constructor
# so that it lives as long as this object
self.py_array = image

def draw_markers(self, points, size, marker, fill, stroke):
"""draw_markers(points, size, marker, fill, stroke)
Draw markers at a collection of points.
:param points: An Nx2 iterable of (x, y) points for marker positions
:param size: An integer pixel size for each marker
:param marker: A Kiva marker enum integer
:param fill: Fill color given as an iterable of 4 numbers (R, G, B, A)
:param stroke: Line color given as an iterable of 4 numbers (R, G, B, A)
:returns: True if any markers were drawn, False otherwise
"""
cdef:
double[:,::1] _points = np.asarray(points, dtype=np.float64, order='c')
double[::1] _fill = np.asarray(fill, dtype=np.float64, order='c')
double[::1] _stroke = np.asarray(stroke, dtype=np.float64, order='c')
unsigned _size = <unsigned>size
_marker_renderer.marker_type _marker = <_marker_renderer.marker_type>marker

if _points.shape[1] != 2:
msg = "points argument must be an iterable of (x, y) pairs."
raise ValueError(msg)
if _stroke.shape[0] != 4:
msg = "stroke argument must be an iterable of 4 numbers."
raise ValueError(msg)
if _fill.shape[0] != 4:
msg = "fill argument must be an iterable of 4 numbers."
raise ValueError(msg)

return self._this.draw_markers(
&_points[0][0], _points.shape[0], _size, _marker,
&_fill[0], &_stroke[0]
)

def transform(self, sx, sy, shx, shy, tx, ty):
"""transform(sx, sy, shx, shy, tx, ty)
Set the transform to be applied to the marker points and size.
:param sx: Scale in X
:param sy: Scale in Y
:param shx: Shear in X
:param shy: Shear in Y
:param tx: Translation in X
:param ty: Translation in Y
"""
cdef:
double _sx = <double>sx
double _sy = <double>sy
double _shx = <double>shx
double _shy = <double>shy
double _tx = <double>tx
double _ty = <double>ty

self._this.transform(_sx, _sy, _shx, _shy, _tx, _ty)


# Template specializations
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_abgr32] renderer_abgr32_t
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_argb32] renderer_argb32_t
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_bgra32] renderer_bgra32_t
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_rgba32] renderer_rgba32_t
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_bgr24] renderer_bgr24_t
ctypedef _marker_renderer.marker_renderer[_marker_renderer.pixfmt_rgb24] renderer_rgb24_t

cdef class MarkerRendererABGR32(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_abgr32_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)

cdef class MarkerRendererARGB32(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_argb32_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)

cdef class MarkerRendererBGRA32(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_bgra32_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)

cdef class MarkerRendererRGBA32(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_rgba32_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)

cdef class MarkerRendererBGR24(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_bgr24_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)

cdef class MarkerRendererRGB24(MarkerRendererBase):
def __cinit__(self, uint8_t[:,:,::1] image, bottom_up=True):
self.base_init(image)
self._this = <renderer_base_t*> new renderer_rgb24_t(
&image[0][0][0], image.shape[1], image.shape[0], image.strides[0], bottom_up
)
7 changes: 7 additions & 0 deletions kiva/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
- :attr:`~.DOT_MARKER`
- :attr:`~.PIXEL_MARKER`
Marker Renderer
===============
This can be used by Kiva backends to implement :py:meth:`draw_marker_at_points`
- :class:`~.MarkerRenderer`
Fonts
=====
Expand Down Expand Up @@ -148,3 +154,4 @@
)
from ._cython_speedups import points_in_polygon
from .fonttools import add_application_fonts, Font
from .marker_renderer import MarkerRenderer
36 changes: 34 additions & 2 deletions kiva/celiagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from kiva.abstract_graphics_context import AbstractGraphicsContext
import kiva.constants as constants
from kiva.fonttools import Font
from kiva.marker_renderer import MarkerRenderer

# These are the symbols that a backend has to define.
__all__ = ["CompiledPath", "Font", "font_metrics_provider", "GraphicsContext"]
Expand Down Expand Up @@ -89,8 +90,12 @@ def __init__(self, size, *args, **kwargs):
self.pix_format = kwargs.get('pix_format', 'rgba32')

shape = (self._height, self._width, 4)
buffer = np.zeros(shape, dtype=np.uint8)
canvas_klass = pix_format_canvases[self.pix_format]
self.gc = canvas_klass(np.zeros(shape, dtype=np.uint8), bottom_up=True)
self.gc = canvas_klass(buffer, bottom_up=True)
self.marker_gc = MarkerRenderer(
buffer, pix_format=self.pix_format, bottom_up=True
)

# init the state variables
clip = agg.Rect(0, 0, self._width, self._height)
Expand Down Expand Up @@ -826,6 +831,33 @@ def draw_path_at_points(self, points, path, mode=constants.FILL_STROKE):
fill=self.fill_paint,
)

def draw_marker_at_points(self, points_array, size,
marker=constants.SQUARE_MARKER):
""" Draw a marker at a collection of points
"""
# Apply the current transform
ctm = self.transform
self.marker_gc.transform(
ctm.sx, ctm.sy,
ctm.shx, ctm.shy,
ctm.tx, ctm.ty,
)

# Grab the fill and stroke colors (where possible)
fill = (0.0, 0.0, 0.0, 0.0)
stroke = (0.0, 0.0, 0.0, 1.0)
if isinstance(self.fill_paint, agg.SolidPaint):
fp = self.fill_paint
fill = (fp.r, fp.g, fp.b, fp.a)
if isinstance(self.stroke_paint, agg.SolidPaint):
sp = self.stroke_paint
stroke = (sp.r, sp.g, sp.b, sp.a)

# Draw using the marker renderer
return self.marker_gc.draw_markers(
points_array, size, marker, fill, stroke
)

def save(self, filename, file_format=None, pil_options=None):
""" Save the contents of the context to a file
"""
Expand All @@ -841,7 +873,7 @@ def save(self, filename, file_format=None, pil_options=None):
os.path.splitext(filename)[1][1:] if isinstance(filename, str)
else ''
)

# Check the output format to see if it can handle an alpha channel.
no_alpha_formats = ('jpg', 'bmp', 'eps', 'jpeg')
if ext in no_alpha_formats or file_format.lower() in no_alpha_formats:
Expand Down
53 changes: 53 additions & 0 deletions kiva/marker_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
from kiva._marker_renderer import (
MarkerRendererABGR32, MarkerRendererARGB32, MarkerRendererBGR24,
MarkerRendererBGRA32, MarkerRendererRGB24, MarkerRendererRGBA32,
)

__all__ = ["MarkerRenderer"]

_renderers = {
"abgr32": (MarkerRendererABGR32, 4),
"argb32": (MarkerRendererARGB32, 4),
"bgra32": (MarkerRendererBGRA32, 4),
"rgba32": (MarkerRendererRGBA32, 4),
"bgr24": (MarkerRendererBGR24, 3),
"rgb24": (MarkerRendererRGB24, 3),
}


def MarkerRenderer(buffer, pix_format="bgra32", bottom_up=True):
""" MarkerRenderer(buffer, pix_format="bgra32", bottom_up=True)
Create a specialized renderer for implementing ``draw_marker_at_points``.
Parameters
----------
buffer : ndarray
A MxNx{3,4} numpy array of uint8 to be used as the backing pixel store
pix_format : str
A string specifying the pixel format. Same as what it passed to
``GraphicsContext``.
bottom_up : bool [optional, defaults to True]
If True, the origin is bottom-left instead of top-left.
Returns
-------
renderer : A new MarkerRenderer instance.
"""
klass, components = _renderers.get(pix_format, (None, 0))
if klass is None:
raise ValueError(f"{pix_format} is not a supported pixel format")

if (str(buffer.dtype) != "uint8" or buffer.ndim != 3
or buffer.shape[2] != components):
raise ValueError(f"Pixel buffer must be MxNx{components} and uint8")

return klass(buffer, bottom_up=bottom_up)
Loading

0 comments on commit 9f201e2

Please sign in to comment.