Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]:
Comment thread
aaronayres35 marked this conversation as resolved.
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
Comment thread
jwiggins marked this conversation as resolved.

if _points.shape[1] != 2:
msg = "points argument must be an iterable of (x, y) pairs."
raise ValueError(msg)
Comment thread
jwiggins marked this conversation as resolved.
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
)
Comment on lines +98 to +138
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You think there's a nicer way to do these specializations?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has no understanding whatsoever about templates/specializations so cannot provide constructive comments Should we ask internally if anyone knows better?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Comment on lines +846 to +854
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to add set_stroke_color and set_fill_color methods to MarkerRenderer* so that we can just pass through colors from GraphicsContext.set_*_color methods? This particular block of code is just here in case a gradient fill color was set.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure i fully understand what you're recommending here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add set_*_color methods to MarkerRenderer, then in the set_*_color methods of GraphicsContext call self.marker_gc.set_*_color(color) so that the marker renderer stays in sync with the simple fill and stroke colors. If you set a gradient on the celiagg backend and then call draw_marker_at_points, the fill color of the markers will be transparent. One could argue that it's a bug in the code doing the drawing, but it's a silent failure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't doing anything about it in this PR


# 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