From ba4016014cb4fb4927e36ce8ea429fed47dcb787 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 3 Oct 2014 16:10:18 -0400 Subject: [PATCH 01/18] Remove use of PyCXX in core C++ extensions --- .travis.yml | 7 +- .../pylab_examples/agg_buffer_to_array.py | 7 +- examples/pylab_examples/mathtext_demo.py | 1 - lib/matplotlib/__init__.py | 7 + lib/matplotlib/axes/_base.py | 4 +- lib/matplotlib/backends/backend_agg.py | 28 +- lib/matplotlib/backends/backend_cairo.py | 4 - lib/matplotlib/backends/backend_gdk.py | 6 +- lib/matplotlib/backends/backend_macosx.py | 2 - lib/matplotlib/backends/backend_mixed.py | 1 - lib/matplotlib/backends/backend_pdf.py | 5 +- lib/matplotlib/backends/backend_pgf.py | 6 +- lib/matplotlib/backends/backend_ps.py | 8 +- lib/matplotlib/backends/backend_svg.py | 10 +- .../backends/backend_webagg_core.py | 3 +- lib/matplotlib/figure.py | 4 +- lib/matplotlib/image.py | 48 +- lib/matplotlib/mathtext.py | 3 +- lib/matplotlib/testing/compare.py | 2 +- .../test_image/rasterize_10dpi.pdf | Bin 2616 -> 2638 bytes .../test_image/rasterize_10dpi.svg | 6 +- lib/matplotlib/tests/test_axes.py | 6 +- lib/matplotlib/transforms.py | 4 +- lib/matplotlib/tri/_tri.cpp | 50 +- lib/mpl_toolkits/mplot3d/axis3d.py | 4 +- setupext.py | 33 +- src/_backend_agg.cpp | 2635 +---------------- src/_backend_agg.h | 1396 +++++++-- src/_backend_agg_basic_types.h | 124 + src/_backend_agg_wrapper.cpp | 730 +++++ src/_backend_agg_wrapper.h | 29 + src/_backend_gdk.c | 19 +- src/_gtkagg.cpp | 265 +- src/_image.cpp | 1873 ++---------- src/_image.h | 477 ++- src/_image_wrapper.cpp | 805 +++++ src/_macosx.m | 4 - src/_path.cpp | 1813 ------------ src/_path.h | 978 ++++++ src/_path_wrapper.cpp | 713 +++++ src/_png.cpp | 729 ++--- src/_tkagg.cpp | 197 +- src/_ttconv.cpp | 179 +- src/agg_py_path_iterator.h | 137 - src/agg_py_transforms.cpp | 127 - src/agg_py_transforms.h | 17 - src/array.h | 80 + src/file_compat.h | 71 +- src/ft2font.cpp | 2188 ++------------ src/ft2font.h | 247 +- src/ft2font_wrapper.cpp | 1728 +++++++++++ src/mplutils.cpp | 37 +- src/mplutils.h | 62 +- src/numpy_cpp.h | 480 +++ src/path_cleanup.cpp | 143 +- src/path_cleanup.h | 22 +- src/path_converters.h | 450 ++- src/py_adaptors.h | 251 ++ src/py_converters.cpp | 545 ++++ src/py_converters.h | 45 + src/py_exceptions.h | 72 + tests.py | 7 + 62 files changed, 9880 insertions(+), 10054 deletions(-) mode change 100644 => 100755 examples/pylab_examples/mathtext_demo.py create mode 100644 src/_backend_agg_basic_types.h create mode 100644 src/_backend_agg_wrapper.cpp create mode 100644 src/_backend_agg_wrapper.h create mode 100644 src/_image_wrapper.cpp delete mode 100644 src/_path.cpp create mode 100644 src/_path.h create mode 100644 src/_path_wrapper.cpp delete mode 100644 src/agg_py_path_iterator.h delete mode 100644 src/agg_py_transforms.cpp delete mode 100644 src/agg_py_transforms.h create mode 100644 src/array.h create mode 100644 src/ft2font_wrapper.cpp create mode 100644 src/numpy_cpp.h create mode 100644 src/py_adaptors.h create mode 100644 src/py_converters.cpp create mode 100644 src/py_converters.h create mode 100644 src/py_exceptions.h diff --git a/.travis.yml b/.travis.yml index e60e8abb6d0c..8c7c1be5411b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,8 @@ matrix: install: - pip install -q --use-mirrors nose python-dateutil $NUMPY pep8 pyparsing pillow - - sudo apt-get update && sudo apt-get -qq install inkscape libav-tools - # We use --no-install-recommends to avoid pulling in additional large latex docs that we don't need + - sudo apt-get update && sudo apt-get -qq install inkscape libav-tools gdb + # We use --no-install-recommends to avoid pulling in additional large latex docs that we don't need - if [[ $BUILD_DOCS == true ]]; then sudo apt-get install -qq --no-install-recommends dvipng texlive-latex-base texlive-latex-extra texlive-fonts-recommended graphviz; fi - if [[ $BUILD_DOCS == true ]]; then pip install sphinx numpydoc linkchecker; fi - python setup.py install @@ -37,11 +37,12 @@ script: - echo Testing using 8 processes # Generate the font caches in a single process before starting the # multiple processes + - gcc --version - python -c "from matplotlib import font_manager" - if [[ $BUILD_DOCS == false ]]; then export MPL_REPO_DIR=$PWD; fi # pep8-conformance test of the examples - if [[ $BUILD_DOCS == false ]]; then mkdir ../tmp_test_dir; fi - if [[ $BUILD_DOCS == false ]]; then cd ../tmp_test_dir; fi - - if [[ $BUILD_DOCS == false ]]; then python ../matplotlib/tests.py -sv --processes=8 --process-timeout=300 $TEST_ARGS; fi + - if [[ $BUILD_DOCS == false ]]; then gdb -return-child-result -batch -ex r -ex bt --args python ../matplotlib/tests.py -sv $TEST_ARGS; fi - if [[ $BUILD_DOCS == true ]]; then cd doc; python make.py html --small; fi # We don't build the LaTeX docs here, so linkchecker will complain - if [[ $BUILD_DOCS == true ]]; then touch build/html/Matplotlib.pdf; fi diff --git a/examples/pylab_examples/agg_buffer_to_array.py b/examples/pylab_examples/agg_buffer_to_array.py index a52434b282ff..acbe2333a319 100644 --- a/examples/pylab_examples/agg_buffer_to_array.py +++ b/examples/pylab_examples/agg_buffer_to_array.py @@ -8,12 +8,7 @@ fig.canvas.draw() # grab the pixel buffer and dump it into a numpy array -buf = fig.canvas.buffer_rgba() -l, b, w, h = fig.bbox.bounds -# The array needs to be copied, because the underlying buffer -# may be reallocated when the window is resized. -X = np.frombuffer(buf, np.uint8).copy() -X.shape = h,w,4 +X = np.array(fig.canvas.renderer._renderer) # now display the array X as an Axes in a new figure fig2 = plt.figure() diff --git a/examples/pylab_examples/mathtext_demo.py b/examples/pylab_examples/mathtext_demo.py old mode 100644 new mode 100755 index 2947672e255c..98a9c73c2f70 --- a/examples/pylab_examples/mathtext_demo.py +++ b/examples/pylab_examples/mathtext_demo.py @@ -24,5 +24,4 @@ ax.set_title(r'$\Delta_i^j \hspace{0.4} \mathrm{versus} \hspace{0.4} \Delta_{i+1}^j$', fontsize=20) - show() diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 66e2eb5e83c5..5978886fa127 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1405,6 +1405,13 @@ def tk_window_focus(): def test(verbosity=1): """run the matplotlib test suite""" + try: + import faulthandler + except ImportError: + pass + else: + faulthandler.enable() + old_backend = rcParams['backend'] try: use('agg') diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9d93e22e0641..91dfc0478ae4 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2063,8 +2063,8 @@ def draw(self, renderer=None, inframe=False): for z, im in zorder_images] l, b, r, t = self.bbox.extents - width = mag * ((round(r) + 0.5) - (round(l) - 0.5)) - height = mag * ((round(t) + 0.5) - (round(b) - 0.5)) + width = int(mag * ((round(r) + 0.5) - (round(l) - 0.5))) + height = int(mag * ((round(t) + 0.5) - (round(b) - 0.5))) im = mimage.from_images(height, width, ims) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 4cb14a6fe130..c7a59be98ce8 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -127,15 +127,19 @@ def draw_path_collection(self, *kl, **kw): return self._renderer.draw_path_collection(*kl, **kw) def _update_methods(self): - #self.draw_path = self._renderer.draw_path # see below - #self.draw_markers = self._renderer.draw_markers - #self.draw_path_collection = self._renderer.draw_path_collection self.draw_quad_mesh = self._renderer.draw_quad_mesh self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles self.draw_image = self._renderer.draw_image self.copy_from_bbox = self._renderer.copy_from_bbox - self.tostring_rgba_minimized = self._renderer.tostring_rgba_minimized + self.get_content_extents = self._renderer.get_content_extents + + def tostring_rgba_minimized(self): + extents = self.get_content_extents() + bbox = [[extents[0], self.height - (extents[1] + extents[3])], + [extents[0] + extents[2], self.height - extents[1]]] + region = self.copy_from_bbox(bbox) + return np.array(region), extents def draw_path(self, gc, path, transform, rgbFace=None): """ @@ -203,7 +207,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): #print x, y, int(x), int(y), s self._renderer.draw_text_image( - font.get_image(), np.round(x - xd), np.round(y + yd) + 1, angle, gc) + font, np.round(x - xd), np.round(y + yd) + 1, angle, gc) def get_text_width_height_descent(self, s, prop, ismath): """ @@ -354,7 +358,7 @@ def restore_region(self, region, bbox=None, xy=None): else: ox, oy = xy - self._renderer.restore_region2(region, x1, y1, x2, y2, ox, oy) + self._renderer.restore_region(region, x1, y1, x2, y2, ox, oy) else: self._renderer.restore_region(region) @@ -394,7 +398,7 @@ def post_processing(image, dpi): width, height = int(self.width), int(self.height) - buffer, bounds = self._renderer.tostring_rgba_minimized() + buffer, bounds = self.tostring_rgba_minimized() l, b, w, h = bounds @@ -407,7 +411,6 @@ def post_processing(image, dpi): img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255., self.dpi) image = fromarray(img, 1) - image.flipud_out() gc = self.new_gc() self._renderer.draw_image(gc, @@ -505,12 +508,13 @@ def print_raw(self, filename_or_obj, *args, **kwargs): original_dpi = renderer.dpi renderer.dpi = self.figure.dpi if is_string_like(filename_or_obj): - filename_or_obj = open(filename_or_obj, 'wb') + fileobj = open(filename_or_obj, 'wb') close = True else: + fileobj = filename_or_obj close = False try: - renderer._renderer.write_rgba(filename_or_obj) + fileobj.write(renderer._renderer.buffer_rgba()) finally: if close: filename_or_obj.close() @@ -528,9 +532,7 @@ def print_png(self, filename_or_obj, *args, **kwargs): else: close = False try: - _png.write_png(renderer._renderer.buffer_rgba(), - renderer.width, renderer.height, - filename_or_obj, self.figure.dpi) + _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi) finally: if close: filename_or_obj.close() diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index 952b879d2c7e..4af8562a0940 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -167,8 +167,6 @@ def draw_image(self, gc, x, y, im): # bbox - not currently used if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) - im.flipud_out() - rows, cols, buf = im.color_conv (BYTE_FORMAT) surface = cairo.ImageSurface.create_for_data ( buf, cairo.FORMAT_ARGB32, cols, rows, cols*4) @@ -183,8 +181,6 @@ def draw_image(self, gc, x, y, im): ctx.paint() ctx.restore() - im.flipud_out() - def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # Note: x,y are device/display coords, not user-coords, unlike other # draw_* methods diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py index fc705febeb59..d3e8e5bb1202 100644 --- a/lib/matplotlib/backends/backend_gdk.py +++ b/lib/matplotlib/backends/backend_gdk.py @@ -109,7 +109,6 @@ def draw_image(self, gc, x, y, im): # int(w), int(h)) # set clip rect? - im.flipud_out() rows, cols, image_str = im.as_rgba_str() image_array = np.fromstring(image_str, np.uint8) @@ -120,7 +119,7 @@ def draw_image(self, gc, x, y, im): width=cols, height=rows) array = pixbuf_get_pixels_array(pixbuf) - array[:,:,:] = image_array + array[:,:,:] = image_array[::-1] gc = self.new_gc() @@ -138,9 +137,6 @@ def draw_image(self, gc, x, y, im): int(x), int(y), cols, rows, gdk.RGB_DITHER_NONE, 0, 0) - # unflip - im.flipud_out() - def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): x, y = int(x), int(y) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 371f687b1190..43434e374e1d 100755 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -110,10 +110,8 @@ def get_image_magnification(self): return self.gc.get_image_magnification() def draw_image(self, gc, x, y, im): - im.flipud_out() nrows, ncols, data = im.as_rgba_str() gc.draw_image(x, y, nrows, ncols, data) - im.flipud_out() def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None): # todo, handle props, angle, origins diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 85b3c42c5368..ee48f9274bbe 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -121,7 +121,6 @@ def stop_rasterizing(self): if w > 0 and h > 0: image = frombuffer(buffer, w, h, True) image.is_grayscale = False - image.flipud_out() gc = self._renderer.new_gc() # TODO: If the mixedmode resolution differs from the figure's # dpi, the image must be scaled (dpi->_figdpi). Not all diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 109f1ef92124..933e84b22988 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1241,6 +1241,7 @@ def _rgb(self, im): rgba = np.fromstring(s, np.uint8) rgba.shape = (h, w, 4) + rgba = rgba[::-1] rgb = rgba[:, :, :3] a = rgba[:, :, 3:] return h, w, rgb.tostring(), a.tostring() @@ -1249,6 +1250,7 @@ def _gray(self, im, rc=0.3, gc=0.59, bc=0.11): rgbat = im.as_rgba_str() rgba = np.fromstring(rgbat[2], np.uint8) rgba.shape = (rgbat[0], rgbat[1], 4) + rgba = rgba[::-1] rgba_f = rgba.astype(np.float32) r = rgba_f[:, :, 0] g = rgba_f[:, :, 1] @@ -1258,7 +1260,6 @@ def _gray(self, im, rc=0.3, gc=0.59, bc=0.11): def writeImages(self): for img, pair in six.iteritems(self.images): - img.flipud_out() if img.is_grayscale: height, width, data = self._gray(img) self.beginStream( @@ -1294,8 +1295,6 @@ def writeImages(self): self.currentstream.write(data) self.endStream() - img.flipud_out() - def markerObject(self, path, trans, fillp, strokep, lw, joinstyle, capstyle): """Return name of a marker XObject representing the given path.""" diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 6ae2867b00a9..e432a340d803 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -14,6 +14,8 @@ import weakref import warnings +import numpy as np + import matplotlib as mpl from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ FigureManagerBase, FigureCanvasBase @@ -619,9 +621,7 @@ def draw_image(self, gc, x, y, im): fname = os.path.splitext(os.path.basename(self.fh.name))[0] fname_img = "%s-img%d.png" % (fname, self.image_counter) self.image_counter += 1 - im.flipud_out() - rows, cols, buf = im.as_rgba_str() - _png.write_png(buf, cols, rows, os.path.join(path, fname_img)) + _png.write_png(np.array(im)[::-1], os.path.join(path, fname_img)) # reference the image in the pgf picture writeln(self.fh, r"\begin{pgfscope}") diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 0d14962ad85e..48c5bdd6cae9 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -414,13 +414,14 @@ def _rgb(self, im): rgba = np.fromstring(s, np.uint8) rgba.shape = (h, w, 4) - rgb = rgba[:,:,:3] + rgb = rgba[::-1,:,:3] return h, w, rgb.tostring() def _gray(self, im, rc=0.3, gc=0.59, bc=0.11): rgbat = im.as_rgba_str() rgba = np.fromstring(rgbat[2], np.uint8) rgba.shape = (rgbat[0], rgbat[1], 4) + rgba = rgba[::-1] rgba_f = rgba.astype(np.float32) r = rgba_f[:,:,0] g = rgba_f[:,:,1] @@ -472,8 +473,6 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None): interpreted as the coordinate of the transform. """ - im.flipud_out() - h, w, bits, imagecmd = self._get_image_h_w_bits_command(im) hexlines = b'\n'.join(self._hex_lines(bits)).decode('ascii') @@ -524,9 +523,6 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None): """ % locals() self._pswriter.write(ps) - # unflip - im.flipud_out() - def _convert_path(self, path, transform, clip=False, simplify=None): ps = [] last_points = None diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 89e4d5986fb8..5b4710142571 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -800,10 +800,7 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None): self.writer.start('a', attrib={'xlink:href': url}) if rcParams['svg.image_inline']: bytesio = io.BytesIO() - im.flipud_out() - rows, cols, buffer = im.as_rgba_str() - _png.write_png(buffer, cols, rows, bytesio) - im.flipud_out() + _png.write_png(np.array(im)[::-1], bytesio) oid = oid or self._make_id('image', bytesio) attrib['xlink:href'] = ( "data:image/png;base64,\n" + @@ -812,10 +809,7 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None): self._imaged[self.basename] = self._imaged.get(self.basename,0) + 1 filename = '%s.image%d.png'%(self.basename, self._imaged[self.basename]) verbose.report( 'Writing image file for inclusion: %s' % filename) - im.flipud_out() - rows, cols, buffer = im.as_rgba_str() - _png.write_png(buffer, cols, rows, filename) - im.flipud_out() + _png.write_png(np.array(im)[::-1], filename) oid = oid or 'Im_' + self._make_id('image', filename) attrib['xlink:href'] = filename diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 6cf0a592fc8c..4971b336671c 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -143,8 +143,7 @@ def get_diff_image(self): # TODO: We should write a new version of write_png that # handles the differencing inline _png.write_png( - output.tostring(), - output.shape[1], output.shape[0], + output, self._png_buffer) # Swap the renderer frames diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b334ca4edf88..5d75bb8f5bb0 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1045,8 +1045,8 @@ def draw(self, renderer): ims = [(im.make_image(mag), im.ox, im.oy, im.get_alpha()) for im in self.images] - im = _image.from_images(self.bbox.height * mag, - self.bbox.width * mag, + im = _image.from_images(int(self.bbox.height * mag), + int(self.bbox.width * mag), ims) im.is_grayscale = False diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index be7e94d5d36c..9dd9599a67f7 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -204,15 +204,19 @@ def _get_unsampled_image(self, A, image_extents, viewlim): self._oldyslice = yslice if self._imcache is None: - if self._A.dtype == np.uint8 and self._A.ndim == 3: - im = _image.frombyte(self._A[yslice, xslice, :], 0) + A = self._A + if self.origin == 'upper': + A = A[::-1] + + if A.dtype == np.uint8 and A.ndim == 3: + im = _image.frombyte(A[yslice, xslice, :], 0) im.is_grayscale = False else: if self._rgbacache is None: - x = self.to_rgba(self._A, bytes=False) + x = self.to_rgba(A, bytes=False) # Avoid side effects: to_rgba can return its argument # unchanged. - if np.may_share_memory(x, self._A): + if np.may_share_memory(x, A): x = x.copy() # premultiply the colors x[..., 0:3] *= x[..., 3:4] @@ -226,9 +230,6 @@ def _get_unsampled_image(self, A, image_extents, viewlim): else: im.is_grayscale = False self._imcache = im - - if self.origin == 'upper': - im.flipud_in() else: im = self._imcache @@ -413,9 +414,7 @@ def write_png(self, fname, noscale=False): im.reset_matrix() im.set_interpolation(0) im.resize(numcols, numrows) - im.flipud_out() - rows, cols, buffer = im.as_rgba_str() - _png.write_png(buffer, cols, rows, fname) + _png.write_png(im, fname) def set_data(self, A): """ @@ -732,7 +731,7 @@ def make_image(self, magnification=1.0): width *= magnification height *= magnification im = _image.pcolor(self._Ax, self._Ay, A, - height, width, + int(height), int(width), (x0, x0+v_width, y0, y0+v_height), self._interpd[self._interpolation]) @@ -1005,7 +1004,11 @@ def make_image(self, magnification=1.0): if self._A is None: raise RuntimeError('You must first set the image array') - x = self.to_rgba(self._A, bytes=True) + A = self._A + if self.origin == 'upper': + A = A[::-1] + + x = self.to_rgba(A, bytes=True) self.magnification = magnification # if magnification is not one, we need to resize ismag = magnification != 1 @@ -1018,7 +1021,7 @@ def make_image(self, magnification=1.0): fc = self.figure.get_facecolor() im.set_bg(*mcolors.colorConverter.to_rgba(fc, 0)) im.is_grayscale = (self.cmap.name == "gray" and - len(self._A.shape) == 2) + len(A.shape) == 2) if ismag: numrows, numcols = self.get_size() @@ -1026,8 +1029,6 @@ def make_image(self, magnification=1.0): numcols *= magnification im.set_interpolation(_image.NEAREST) im.resize(numcols, numrows) - if self.origin == 'upper': - im.flipud_out() return im @@ -1047,8 +1048,7 @@ def draw(self, renderer, *args, **kwargs): def write_png(self, fname): """Write the image to png file with fname""" im = self.make_image() - rows, cols, buffer = im.as_rgba_str() - _png.write_png(buffer, cols, rows, fname) + _png.write_png(im, fname) class BboxImage(_AxesImageBase): @@ -1131,24 +1131,24 @@ def make_image(self, renderer, magnification=1.0): 'array or the image attribute') if self._imcache is None: - if self._A.dtype == np.uint8 and len(self._A.shape) == 3: - im = _image.frombyte(self._A, 0) + A = self._A + if self.origin == 'upper': + A = A[::-1] + if A.dtype == np.uint8 and len(A.shape) == 3: + im = _image.frombyte(A, 0) im.is_grayscale = False else: if self._rgbacache is None: - x = self.to_rgba(self._A, bytes=True) + x = self.to_rgba(A, bytes=True) self._rgbacache = x else: x = self._rgbacache im = _image.frombyte(x, 0) - if len(self._A.shape) == 2: + if len(A.shape) == 2: im.is_grayscale = self.cmap.is_gray() else: im.is_grayscale = False self._imcache = im - - if self.origin == 'upper': - im.flipud_in() else: im = self._imcache diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index 388e209ec32f..ce0807376ec1 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -3092,8 +3092,7 @@ def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): image in pixels. """ rgba, depth = self.to_rgba(texstr, color=color, dpi=dpi, fontsize=fontsize) - numrows, numcols, tmp = rgba.shape - _png.write_png(rgba.tostring(), numcols, numrows, filename) + _png.write_png(rgba, filename) return depth def get_depth(self, texstr, dpi=120, fontsize=14): diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index ecce3e637324..e02f3c2f3c34 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -375,4 +375,4 @@ def save_diff_image(expected, actual, output): # Hard-code the alpha channel to fully solid save_image_np[:, :, 3] = 255 - _png.write_png(save_image_np.tostring(), width, height, output) + _png.write_png(save_image_np, output) diff --git a/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf b/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf index dcc3dc53d8cf7e156661e978120ae5c6c129f1c0..583715ea662ff6b8a844f885d74564ef2c8ae73a 100644 GIT binary patch delta 819 zcmdlXa!zE!M#g$yzas`bt>5jAS~p7X{+T&lS+O@EJfeW%NWYtGK;pr#g_puEFVoSo zE|aN$A6lSz&{{_C+h3;wwe*{!S{ENhBn5fjk+FNxJ8SaUKM_xTW+@!XU7ma4UiYn( zu03;9j;^wPw_8Nkz1wc*h0@Asr`S{6>N4Mc)a_2K|5bO?*zV$-dXwa?OySGZ3e{VV zM9y$7c+-D=U(Cfj-0$`oTxjUrb>iJM-#M&a(mVDlK6?})zBs*k{jukdk{;UUTyonz z;o+&XDT{RUJ*<><&*j%g%8ANHOuPCj{LH=0@h`phy;+}^zrv^R?%B`P<=xHI-J<-i zUFRJ5och_ddfPT6MQYEo_cu|wAg_I~;xaT;Fi-$7jLb|X zUt!Xktjesy$Tj&7liFlYW+#3_1w$jCf*=KbPa_3G|(Opp*O<9*>yxnA`Jz{5Ys}{MB#eP$$ z{iX(!8(B0VhKo$z&%(oG$u;=_v&iILHmS+KSuD)08X6h9PF!%><7T4!<+_KBg7fJH z28Js|hrO|wY6dmc*l=?SYc3O?iJ^g^v7v#1k&%h6fyw0i><)~klNC7PnT!o5H*yHu z0z&}=6!H|fzzhQeb5o$l(ZnoFG1Xa^8%@5*AQSERM0XG@SgB(~Z;El1o+9)!&T^ E03@>$_y7O^ delta 799 zcmX>nvO{FUM#g$?zas`bZQt!${U01Vw>q}3#iS`V@gSoBf3MYoLrt%fm#!%a>Wd-914@wO4WXjqlrZlcP7a8q0laN!*=U|Eun3`ky23>Q&lKuIde){qUTG z@l1pEi-8t#zl)B(oA>b@x4T4Po883SrN%QIzD@kYd$B7x+NV)upJaU3)kFVp@SMES zblrM$R?u(J?G7iILigUco5fq)ainx-i)aalNZ7tf;Z4up^G?so+ikZ0`}T?*=8+#3 zc1&bFp?LUlqt_&*H4(SGUR@AY?fJm2$kr0Wm714QTvC*pm9l*zR#Ry zjL1E#=zpw0_4^8~WeMy8vqSaX^9j13J9 zEDSA-4U7!64U8s#Wp`jSnQX=ppJrgGU;qLNc?w)$hJk^(u@R=2DbQDF>MTq#)mfNh zQDn4zHwrg_F@nC2N - @@ -45,8 +45,8 @@ L86.0824 7.2 z " style="fill:#ffffff;"/> - + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index aa9f9a7d703f..af8ddca37f4d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4,6 +4,8 @@ import six from six.moves import xrange +import io + from nose.tools import assert_equal, assert_raises import datetime @@ -3392,11 +3394,13 @@ def test_margins(): @cleanup def test_pathological_hexbin(): # issue #2863 + out = io.BytesIO() + with warnings.catch_warnings(record=True) as w: mylist = [10] * 100 fig, ax = plt.subplots(1, 1) ax.hexbin(mylist, mylist) - plt.show() + fig.savefig(out) assert_equal(len(w), 0) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 2589fa0e2ac8..00e89629e268 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -662,7 +662,7 @@ def count_overlaps(self, bboxes): bboxes is a sequence of :class:`BboxBase` objects """ - return count_bboxes_overlapping_bbox(self, bboxes) + return count_bboxes_overlapping_bbox(self, [np.array(x) for x in bboxes]) def expanded(self, sw, sh): """ @@ -1668,7 +1668,7 @@ def transform_affine(self, points): def transform_point(self, point): mtx = self.get_matrix() - return affine_transform(point, mtx) + return affine_transform([point], mtx)[0] transform_point.__doc__ = AffineBase.transform_point.__doc__ if DEBUG: diff --git a/lib/matplotlib/tri/_tri.cpp b/lib/matplotlib/tri/_tri.cpp index 39bd98ba3cbf..94dc2eb3e975 100644 --- a/lib/matplotlib/tri/_tri.cpp +++ b/lib/matplotlib/tri/_tri.cpp @@ -234,13 +234,13 @@ Triangulation::Triangulation(PyArrayObject* x, _edges(edges), _neighbors(neighbors) { - _VERBOSE("Triangulation::Triangulation"); + correct_triangles(); } Triangulation::~Triangulation() { - _VERBOSE("Triangulation::~Triangulation"); + Py_XDECREF(_x); Py_XDECREF(_y); Py_XDECREF(_triangles); @@ -251,7 +251,7 @@ Triangulation::~Triangulation() void Triangulation::calculate_boundaries() { - _VERBOSE("Triangulation::calculate_boundaries"); + get_neighbors(); // Ensure _neighbors has been created. @@ -309,7 +309,7 @@ void Triangulation::calculate_boundaries() void Triangulation::calculate_edges() { - _VERBOSE("Triangulation::calculate_edges"); + Py_XDECREF(_edges); // Create set of all edges, storing them with start point index less than @@ -338,7 +338,7 @@ void Triangulation::calculate_edges() void Triangulation::calculate_neighbors() { - _VERBOSE("Triangulation::calculate_neighbors"); + Py_XDECREF(_neighbors); // Create _neighbors array with shape (ntri,3) and initialise all to -1. @@ -382,7 +382,7 @@ void Triangulation::calculate_neighbors() Py::Object Triangulation::calculate_plane_coefficients(const Py::Tuple &args) { - _VERBOSE("Triangulation::calculate_plane_coefficients"); + args.verify_length(1); PyArrayObject* z = (PyArrayObject*)PyArray_ContiguousFromObject( @@ -485,7 +485,7 @@ void Triangulation::correct_triangles() const Triangulation::Boundaries& Triangulation::get_boundaries() const { - _VERBOSE("Triangulation::get_boundaries"); + if (_boundaries.empty()) const_cast(this)->calculate_boundaries(); return _boundaries; @@ -517,7 +517,7 @@ int Triangulation::get_edge_in_triangle(int tri, int point) const Py::Object Triangulation::get_edges() { - _VERBOSE("Triangulation::get_edges"); + if (_edges == 0) calculate_edges(); return Py::asObject(Py::new_reference_to((PyObject*)_edges)); @@ -544,7 +544,7 @@ TriEdge Triangulation::get_neighbor_edge(int tri, int edge) const Py::Object Triangulation::get_neighbors() { - _VERBOSE("Triangulation::get_neighbors"); + if (_neighbors == 0) calculate_neighbors(); return Py::asObject(Py::new_reference_to((PyObject*)_neighbors)); } @@ -592,7 +592,7 @@ const int* Triangulation::get_triangles_ptr() const void Triangulation::init_type() { - _VERBOSE("Triangulation::init_type"); + behaviors().name("Triangulation"); behaviors().doc("Triangulation"); @@ -616,7 +616,7 @@ bool Triangulation::is_masked(int tri) const Py::Object Triangulation::set_mask(const Py::Tuple &args) { - _VERBOSE("Triangulation::set_mask"); + args.verify_length(1); Py_XDECREF(_mask); @@ -667,12 +667,12 @@ TriContourGenerator::TriContourGenerator(Py::Object triangulation, _boundaries_visited(0), _boundaries_used(0) { - _VERBOSE("TriContourGenerator::TriContourGenerator"); + } TriContourGenerator::~TriContourGenerator() { - _VERBOSE("TriContourGenerator::~TriContourGenerator"); + Py_XDECREF(_z); } @@ -761,7 +761,7 @@ Py::Object TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour Py::Object TriContourGenerator::create_contour(const Py::Tuple &args) { - _VERBOSE("TriContourGenerator::create_contour"); + args.verify_length(1); double level = (Py::Float)args[0]; @@ -777,7 +777,7 @@ Py::Object TriContourGenerator::create_contour(const Py::Tuple &args) Py::Object TriContourGenerator::create_filled_contour(const Py::Tuple &args) { - _VERBOSE("TriContourGenerator::create_filled_contour"); + args.verify_length(2); double lower_level = (Py::Float)args[0]; @@ -1089,7 +1089,7 @@ const double& TriContourGenerator::get_z(int point) const void TriContourGenerator::init_type() { - _VERBOSE("TriContourGenerator::init_type"); + behaviors().name("TriContourGenerator"); behaviors().doc("TriContourGenerator"); @@ -1124,12 +1124,12 @@ TrapezoidMapTriFinder::TrapezoidMapTriFinder(Py::Object triangulation) _points(0), _tree(0) { - _VERBOSE("TrapezoidMapTriFinder::TrapezoidMapTriFinder"); + } TrapezoidMapTriFinder::~TrapezoidMapTriFinder() { - _VERBOSE("TrapezoidMapTriFinder::~TrapezoidMapTriFinder"); + clear(); } @@ -1452,7 +1452,7 @@ TrapezoidMapTriFinder::find_trapezoids_intersecting_edge( Py::Object TrapezoidMapTriFinder::get_tree_stats() { - _VERBOSE("TrapezoidMapTriFinder::get_tree_stats"); + NodeStats stats; _tree->get_stats(0, stats); @@ -1477,7 +1477,7 @@ TrapezoidMapTriFinder::get_triangulation() const void TrapezoidMapTriFinder::init_type() { - _VERBOSE("TrapezoidMapTriFinder::init_type"); + behaviors().name("TrapezoidMapTriFinder"); behaviors().doc("TrapezoidMapTriFinder"); @@ -1499,7 +1499,7 @@ TrapezoidMapTriFinder::init_type() Py::Object TrapezoidMapTriFinder::initialize() { - _VERBOSE("TrapezoidMapTriFinder::initialize"); + clear(); const Triangulation& triang = get_triangulation(); @@ -1594,7 +1594,7 @@ TrapezoidMapTriFinder::initialize() Py::Object TrapezoidMapTriFinder::print_tree() { - _VERBOSE("TrapezoidMapTriFinder::print_tree"); + assert(_tree != 0 && "Null Node tree"); _tree->print(); @@ -2229,7 +2229,7 @@ TriModule::TriModule() Py::Object TriModule::new_triangulation(const Py::Tuple &args) { - _VERBOSE("TriModule::new_triangulation"); + args.verify_length(6); // x and y. @@ -2310,7 +2310,7 @@ Py::Object TriModule::new_triangulation(const Py::Tuple &args) Py::Object TriModule::new_tricontourgenerator(const Py::Tuple &args) { - _VERBOSE("TriModule::new_tricontourgenerator"); + args.verify_length(2); Py::Object tri = args[0]; @@ -2332,7 +2332,7 @@ Py::Object TriModule::new_tricontourgenerator(const Py::Tuple &args) Py::Object TriModule::new_TrapezoidMapTriFinder(const Py::Tuple &args) { - _VERBOSE("TriModule::new_TrapezoidMapTriFinder"); + args.verify_length(1); Py::Object triangulation = args[0]; diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index c9bac1ae95bd..12516fc715ff 100755 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -260,8 +260,8 @@ def draw(self, renderer): # edge points of the plane to display coordinates and calculate # an angle from that. # TODO: Maybe Text objects should handle this themselves? - dx, dy = (self.axes.transAxes.transform(peparray[0:2, 1]) - - self.axes.transAxes.transform(peparray[0:2, 0])) + dx, dy = (self.axes.transAxes.transform([peparray[0:2, 1]]) - + self.axes.transAxes.transform([peparray[0:2, 0]]))[0] lxyz = 0.5*(edgep1 + edgep2) diff --git a/setupext.py b/setupext.py index ba78c6726dce..27a8b8bf5ec3 100755 --- a/setupext.py +++ b/setupext.py @@ -1005,12 +1005,12 @@ class FT2Font(SetupPackage): def get_extension(self): sources = [ 'src/ft2font.cpp', + 'src/ft2font_wrapper.cpp', 'src/mplutils.cpp' ] ext = make_extension('matplotlib.ft2font', sources) FreeType().add_flags(ext) Numpy().add_flags(ext) - CXX().add_flags(ext) return ext @@ -1039,14 +1039,14 @@ def check(self): def get_extension(self): sources = [ - 'src/_png.cpp', 'src/mplutils.cpp' + 'src/_png.cpp', + 'src/mplutils.cpp' ] ext = make_extension('matplotlib._png', sources) pkg_config.setup_extension( ext, 'libpng', default_libraries=['png', 'z'], alt_exec='libpng-config --ldflags') Numpy().add_flags(ext) - CXX().add_flags(ext) return ext @@ -1092,7 +1092,6 @@ def get_extension(self): ] ext = make_extension('matplotlib.ttconv', sources) Numpy().add_flags(ext) - CXX().add_flags(ext) ext.include_dirs.append('extern') return ext @@ -1102,15 +1101,13 @@ class Path(SetupPackage): def get_extension(self): sources = [ - 'src/_path.cpp', - 'src/path_cleanup.cpp', - 'src/agg_py_transforms.cpp' + 'src/py_converters.cpp', + 'src/_path_wrapper.cpp' ] ext = make_extension('matplotlib._path', sources) Numpy().add_flags(ext) LibAgg().add_flags(ext) - CXX().add_flags(ext) return ext @@ -1119,12 +1116,13 @@ class Image(SetupPackage): def get_extension(self): sources = [ - 'src/_image.cpp', 'src/mplutils.cpp' + 'src/_image.cpp', + 'src/mplutils.cpp', + 'src/_image_wrapper.cpp' ] ext = make_extension('matplotlib._image', sources) Numpy().add_flags(ext) LibAgg().add_flags(ext) - CXX().add_flags(ext) return ext @@ -1317,14 +1315,14 @@ class BackendAgg(OptionalBackendPackage): def get_extension(self): sources = [ "src/mplutils.cpp", - "src/agg_py_transforms.cpp", - "src/_backend_agg.cpp" + "src/py_converters.cpp", + "src/_backend_agg.cpp", + "src/_backend_agg_wrapper.cpp" ] ext = make_extension('matplotlib.backends._backend_agg', sources) Numpy().add_flags(ext) LibAgg().add_flags(ext) FreeType().add_flags(ext) - CXX().add_flags(ext) return ext @@ -1363,7 +1361,7 @@ def check_requirements(self): def get_extension(self): sources = [ - 'src/agg_py_transforms.cpp', + 'src/py_converters.cpp', 'src/_tkagg.cpp' ] @@ -1371,7 +1369,6 @@ def get_extension(self): self.add_flags(ext) Numpy().add_flags(ext) LibAgg().add_flags(ext) - CXX().add_flags(ext) return ext def query_tcltk(self): @@ -1740,14 +1737,13 @@ def get_package_data(self): def get_extension(self): sources = [ - 'src/agg_py_transforms.cpp', + 'src/py_converters.cpp', 'src/_gtkagg.cpp', 'src/mplutils.cpp' ] ext = make_extension('matplotlib.backends._gtkagg', sources) self.add_flags(ext) LibAgg().add_flags(ext) - CXX().add_flags(ext) Numpy().add_flags(ext) return ext @@ -1919,14 +1915,13 @@ def check_requirements(self): def get_extension(self): sources = [ 'src/_macosx.m', - 'src/agg_py_transforms.cpp', + 'src/py_converters.cpp', 'src/path_cleanup.cpp' ] ext = make_extension('matplotlib.backends._macosx', sources) Numpy().add_flags(ext) LibAgg().add_flags(ext) - CXX().add_flags(ext) ext.extra_link_args.extend(['-framework', 'Cocoa']) return ext diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 9d12c860f28b..2590309ec678 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -3,174 +3,23 @@ /* A rewrite of _backend_agg using PyCXX to handle ref counting, etc.. */ -/* Python API mandates Python.h is included *first* */ -#include "Python.h" +#define NO_IMPORT_ARRAY -/* TODO: Remove this dependency */ -#include "ft2font.h" -#include "_image.h" #include "_backend_agg.h" #include "mplutils.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "agg_conv_curve.h" -#include "agg_conv_transform.h" -#include "agg_image_accessors.h" -#include "agg_renderer_primitives.h" -#include "agg_scanline_storage_aa.h" -#include "agg_scanline_storage_bin.h" -#include "agg_span_allocator.h" -#include "agg_span_converter.h" -#include "agg_span_image_filter_gray.h" -#include "agg_span_image_filter_rgba.h" -#include "agg_span_interpolator_linear.h" -#include "agg_span_pattern_rgba.h" -#include "agg_span_gouraud_rgba.h" -#include "agg_conv_shorten_path.h" -#include "util/agg_color_conv_rgb8.h" - #include "MPL_isnan.h" -#include "numpy/arrayobject.h" -#include "agg_py_transforms.h" - -#include "file_compat.h" - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif -#ifndef M_PI_4 -#define M_PI_4 0.785398163397448309616 -#endif -#ifndef M_PI_2 -#define M_PI_2 1.57079632679489661923 -#endif - - -/* - Convert dashes from the Python representation as nested sequences to - the C++ representation as a std::vector > - (GCAgg::dash_t) */ -void -convert_dashes(const Py::Tuple& dashes, double dpi, - GCAgg::dash_t& dashes_out, double& dashOffset_out) -{ - if (dashes.length() != 2) - { - throw Py::ValueError( - Printf("Dash descriptor must be a length 2 tuple; found %d", - dashes.length()).str() - ); - } - - dashes_out.clear(); - dashOffset_out = 0.0; - if (dashes[0].ptr() == Py_None) - { - return; - } - - dashOffset_out = double(Py::Float(dashes[0])) * dpi / 72.0; - - Py::SeqBase dashSeq = dashes[1]; - - size_t Ndash = dashSeq.length(); - if (Ndash % 2 != 0) - { - throw Py::ValueError( - Printf("Dash sequence must be an even length sequence; found %d", Ndash).str() - ); - } - - dashes_out.clear(); - dashes_out.reserve(Ndash / 2); - - double val0, val1; - for (size_t i = 0; i < Ndash; i += 2) - { - val0 = double(Py::Float(dashSeq[i])) * dpi / 72.0; - val1 = double(Py::Float(dashSeq[i+1])) * dpi / 72.0; - dashes_out.push_back(std::make_pair(val0, val1)); - } -} - - -Py::Object -BufferRegion::to_string(const Py::Tuple &args) -{ - // owned=true to prevent memory leak - #if PY3K - return Py::Bytes - #else - return Py::String - #endif - (PyBytes_FromStringAndSize((const char*)data, height*stride), true); -} - - -Py::Object -BufferRegion::set_x(const Py::Tuple &args) -{ - args.verify_length(1); - size_t x = (long) Py::Int(args[0]); - rect.x1 = x; - return Py::Object(); -} - - -Py::Object -BufferRegion::set_y(const Py::Tuple &args) -{ - args.verify_length(1); - size_t y = (long)Py::Int(args[0]); - rect.y1 = y; - return Py::Object(); -} - - -Py::Object -BufferRegion::get_extents(const Py::Tuple &args) -{ - args.verify_length(0); - - Py::Tuple extents(4); - extents[0] = Py::Int(rect.x1); - extents[1] = Py::Int(rect.y1); - extents[2] = Py::Int(rect.x2); - extents[3] = Py::Int(rect.y2); - - return extents; -} - - -Py::Object -BufferRegion::to_string_argb(const Py::Tuple &args) +void BufferRegion::to_string_argb(uint8_t *buf) { - // owned=true to prevent memory leak - Py_ssize_t length; - unsigned char* pix; - unsigned char* begin; + unsigned char *pix; unsigned char tmp; size_t i, j; - PyObject* str = PyBytes_FromStringAndSize((const char*)data, height * stride); - if (PyBytes_AsStringAndSize(str, (char**)&begin, &length)) - { - throw Py::TypeError("Could not create memory for blit"); - } + memcpy(buf, data, height * stride); - for (i = 0; i < (size_t)height; ++i) - { - pix = begin + i * stride; - for (j = 0; j < (size_t)width; ++j) - { + for (i = 0; i < (size_t)height; ++i) { + pix = buf + i * stride; + for (j = 0; j < (size_t)width; ++j) { // Convert rgba to argb tmp = pix[2]; pix[2] = pix[0]; @@ -178,253 +27,33 @@ BufferRegion::to_string_argb(const Py::Tuple &args) pix += 4; } } - - #if PY3K - return Py::Bytes - #else - return Py::String - #endif - (str, true); -} - - -GCAgg::GCAgg(const Py::Object &gc, double dpi) : - dpi(dpi), isaa(true), dashOffset(0.0) -{ - _VERBOSE("GCAgg::GCAgg"); - linewidth = points_to_pixels(gc.getAttr("_linewidth")) ; - alpha = Py::Float(gc.getAttr("_alpha")); - forced_alpha = Py::Boolean(gc.getAttr("_forced_alpha")); - color = get_color(gc); - _set_antialiased(gc); - _set_linecap(gc); - _set_joinstyle(gc); - _set_dashes(gc); - _set_clip_rectangle(gc); - _set_clip_path(gc); - _set_snap(gc); - _set_hatch_path(gc); - _set_sketch_params(gc); -} - - -void -GCAgg::_set_antialiased(const Py::Object& gc) -{ - _VERBOSE("GCAgg::antialiased"); - isaa = Py::Boolean(gc.getAttr("_antialiased")); -} - - -agg::rgba -GCAgg::get_color(const Py::Object& gc) -{ - _VERBOSE("GCAgg::get_color"); - Py::Tuple rgb = Py::Tuple(gc.getAttr("_rgb")); - - double r = Py::Float(rgb[0]); - double g = Py::Float(rgb[1]); - double b = Py::Float(rgb[2]); - double a = Py::Float(rgb[3]); - return agg::rgba(r, g, b, a); -} - - -double -GCAgg::points_to_pixels(const Py::Object& points) -{ - _VERBOSE("GCAgg::points_to_pixels"); - double p = Py::Float(points) ; - return p * dpi / 72.0; -} - - -void -GCAgg::_set_linecap(const Py::Object& gc) -{ - _VERBOSE("GCAgg::_set_linecap"); - - std::string capstyle = Py::String(gc.getAttr("_capstyle")).encode("utf-8"); - - if (capstyle == "butt") - { - cap = agg::butt_cap; - } - else if (capstyle == "round") - { - cap = agg::round_cap; - } - else if (capstyle == "projecting") - { - cap = agg::square_cap; - } - else - { - throw Py::ValueError(Printf("GC _capstyle attribute must be one of butt, round, projecting; found %s", capstyle.c_str()).str()); - } -} - - -void -GCAgg::_set_joinstyle(const Py::Object& gc) -{ - _VERBOSE("GCAgg::_set_joinstyle"); - - std::string joinstyle = Py::String(gc.getAttr("_joinstyle")).encode("utf-8"); - - if (joinstyle == "miter") - { - join = agg::miter_join_revert; - } - else if (joinstyle == "round") - { - join = agg::round_join; - } - else if (joinstyle == "bevel") - { - join = agg::bevel_join; - } - else - { - throw Py::ValueError(Printf("GC _joinstyle attribute must be one of butt, round, projecting; found %s", joinstyle.c_str()).str()); - } -} - - -void -GCAgg::_set_dashes(const Py::Object& gc) -{ - //return the dashOffset, dashes sequence tuple. - _VERBOSE("GCAgg::_set_dashes"); - - Py::Object dash_obj(gc.getAttr("_dashes")); - if (dash_obj.ptr() == Py_None) - { - dashes.clear(); - return; - } - - convert_dashes(dash_obj, dpi, dashes, dashOffset); -} - - -void -GCAgg::_set_clip_rectangle(const Py::Object& gc) -{ - //set the clip rectangle from the gc - - _VERBOSE("GCAgg::_set_clip_rectangle"); - - Py::Object o(gc.getAttr("_cliprect")); - cliprect = o; -} - - -void -GCAgg::_set_clip_path(const Py::Object& gc) -{ - //set the clip path from the gc - - _VERBOSE("GCAgg::_set_clip_path"); - - Py::Object method_obj = gc.getAttr("get_clip_path"); - Py::Callable method(method_obj); - Py::Tuple path_and_transform = method.apply(Py::Tuple()); - if (path_and_transform[0].ptr() != Py_None) - { - clippath = path_and_transform[0]; - clippath_trans = py_to_agg_transformation_matrix(path_and_transform[1].ptr()); - } -} - - -void -GCAgg::_set_snap(const Py::Object& gc) -{ - //set the snap setting - - _VERBOSE("GCAgg::_set_snap"); - - Py::Object method_obj = gc.getAttr("get_snap"); - Py::Callable method(method_obj); - Py::Object py_snap = method.apply(Py::Tuple()); - if (py_snap.isNone()) - { - snap_mode = SNAP_AUTO; - } - else if (py_snap.isTrue()) - { - snap_mode = SNAP_TRUE; - } - else - { - snap_mode = SNAP_FALSE; - } -} - - -void -GCAgg::_set_hatch_path(const Py::Object& gc) -{ - _VERBOSE("GCAgg::_set_hatch_path"); - - Py::Object method_obj = gc.getAttr("get_hatch_path"); - Py::Callable method(method_obj); - hatchpath = method.apply(Py::Tuple()); - if (hatchpath.ptr() == NULL) - throw Py::Exception(); -} - -void -GCAgg::_set_sketch_params(const Py::Object& gc) -{ - _VERBOSE("GCAgg::_get_sketch_params"); - - Py::Object method_obj = gc.getAttr("get_sketch_params"); - Py::Callable method(method_obj); - Py::Object result = method.apply(Py::Tuple()); - if (result.ptr() == Py_None) { - sketch_scale = 0.0; - } else { - Py::Tuple sketch_params(result); - sketch_scale = Py::Float(sketch_params[0]); - sketch_length = Py::Float(sketch_params[1]); - sketch_randomness = Py::Float(sketch_params[2]); - } } - -const size_t -RendererAgg::PIXELS_PER_INCH(96); - - -RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi, - int debug) : - width(width), - height(height), - dpi(dpi), - NUMBYTES(width*height*4), - pixBuffer(NULL), - renderingBuffer(), - alphaBuffer(NULL), - alphaMaskRenderingBuffer(), - alphaMask(alphaMaskRenderingBuffer), - pixfmtAlphaMask(alphaMaskRenderingBuffer), - rendererBaseAlphaMask(), - rendererAlphaMask(), - scanlineAlphaMask(), - slineP8(), - slineBin(), - pixFmt(), - rendererBase(), - rendererAA(), - rendererBin(), - theRasterizer(), - debug(debug), - _fill_color(agg::rgba(1, 1, 1, 0)) -{ - _VERBOSE("RendererAgg::RendererAgg"); - unsigned stride(width*4); +RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) + : width(width), + height(height), + dpi(dpi), + NUMBYTES(width * height * 4), + pixBuffer(NULL), + renderingBuffer(), + alphaBuffer(NULL), + alphaMaskRenderingBuffer(), + alphaMask(alphaMaskRenderingBuffer), + pixfmtAlphaMask(alphaMaskRenderingBuffer), + rendererBaseAlphaMask(), + rendererAlphaMask(), + scanlineAlphaMask(), + slineP8(), + slineBin(), + pixFmt(), + rendererBase(), + rendererAA(), + rendererBin(), + theRasterizer(), + lastclippath(NULL), + _fill_color(agg::rgba(1, 1, 1, 0)) +{ + unsigned stride(width * 4); pixBuffer = new agg::int8u[NUMBYTES]; renderingBuffer.attach(pixBuffer, width, height, stride); @@ -433,16 +62,18 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi, rendererBase.clear(_fill_color); rendererAA.attach(rendererBase); rendererBin.attach(rendererBase); - hatchRenderingBuffer.attach(hatchBuffer, HATCH_SIZE, HATCH_SIZE, - HATCH_SIZE*4); + hatchRenderingBuffer.attach(hatchBuffer, HATCH_SIZE, HATCH_SIZE, HATCH_SIZE * 4); } +RendererAgg::~RendererAgg() +{ + delete[] alphaBuffer; + delete[] pixBuffer; +} -void -RendererAgg::create_alpha_buffers() +void RendererAgg::create_alpha_buffers() { - if (!alphaBuffer) - { + if (!alphaBuffer) { alphaBuffer = new agg::int8u[width * height]; alphaMaskRenderingBuffer.attach(alphaBuffer, width, height, width); rendererBaseAlphaMask.attach(pixfmtAlphaMask); @@ -450,2184 +81,150 @@ RendererAgg::create_alpha_buffers() } } - -template -void -RendererAgg::set_clipbox(const Py::Object& cliprect, R& rasterizer) -{ - //set the clip rectangle from the gc - - _VERBOSE("RendererAgg::set_clipbox"); - - double l, b, r, t; - if (py_convert_bbox(cliprect.ptr(), l, b, r, t)) - { - rasterizer.clip_box(std::max(int(floor(l + 0.5)), 0), - std::max(int(floor(height - b + 0.5)), 0), - std::min(int(floor(r + 0.5)), int(width)), - std::min(int(floor(height - t + 0.5)), int(height))); - } - else - { - rasterizer.clip_box(0, 0, width, height); - } - - _VERBOSE("RendererAgg::set_clipbox done"); -} - - -std::pair -RendererAgg::_get_rgba_face(const Py::Object& rgbFace, double alpha, bool forced_alpha) -{ - _VERBOSE("RendererAgg::_get_rgba_face"); - std::pair face; - - if (rgbFace.ptr() == Py_None) - { - face.first = false; - } - else - { - face.first = true; - Py::Tuple rgb = Py::Tuple(rgbFace); - if (forced_alpha || rgb.length() < 4) - { - face.second = rgb_to_color(rgb, alpha); - } - else - { - face.second = rgb_to_color(rgb, Py::Float(rgb[3])); - } - } - return face; -} - - -Py::Object -RendererAgg::copy_from_bbox(const Py::Tuple& args) +BufferRegion *RendererAgg::copy_from_bbox(agg::rect_d in_rect) { - //copy region in bbox to buffer and return swig/agg buffer object - args.verify_length(1); - - Py::Object box_obj = args[0]; - double l, b, r, t; - if (!py_convert_bbox(box_obj.ptr(), l, b, r, t)) - { - throw Py::TypeError("Invalid bbox provided to copy_from_bbox"); - } - - agg::rect_i rect((int)l, height - (int)t, (int)r, height - (int)b); + agg::rect_i rect( + (int)in_rect.x1, height - (int)in_rect.y2, (int)in_rect.x2, height - (int)in_rect.y1); - BufferRegion* reg = NULL; - try - { - reg = new BufferRegion(rect, true); - } - catch (...) - { - throw Py::MemoryError( - "RendererAgg::copy_from_bbox could not allocate memory for buffer"); - } + BufferRegion *reg = NULL; + reg = new BufferRegion(rect); - if (!reg) - { - throw Py::MemoryError( - "RendererAgg::copy_from_bbox could not allocate memory for buffer"); - } + agg::rendering_buffer rbuf; + rbuf.attach(reg->get_data(), reg->get_width(), reg->get_height(), reg->get_stride()); - try - { - agg::rendering_buffer rbuf; - rbuf.attach(reg->data, reg->width, reg->height, reg->stride); + pixfmt pf(rbuf); + renderer_base rb(pf); + rb.copy_from(renderingBuffer, &rect, -rect.x1, -rect.y1); - pixfmt pf(rbuf); - renderer_base rb(pf); - rb.copy_from(renderingBuffer, &rect, -rect.x1, -rect.y1); - } - catch (...) - { - delete reg; - throw Py::RuntimeError("An unknown error occurred in copy_from_bbox"); - } - return Py::asObject(reg); + return reg; } - -Py::Object -RendererAgg::restore_region(const Py::Tuple& args) +void RendererAgg::restore_region(BufferRegion ®ion) { - //copy BufferRegion to buffer - args.verify_length(1); - BufferRegion* region = static_cast(args[0].ptr()); - - if (region->data == NULL) - { - throw Py::ValueError("Cannot restore_region from NULL data"); + if (region.get_data() == NULL) { + throw "Cannot restore_region from NULL data"; } agg::rendering_buffer rbuf; - rbuf.attach(region->data, - region->width, - region->height, - region->stride); + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); - rendererBase.copy_from(rbuf, 0, region->rect.x1, region->rect.y1); - - return Py::Object(); + rendererBase.copy_from(rbuf, 0, region.get_rect().x1, region.get_rect().y1); } - // Restore the part of the saved region with offsets -Py::Object -RendererAgg::restore_region2(const Py::Tuple& args) +void +RendererAgg::restore_region(BufferRegion ®ion, int x, int y, int xx1, int yy1, int xx2, int yy2) { - //copy BufferRegion to buffer - args.verify_length(7); - - int x(0), y(0), xx1(0), yy1(0), xx2(0), yy2(0); - try - { - xx1 = Py::Int(args[1]); - yy1 = Py::Int(args[2]); - xx2 = Py::Int(args[3]); - yy2 = Py::Int(args[4]); - x = Py::Int(args[5]); - y = Py::Int(args[6]); + if (region.get_data() == NULL) { + throw "Cannot restore_region from NULL data"; } - catch (Py::TypeError) - { - throw Py::TypeError("Invalid input arguments to restore_region2"); - } - - BufferRegion* region = static_cast(args[0].ptr()); + agg::rect_i &rrect = region.get_rect(); - if (region->data == NULL) - { - throw Py::ValueError("Cannot restore_region from NULL data"); - } - - agg::rect_i rect(xx1 - region->rect.x1, (yy1 - region->rect.y1), - xx2 - region->rect.x1, (yy2 - region->rect.y1)); + agg::rect_i rect(xx1 - rrect.x1, (yy1 - rrect.y1), xx2 - rrect.x1, (yy2 - rrect.y1)); agg::rendering_buffer rbuf; - rbuf.attach(region->data, - region->width, - region->height, - region->stride); + rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride()); rendererBase.copy_from(rbuf, &rect, x, y); - - return Py::Object(); } - -bool -RendererAgg::render_clippath(const Py::Object& clippath, - const agg::trans_affine& clippath_trans) +bool RendererAgg::render_clippath(py::PathIterator &clippath, + const agg::trans_affine &clippath_trans) { - typedef agg::conv_transform transformed_path_t; + typedef agg::conv_transform transformed_path_t; typedef agg::conv_curve curve_t; - bool has_clippath = (clippath.ptr() != Py_None); + bool has_clippath = (clippath.total_vertices() != 0); if (has_clippath && - (clippath.ptr() != lastclippath.ptr() || - clippath_trans != lastclippath_transform)) - { + (clippath.get_id() != lastclippath || clippath_trans != lastclippath_transform)) { create_alpha_buffers(); agg::trans_affine trans(clippath_trans); trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); - PathIterator clippath_iter(clippath); rendererBaseAlphaMask.clear(agg::gray8(0, 0)); - transformed_path_t transformed_clippath(clippath_iter, trans); + transformed_path_t transformed_clippath(clippath, trans); curve_t curved_clippath(transformed_clippath); - try { - theRasterizer.add_path(curved_clippath); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } + theRasterizer.add_path(curved_clippath); rendererAlphaMask.color(agg::gray8(255, 255)); agg::render_scanlines(theRasterizer, scanlineAlphaMask, rendererAlphaMask); - lastclippath = clippath; + lastclippath = clippath.get_id(); lastclippath_transform = clippath_trans; } return has_clippath; } -#define MARKER_CACHE_SIZE 512 - - -Py::Object -RendererAgg::draw_markers(const Py::Tuple& args) +void RendererAgg::tostring_rgb(uint8_t *buf) { - typedef agg::conv_transform transformed_path_t; - typedef PathSnapper snap_t; - typedef agg::conv_curve curve_t; - typedef agg::conv_stroke stroke_t; - typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; - typedef agg::renderer_base amask_ren_type; - typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; - args.verify_length(5, 6); - - Py::Object gc_obj = args[0]; - Py::Object marker_path_obj = args[1]; - agg::trans_affine marker_trans = py_to_agg_transformation_matrix(args[2].ptr()); - Py::Object path_obj = args[3]; - agg::trans_affine trans = py_to_agg_transformation_matrix(args[4].ptr()); - Py::Object face_obj; - if (args.size() == 6) - { - face_obj = args[5]; - } - - GCAgg gc(gc_obj, dpi); - - // Deal with the difference in y-axis direction - marker_trans *= agg::trans_affine_scaling(1.0, -1.0); - trans *= agg::trans_affine_scaling(1.0, -1.0); - trans *= agg::trans_affine_translation(0.5, (double)height + 0.5); - - PathIterator marker_path(marker_path_obj); - transformed_path_t marker_path_transformed(marker_path, marker_trans); - snap_t marker_path_snapped(marker_path_transformed, - gc.snap_mode, - marker_path.total_vertices(), - gc.linewidth); - curve_t marker_path_curve(marker_path_snapped); - - PathIterator path(path_obj); - transformed_path_t path_transformed(path, trans); - snap_t path_snapped(path_transformed, - SNAP_FALSE, - path.total_vertices(), - 0.0); - curve_t path_curve(path_snapped); - path_curve.rewind(0); - - facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); - - //maxim's suggestions for cached scanlines - agg::scanline_storage_aa8 scanlines; - theRasterizer.reset(); - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - - agg::int8u staticFillCache[MARKER_CACHE_SIZE]; - agg::int8u staticStrokeCache[MARKER_CACHE_SIZE]; - agg::int8u* fillCache = staticFillCache; - agg::int8u* strokeCache = staticStrokeCache; - - try - { - unsigned fillSize = 0; - if (face.first) - { - try { - theRasterizer.add_path(marker_path_curve); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, scanlines); - fillSize = scanlines.byte_size(); - if (fillSize >= MARKER_CACHE_SIZE) - { - fillCache = new agg::int8u[fillSize]; - } - scanlines.serialize(fillCache); - } + // "Return the rendered buffer as an RGB string" - stroke_t stroke(marker_path_curve); - stroke.width(gc.linewidth); - stroke.line_cap(gc.cap); - stroke.line_join(gc.join); - theRasterizer.reset(); - try { - theRasterizer.add_path(stroke); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, scanlines); - unsigned strokeSize = scanlines.byte_size(); - if (strokeSize >= MARKER_CACHE_SIZE) - { - strokeCache = new agg::int8u[strokeSize]; - } - scanlines.serialize(strokeCache); - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, rendererBase); - bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); - - double x, y; - - agg::serialized_scanlines_adaptor_aa8 sa; - agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl; - - agg::rect_d clipping_rect( - -1.0 - scanlines.max_x(), - -1.0 - scanlines.max_y(), - 1.0 + width - scanlines.min_x(), - 1.0 + height - scanlines.min_y()); - - if (has_clippath) - { - while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) - { - if (MPL_notisfinite64(x) || MPL_notisfinite64(y)) - { - continue; - } - - /* These values are correctly snapped above -- so we don't want - to round here, we really only want to truncate */ - x = floor(x); - y = floor(y); - - // Cull points outside the boundary of the image. - // Values that are too large may overflow and create - // segfaults. - // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 - if (!clipping_rect.hit_test(x, y)) - { - continue; - } - - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_aa_renderer_type ren(r); - - if (face.first) - { - ren.color(face.second); - sa.init(fillCache, fillSize, x, y); - agg::render_scanlines(sa, sl, ren); - } - ren.color(gc.color); - sa.init(strokeCache, strokeSize, x, y); - agg::render_scanlines(sa, sl, ren); - } - } - else - { - while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) - { - if (MPL_notisfinite64(x) || MPL_notisfinite64(y)) - { - continue; - } - - /* These values are correctly snapped above -- so we don't want - to round here, we really only want to truncate */ - x = floor(x); - y = floor(y); - - // Cull points outside the boundary of the image. - // Values that are too large may overflow and create - // segfaults. - // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 - if (!clipping_rect.hit_test(x, y)) - { - continue; - } - - if (face.first) - { - rendererAA.color(face.second); - sa.init(fillCache, fillSize, x, y); - agg::render_scanlines(sa, sl, rendererAA); - } - - rendererAA.color(gc.color); - sa.init(strokeCache, strokeSize, x, y); - agg::render_scanlines(sa, sl, rendererAA); - } - } - } - catch (...) - { - if (fillCache != staticFillCache) - delete[] fillCache; - if (strokeCache != staticStrokeCache) - delete[] strokeCache; - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - throw; - } - - if (fillCache != staticFillCache) - delete[] fillCache; - if (strokeCache != staticStrokeCache) - delete[] strokeCache; + int row_len = width * 3; - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); + agg::rendering_buffer renderingBufferTmp; + renderingBufferTmp.attach(buf, width, height, row_len); - return Py::Object(); + agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_rgb24()); } - -/** - * This is a custom span generator that converts spans in the - * 8-bit inverted greyscale font buffer to rgba that agg can use. - */ -template -class font_to_rgba +void RendererAgg::tostring_argb(uint8_t *buf) { -public: - typedef ChildGenerator child_type; - typedef agg::rgba8 color_type; - typedef typename child_type::color_type child_color_type; - typedef agg::span_allocator span_alloc_type; - -private: - child_type* _gen; - color_type _color; - span_alloc_type _allocator; + //"Return the rendered buffer as an RGB string"; -public: - font_to_rgba(child_type* gen, color_type color) : - _gen(gen), - _color(color) - { + int row_len = width * 4; - } + agg::rendering_buffer renderingBufferTmp; + renderingBufferTmp.attach(buf, width, height, row_len); + agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_argb32()); +} - inline void - generate(color_type* output_span, int x, int y, unsigned len) - { - _allocator.allocate(len); - child_color_type* input_span = _allocator.span(); - _gen->generate(input_span, x, y, len); +void RendererAgg::tostring_bgra(uint8_t *buf) +{ + //"Return the rendered buffer as an RGB string"; - do - { - *output_span = _color; - output_span->a = ((unsigned int)_color.a * - (unsigned int)input_span->v) >> 8; - ++output_span; - ++input_span; - } - while (--len); - } + int row_len = width * 4; - void - prepare() - { - _gen->prepare(); - } -}; + agg::rendering_buffer renderingBufferTmp; + renderingBufferTmp.attach(buf, width, height, row_len); + agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_bgra32()); +} -// MGDTODO: Support clip paths -Py::Object -RendererAgg::draw_text_image(const Py::Tuple& args) +agg::rect_i RendererAgg::get_content_extents() { - _VERBOSE("RendererAgg::draw_text"); - - typedef agg::span_allocator color_span_alloc_type; - typedef agg::span_interpolator_linear<> interpolator_type; - typedef agg::image_accessor_clip image_accessor_type; - typedef agg::span_image_filter_gray image_span_gen_type; - typedef font_to_rgba span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; - - args.verify_length(5); - - const unsigned char* buffer = NULL; - int width, height; - Py::Object image_obj = args[0]; + agg::rect_i r(width, height, 0, 0); - if (PyArray_Check(image_obj.ptr())) - { - PyObject* image_array = PyArray_FromObject( - image_obj.ptr(), NPY_UBYTE, 2, 2); - if (!image_array) - { - throw Py::ValueError( - "First argument to draw_text_image must be a FT2Font.Image object or a Nx2 uint8 numpy array."); - } - image_obj = Py::Object(image_array, true); - buffer = (unsigned char *)PyArray_DATA((PyArrayObject*)image_array); - width = PyArray_DIM((PyArrayObject*)image_array, 1); - height = PyArray_DIM((PyArrayObject*)image_array, 0); - } - else - { - FT2Image* image = static_cast( - Py::getPythonExtensionBase(image_obj.ptr())); - if (!image->get_buffer()) - { - throw Py::ValueError( - "First argument to draw_text_image must be a FT2Font.Image object or a Nx2 uint8 numpy array."); + // Looks at the alpha channel to find the minimum extents of the image + unsigned char *pixel = pixBuffer + 3; + for (int y = 0; y < (int)height; ++y) { + for (int x = 0; x < (int)width; ++x) { + if (*pixel) { + if (x < r.x1) + r.x1 = x; + if (y < r.y1) + r.y1 = y; + if (x > r.x2) + r.x2 = x; + if (y > r.y2) + r.y2 = y; + } + pixel += 4; } - buffer = image->get_buffer(); - width = image->get_width(); - height = image->get_height(); - } - - int x(0), y(0); - try - { - x = Py::Int(args[1]); - y = Py::Int(args[2]); - } - catch (Py::TypeError) - { - throw Py::TypeError("Invalid input arguments to draw_text_image"); } - double angle = Py::Float(args[3]); + r.x1 = std::max(0, r.x1 - 1); + r.y1 = std::max(0, r.y1 - 1); + r.x2 = std::max(r.x2 + 1, (int)width); + r.y2 = std::max(r.y2 + 1, (int)height); - GCAgg gc(args[4], dpi); - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - - agg::rendering_buffer srcbuf((agg::int8u*)buffer, width, height, width); - agg::pixfmt_gray8 pixf_img(srcbuf); - - agg::trans_affine mtx; - mtx *= agg::trans_affine_translation(0, -height); - mtx *= agg::trans_affine_rotation(-angle * agg::pi / 180.0); - mtx *= agg::trans_affine_translation(x, y); - - agg::path_storage rect; - rect.move_to(0, 0); - rect.line_to(width, 0); - rect.line_to(width, height); - rect.line_to(0, height); - rect.line_to(0, 0); - agg::conv_transform rect2(rect, mtx); - - agg::trans_affine inv_mtx(mtx); - inv_mtx.invert(); - - agg::image_filter_lut filter; - filter.calculate(agg::image_filter_spline36()); - interpolator_type interpolator(inv_mtx); - color_span_alloc_type sa; - image_accessor_type ia(pixf_img, 0); - image_span_gen_type image_span_generator(ia, interpolator, filter); - span_gen_type output_span_generator(&image_span_generator, gc.color); - renderer_type ri(rendererBase, sa, output_span_generator); - - try { - theRasterizer.add_path(rect2); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, ri); - - return Py::Object(); + return r; } -class span_conv_alpha +void RendererAgg::clear() { -public: - typedef agg::rgba8 color_type; - - double m_alpha; - - span_conv_alpha(double alpha) : - m_alpha(alpha) - { - } - - void prepare() {} - void generate(color_type* span, int x, int y, unsigned len) const - { - do - { - span->a = (agg::int8u)((double)span->a * m_alpha); - ++span; - } - while(--len); - } -}; + //"clear the rendered buffer"; - -Py::Object -RendererAgg::draw_image(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::draw_image"); - - args.verify_length(4, 7); // 7 if affine matrix if given - - GCAgg gc(args[0], dpi); - Image *image = static_cast(args[3].ptr()); - bool has_clippath = false; - agg::trans_affine affine_trans; - bool has_affine = false; - double x, y, w, h; - double alpha; - - if (args.size() == 7) - { - has_affine = true; - x = Py::Float(args[1]); - y = Py::Float(args[2]); - w = Py::Float(args[4]); - h = Py::Float(args[5]); - affine_trans = py_to_agg_transformation_matrix(args[6].ptr()); - } - else - { - x = mpl_round(Py::Float(args[1])); - y = mpl_round(Py::Float(args[2])); - w = h = 0; /* w and h not used in this case, but assign to prevent - warnings from the compiler */ - } - - alpha = gc.alpha; - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - has_clippath = render_clippath(gc.clippath, gc.clippath_trans); - - Py::Tuple empty; - image->flipud_out(empty); - pixfmt pixf(*(image->rbufOut)); - - if (has_affine | has_clippath) - { - agg::trans_affine mtx; - agg::path_storage rect; - - if (has_affine) - { - mtx *= agg::trans_affine_scaling(1, -1); - mtx *= agg::trans_affine_translation(0, image->rowsOut); - mtx *= agg::trans_affine_scaling(w / (image->colsOut), - h / (image->rowsOut)); - mtx *= agg::trans_affine_translation(x, y); - mtx *= affine_trans; - mtx *= agg::trans_affine_scaling(1.0, -1.0); - mtx *= agg::trans_affine_translation(0.0, (double) height); - } - else - { - mtx *= agg::trans_affine_translation( - (int)x, - (int)(height - (y + image->rowsOut))); - } - - rect.move_to(0, 0); - rect.line_to(image->colsOut, 0); - rect.line_to(image->colsOut, image->rowsOut); - rect.line_to(0, image->rowsOut); - rect.line_to(0, 0); - - agg::conv_transform rect2(rect, mtx); - - agg::trans_affine inv_mtx(mtx); - inv_mtx.invert(); - - typedef agg::span_allocator color_span_alloc_type; - typedef agg::image_accessor_clip - image_accessor_type; - typedef agg::span_interpolator_linear<> interpolator_type; - typedef agg::span_image_filter_rgba_nn image_span_gen_type; - typedef agg::span_converter span_conv; - - color_span_alloc_type sa; - image_accessor_type ia(pixf, agg::rgba8(0, 0, 0, 0)); - interpolator_type interpolator(inv_mtx); - image_span_gen_type image_span_generator(ia, interpolator); - span_conv_alpha conv_alpha(alpha); - span_conv spans(image_span_generator, conv_alpha); - - if (has_clippath) - { - typedef agg::pixfmt_amask_adaptor - pixfmt_amask_type; - typedef agg::renderer_base amask_ren_type; - typedef agg::renderer_scanline_aa - renderer_type_alpha; - - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - renderer_type_alpha ri(r, sa, spans); - - try { - theRasterizer.add_path(rect2); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ri); - } - else - { - typedef agg::renderer_base ren_type; - typedef agg::renderer_scanline_aa - renderer_type; - - ren_type r(pixFmt); - renderer_type ri(r, sa, spans); - - try { - theRasterizer.add_path(rect2); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, ri); - } - - } - else - { - set_clipbox(gc.cliprect, rendererBase); - rendererBase.blend_from( - pixf, 0, (int)x, (int)(height - (y + image->rowsOut)), - (agg::int8u)(alpha * 255)); - } - - rendererBase.reset_clipping(true); - image->flipud_out(empty); - - return Py::Object(); -} - - -template -void RendererAgg::_draw_path(path_t& path, bool has_clippath, - const facepair_t& face, const GCAgg& gc) -{ - typedef agg::conv_stroke stroke_t; - typedef agg::conv_dash dash_t; - typedef agg::conv_stroke stroke_dash_t; - typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; - typedef agg::renderer_base amask_ren_type; - typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; - typedef agg::renderer_scanline_bin_solid amask_bin_renderer_type; - - // Render face - if (face.first) - { - try { - theRasterizer.add_path(path); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - - if (gc.isaa) - { - if (has_clippath) - { - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_aa_renderer_type ren(r); - ren.color(face.second); - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); - } - else - { - rendererAA.color(face.second); - agg::render_scanlines(theRasterizer, slineP8, rendererAA); - } - } - else - { - if (has_clippath) - { - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_bin_renderer_type ren(r); - ren.color(face.second); - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); - } - else - { - rendererBin.color(face.second); - agg::render_scanlines(theRasterizer, slineP8, rendererBin); - } - } - } - - // Render hatch - if (!gc.hatchpath.isNone()) - { - // Reset any clipping that may be in effect, since we'll be - // drawing the hatch in a scratch buffer at origin (0, 0) - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - - // Create and transform the path - typedef agg::conv_transform hatch_path_trans_t; - typedef agg::conv_curve hatch_path_curve_t; - typedef agg::conv_stroke hatch_path_stroke_t; - - PathIterator hatch_path(gc.hatchpath); - agg::trans_affine hatch_trans; - hatch_trans *= agg::trans_affine_scaling(1.0, -1.0); - hatch_trans *= agg::trans_affine_translation(0.0, 1.0); - hatch_trans *= agg::trans_affine_scaling(HATCH_SIZE, HATCH_SIZE); - hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans); - hatch_path_curve_t hatch_path_curve(hatch_path_trans); - hatch_path_stroke_t hatch_path_stroke(hatch_path_curve); - hatch_path_stroke.width(1.0); - hatch_path_stroke.line_cap(agg::square_cap); - - // Render the path into the hatch buffer - pixfmt hatch_img_pixf(hatchRenderingBuffer); - renderer_base rb(hatch_img_pixf); - renderer_aa rs(rb); - rb.clear(_fill_color); - rs.color(gc.color); - - try { - theRasterizer.add_path(hatch_path_curve); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, rs); - try { - theRasterizer.add_path(hatch_path_stroke); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - agg::render_scanlines(theRasterizer, slineP8, rs); - - // Put clipping back on, if originally set on entry to this - // function - set_clipbox(gc.cliprect, theRasterizer); - if (has_clippath) - render_clippath(gc.clippath, gc.clippath_trans); - - // Transfer the hatch to the main image buffer - typedef agg::image_accessor_wrap < pixfmt, - agg::wrap_mode_repeat_auto_pow2, - agg::wrap_mode_repeat_auto_pow2 > img_source_type; - typedef agg::span_pattern_rgba span_gen_type; - agg::span_allocator sa; - img_source_type img_src(hatch_img_pixf); - span_gen_type sg(img_src, 0, 0); - try { - theRasterizer.add_path(path); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - - if (has_clippath) - { - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type ren(pfa); - agg::render_scanlines_aa(theRasterizer, slineP8, ren, sa, sg); - } - else - { - agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, sa, sg); - } - } - - // Render stroke - if (gc.linewidth != 0.0) - { - double linewidth = gc.linewidth; - if (!gc.isaa) - { - linewidth = (linewidth < 0.5) ? 0.5 : mpl_round(linewidth); - } - if (gc.dashes.size() == 0) - { - stroke_t stroke(path); - stroke.width(linewidth); - stroke.line_cap(gc.cap); - stroke.line_join(gc.join); - try { - theRasterizer.add_path(stroke); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - } - else - { - dash_t dash(path); - for (GCAgg::dash_t::const_iterator i = gc.dashes.begin(); - i != gc.dashes.end(); ++i) - { - double val0 = i->first; - double val1 = i->second; - if (!gc.isaa) - { - val0 = (int)val0 + 0.5; - val1 = (int)val1 + 0.5; - } - dash.add_dash(val0, val1); - } - stroke_dash_t stroke(dash); - stroke.line_cap(gc.cap); - stroke.line_join(gc.join); - stroke.width(linewidth); - try { - theRasterizer.add_path(stroke); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what()); - } - } - - if (gc.isaa) - { - if (has_clippath) - { - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_aa_renderer_type ren(r); - ren.color(gc.color); - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); - } - else - { - rendererAA.color(gc.color); - agg::render_scanlines(theRasterizer, slineP8, rendererAA); - } - } - else - { - if (has_clippath) - { - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_bin_renderer_type ren(r); - ren.color(gc.color); - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); - } - else - { - rendererBin.color(gc.color); - agg::render_scanlines(theRasterizer, slineBin, rendererBin); - } - } - } -} - - -Py::Object -RendererAgg::draw_path(const Py::Tuple& args) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removed_t; - typedef PathClipper clipped_t; - typedef PathSnapper snapped_t; - typedef PathSimplifier simplify_t; - typedef agg::conv_curve curve_t; - typedef Sketch sketch_t; - - _VERBOSE("RendererAgg::draw_path"); - args.verify_length(3, 4); - - GCAgg gc(args[0], dpi); - PathIterator path(args[1]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[2].ptr()); - Py::Object face_obj; - if (args.size() == 4) - face_obj = args[3]; - - facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); - - trans *= agg::trans_affine_scaling(1.0, -1.0); - trans *= agg::trans_affine_translation(0.0, (double)height); - bool clip = !face.first && gc.hatchpath.isNone() && !path.has_curves(); - bool simplify = path.should_simplify() && clip; - double snapping_linewidth = gc.linewidth; - if (gc.color.a == 0.0) { - snapping_linewidth = 0.0; - } - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, path.has_curves()); - clipped_t clipped(nan_removed, clip, width, height); - snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth); - simplify_t simplified(snapped, simplify, path.simplify_threshold()); - curve_t curve(simplified); - sketch_t sketch(curve, gc.sketch_scale, gc.sketch_length, gc.sketch_randomness); - - try - { - _draw_path(sketch, has_clippath, face, gc); - } - catch (const char* e) - { - throw Py::RuntimeError(e); - } - - return Py::Object(); -} - - -template -Py::Object -RendererAgg::_draw_path_collection_generic -(GCAgg& gc, - agg::trans_affine master_transform, - const Py::Object& cliprect, - const Py::Object& clippath, - const agg::trans_affine& clippath_trans, - const PathGenerator& path_generator, - const Py::Object& transforms_obj, - const Py::Object& offsets_obj, - const agg::trans_affine& offset_trans, - const Py::Object& facecolors_obj, - const Py::Object& edgecolors_obj, - const Py::SeqBase& linewidths, - const Py::SeqBase& linestyles_obj, - const Py::SeqBase& antialiaseds, - const bool data_offsets) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removed_t; - typedef PathClipper clipped_t; - typedef PathSnapper snapped_t; - typedef agg::conv_curve snapped_curve_t; - typedef agg::conv_curve curve_t; - - PyArrayObject* offsets = (PyArrayObject*)PyArray_FromObject - (offsets_obj.ptr(), NPY_DOUBLE, 0, 2); - if (!offsets || - (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || - (PyArray_NDIM(offsets) == 1 && PyArray_DIM(offsets, 0) != 0)) - { - Py_XDECREF(offsets); - throw Py::ValueError("Offsets array must be Nx2"); - } - Py::Object offsets_arr_obj((PyObject*)offsets, true); - - PyArrayObject* facecolors = (PyArrayObject*)PyArray_FromObject - (facecolors_obj.ptr(), NPY_DOUBLE, 1, 2); - if (!facecolors || - (PyArray_NDIM(facecolors) == 1 && PyArray_DIM(facecolors, 0) != 0) || - (PyArray_NDIM(facecolors) == 2 && PyArray_DIM(facecolors, 1) != 4)) - { - Py_XDECREF(facecolors); - throw Py::ValueError("Facecolors must be a Nx4 numpy array or empty"); - } - Py::Object facecolors_arr_obj((PyObject*)facecolors, true); - - PyArrayObject* edgecolors = (PyArrayObject*)PyArray_FromObject - (edgecolors_obj.ptr(), NPY_DOUBLE, 1, 2); - if (!edgecolors || - (PyArray_NDIM(edgecolors) == 1 && PyArray_DIM(edgecolors, 0) != 0) || - (PyArray_NDIM(edgecolors) == 2 && PyArray_DIM(edgecolors, 1) != 4)) - { - Py_XDECREF(edgecolors); - throw Py::ValueError("Edgecolors must be a Nx4 numpy array"); - } - Py::Object edgecolors_arr_obj((PyObject*)edgecolors, true); - - PyArrayObject* transforms_arr = (PyArrayObject*)PyArray_FromObject - (transforms_obj.ptr(), NPY_DOUBLE, 1, 3); - if (!transforms_arr || - (PyArray_NDIM(transforms_arr) == 1 && PyArray_DIM(transforms_arr, 0) != 0) || - (PyArray_NDIM(transforms_arr) == 2) || - (PyArray_NDIM(transforms_arr) == 3 && - ((PyArray_DIM(transforms_arr, 1) != 3) || - (PyArray_DIM(transforms_arr, 2) != 3)))) - { - Py_XDECREF(transforms_arr); - throw Py::ValueError("Transforms must be a Nx3x3 numpy array"); - } - - size_t Npaths = path_generator.num_paths(); - size_t Noffsets = PyArray_DIM(offsets, 0); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = PyArray_DIM(transforms_arr, 0); - size_t Nfacecolors = PyArray_DIM(facecolors, 0); - size_t Nedgecolors = PyArray_DIM(edgecolors, 0); - size_t Nlinewidths = linewidths.length(); - size_t Nlinestyles = std::min(linestyles_obj.length(), N); - size_t Naa = antialiaseds.length(); - - if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) - { - Py_XDECREF(transforms_arr); - return Py::Object(); - } - - size_t i = 0; - - // Convert all of the transforms up front - typedef std::vector transforms_t; - transforms_t transforms; - transforms.reserve(Ntransforms); - for (i = 0; i < Ntransforms; ++i) - { - /* TODO: Use a Numpy iterator */ - agg::trans_affine trans( - *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 0), - *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 0), - *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 1), - *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 1), - *(double *)PyArray_GETPTR3(transforms_arr, i, 0, 2), - *(double *)PyArray_GETPTR3(transforms_arr, i, 1, 2)); - trans *= master_transform; - - transforms.push_back(trans); - } - - // Convert all the dashes up front - typedef std::vector > dashes_t; - dashes_t dashes; - dashes.resize(Nlinestyles); - i = 0; - for (dashes_t::iterator d = dashes.begin(); - d != dashes.end(); ++d, ++i) - { - convert_dashes(Py::Tuple(linestyles_obj[i]), dpi, d->second, - d->first); - } - - // Handle any clipping globally - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(cliprect, theRasterizer); - bool has_clippath = render_clippath(clippath, clippath_trans); - - // Set some defaults, assuming no face or edge - gc.linewidth = 0.0; - facepair_t face; - face.first = Nfacecolors != 0; - agg::trans_affine trans; - - for (i = 0; i < N; ++i) - { - typename PathGenerator::path_iterator path = path_generator(i); - - if (Ntransforms) - { - trans = transforms[i % Ntransforms]; - } - else - { - trans = master_transform; - } - - if (Noffsets) - { - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - if (data_offsets) { - trans = agg::trans_affine_translation(xo, yo) * trans; - } else { - trans *= agg::trans_affine_translation(xo, yo); - } - } - - // These transformations must be done post-offsets - trans *= agg::trans_affine_scaling(1.0, -1.0); - trans *= agg::trans_affine_translation(0.0, (double)height); - - if (Nfacecolors) - { - size_t fi = i % Nfacecolors; - face.second = agg::rgba( - *(double*)PyArray_GETPTR2(facecolors, fi, 0), - *(double*)PyArray_GETPTR2(facecolors, fi, 1), - *(double*)PyArray_GETPTR2(facecolors, fi, 2), - *(double*)PyArray_GETPTR2(facecolors, fi, 3)); - } - - if (Nedgecolors) - { - size_t ei = i % Nedgecolors; - gc.color = agg::rgba( - *(double*)PyArray_GETPTR2(edgecolors, ei, 0), - *(double*)PyArray_GETPTR2(edgecolors, ei, 1), - *(double*)PyArray_GETPTR2(edgecolors, ei, 2), - *(double*)PyArray_GETPTR2(edgecolors, ei, 3)); - - if (Nlinewidths) - { - gc.linewidth = double(Py::Float(linewidths[i % Nlinewidths])) * dpi / 72.0; - } - else - { - gc.linewidth = 1.0; - } - if (Nlinestyles) - { - gc.dashes = dashes[i % Nlinestyles].second; - gc.dashOffset = dashes[i % Nlinestyles].first; - } - } - - bool do_clip = !face.first && gc.hatchpath.isNone() && !has_curves; - - if (check_snap) - { - gc.isaa = Py::Boolean(antialiaseds[i % Naa]); - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, has_curves); - clipped_t clipped(nan_removed, do_clip, width, height); - snapped_t snapped(clipped, gc.snap_mode, - path.total_vertices(), gc.linewidth); - if (has_curves) - { - snapped_curve_t curve(snapped); - _draw_path(curve, has_clippath, face, gc); - } - else - { - _draw_path(snapped, has_clippath, face, gc); - } - } - else - { - gc.isaa = Py::Boolean(antialiaseds[i % Naa]); - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, has_curves); - clipped_t clipped(nan_removed, do_clip, width, height); - if (has_curves) - { - curve_t curve(clipped); - _draw_path(curve, has_clippath, face, gc); - } - else - { - _draw_path(clipped, has_clippath, face, gc); - } - } - } - - Py_XDECREF(transforms_arr); - - return Py::Object(); -} - - -class PathListGenerator -{ - const Py::SeqBase& m_paths; - size_t m_npaths; - -public: - typedef PathIterator path_iterator; - - inline - PathListGenerator(const Py::SeqBase& paths) : - m_paths(paths), m_npaths(paths.size()) - { - - } - - inline size_t - num_paths() const - { - return m_npaths; - } - - inline path_iterator - operator()(size_t i) const - { - return PathIterator(m_paths[i % m_npaths]); - } -}; - - -Py::Object -RendererAgg::draw_path_collection(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::draw_path_collection"); - args.verify_length(13); - - Py::Object gc_obj = args[0]; - GCAgg gc(gc_obj, dpi); - agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[1].ptr()); - Py::SeqBase path = args[2]; - PathListGenerator path_generator(path); - Py::Object transforms_obj = args[3]; - Py::Object offsets_obj = args[4]; - agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[5].ptr()); - Py::Object facecolors_obj = args[6]; - Py::Object edgecolors_obj = args[7]; - Py::SeqBase linewidths = args[8]; - Py::SeqBase linestyles_obj = args[9]; - Py::SeqBase antialiaseds = args[10]; - // We don't actually care about urls for Agg, so just ignore it. - // Py::SeqBase urls = args[11]; - std::string offset_position = Py::String(args[12]).encode("utf-8"); - - bool data_offsets = (offset_position == "data"); - - try - { - _draw_path_collection_generic - (gc, - master_transform, - gc.cliprect, - gc.clippath, - gc.clippath_trans, - path_generator, - transforms_obj, - offsets_obj, - offset_trans, - facecolors_obj, - edgecolors_obj, - linewidths, - linestyles_obj, - antialiaseds, - data_offsets); - } - catch (const char *e) - { - throw Py::RuntimeError(e); - } - - return Py::Object(); -} - - -class QuadMeshGenerator -{ - size_t m_meshWidth; - size_t m_meshHeight; - PyArrayObject* m_coordinates; - - class QuadMeshPathIterator - { - size_t m_iterator; - size_t m_m, m_n; - PyArrayObject* m_coordinates; - - public: - QuadMeshPathIterator(size_t m, size_t n, PyArrayObject* coordinates) : - m_iterator(0), m_m(m), m_n(n), m_coordinates(coordinates) - { - - } - - private: - inline unsigned - vertex(unsigned idx, double* x, double* y) - { - size_t m = m_m + ((idx & 0x2) >> 1); - size_t n = m_n + (((idx + 1) & 0x2) >> 1); - double* pair = (double*)PyArray_GETPTR2(m_coordinates, n, m); - *x = *pair++; - *y = *pair; - return (idx) ? agg::path_cmd_line_to : agg::path_cmd_move_to; - } - - public: - inline unsigned - vertex(double* x, double* y) - { - if (m_iterator >= total_vertices()) - { - return agg::path_cmd_stop; - } - return vertex(m_iterator++, x, y); - } - - inline void - rewind(unsigned path_id) - { - m_iterator = path_id; - } - - inline unsigned - total_vertices() - { - return 5; - } - - inline bool - should_simplify() - { - return false; - } - }; - -public: - typedef QuadMeshPathIterator path_iterator; - - inline - QuadMeshGenerator(size_t meshWidth, size_t meshHeight, PyObject* coordinates) : - m_meshWidth(meshWidth), m_meshHeight(meshHeight), m_coordinates(NULL) - { - PyArrayObject* coordinates_array = \ - (PyArrayObject*)PyArray_ContiguousFromObject( - coordinates, NPY_DOUBLE, 3, 3); - - if (!coordinates_array) - { - throw Py::ValueError("Invalid coordinates array."); - } - - m_coordinates = coordinates_array; - } - - inline - ~QuadMeshGenerator() - { - Py_XDECREF(m_coordinates); - } - - inline size_t - num_paths() const - { - return m_meshWidth * m_meshHeight; - } - - inline path_iterator - operator()(size_t i) const - { - return QuadMeshPathIterator(i % m_meshWidth, i / m_meshWidth, m_coordinates); - } -}; - -Py::Object -RendererAgg::draw_quad_mesh(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::draw_quad_mesh"); - args.verify_length(10); - - //segments, trans, clipbox, colors, linewidths, antialiaseds - GCAgg gc(args[0], dpi); - agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[1].ptr()); - size_t mesh_width = (long)Py::Int(args[2]); - size_t mesh_height = (long)Py::Int(args[3]); - Py::Object coordinates = args[4]; - Py::Object offsets_obj = args[5]; - agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[6].ptr()); - Py::Object facecolors_obj = args[7]; - bool antialiased = (bool)Py::Boolean(args[8]); - Py::Object edgecolors_obj = args[9]; - - QuadMeshGenerator path_generator(mesh_width, mesh_height, coordinates.ptr()); - - Py::Object transforms_obj = Py::List(0); - Py::Tuple linewidths(1); - linewidths[0] = Py::Float(gc.linewidth); - Py::SeqBase linestyles_obj; - Py::Tuple antialiaseds(1); - antialiaseds[0] = Py::Int(antialiased ? 1 : 0); - - if (edgecolors_obj.isNone()) { - if (antialiased) - { - edgecolors_obj = facecolors_obj; - } - else - { - npy_intp dims[] = { 0, 0 }; - edgecolors_obj = PyArray_SimpleNew(1, dims, NPY_DOUBLE); - } - } - - try - { - _draw_path_collection_generic - (gc, - master_transform, - gc.cliprect, - gc.clippath, - gc.clippath_trans, - path_generator, - transforms_obj, - offsets_obj, - offset_trans, - facecolors_obj, - edgecolors_obj, - linewidths, - linestyles_obj, - antialiaseds, - false); - } - catch (const char* e) - { - throw Py::RuntimeError(e); - } - - return Py::Object(); -} - -void -RendererAgg::_draw_gouraud_triangle(const double* points, - const double* colors, - agg::trans_affine trans, - bool has_clippath) -{ - typedef agg::rgba8 color_t; - typedef agg::span_gouraud_rgba span_gen_t; - typedef agg::span_allocator span_alloc_t; - - trans *= agg::trans_affine_scaling(1.0, -1.0); - trans *= agg::trans_affine_translation(0.0, (double)height); - - double tpoints[6]; - - for (int i = 0; i < 6; i += 2) - { - tpoints[i] = points[i]; - tpoints[i+1] = points[i+1]; - trans.transform(&tpoints[i], &tpoints[i+1]); - } - - span_alloc_t span_alloc; - span_gen_t span_gen; - - span_gen.colors( - agg::rgba(colors[0], colors[1], colors[2], colors[3]), - agg::rgba(colors[4], colors[5], colors[6], colors[7]), - agg::rgba(colors[8], colors[9], colors[10], colors[11])); - span_gen.triangle( - tpoints[0], tpoints[1], - tpoints[2], tpoints[3], - tpoints[4], tpoints[5], - 0.5); - - try { - theRasterizer.add_path(span_gen); - } catch (std::overflow_error &e) { - throw Py::OverflowError(e.what() - ); - } - - if (has_clippath) - { - typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; - typedef agg::renderer_base amask_ren_type; - typedef agg::renderer_scanline_aa - amask_aa_renderer_type; - - pixfmt_amask_type pfa(pixFmt, alphaMask); - amask_ren_type r(pfa); - amask_aa_renderer_type ren(r, span_alloc, span_gen); - agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); - } - else - { - agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, span_alloc, span_gen); - } -} - - -Py::Object -RendererAgg::draw_gouraud_triangle(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::draw_gouraud_triangle"); - args.verify_length(4); - - GCAgg gc(args[0], dpi); - Py::Object points_obj = args[1]; - Py::Object colors_obj = args[2]; - agg::trans_affine trans = py_to_agg_transformation_matrix(args[3].ptr()); - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); - - PyArrayObject* points = (PyArrayObject*)PyArray_ContiguousFromAny - (points_obj.ptr(), NPY_DOUBLE, 2, 2); - if (!points || - PyArray_DIM(points, 0) != 3 || PyArray_DIM(points, 1) != 2) - { - Py_XDECREF(points); - throw Py::ValueError("points must be a 3x2 numpy array"); - } - points_obj = Py::Object((PyObject*)points, true); - - PyArrayObject* colors = (PyArrayObject*)PyArray_ContiguousFromAny - (colors_obj.ptr(), NPY_DOUBLE, 2, 2); - if (!colors || - PyArray_DIM(colors, 0) != 3 || PyArray_DIM(colors, 1) != 4) - { - Py_XDECREF(colors); - throw Py::ValueError("colors must be a 3x4 numpy array"); - } - colors_obj = Py::Object((PyObject*)colors, true); - - _draw_gouraud_triangle( - (double*)PyArray_DATA(points), (double*)PyArray_DATA(colors), - trans, has_clippath); - - return Py::Object(); -} - - -Py::Object -RendererAgg::draw_gouraud_triangles(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::draw_gouraud_triangles"); - args.verify_length(4); - - typedef agg::rgba8 color_t; - typedef agg::span_gouraud_rgba span_gen_t; - - GCAgg gc(args[0], dpi); - Py::Object points_obj = args[1]; - Py::Object colors_obj = args[2]; - agg::trans_affine trans = py_to_agg_transformation_matrix(args[3].ptr()); - double c_points[6]; - double c_colors[12]; - - theRasterizer.reset_clipping(); - rendererBase.reset_clipping(true); - set_clipbox(gc.cliprect, theRasterizer); - bool has_clippath = render_clippath(gc.clippath, gc.clippath_trans); - - PyArrayObject* points = (PyArrayObject*)PyArray_FromObject - (points_obj.ptr(), NPY_DOUBLE, 3, 3); - if (!points || - PyArray_DIM(points, 1) != 3 || PyArray_DIM(points, 2) != 2) - { - Py_XDECREF(points); - throw Py::ValueError("points must be a Nx3x2 numpy array"); - } - points_obj = Py::Object((PyObject*)points, true); - - PyArrayObject* colors = (PyArrayObject*)PyArray_FromObject - (colors_obj.ptr(), NPY_DOUBLE, 3, 3); - if (!colors || - PyArray_DIM(colors, 1) != 3 || PyArray_DIM(colors, 2) != 4) - { - Py_XDECREF(colors); - throw Py::ValueError("colors must be a Nx3x4 numpy array"); - } - colors_obj = Py::Object((PyObject*)colors, true); - - if (PyArray_DIM(points, 0) != PyArray_DIM(colors, 0)) - { - throw Py::ValueError("points and colors arrays must be the same length"); - } - - for (int i = 0; i < PyArray_DIM(points, 0); ++i) - { - for (int j = 0; j < 3; ++j) { - for (int k = 0; k < 2; ++k) { - c_points[j*2+k] = *(double *)PyArray_GETPTR3(points, i, j, k); - } - } - - for (int j = 0; j < 3; ++j) { - for (int k = 0; k < 4; ++k) { - c_colors[j*4+k] = *(double *)PyArray_GETPTR3(colors, i, j, k); - } - } - - _draw_gouraud_triangle( - c_points, c_colors, trans, has_clippath); - } - - return Py::Object(); -} - - -Py::Object -RendererAgg::write_rgba(const Py::Tuple& args) -{ - _VERBOSE("RendererAgg::write_rgba"); - - args.verify_length(1); - - FILE *fp = NULL; - mpl_off_t offset; - Py::Object py_fileobj = Py::Object(args[0]); - PyObject* py_file = NULL; - bool close_file = false; - - if (py_fileobj.isString()) - { - if ((py_file = mpl_PyFile_OpenFile(py_fileobj.ptr(), (char *)"wb")) == NULL) { - throw Py::Exception(); - } - } - else - { - py_file = py_fileobj.ptr(); - } - - if ((fp = mpl_PyFile_Dup(py_file, (char *)"wb", &offset))) - { - if (fwrite(pixBuffer, 1, NUMBYTES, fp) != NUMBYTES) - { - if (mpl_PyFile_DupClose(py_file, fp, offset)) { - throw Py::RuntimeError("Error closing dupe file handle"); - } - - if (close_file) { - mpl_PyFile_CloseFile(py_file); - Py_DECREF(py_file); - } - - throw Py::RuntimeError("Error writing to file"); - } - - if (mpl_PyFile_DupClose(py_file, fp, offset)) { - throw Py::RuntimeError("Error closing dupe file handle"); - } - - if (close_file) { - mpl_PyFile_CloseFile(py_file); - Py_DECREF(py_file); - } - } - else - { - PyErr_Clear(); - PyObject* write_method = PyObject_GetAttrString(py_fileobj.ptr(), - "write"); - if (!(write_method && PyCallable_Check(write_method))) - { - Py_XDECREF(write_method); - throw Py::TypeError( - "Object does not appear to be a 8-bit string path or a Python file-like object"); - } - - #if PY3K - PyObject_CallFunction(write_method, (char *)"y#", pixBuffer, NUMBYTES); - #else - PyObject_CallFunction(write_method, (char *)"s#", pixBuffer, NUMBYTES); - #endif - - Py_XDECREF(write_method); - } - - return Py::Object(); -} - - -Py::Object -RendererAgg::tostring_rgb(const Py::Tuple& args) -{ - //"Return the rendered buffer as an RGB string"; - - _VERBOSE("RendererAgg::tostring_rgb"); - - args.verify_length(0); - int row_len = width * 3; - unsigned char* buf_tmp = new unsigned char[row_len * height]; - if (buf_tmp == NULL) - { - //todo: also handle allocation throw - throw Py::MemoryError( - "RendererAgg::tostring_rgb could not allocate memory"); - } - - try - { - agg::rendering_buffer renderingBufferTmp; - renderingBufferTmp.attach(buf_tmp, - width, - height, - row_len); - - agg::color_conv(&renderingBufferTmp, &renderingBuffer, - agg::color_conv_rgba32_to_rgb24()); - } - catch (...) - { - delete [] buf_tmp; - throw Py::RuntimeError("Unknown exception occurred in tostring_rgb"); - } - - //todo: how to do this with native CXX - #if PY3K - PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); - #else - PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); - #endif - - delete [] buf_tmp; - return Py::asObject(o); -} - - -Py::Object -RendererAgg::tostring_argb(const Py::Tuple& args) -{ - //"Return the rendered buffer as an RGB string"; - - _VERBOSE("RendererAgg::tostring_argb"); - - args.verify_length(0); - int row_len = width * 4; - unsigned char* buf_tmp = new unsigned char[row_len * height]; - if (buf_tmp == NULL) - { - //todo: also handle allocation throw - throw Py::MemoryError("RendererAgg::tostring_argb could not allocate memory"); - } - - try - { - agg::rendering_buffer renderingBufferTmp; - renderingBufferTmp.attach(buf_tmp, width, height, row_len); - agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_argb32()); - } - catch (...) - { - delete [] buf_tmp; - throw Py::RuntimeError("Unknown exception occurred in tostring_argb"); - } - - //todo: how to do this with native CXX - - #if PY3K - PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); - #else - PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); - #endif - delete [] buf_tmp; - return Py::asObject(o); -} - - -Py::Object -RendererAgg::tostring_bgra(const Py::Tuple& args) -{ - //"Return the rendered buffer as an RGB string"; - - _VERBOSE("RendererAgg::tostring_bgra"); - - args.verify_length(0); - int row_len = width * 4; - unsigned char* buf_tmp = new unsigned char[row_len * height]; - if (buf_tmp == NULL) - { - //todo: also handle allocation throw - throw Py::MemoryError("RendererAgg::tostring_bgra could not allocate memory"); - } - - try - { - agg::rendering_buffer renderingBufferTmp; - renderingBufferTmp.attach(buf_tmp, - width, - height, - row_len); - - agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_bgra32()); - } - catch (...) - { - delete [] buf_tmp; - throw Py::RuntimeError("Unknown exception occurred in tostring_bgra"); - } - - //todo: how to do this with native CXX - #if PY3K - PyObject* o = Py_BuildValue("y#", buf_tmp, row_len * height); - #else - PyObject* o = Py_BuildValue("s#", buf_tmp, row_len * height); - #endif - delete [] buf_tmp; - return Py::asObject(o); -} - - -Py::Object -RendererAgg::buffer_rgba(const Py::Tuple& args) -{ - //"expose the rendered buffer as Python buffer object, starting from postion x,y"; - - _VERBOSE("RendererAgg::buffer_rgba"); - - args.verify_length(0); - - #if PY3K - return Py::asObject(PyMemoryView_FromObject(this)); - #else - int row_len = width * 4; - return Py::asObject(PyBuffer_FromReadWriteMemory( - pixBuffer, row_len*height)); - #endif -} - - -Py::Object -RendererAgg::tostring_rgba_minimized(const Py::Tuple& args) -{ - args.verify_length(0); - - int xmin = width; - int ymin = height; - int xmax = 0; - int ymax = 0; - - // Looks at the alpha channel to find the minimum extents of the image - unsigned char* pixel = pixBuffer + 3; - for (int y = 0; y < (int)height; ++y) - { - for (int x = 0; x < (int)width; ++x) - { - if (*pixel) - { - if (x < xmin) xmin = x; - if (y < ymin) ymin = y; - if (x > xmax) xmax = x; - if (y > ymax) ymax = y; - } - pixel += 4; - } - } - - int newwidth = 0; - int newheight = 0; - PyObject *data; - - if (xmin < xmax && ymin < ymax) - { - // Expand the bounds by 1 pixel on all sides - xmin = std::max(0, xmin - 1); - ymin = std::max(0, ymin - 1); - xmax = std::min(xmax, (int)width); - ymax = std::min(ymax, (int)height); - - newwidth = xmax - xmin; - newheight = ymax - ymin; - int newsize = newwidth * newheight * 4; - - // NULL pointer causes Python to allocate uninitialized memory. - // We then grab Python's pointer to uninitialized memory using - // the _AsString() API. - unsigned int* dst; - - data = PyBytes_FromStringAndSize(NULL, newsize); - if (data == NULL) - { - throw Py::MemoryError("RendererAgg::tostring_rgba_minimized could not allocate memory"); - } - dst = (unsigned int *)PyBytes_AsString(data); - - unsigned int* src = (unsigned int*)pixBuffer; - for (int y = ymin; y < ymax; ++y) - { - for (int x = xmin; x < xmax; ++x, ++dst) - { - *dst = src[y * width + x]; - } - } - } else { - data = PyBytes_FromStringAndSize(NULL, 0); - if (data == NULL) - { - throw Py::MemoryError("RendererAgg::tostring_rgba_minimized could not allocate memory"); - } - } - - Py::Tuple bounds(4); - bounds[0] = Py::Int(xmin); - bounds[1] = Py::Int(ymin); - bounds[2] = Py::Int(newwidth); - bounds[3] = Py::Int(newheight); - - Py::Tuple result(2); - result[0] = Py::Object(data, true); - result[1] = bounds; - - return result; -} - - -Py::Object -RendererAgg::clear(const Py::Tuple& args) -{ - //"clear the rendered buffer"; - - _VERBOSE("RendererAgg::clear"); - - args.verify_length(0); rendererBase.clear(_fill_color); - - return Py::Object(); -} - - -agg::rgba -RendererAgg::rgb_to_color(const Py::SeqBase& rgb, double alpha) -{ - _VERBOSE("RendererAgg::rgb_to_color"); - - double r = Py::Float(rgb[0]); - double g = Py::Float(rgb[1]); - double b = Py::Float(rgb[2]); - return agg::rgba(r, g, b, alpha); -} - - -double -RendererAgg::points_to_pixels(const Py::Object& points) -{ - _VERBOSE("RendererAgg::points_to_pixels"); - double p = Py::Float(points) ; - return p * dpi / 72.0; -} - -#if PY3K -int -RendererAgg::buffer_get( Py_buffer* buf, int flags ) -{ - return PyBuffer_FillInfo(buf, this, pixBuffer, width * height * 4, 1, - PyBUF_SIMPLE); -} -#endif - -RendererAgg::~RendererAgg() -{ - - _VERBOSE("RendererAgg::~RendererAgg"); - - delete [] alphaBuffer; - delete [] pixBuffer; -} - -/* ------------ module methods ------------- */ -Py::Object _backend_agg_module::new_renderer(const Py::Tuple &args, - const Py::Dict &kws) -{ - - if (args.length() != 3) - { - throw Py::RuntimeError("Incorrect # of args to RendererAgg(width, height, dpi)."); - } - - int debug; - if (kws.hasKey("debug")) - { - debug = Py::Int(kws["debug"]); - } - else - { - debug = 0; - } - - unsigned int width = (int)Py::Int(args[0]); - unsigned int height = (int)Py::Int(args[1]); - double dpi = Py::Float(args[2]); - - if (width > 1 << 15 || height > 1 << 15) - { - throw Py::ValueError("width and height must each be below 32768"); - } - - if (dpi <= 0.0) - { - throw Py::ValueError("dpi must be positive"); - } - - RendererAgg* renderer = NULL; - try - { - renderer = new RendererAgg(width, height, dpi, debug); - } - catch (std::bad_alloc) - { - throw Py::RuntimeError("Could not allocate memory for image"); - } - - return Py::asObject(renderer); -} - - -void BufferRegion::init_type() -{ - behaviors().name("BufferRegion"); - behaviors().doc("A wrapper to pass agg buffer objects to and from the python level"); - - - add_varargs_method("set_x", &BufferRegion::set_x, - "set_x(x)"); - - add_varargs_method("set_y", &BufferRegion::set_y, - "set_y(y)"); - - add_varargs_method("get_extents", &BufferRegion::get_extents, - "get_extents()"); - - add_varargs_method("to_string", &BufferRegion::to_string, - "to_string()"); - add_varargs_method("to_string_argb", &BufferRegion::to_string_argb, - "to_string_argb()"); -} - - -void RendererAgg::init_type() -{ - behaviors().name("RendererAgg"); - behaviors().doc("The agg backend extension module"); - - add_varargs_method("draw_path", &RendererAgg::draw_path, - "draw_path(gc, path, transform, rgbFace)\n"); - add_varargs_method("draw_path_collection", &RendererAgg::draw_path_collection, - "draw_path_collection(gc, master_transform, paths, transforms, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds)\n"); - add_varargs_method("draw_quad_mesh", &RendererAgg::draw_quad_mesh, - "draw_quad_mesh(gc, master_transform, meshWidth, meshHeight, coordinates, offsets, offsetTrans, facecolors, antialiaseds, showedges)\n"); - add_varargs_method("draw_gouraud_triangle", &RendererAgg::draw_gouraud_triangle, - "draw_gouraud_triangle(gc, points, colors, master_transform)\n"); - add_varargs_method("draw_gouraud_triangles", &RendererAgg::draw_gouraud_triangles, - "draw_gouraud_triangles(gc, points, colors, master_transform)\n"); - add_varargs_method("draw_markers", &RendererAgg::draw_markers, - "draw_markers(gc, marker_path, marker_trans, path, rgbFace)\n"); - add_varargs_method("draw_text_image", &RendererAgg::draw_text_image, - "draw_text_image(font_image, x, y, r, g, b, a)\n"); - add_varargs_method("draw_image", &RendererAgg::draw_image, - "draw_image(gc, x, y, im)"); - add_varargs_method("write_rgba", &RendererAgg::write_rgba, - "write_rgba(fname)"); - add_varargs_method("tostring_rgb", &RendererAgg::tostring_rgb, - "s = tostring_rgb()"); - add_varargs_method("tostring_argb", &RendererAgg::tostring_argb, - "s = tostring_argb()"); - add_varargs_method("tostring_bgra", &RendererAgg::tostring_bgra, - "s = tostring_bgra()"); - add_varargs_method("tostring_rgba_minimized", &RendererAgg::tostring_rgba_minimized, - "s = tostring_rgba_minimized()"); - add_varargs_method("buffer_rgba", &RendererAgg::buffer_rgba, - "buffer = buffer_rgba()"); - add_varargs_method("clear", &RendererAgg::clear, - "clear()"); - add_varargs_method("copy_from_bbox", &RendererAgg::copy_from_bbox, - "copy_from_bbox(bbox)"); - add_varargs_method("restore_region", &RendererAgg::restore_region, - "restore_region(region)"); - add_varargs_method("restore_region2", &RendererAgg::restore_region2, - "restore_region(region, x1, y1, x2, y2, x3, y3)"); - - #if PY3K - behaviors().supportBufferType(); - #endif -} - -PyMODINIT_FUNC -#if PY3K -PyInit__backend_agg(void) -#else -init_backend_agg(void) -#endif -{ - //static _backend_agg_module* _backend_agg = new _backend_agg_module; - - _VERBOSE("init_backend_agg"); - - import_array(); - - static _backend_agg_module* _backend_agg = NULL; - _backend_agg = new _backend_agg_module; - - #if PY3K - return _backend_agg->module().ptr(); - #endif } diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 100ac4819f73..172fbfc5d1c1 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -1,57 +1,42 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ -/* _backend_agg.h - A rewrite of _backend_agg using PyCXX to handle - ref counting, etc.. +/* _backend_agg.h */ -#ifndef __BACKEND_AGG_H -#define __BACKEND_AGG_H -#include -#include "CXX/Extensions.hxx" - -#include "agg_arrowhead.h" -#include "agg_basics.h" -#include "agg_bezier_arc.h" -#include "agg_color_rgba.h" -#include "agg_conv_concat.h" -#include "agg_conv_contour.h" +#ifndef __BACKEND_AGG_H__ +#define __BACKEND_AGG_H__ + +#include + +#include "agg_alpha_mask_u8.h" #include "agg_conv_curve.h" #include "agg_conv_dash.h" -#include "agg_conv_marker.h" -#include "agg_conv_marker_adaptor.h" -#include "agg_math_stroke.h" #include "agg_conv_stroke.h" -#include "agg_ellipse.h" -#include "agg_embedded_raster_fonts.h" -#include "agg_path_storage.h" -#include "agg_pixfmt_rgb.h" -#include "agg_pixfmt_rgba.h" -#include "agg_pixfmt_gray.h" -#include "agg_alpha_mask_u8.h" +#include "agg_image_accessors.h" #include "agg_pixfmt_amask_adaptor.h" -#include "agg_rasterizer_outline.h" +#include "agg_pixfmt_gray.h" +#include "agg_pixfmt_rgba.h" #include "agg_rasterizer_scanline_aa.h" -#include "agg_renderer_outline_aa.h" -#include "agg_renderer_raster_text.h" +#include "agg_renderer_base.h" #include "agg_renderer_scanline.h" #include "agg_rendering_buffer.h" #include "agg_scanline_bin.h" -#include "agg_scanline_u.h" #include "agg_scanline_p.h" -#include "agg_vcgen_markers_term.h" +#include "agg_scanline_storage_aa.h" +#include "agg_scanline_storage_bin.h" +#include "agg_scanline_u.h" +#include "agg_span_allocator.h" +#include "agg_span_converter.h" +#include "agg_span_gouraud_rgba.h" +#include "agg_span_image_filter_gray.h" +#include "agg_span_image_filter_rgba.h" +#include "agg_span_interpolator_linear.h" +#include "agg_span_pattern_rgba.h" +#include "util/agg_color_conv_rgb8.h" -#include "agg_py_path_iterator.h" +#include "_backend_agg_basic_types.h" #include "path_converters.h" - -// These are copied directly from path.py, and must be kept in sync -#define STOP 0 -#define MOVETO 1 -#define LINETO 2 -#define CURVE3 3 -#define CURVE4 4 -#define CLOSEPOLY 5 - -const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 }; +#include "array.h" typedef agg::pixfmt_rgba32_plain pixfmt; typedef agg::renderer_base renderer_base; @@ -69,11 +54,10 @@ typedef agg::renderer_scanline_aa_solid renderer_ // a helper class to pass agg::buffer objects around. agg::buffer is // a class in the swig wrapper -class BufferRegion : public Py::PythonExtension +class BufferRegion { -public: - BufferRegion(const agg::rect_i &r, bool freemem = true) : - rect(r), freemem(freemem) + public: + BufferRegion(const agg::rect_i &r) : rect(r) { width = r.x2 - r.x1; height = r.y2 - r.y1; @@ -81,97 +65,63 @@ class BufferRegion : public Py::PythonExtension data = new agg::int8u[stride * height]; } - agg::int8u* data; - agg::rect_i rect; - int width; - int height; - int stride; + virtual ~BufferRegion() + { + delete[] data; + }; - bool freemem; + agg::int8u *get_data() + { + return data; + } - // set the x and y corners of the rectangle - Py::Object set_x(const Py::Tuple &args); - Py::Object set_y(const Py::Tuple &args); + agg::rect_i &get_rect() + { + return rect; + } - Py::Object get_extents(const Py::Tuple &args); + int get_width() + { + return width; + } - Py::Object to_string(const Py::Tuple &args); - Py::Object to_string_argb(const Py::Tuple &args); - static void init_type(void); + int get_height() + { + return height; + } - virtual ~BufferRegion() + int get_stride() { - if (freemem) - { - delete [] data; - data = NULL; - } - }; + return stride; + } -private: - // prevent copying - BufferRegion(const BufferRegion&); - BufferRegion& operator=(const BufferRegion&); -}; + void to_string_argb(uint8_t *buf); -class GCAgg -{ -public: - GCAgg(const Py::Object& gc, double dpi); + private: + agg::int8u *data; + agg::rect_i rect; + int width; + int height; + int stride; - double dpi; - bool isaa; - - agg::line_cap_e cap; - agg::line_join_e join; - - double linewidth; - double alpha; - bool forced_alpha; - agg::rgba color; - - Py::Object cliprect; - Py::Object clippath; - agg::trans_affine clippath_trans; - - //dashes - typedef std::vector > dash_t; - double dashOffset; - dash_t dashes; - e_snap_mode snap_mode; - - Py::Object hatchpath; - - double sketch_scale; - double sketch_length; - double sketch_randomness; - -protected: - agg::rgba get_color(const Py::Object& gc); - double points_to_pixels(const Py::Object& points); - void _set_linecap(const Py::Object& gc) ; - void _set_joinstyle(const Py::Object& gc) ; - void _set_dashes(const Py::Object& gc) ; - void _set_clip_rectangle(const Py::Object& gc); - void _set_clip_path(const Py::Object& gc); - void _set_antialiased(const Py::Object& gc); - void _set_snap(const Py::Object& gc); - void _set_hatch_path(const Py::Object& gc); - void _set_sketch_params(const Py::Object& gc); + private: + // prevent copying + BufferRegion(const BufferRegion &); + BufferRegion &operator=(const BufferRegion &); }; - -//struct AMRenderer { -// -//} +#define MARKER_CACHE_SIZE 512 // the renderer -class RendererAgg: public Py::PythonExtension +class RendererAgg { + /* TODO: Remove facepair_t */ typedef std::pair facepair_t; -public: - RendererAgg(unsigned int width, unsigned int height, double dpi, int debug); - static void init_type(void); + + public: + RendererAgg(unsigned int width, unsigned int height, double dpi); + + virtual ~RendererAgg(); unsigned int get_width() { @@ -183,40 +133,86 @@ class RendererAgg: public Py::PythonExtension return height; } - // the drawing methods - //Py::Object _draw_markers_nocache(const Py::Tuple & args); - //Py::Object _draw_markers_cache(const Py::Tuple & args); - Py::Object draw_markers(const Py::Tuple & args); - Py::Object draw_text_image(const Py::Tuple & args); - Py::Object draw_image(const Py::Tuple & args); - Py::Object draw_path(const Py::Tuple & args); - Py::Object draw_path_collection(const Py::Tuple & args); - Py::Object draw_quad_mesh(const Py::Tuple& args); - Py::Object draw_gouraud_triangle(const Py::Tuple& args); - Py::Object draw_gouraud_triangles(const Py::Tuple& args); - - Py::Object write_rgba(const Py::Tuple & args); - Py::Object tostring_rgb(const Py::Tuple & args); - Py::Object tostring_argb(const Py::Tuple & args); - Py::Object tostring_bgra(const Py::Tuple & args); - Py::Object tostring_rgba_minimized(const Py::Tuple & args); - Py::Object buffer_rgba(const Py::Tuple & args); - Py::Object clear(const Py::Tuple & args); - - Py::Object copy_from_bbox(const Py::Tuple & args); - Py::Object restore_region(const Py::Tuple & args); - Py::Object restore_region2(const Py::Tuple & args); - - #if PY3K - virtual int buffer_get( Py_buffer *, int flags ); - #endif + template + void draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color); - virtual ~RendererAgg(); + template + void draw_markers(GCAgg &gc, + PathIterator &marker_path, + agg::trans_affine &marker_path_trans, + PathIterator &path, + agg::trans_affine &trans, + agg::rgba face); + + template + void draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle); + + template + void draw_image(GCAgg &gc, + double x, + double y, + ImageArray &image, + double w, + double h, + agg::trans_affine trans, + bool resize); + + template + void draw_path_collection(GCAgg &gc, + agg::trans_affine &master_transform, + PathGenerator &path, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + e_offset_position offset_position); + + template + void draw_quad_mesh(GCAgg &gc, + agg::trans_affine &master_transform, + size_t mesh_width, + size_t mesh_height, + CoordinateArray &coordinates, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + bool antialiased, + ColorArray &edgecolors); + + template + void draw_gouraud_triangle(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans); + + template + void draw_gouraud_triangles(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans); + + void tostring_rgb(uint8_t *buf); + void tostring_argb(uint8_t *buf); + void tostring_bgra(uint8_t *buf); + agg::rect_i get_content_extents(); + void clear(); + + BufferRegion *copy_from_bbox(agg::rect_d in_rect); + void restore_region(BufferRegion ®); + void restore_region(BufferRegion ®ion, int x, int y, int xx1, int yy1, int xx2, int yy2); - static const size_t PIXELS_PER_INCH; unsigned int width, height; double dpi; - size_t NUMBYTES; //the number of bytes in buffer + size_t NUMBYTES; // the number of bytes in buffer agg::int8u *pixBuffer; agg::rendering_buffer renderingBuffer; @@ -237,90 +233,1072 @@ class RendererAgg: public Py::PythonExtension renderer_bin rendererBin; rasterizer theRasterizer; - Py::Object lastclippath; + void *lastclippath; agg::trans_affine lastclippath_transform; static const size_t HATCH_SIZE = 72; agg::int8u hatchBuffer[HATCH_SIZE * HATCH_SIZE * 4]; agg::rendering_buffer hatchRenderingBuffer; - const int debug; - agg::rgba _fill_color; + protected: + inline double points_to_pixels(double points) + { + return points * dpi / 72.0; + } + + template + void set_clipbox(const agg::rect_d &cliprect, R &rasterizer); + + bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans); + + template + void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc); + + template + void _draw_path_collection_generic(GCAgg &gc, + agg::trans_affine master_transform, + const agg::rect_d &cliprect, + PathIterator &clippath, + const agg::trans_affine &clippath_trans, + PathGenerator &path_generator, + TransformArray &transforms, + OffsetArray &offsets, + const agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + e_offset_position offset_position, + int check_snap, + int has_curves); -protected: - double points_to_pixels(const Py::Object& points); - agg::rgba rgb_to_color(const Py::SeqBase& rgb, double alpha); - facepair_t _get_rgba_face(const Py::Object& rgbFace, double alpha, bool forced_alpha); - - template - void set_clipbox(const Py::Object& cliprect, R& rasterizer); - - bool render_clippath(const Py::Object& clippath, const agg::trans_affine& clippath_trans); - - template - void _draw_path(PathIteratorType& path, bool has_clippath, - const facepair_t& face, const GCAgg& gc); - - template - Py::Object - _draw_path_collection_generic - (GCAgg& gc, - agg::trans_affine master_transform, - const Py::Object& cliprect, - const Py::Object& clippath, - const agg::trans_affine& clippath_trans, - const PathGenerator& path_generator, - const Py::Object& transforms_obj, - const Py::Object& offsets_obj, - const agg::trans_affine& offset_trans, - const Py::Object& facecolors_obj, - const Py::Object& edgecolors_obj, - const Py::SeqBase& linewidths, - const Py::SeqBase& linestyles_obj, - const Py::SeqBase& antialiaseds, - const bool data_offsets); - - void - _draw_gouraud_triangle( - const double* points, const double* colors, - agg::trans_affine trans, bool has_clippath); - -private: + template + void _draw_gouraud_triangle(PointArray &points, + ColorArray &colors, + agg::trans_affine trans, + bool has_clippath); + + private: void create_alpha_buffers(); // prevent copying - RendererAgg(const RendererAgg&); - RendererAgg& operator=(const RendererAgg&); + RendererAgg(const RendererAgg &); + RendererAgg &operator=(const RendererAgg &); }; -// the extension module -class _backend_agg_module : public Py::ExtensionModule<_backend_agg_module> +/*************************************************************************** + * Implementation + */ + +template +inline void +RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, GCAgg &gc) +{ + typedef agg::conv_stroke stroke_t; + typedef agg::conv_dash dash_t; + typedef agg::conv_stroke stroke_dash_t; + typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; + typedef agg::renderer_base amask_ren_type; + typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; + typedef agg::renderer_scanline_bin_solid amask_bin_renderer_type; + + // Render face + if (face.first) { + theRasterizer.add_path(path); + + if (gc.isaa) { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + ren.color(face.second); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererAA.color(face.second); + agg::render_scanlines(theRasterizer, slineP8, rendererAA); + } + } else { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_bin_renderer_type ren(r); + ren.color(face.second); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererBin.color(face.second); + agg::render_scanlines(theRasterizer, slineP8, rendererBin); + } + } + } + + // Render hatch + if (gc.has_hatchpath()) { + // Reset any clipping that may be in effect, since we'll be + // drawing the hatch in a scratch buffer at origin (0, 0) + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + + // Create and transform the path + typedef agg::conv_transform hatch_path_trans_t; + typedef agg::conv_curve hatch_path_curve_t; + typedef agg::conv_stroke hatch_path_stroke_t; + + py::PathIterator hatch_path(gc.hatchpath); + agg::trans_affine hatch_trans; + hatch_trans *= agg::trans_affine_scaling(1.0, -1.0); + hatch_trans *= agg::trans_affine_translation(0.0, 1.0); + hatch_trans *= agg::trans_affine_scaling(HATCH_SIZE, HATCH_SIZE); + hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans); + hatch_path_curve_t hatch_path_curve(hatch_path_trans); + hatch_path_stroke_t hatch_path_stroke(hatch_path_curve); + hatch_path_stroke.width(1.0); + hatch_path_stroke.line_cap(agg::square_cap); + + // Render the path into the hatch buffer + pixfmt hatch_img_pixf(hatchRenderingBuffer); + renderer_base rb(hatch_img_pixf); + renderer_aa rs(rb); + rb.clear(_fill_color); + rs.color(gc.color); + + theRasterizer.add_path(hatch_path_curve); + agg::render_scanlines(theRasterizer, slineP8, rs); + theRasterizer.add_path(hatch_path_stroke); + agg::render_scanlines(theRasterizer, slineP8, rs); + + // Put clipping back on, if originally set on entry to this + // function + set_clipbox(gc.cliprect, theRasterizer); + if (has_clippath) { + render_clippath(gc.clippath.path, gc.clippath.trans); + } + + // Transfer the hatch to the main image buffer + typedef agg::image_accessor_wrap img_source_type; + typedef agg::span_pattern_rgba span_gen_type; + agg::span_allocator sa; + img_source_type img_src(hatch_img_pixf); + span_gen_type sg(img_src, 0, 0); + theRasterizer.add_path(path); + + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type ren(pfa); + agg::render_scanlines_aa(theRasterizer, slineP8, ren, sa, sg); + } else { + agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, sa, sg); + } + } + + // Render stroke + if (gc.linewidth != 0.0) { + double linewidth = points_to_pixels(gc.linewidth); + if (!gc.isaa) { + linewidth = (linewidth < 0.5) ? 0.5 : mpl_round(linewidth); + } + if (gc.dashes.size() == 0) { + stroke_t stroke(path); + stroke.width(points_to_pixels(gc.linewidth)); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + theRasterizer.add_path(stroke); + } else { + dash_t dash(path); + gc.dashes.dash_to_stroke(dash, dpi, gc.isaa); + stroke_dash_t stroke(dash); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + stroke.width(linewidth); + theRasterizer.add_path(stroke); + } + + if (gc.isaa) { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + ren.color(gc.color); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererAA.color(gc.color); + agg::render_scanlines(theRasterizer, slineP8, rendererAA); + } + } else { + if (has_clippath) { + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_bin_renderer_type ren(r); + ren.color(gc.color); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + rendererBin.color(gc.color); + agg::render_scanlines(theRasterizer, slineBin, rendererBin); + } + } + } +} + +template +inline void +RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removed_t; + typedef PathClipper clipped_t; + typedef PathSnapper snapped_t; + typedef PathSimplifier simplify_t; + typedef agg::conv_curve curve_t; + typedef Sketch sketch_t; + + facepair_t face(color.a != 0.0, color); + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); + + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + bool clip = !face.first && gc.has_hatchpath() && !path.has_curves(); + bool simplify = path.should_simplify() && clip; + double snapping_linewidth = points_to_pixels(gc.linewidth); + if (gc.color.a == 0.0) { + snapping_linewidth = 0.0; + } + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, path.has_curves()); + clipped_t clipped(nan_removed, clip, width, height); + snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth); + simplify_t simplified(snapped, simplify, path.simplify_threshold()); + curve_t curve(simplified); + sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness); + + _draw_path(sketch, has_clippath, face, gc); +} + +template +inline void RendererAgg::draw_markers(GCAgg &gc, + PathIterator &marker_path, + agg::trans_affine &marker_trans, + PathIterator &path, + agg::trans_affine &trans, + agg::rgba color) { -public: - _backend_agg_module() - : Py::ExtensionModule<_backend_agg_module>("_backend_agg") + typedef agg::conv_transform transformed_path_t; + typedef PathSnapper snap_t; + typedef agg::conv_curve curve_t; + typedef agg::conv_stroke stroke_t; + typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; + typedef agg::renderer_base amask_ren_type; + typedef agg::renderer_scanline_aa_solid amask_aa_renderer_type; + + // Deal with the difference in y-axis direction + marker_trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.5, (double)height + 0.5); + + transformed_path_t marker_path_transformed(marker_path, marker_trans); + snap_t marker_path_snapped(marker_path_transformed, + gc.snap_mode, + marker_path.total_vertices(), + points_to_pixels(gc.linewidth)); + curve_t marker_path_curve(marker_path_snapped); + + transformed_path_t path_transformed(path, trans); + snap_t path_snapped(path_transformed, SNAP_FALSE, path.total_vertices(), 0.0); + curve_t path_curve(path_snapped); + path_curve.rewind(0); + + facepair_t face(color.a != 0.0, color); + + // maxim's suggestions for cached scanlines + agg::scanline_storage_aa8 scanlines; + theRasterizer.reset(); + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + + agg::int8u staticFillCache[MARKER_CACHE_SIZE]; + agg::int8u staticStrokeCache[MARKER_CACHE_SIZE]; + agg::int8u *fillCache = staticFillCache; + agg::int8u *strokeCache = staticStrokeCache; + + try { - RendererAgg::init_type(); - BufferRegion::init_type(); + unsigned fillSize = 0; + if (face.first) { + theRasterizer.add_path(marker_path_curve); + agg::render_scanlines(theRasterizer, slineP8, scanlines); + fillSize = scanlines.byte_size(); + if (fillSize >= MARKER_CACHE_SIZE) { + fillCache = new agg::int8u[fillSize]; + } + scanlines.serialize(fillCache); + } + + stroke_t stroke(marker_path_curve); + stroke.width(points_to_pixels(gc.linewidth)); + stroke.line_cap(gc.cap); + stroke.line_join(gc.join); + theRasterizer.reset(); + theRasterizer.add_path(stroke); + agg::render_scanlines(theRasterizer, slineP8, scanlines); + unsigned strokeSize = scanlines.byte_size(); + if (strokeSize >= MARKER_CACHE_SIZE) { + strokeCache = new agg::int8u[strokeSize]; + } + scanlines.serialize(strokeCache); + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, rendererBase); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); - add_keyword_method("RendererAgg", &_backend_agg_module::new_renderer, - "RendererAgg(width, height, dpi)"); - initialize("The agg rendering backend"); + double x, y; + + agg::serialized_scanlines_adaptor_aa8 sa; + agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl; + + agg::rect_d clipping_rect(-1.0 - scanlines.max_x(), + -1.0 - scanlines.max_y(), + 1.0 + width - scanlines.min_x(), + 1.0 + height - scanlines.min_y()); + + if (has_clippath) { + while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) { + if (MPL_notisfinite64(x) || MPL_notisfinite64(y)) { + continue; + } + + /* These values are correctly snapped above -- so we don't want + to round here, we really only want to truncate */ + x = floor(x); + y = floor(y); + + // Cull points outside the boundary of the image. + // Values that are too large may overflow and create + // segfaults. + // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 + if (!clipping_rect.hit_test(x, y)) { + continue; + } + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r); + + if (face.first) { + ren.color(face.second); + sa.init(fillCache, fillSize, x, y); + agg::render_scanlines(sa, sl, ren); + } + ren.color(gc.color); + sa.init(strokeCache, strokeSize, x, y); + agg::render_scanlines(sa, sl, ren); + } + } else { + while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) { + if (MPL_notisfinite64(x) || MPL_notisfinite64(y)) { + continue; + } + + /* These values are correctly snapped above -- so we don't want + to round here, we really only want to truncate */ + x = floor(x); + y = floor(y); + + // Cull points outside the boundary of the image. + // Values that are too large may overflow and create + // segfaults. + // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720 + if (!clipping_rect.hit_test(x, y)) { + continue; + } + + if (face.first) { + rendererAA.color(face.second); + sa.init(fillCache, fillSize, x, y); + agg::render_scanlines(sa, sl, rendererAA); + } + + rendererAA.color(gc.color); + sa.init(strokeCache, strokeSize, x, y); + agg::render_scanlines(sa, sl, rendererAA); + } + } + } + catch (...) + { + if (fillCache != staticFillCache) + delete[] fillCache; + if (strokeCache != staticStrokeCache) + delete[] strokeCache; + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + throw; } - virtual ~_backend_agg_module() {} + if (fillCache != staticFillCache) + delete[] fillCache; + if (strokeCache != staticStrokeCache) + delete[] strokeCache; -private: + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); +} - Py::Object new_renderer(const Py::Tuple &args, const Py::Dict &kws); +/** + * This is a custom span generator that converts spans in the + * 8-bit inverted greyscale font buffer to rgba that agg can use. + */ +template +class font_to_rgba +{ + public: + typedef ChildGenerator child_type; + typedef agg::rgba8 color_type; + typedef typename child_type::color_type child_color_type; + typedef agg::span_allocator span_alloc_type; - // prevent copying - _backend_agg_module(const _backend_agg_module&); - _backend_agg_module& operator=(const _backend_agg_module&); + private: + child_type *_gen; + color_type _color; + span_alloc_type _allocator; + + public: + font_to_rgba(child_type *gen, color_type color) : _gen(gen), _color(color) + { + } + + inline void generate(color_type *output_span, int x, int y, unsigned len) + { + _allocator.allocate(len); + child_color_type *input_span = _allocator.span(); + _gen->generate(input_span, x, y, len); + + do { + *output_span = _color; + output_span->a = ((unsigned int)_color.a * (unsigned int)input_span->v) >> 8; + ++output_span; + ++input_span; + } while (--len); + } + + void prepare() + { + _gen->prepare(); + } +}; + +template +inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle) +{ + typedef agg::span_allocator color_span_alloc_type; + typedef agg::span_interpolator_linear<> interpolator_type; + typedef agg::image_accessor_clip image_accessor_type; + typedef agg::span_image_filter_gray image_span_gen_type; + typedef font_to_rgba span_gen_type; + typedef agg::renderer_scanline_aa + renderer_type; + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + + agg::rendering_buffer srcbuf( + image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), (unsigned)image.dim(1)); + agg::pixfmt_gray8 pixf_img(srcbuf); + + agg::trans_affine mtx; + mtx *= agg::trans_affine_translation(0, -image.dim(0)); + mtx *= agg::trans_affine_rotation(-angle * agg::pi / 180.0); + mtx *= agg::trans_affine_translation(x, y); + + agg::path_storage rect; + rect.move_to(0, 0); + rect.line_to(image.dim(1), 0); + rect.line_to(image.dim(1), image.dim(0)); + rect.line_to(0, image.dim(0)); + rect.line_to(0, 0); + agg::conv_transform rect2(rect, mtx); + + agg::trans_affine inv_mtx(mtx); + inv_mtx.invert(); + + agg::image_filter_lut filter; + filter.calculate(agg::image_filter_spline36()); + interpolator_type interpolator(inv_mtx); + color_span_alloc_type sa; + image_accessor_type ia(pixf_img, 0); + image_span_gen_type image_span_generator(ia, interpolator, filter); + span_gen_type output_span_generator(&image_span_generator, gc.color); + renderer_type ri(rendererBase, sa, output_span_generator); + + theRasterizer.add_path(rect2); + agg::render_scanlines(theRasterizer, slineP8, ri); +} + +class span_conv_alpha +{ + public: + typedef agg::rgba8 color_type; + + double m_alpha; + + span_conv_alpha(double alpha) : m_alpha(alpha) + { + } + + void prepare() + { + } + void generate(color_type *span, int x, int y, unsigned len) const + { + do { + span->a = (agg::int8u)((double)span->a * m_alpha); + ++span; + } while (--len); + } +}; + +template +inline void RendererAgg::draw_image(GCAgg &gc, + double x, + double y, + ImageArray &image, + double w, + double h, + agg::trans_affine trans, + bool resize) +{ + double alpha = gc.alpha; + + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); + + agg::rendering_buffer buffer; + buffer.attach( + image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), -(int)image.dim(1) * 4); + pixfmt pixf(buffer); + + if (resize | has_clippath) { + agg::trans_affine mtx; + agg::path_storage rect; + + if (resize) { + mtx *= agg::trans_affine_scaling(1, -1); + mtx *= agg::trans_affine_translation(0, image.dim(0)); + mtx *= agg::trans_affine_scaling(w / (image.dim(1)), h / (image.dim(0))); + mtx *= agg::trans_affine_translation(x, y); + mtx *= trans; + mtx *= agg::trans_affine_scaling(1.0, -1.0); + mtx *= agg::trans_affine_translation(0.0, (double)height); + } else { + mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.dim(0)))); + } + + rect.move_to(0, 0); + rect.line_to(image.dim(1), 0); + rect.line_to(image.dim(1), image.dim(0)); + rect.line_to(0, image.dim(0)); + rect.line_to(0, 0); + + agg::conv_transform rect2(rect, mtx); + + agg::trans_affine inv_mtx(mtx); + inv_mtx.invert(); + + typedef agg::span_allocator color_span_alloc_type; + typedef agg::image_accessor_clip image_accessor_type; + typedef agg::span_interpolator_linear<> interpolator_type; + typedef agg::span_image_filter_rgba_nn + image_span_gen_type; + typedef agg::span_converter span_conv; + + color_span_alloc_type sa; + image_accessor_type ia(pixf, agg::rgba8(0, 0, 0, 0)); + interpolator_type interpolator(inv_mtx); + image_span_gen_type image_span_generator(ia, interpolator); + span_conv_alpha conv_alpha(alpha); + span_conv spans(image_span_generator, conv_alpha); + + if (has_clippath) { + typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; + typedef agg::renderer_base amask_ren_type; + typedef agg::renderer_scanline_aa + renderer_type_alpha; + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + renderer_type_alpha ri(r, sa, spans); + + theRasterizer.add_path(rect2); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ri); + } else { + typedef agg::renderer_base ren_type; + typedef agg::renderer_scanline_aa + renderer_type; + + ren_type r(pixFmt); + renderer_type ri(r, sa, spans); + + theRasterizer.add_path(rect2); + agg::render_scanlines(theRasterizer, slineP8, ri); + } + + } else { + set_clipbox(gc.cliprect, rendererBase); + rendererBase.blend_from( + pixf, 0, (int)x, (int)(height - (y + image.dim(0))), (agg::int8u)(alpha * 255)); + } + + rendererBase.reset_clipping(true); +} + +template +inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, + agg::trans_affine master_transform, + const agg::rect_d &cliprect, + PathIterator &clippath, + const agg::trans_affine &clippath_trans, + PathGenerator &path_generator, + TransformArray &transforms, + OffsetArray &offsets, + const agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + e_offset_position offset_position, + int check_snap, + int has_curves) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removed_t; + typedef PathClipper clipped_t; + typedef PathSnapper snapped_t; + typedef agg::conv_curve snapped_curve_t; + typedef agg::conv_curve curve_t; + + if (offsets.dim(0) != 0 && offsets.dim(1) != 2) { + throw "Offsets array must be Nx2 or empty"; + } + + if (facecolors.dim(0) != 0 && facecolors.dim(1) != 4) { + throw "Facecolors array must be a Nx4 array or empty"; + } + + if (edgecolors.dim(0) != 0 && edgecolors.dim(1) != 4) { + throw "Edgecolors array must by Nx4 or empty"; + } + + if (transforms.dim(0) != 0 && (transforms.dim(1) != 3 || transforms.dim(2) != 3)) { + throw "Transforms array must by Nx3x3 or empty"; + } + + size_t Npaths = path_generator.num_paths(); + size_t Noffsets = offsets.size(); + size_t N = std::max(Npaths, Noffsets); + + size_t Ntransforms = transforms.size(); + size_t Nfacecolors = facecolors.size(); + size_t Nedgecolors = edgecolors.size(); + size_t Nlinewidths = linewidths.size(); + size_t Nlinestyles = std::min(linestyles.size(), N); + size_t Naa = antialiaseds.size(); + + if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { + return; + } + + // Handle any clipping globally + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(cliprect, theRasterizer); + bool has_clippath = render_clippath(clippath, clippath_trans); + + // Set some defaults, assuming no face or edge + gc.linewidth = 0.0; + facepair_t face; + face.first = Nfacecolors != 0; + agg::trans_affine trans; + + for (int i = 0; i < (int)N; ++i) { + typename PathGenerator::path_iterator path = path_generator(i); + + if (Ntransforms) { + typename TransformArray::sub_t subtrans = transforms[i % Ntransforms]; + trans = agg::trans_affine(subtrans(0, 0), + subtrans(1, 0), + subtrans(0, 1), + subtrans(1, 1), + subtrans(0, 2), + subtrans(1, 2)); + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + if (offset_position == OFFSET_POSITION_DATA) { + trans = agg::trans_affine_translation(xo, yo) * trans; + } else { + trans *= agg::trans_affine_translation(xo, yo); + } + } + + // These transformations must be done post-offsets + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + + if (Nfacecolors) { + typename ColorArray::sub_t facecolor = facecolors[i % Nfacecolors]; + face.second = agg::rgba(facecolor(0), facecolor(1), facecolor(2), facecolor(3)); + } + + if (Nedgecolors) { + typename ColorArray::sub_t edgecolor = edgecolors[i % Nedgecolors]; + gc.color = agg::rgba(edgecolor(0), edgecolor(1), edgecolor(2), edgecolor(3)); + + if (Nlinewidths) { + gc.linewidth = linewidths(i % Nlinewidths); + } else { + gc.linewidth = 1.0; + } + if (Nlinestyles) { + gc.dashes = linestyles[i % Nlinestyles]; + } + } + + bool do_clip = !face.first && !gc.has_hatchpath() && !has_curves; + + if (check_snap) { + gc.isaa = antialiaseds(i % Naa); + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_curves); + clipped_t clipped(nan_removed, do_clip, width, height); + snapped_t snapped( + clipped, gc.snap_mode, path.total_vertices(), points_to_pixels(gc.linewidth)); + if (has_curves) { + snapped_curve_t curve(snapped); + _draw_path(curve, has_clippath, face, gc); + } else { + _draw_path(snapped, has_clippath, face, gc); + } + } else { + gc.isaa = antialiaseds(i % Naa); + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_curves); + clipped_t clipped(nan_removed, do_clip, width, height); + if (has_curves) { + curve_t curve(clipped); + _draw_path(curve, has_clippath, face, gc); + } else { + _draw_path(clipped, has_clippath, face, gc); + } + } + } +} + +template +inline void RendererAgg::draw_path_collection(GCAgg &gc, + agg::trans_affine &master_transform, + PathGenerator &path, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + ColorArray &edgecolors, + LineWidthArray &linewidths, + DashesVector &linestyles, + AntialiasedArray &antialiaseds, + e_offset_position offset_position) +{ + _draw_path_collection_generic(gc, + master_transform, + gc.cliprect, + gc.clippath.path, + gc.clippath.trans, + path, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + linestyles, + antialiaseds, + offset_position, + 1, + 1); +} + +template +class QuadMeshGenerator +{ + unsigned m_meshWidth; + unsigned m_meshHeight; + CoordinateArray m_coordinates; + + class QuadMeshPathIterator + { + unsigned m_iterator; + unsigned m_m, m_n; + const CoordinateArray *m_coordinates; + + public: + QuadMeshPathIterator(unsigned m, unsigned n, const CoordinateArray *coordinates) + : m_iterator(0), m_m(m), m_n(n), m_coordinates(coordinates) + { + } + + private: + inline unsigned vertex(unsigned idx, double *x, double *y) + { + size_t m = m_m + ((idx & 0x2) >> 1); + size_t n = m_n + (((idx + 1) & 0x2) >> 1); + *x = (*m_coordinates)(n, m, 0); + *y = (*m_coordinates)(n, m, 1); + return (idx) ? agg::path_cmd_line_to : agg::path_cmd_move_to; + } + + public: + inline unsigned vertex(double *x, double *y) + { + if (m_iterator >= total_vertices()) { + return agg::path_cmd_stop; + } + return vertex(m_iterator++, x, y); + } + + inline void rewind(unsigned path_id) + { + m_iterator = path_id; + } + + inline unsigned total_vertices() + { + return 5; + } + + inline bool should_simplify() + { + return false; + } + }; + + public: + typedef QuadMeshPathIterator path_iterator; + + inline QuadMeshGenerator(unsigned meshWidth, unsigned meshHeight, CoordinateArray &coordinates) + : m_meshWidth(meshWidth), m_meshHeight(meshHeight), m_coordinates(coordinates) + { + } + + inline size_t num_paths() const + { + return m_meshWidth * m_meshHeight; + } + + inline path_iterator operator()(size_t i) const + { + return QuadMeshPathIterator(i % m_meshWidth, i / m_meshWidth, &m_coordinates); + } }; +template +inline void RendererAgg::draw_quad_mesh(GCAgg &gc, + agg::trans_affine &master_transform, + size_t mesh_width, + size_t mesh_height, + CoordinateArray &coordinates, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + ColorArray &facecolors, + bool antialiased, + ColorArray &edgecolors) +{ + QuadMeshGenerator path_generator(mesh_width, mesh_height, coordinates); + + array::empty transforms; + array::scalar linewidths(points_to_pixels(gc.linewidth)); + array::scalar antialiaseds(antialiased); + DashesVector linestyles; + ColorArray *edgecolors_ptr = &edgecolors; + + if (edgecolors.size() == 0) { + if (antialiased) { + edgecolors_ptr = &facecolors; + } + } + _draw_path_collection_generic(gc, + master_transform, + gc.cliprect, + gc.clippath.path, + gc.clippath.trans, + path_generator, + transforms, + offsets, + offset_trans, + facecolors, + *edgecolors_ptr, + linewidths, + linestyles, + antialiaseds, + OFFSET_POSITION_FIGURE, + 0, + 0); +} + +template +inline void RendererAgg::_draw_gouraud_triangle(PointArray &points, + ColorArray &colors, + agg::trans_affine trans, + bool has_clippath) +{ + typedef agg::rgba8 color_t; + typedef agg::span_gouraud_rgba span_gen_t; + typedef agg::span_allocator span_alloc_t; + + trans *= agg::trans_affine_scaling(1.0, -1.0); + trans *= agg::trans_affine_translation(0.0, (double)height); + + double tpoints[3][2]; + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 2; ++j) { + tpoints[i][j] = points(i, j); + } + trans.transform(&tpoints[i][0], &tpoints[i][1]); + } + + span_alloc_t span_alloc; + span_gen_t span_gen; + + span_gen.colors(agg::rgba(colors(0, 0), colors(0, 1), colors(0, 2), colors(0, 3)), + agg::rgba(colors(1, 0), colors(1, 1), colors(1, 2), colors(1, 3)), + agg::rgba(colors(2, 0), colors(2, 1), colors(2, 2), colors(2, 3))); + span_gen.triangle(tpoints[0][0], + tpoints[0][1], + tpoints[1][0], + tpoints[1][1], + tpoints[2][0], + tpoints[2][1], + 0.5); + + theRasterizer.add_path(span_gen); + + if (has_clippath) { + typedef agg::pixfmt_amask_adaptor pixfmt_amask_type; + typedef agg::renderer_base amask_ren_type; + typedef agg::renderer_scanline_aa + amask_aa_renderer_type; + + pixfmt_amask_type pfa(pixFmt, alphaMask); + amask_ren_type r(pfa); + amask_aa_renderer_type ren(r, span_alloc, span_gen); + agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); + } else { + agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, span_alloc, span_gen); + } +} + +template +inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans) +{ + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); + + if (points.dim(0) != 3 || points.dim(1) != 2) { + throw "points must be a 3x2 array"; + } + + if (colors.dim(0) != 3 || colors.dim(1) != 4) { + throw "colors must be a 3x4 array"; + } + + _draw_gouraud_triangle(points, colors, trans, has_clippath); +} + +template +inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, + PointArray &points, + ColorArray &colors, + agg::trans_affine &trans) +{ + theRasterizer.reset_clipping(); + rendererBase.reset_clipping(true); + set_clipbox(gc.cliprect, theRasterizer); + bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); + + if (points.dim(1) != 3 || points.dim(2) != 2) { + throw "points must be a Nx3x2 array"; + } + + if (colors.dim(1) != 3 || colors.dim(2) != 4) { + throw "colors must be a Nx3x4 array"; + } + + if (points.dim(0) != colors.dim(0)) { + throw "points and colors arrays must be the same length"; + } + + for (int i = 0; i < points.dim(0); ++i) { + typename PointArray::sub_t point = points[i]; + typename ColorArray::sub_t color = colors[i]; + + _draw_gouraud_triangle(point, color, trans, has_clippath); + } +} + +template +void RendererAgg::set_clipbox(const agg::rect_d &cliprect, R &rasterizer) +{ + // set the clip rectangle from the gc + + if (cliprect.x1 != 0.0 || cliprect.y1 != 0.0 || cliprect.x2 != 0.0 || cliprect.y2 != 0.0) { + rasterizer.clip_box(std::max(int(floor(cliprect.x1 + 0.5)), 0), + std::max(int(floor(height - cliprect.y1 + 0.5)), 0), + std::min(int(floor(cliprect.x2 + 0.5)), int(width)), + std::min(int(floor(height - cliprect.y2 + 0.5)), int(height))); + } else { + rasterizer.clip_box(0, 0, width, height); + } +} #endif diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h new file mode 100644 index 000000000000..cea2b7e14f0f --- /dev/null +++ b/src/_backend_agg_basic_types.h @@ -0,0 +1,124 @@ +#ifndef __BACKEND_AGG_BASIC_TYPES_H__ +#define __BACKEND_AGG_BASIC_TYPES_H__ + +/* Contains some simple types from the Agg backend that are also used + by other modules */ + +#include + +#include "agg_color_rgba.h" +#include "agg_math_stroke.h" +#include "path_converters.h" + +#include "py_adaptors.h" + +struct ClipPath +{ + py::PathIterator path; + agg::trans_affine trans; +}; + +struct SketchParams +{ + double scale; + double length; + double randomness; +}; + +class Dashes +{ + typedef std::vector > dash_t; + double dash_offset; + dash_t dashes; + + public: + double get_dash_offset() const + { + return dash_offset; + } + void set_dash_offset(double x) + { + dash_offset = x; + } + void add_dash_pair(double length, double skip) + { + dashes.push_back(std::make_pair(length, skip)); + } + size_t size() const + { + return dashes.size(); + } + + template + void dash_to_stroke(T &stroke, double dpi, bool isaa) + { + for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) { + double val0 = i->first; + double val1 = i->second; + val0 = val0 * dpi / 72.0; + val1 = val1 * dpi / 72.0; + if (!isaa) { + val0 = (int)val0 + 0.5; + val1 = (int)val1 + 0.5; + } + stroke.add_dash(val0, val1); + } + } +}; + +typedef std::vector DashesVector; + +enum e_offset_position { + OFFSET_POSITION_FIGURE, + OFFSET_POSITION_DATA +}; + +class GCAgg +{ + public: + GCAgg() + : linewidth(1.0), + alpha(1.0), + cap(agg::butt_cap), + join(agg::round_join), + snap_mode(SNAP_FALSE) + { + } + + ~GCAgg() + { + } + + double linewidth; + double alpha; + bool forced_alpha; + agg::rgba color; + bool isaa; + + agg::line_cap_e cap; + agg::line_join_e join; + + agg::rect_d cliprect; + + ClipPath clippath; + + Dashes dashes; + + e_snap_mode snap_mode; + + py::PathIterator hatchpath; + + SketchParams sketch; + + bool has_hatchpath() + { + return hatchpath.total_vertices(); + } + + private: + // prevent copying + GCAgg(const GCAgg &); + GCAgg &operator=(const GCAgg &); +}; + +#endif diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp new file mode 100644 index 000000000000..1e9b62e3b31d --- /dev/null +++ b/src/_backend_agg_wrapper.cpp @@ -0,0 +1,730 @@ +#include "_backend_agg_wrapper.h" + +/********************************************************************** + * BufferRegion + * */ + +static PyObject *PyBufferRegion_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyBufferRegion *self; + self = (PyBufferRegion *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static void PyBufferRegion_dealloc(PyBufferRegion *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyBufferRegion_to_string(PyBufferRegion *self, PyObject *args, PyObject *kwds) +{ + return PyBytes_FromStringAndSize((const char *)self->x->get_data(), + self->x->get_height() * self->x->get_stride()); +} + +/* TODO: This doesn't seem to be used internally. Remove? */ + +static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args, PyObject *kwds) +{ + int x; + if (!PyArg_ParseTuple(args, "i:set_x", &x)) { + return NULL; + } + self->x->get_rect().x1 = x; + + Py_RETURN_NONE; +} + +static PyObject *PyBufferRegion_set_y(PyBufferRegion *self, PyObject *args, PyObject *kwds) +{ + int y; + if (!PyArg_ParseTuple(args, "i:set_y", &y)) { + return NULL; + } + self->x->get_rect().y1 = y; + + Py_RETURN_NONE; +} + +static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args, PyObject *kwds) +{ + agg::rect_i rect = self->x->get_rect(); + + return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); +} + +static PyObject *PyBufferRegion_to_string_argb(PyBufferRegion *self, PyObject *args, PyObject *kwds) +{ + PyObject *bufobj; + uint8_t *buf; + + bufobj = PyBytes_FromStringAndSize(NULL, self->x->get_height() * self->x->get_stride()); + buf = (uint8_t *)PyBytes_AS_STRING(bufobj); + + CALL_CPP_CLEANUP("to_string_argb", (self->x->to_string_argb(buf)), Py_DECREF(bufobj)); + + return bufobj; +} + +int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) +{ + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = self->x->get_data(); + buf->len = self->x->get_width() * self->x->get_height() * 4; + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 3; + self->shape[0] = self->x->get_height(); + self->shape[1] = self->x->get_width(); + self->shape[2] = 4; + buf->shape = self->shape; + self->strides[0] = self->x->get_width() * 4; + self->strides[1] = 4; + self->strides[2] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject PyBufferRegionType; + +static PyTypeObject *PyBufferRegion_init_type(PyObject *m, PyTypeObject *type) +{ + static PyMethodDef methods[] = { + { "to_string", (PyCFunction)PyBufferRegion_to_string, METH_NOARGS, NULL }, + { "to_string_argb", (PyCFunction)PyBufferRegion_to_string_argb, METH_NOARGS, NULL }, + { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, + { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, + { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, + { NULL } + }; + + static PyBufferProcs buffer_procs; + memset(&buffer_procs, 0, sizeof(PyBufferProcs)); + buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib.backends._backend_agg.BufferRegion"; + type->tp_basicsize = sizeof(PyBufferRegion); + type->tp_dealloc = (destructor)PyBufferRegion_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER; + type->tp_methods = methods; + type->tp_new = PyBufferRegion_new; + type->tp_as_buffer = &buffer_procs; + + if (PyType_Ready(type) < 0) { + return NULL; + } + + /* Don't need to add to module, since you can't create buffer + regions directly from Python */ + + return type; +} + +/********************************************************************** + * RendererAgg + * */ + +static PyObject *PyRendererAgg_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyRendererAgg *self; + self = (PyRendererAgg *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static int PyRendererAgg_init(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + unsigned int width; + unsigned int height; + double dpi; + int debug = 0; + + if (!PyArg_ParseTuple(args, "IId|i:RendererAgg", &width, &height, &dpi, &debug)) { + return -1; + } + + if (dpi <= 0.0) { + PyErr_SetString(PyExc_ValueError, "dpi must be positive"); + return -1; + } + + CALL_CPP_INIT("RendererAgg", self->x = new RendererAgg(width, height, dpi)) + + return 0; +} + +static void PyRendererAgg_dealloc(PyRendererAgg *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + py::PathIterator path; + agg::trans_affine trans; + PyObject *faceobj = NULL; + agg::rgba face; + + if (!PyArg_ParseTuple(args, + "O&O&O&|O:draw_path", + &convert_gcagg, + &gc, + &convert_path, + &path, + &convert_trans_affine, + &trans, + &faceobj)) { + return NULL; + } + + if (!convert_face(faceobj, gc, &face)) { + return NULL; + } + + CALL_CPP("draw_path", (self->x->draw_path(gc, path, trans, face))); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view image; + double x; + double y; + double angle; + GCAgg gc; + + if (!PyArg_ParseTuple(args, + "O&dddO&:draw_text_image", + &numpy::convert_array_contiguous, + &image, + &x, + &y, + &angle, + &convert_gcagg, + &gc)) { + return NULL; + } + + CALL_CPP("draw_text_image", (self->x->draw_text_image(gc, image, x, y, angle))); + + Py_RETURN_NONE; +} + +PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + py::PathIterator marker_path; + agg::trans_affine marker_path_trans; + py::PathIterator path; + agg::trans_affine trans; + PyObject *faceobj = NULL; + agg::rgba face; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&O&|O:draw_markers", + &convert_gcagg, + &gc, + &convert_path, + &marker_path, + &convert_trans_affine, + &marker_path_trans, + &convert_path, + &path, + &convert_trans_affine, + &trans, + &faceobj)) { + return NULL; + } + + if (!convert_face(faceobj, gc, &face)) { + return NULL; + } + + CALL_CPP("draw_markers", + (self->x->draw_markers(gc, marker_path, marker_path_trans, path, trans, face))); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_image(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + double x; + double y; + numpy::array_view image; + double w = 0; + double h = 0; + agg::trans_affine trans; + bool resize = false; + + if (!PyArg_ParseTuple(args, + "O&ddO&|ddO&:draw_image", + &convert_gcagg, + &gc, + &x, + &y, + &numpy::convert_array_contiguous, + &image, + &w, + &h, + &convert_trans_affine, + &trans)) { + return NULL; + } + + if (PyTuple_Size(args) == 4) { + x = mpl_round(x); + y = mpl_round(y); + } else { + resize = true; + } + + CALL_CPP("draw_image", (self->x->draw_image(gc, x, y, image, w, h, trans, resize))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + agg::trans_affine master_transform; + PyObject *pathobj; + numpy::array_view transforms; + numpy::array_view offsets; + agg::trans_affine offset_trans; + numpy::array_view facecolors; + numpy::array_view edgecolors; + numpy::array_view linewidths; + DashesVector dashes; + numpy::array_view antialiaseds; + PyObject *ignored; + e_offset_position offset_position; + + if (!PyArg_ParseTuple(args, + "O&O&OO&O&O&O&O&O&O&O&OO&:draw_path_collection", + &convert_gcagg, + &gc, + &convert_trans_affine, + &master_transform, + &pathobj, + &numpy::convert_array, + &transforms, + &numpy::convert_array, + &offsets, + &convert_trans_affine, + &offset_trans, + &numpy::convert_array, + &facecolors, + &numpy::convert_array, + &edgecolors, + &numpy::convert_array, + &linewidths, + &convert_dashes_vector, + &dashes, + &numpy::convert_array, + &antialiaseds, + &ignored, + &convert_offset_position, + &offset_position)) { + return NULL; + } + + try + { + py::PathGenerator path(pathobj); + + CALL_CPP("draw_path_collection", + (self->x->draw_path_collection(gc, + master_transform, + path, + transforms, + offsets, + offset_trans, + facecolors, + edgecolors, + linewidths, + dashes, + antialiaseds, + offset_position))); + } + catch (py::exception &e) + { + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + agg::trans_affine master_transform; + size_t mesh_width; + size_t mesh_height; + numpy::array_view coordinates; + numpy::array_view offsets; + agg::trans_affine offset_trans; + numpy::array_view facecolors; + int antialiased; + numpy::array_view edgecolors; + + if (!PyArg_ParseTuple(args, + "O&O&IIO&O&O&O&iO&:draw_quad_mesh", + &convert_gcagg, + &gc, + &convert_trans_affine, + &master_transform, + &mesh_width, + &mesh_height, + &numpy::convert_array, + &coordinates, + &numpy::convert_array, + &offsets, + &convert_trans_affine, + &offset_trans, + &numpy::convert_array, + &facecolors, + &antialiased, + &numpy::convert_array, + &edgecolors)) { + return NULL; + } + + CALL_CPP("draw_quad_mesh", + (self->x->draw_quad_mesh(gc, + master_transform, + mesh_width, + mesh_height, + coordinates, + offsets, + offset_trans, + facecolors, + antialiased, + edgecolors))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + numpy::array_view points; + numpy::array_view colors; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&|O:draw_gouraud_triangle", + &convert_gcagg, + &gc, + &numpy::convert_array, + &points, + &numpy::convert_array, + &colors, + &convert_trans_affine, + &trans)) { + return NULL; + } + + CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans))); + + Py_RETURN_NONE; +} + +static PyObject * +PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + GCAgg gc; + numpy::array_view points; + numpy::array_view colors; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&|O:draw_gouraud_triangles", + &convert_gcagg, + &gc, + &numpy::convert_array, + &points, + &numpy::convert_array, + &colors, + &convert_trans_affine, + &trans)) { + return NULL; + } + + CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans)); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_tostring_rgb(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + PyObject *buffobj = NULL; + + buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 3); + if (buffobj == NULL) { + return NULL; + } + + CALL_CPP_CLEANUP("tostring_rgb", + (self->x->tostring_rgb((uint8_t *)PyBytes_AS_STRING(buffobj))), + Py_DECREF(buffobj)); + + return buffobj; +} + +static PyObject *PyRendererAgg_tostring_argb(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + PyObject *buffobj = NULL; + + buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 4); + if (buffobj == NULL) { + return NULL; + } + + CALL_CPP_CLEANUP("tostring_argb", + (self->x->tostring_argb((uint8_t *)PyBytes_AS_STRING(buffobj))), + Py_DECREF(buffobj)); + + return buffobj; +} + +static PyObject *PyRendererAgg_tostring_bgra(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + PyObject *buffobj = NULL; + + buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 4); + if (buffobj == NULL) { + return NULL; + } + + CALL_CPP_CLEANUP("to_string_bgra", + (self->x->tostring_bgra((uint8_t *)PyBytes_AS_STRING(buffobj))), + Py_DECREF(buffobj)); + + return buffobj; +} + +static PyObject * +PyRendererAgg_get_content_extents(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + agg::rect_i extents; + + CALL_CPP("get_content_extents", (extents = self->x->get_content_extents())); + + return Py_BuildValue( + "iiii", extents.x1, extents.y1, extents.x2 - extents.x1, extents.y2 - extents.y1); +} + +static PyObject *PyRendererAgg_buffer_rgba(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ +#if PY3K + return PyBytes_FromStringAndSize((const char *)self->x->pixBuffer, + self->x->get_width() * self->x->get_height() * 4); +#else + return PyBuffer_FromReadWriteMemory(self->x->pixBuffer, + self->x->get_width() * self->x->get_height() * 4); +#endif +} + +int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags) +{ + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = self->x->pixBuffer; + buf->len = self->x->get_width() * self->x->get_height() * 4; + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 3; + self->shape[0] = self->x->get_height(); + self->shape[1] = self->x->get_width(); + self->shape[2] = 4; + buf->shape = self->shape; + self->strides[0] = self->x->get_width() * 4; + self->strides[1] = 4; + self->strides[2] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + CALL_CPP("clear", self->x->clear()); + + Py_RETURN_NONE; +} + +static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + agg::rect_d bbox; + BufferRegion *reg; + PyObject *regobj; + + if (!PyArg_ParseTuple(args, "O&:copy_from_bbox", &convert_rect, &bbox)) { + return 0; + } + + CALL_CPP("copy_from_bbox", (reg = self->x->copy_from_bbox(bbox))); + + regobj = PyBufferRegion_new(&PyBufferRegionType, NULL, NULL); + ((PyBufferRegion *)regobj)->x = reg; + + return regobj; +} + +static PyObject *PyRendererAgg_restore_region(PyRendererAgg *self, PyObject *args, PyObject *kwds) +{ + PyBufferRegion *regobj; + int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0; + + if (!PyArg_ParseTuple(args, + "O!|iiiiii:restore_region", + &PyBufferRegionType, + ®obj, + &xx1, + &yy1, + &xx2, + &yy2, + &x, + &y)) { + return 0; + } + + if (PySequence_Size(args) == 1) { + CALL_CPP("restore_region", (self->x->restore_region(*(regobj->x)))); + } else { + CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y)); + } + + Py_RETURN_NONE; +} + +PyTypeObject PyRendererAggType; + +static PyTypeObject *PyRendererAgg_init_type(PyObject *m, PyTypeObject *type) +{ + static PyMethodDef methods[] = { + {"draw_path", (PyCFunction)PyRendererAgg_draw_path, METH_VARARGS, NULL}, + {"draw_markers", (PyCFunction)PyRendererAgg_draw_markers, METH_VARARGS, NULL}, + {"draw_text_image", (PyCFunction)PyRendererAgg_draw_text_image, METH_VARARGS, NULL}, + {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL}, + {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL}, + {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL}, + {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL}, + {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, + + {"tostring_rgb", (PyCFunction)PyRendererAgg_tostring_rgb, METH_NOARGS, NULL}, + {"tostring_argb", (PyCFunction)PyRendererAgg_tostring_argb, METH_NOARGS, NULL}, + {"tostring_bgra", (PyCFunction)PyRendererAgg_tostring_bgra, METH_NOARGS, NULL}, + {"get_content_extents", (PyCFunction)PyRendererAgg_get_content_extents, METH_NOARGS, NULL}, + {"buffer_rgba", (PyCFunction)PyRendererAgg_buffer_rgba, METH_NOARGS, NULL}, + {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL}, + + {"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL}, + {"restore_region", (PyCFunction)PyRendererAgg_restore_region, METH_VARARGS, NULL}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + memset(&buffer_procs, 0, sizeof(PyBufferProcs)); + buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib.backends._backend_agg.RendererAgg"; + type->tp_basicsize = sizeof(PyRendererAgg); + type->tp_dealloc = (destructor)PyRendererAgg_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER; + type->tp_methods = methods; + type->tp_init = (initproc)PyRendererAgg_init; + type->tp_new = PyRendererAgg_new; + type->tp_as_buffer = &buffer_procs; + + if (PyType_Ready(type) < 0) { + return NULL; + } + + if (PyModule_AddObject(m, "RendererAgg", (PyObject *)type)) { + return NULL; + } + + return type; +} + +extern "C" { + +struct module_state +{ +/* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif +}; + +#if PY3K +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_backend_agg", + NULL, + sizeof(struct module_state), + NULL, + NULL, + NULL, + NULL, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__backend_agg(void) + +#else +#define INITERROR return + +PyMODINIT_FUNC init_backend_agg(void) +#endif + +{ + PyObject *m; + +#if PY3K + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("_backend_agg", NULL, NULL); +#endif + + if (m == NULL) { + INITERROR; + } + + import_array(); + + if (!PyRendererAgg_init_type(m, &PyRendererAggType)) { + INITERROR; + } + + if (!PyBufferRegion_init_type(m, &PyBufferRegionType)) { + INITERROR; + } + +#if PY3K + return m; +#endif +} + +} // extern "C" diff --git a/src/_backend_agg_wrapper.h b/src/_backend_agg_wrapper.h new file mode 100644 index 000000000000..f625b4ce43b7 --- /dev/null +++ b/src/_backend_agg_wrapper.h @@ -0,0 +1,29 @@ +#ifndef __BACKEND_AGG_WRAPPER_H__ +#define __BACKEND_AGG_WRAPPER_H__ + +#include "mplutils.h" +#include "py_converters.h" +#include "_backend_agg.h" + +extern "C" { + +typedef struct +{ + PyObject_HEAD; + RendererAgg *x; + Py_ssize_t shape[3]; + Py_ssize_t strides[3]; + Py_ssize_t suboffsets[3]; +} PyRendererAgg; + +typedef struct +{ + PyObject_HEAD; + BufferRegion *x; + Py_ssize_t shape[3]; + Py_ssize_t strides[3]; + Py_ssize_t suboffsets[3]; +} PyBufferRegion; +} + +#endif diff --git a/src/_backend_gdk.c b/src/_backend_gdk.c index 7d52c652586e..9ccc3c5f0acb 100644 --- a/src/_backend_gdk.c +++ b/src/_backend_gdk.c @@ -7,12 +7,10 @@ #include - static PyTypeObject *_PyGdkPixbuf_Type; #define PyGdkPixbuf_Type (*_PyGdkPixbuf_Type) -static PyObject * -pixbuf_get_pixels_array(PyObject *self, PyObject *args) +static PyObject *pixbuf_get_pixels_array(PyObject *self, PyObject *args) { /* 1) read in Python pixbuf, get the underlying gdk_pixbuf */ PyGObject *py_pixbuf; @@ -20,9 +18,8 @@ pixbuf_get_pixels_array(PyObject *self, PyObject *args) PyArrayObject *array; npy_intp dims[3] = { 0, 0, 3 }; - if (!PyArg_ParseTuple(args, "O!:pixbuf_get_pixels_array", - &PyGdkPixbuf_Type, &py_pixbuf)) - return NULL; + if (!PyArg_ParseTuple(args, "O!:pixbuf_get_pixels_array", &PyGdkPixbuf_Type, &py_pixbuf)) + return NULL; gdk_pixbuf = GDK_PIXBUF(py_pixbuf->obj); @@ -35,8 +32,8 @@ pixbuf_get_pixels_array(PyObject *self, PyObject *args) if (gdk_pixbuf_get_has_alpha(gdk_pixbuf)) dims[2] = 4; - array = (PyArrayObject *)PyArray_SimpleNewFromData(3, dims, NPY_UBYTE, - (char *)gdk_pixbuf_get_pixels(gdk_pixbuf)); + array = (PyArrayObject *)PyArray_SimpleNewFromData( + 3, dims, PyArray_UBYTE, (char *)gdk_pixbuf_get_pixels(gdk_pixbuf)); if (array == NULL) return NULL; @@ -52,12 +49,10 @@ static PyMethodDef _backend_gdk_functions[] = { { NULL, NULL, 0 } }; -PyMODINIT_FUNC -init_backend_gdk(void) +PyMODINIT_FUNC init_backend_gdk(void) { PyObject *mod; - mod = Py_InitModule("matplotlib.backends._backend_gdk", - _backend_gdk_functions); + mod = Py_InitModule("matplotlib.backends._backend_gdk", _backend_gdk_functions); import_array(); init_pygtk(); diff --git a/src/_gtkagg.cpp b/src/_gtkagg.cpp index e2c112b8acda..af16a78f59fa 100644 --- a/src/_gtkagg.cpp +++ b/src/_gtkagg.cpp @@ -1,153 +1,164 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ +#include + #include #include -#include -#include -#include -#include -#include -#include -#include - #include "agg_basics.h" -#include "numpy/arrayobject.h" -#include "_backend_agg.h" -#include "agg_py_transforms.h" +#include "agg_pixfmt_rgba.h" +#include "agg_renderer_base.h" +#include "agg_rendering_buffer.h" -// the extension module -class _gtkagg_module : public Py::ExtensionModule<_gtkagg_module> +#include "numpy_cpp.h" +#include "py_converters.h" + +static PyObject *Py_agg_to_gtk_drawable(PyObject *self, PyObject *args, PyObject *kwds) { -public: - _gtkagg_module() - : Py::ExtensionModule<_gtkagg_module>("_gtkagg") - { - add_varargs_method("agg_to_gtk_drawable", - &_gtkagg_module::agg_to_gtk_drawable, - "Draw to a gtk drawable from a agg buffer."); - initialize("The _gtkagg module"); + typedef agg::pixfmt_rgba32_plain pixfmt; + typedef agg::renderer_base renderer_base; + + PyGObject *py_drawable; + numpy::array_view buffer; + agg::rect_d rect; + + // args are gc, renderer, bbox where bbox is a transforms BBox + // (possibly None). If bbox is None, blit the entire agg buffer + // to gtk. If bbox is not None, blit only the region defined by + // the bbox + + if (!PyArg_ParseTuple(args, + "OO&O&:agg_to_gtk_drawable", + &py_drawable, + &numpy::convert_array, + &buffer, + &convert_rect, + &rect)) { + return NULL; } - virtual ~_gtkagg_module() {} - -private: - - Py::Object agg_to_gtk_drawable(const Py::Tuple &args) - { - // args are gc, renderer, bbox where bbox is a transforms BBox - // (possibly None). If bbox is None, blit the entire agg buffer - // to gtk. If bbox is not None, blit only the region defined by - // the bbox - args.verify_length(3); - - PyGObject *py_drawable = (PyGObject *)(args[0].ptr()); - RendererAgg* aggRenderer = static_cast(args[1].ptr()); - - GdkDrawable *drawable = GDK_DRAWABLE(py_drawable->obj); - GdkGC* gc = gdk_gc_new(drawable); + if (buffer.dim(2) != 4) { + PyErr_SetString(PyExc_ValueError, "Invalid image buffer. Must be NxMx4."); + return NULL; + } - int srcstride = aggRenderer->get_width() * 4; - int srcwidth = (int)aggRenderer->get_width(); - int srcheight = (int)aggRenderer->get_height(); + GdkDrawable *drawable = GDK_DRAWABLE(py_drawable->obj); + GdkGC *gc = gdk_gc_new(drawable); + + int srcstride = buffer.dim(1) * 4; + int srcwidth = buffer.dim(1); + int srcheight = buffer.dim(0); + + // these three will be overridden below + int destx = 0; + int desty = 0; + int destwidth = 1; + int destheight = 1; + int deststride = 1; + + std::vector destbuffer; + agg::int8u *destbufferptr; + + if (rect.x1 == 0.0 && rect.x2 == 0.0 && rect.y1 == 0.0 && rect.y2 == 0.0) { + // bbox is None; copy the entire image + destbufferptr = (agg::int8u *)buffer; + destwidth = srcwidth; + destheight = srcheight; + deststride = srcstride; + } else { + destx = (int)rect.x1; + desty = srcheight - (int)rect.y2; + destwidth = (int)(rect.x2 - rect.x1); + destheight = (int)(rect.y2 - rect.y1); + deststride = destwidth * 4; + destbuffer.reserve(destheight * deststride); + destbufferptr = &destbuffer[0]; + + agg::rendering_buffer destrbuf; + destrbuf.attach(destbufferptr, destwidth, destheight, deststride); + pixfmt destpf(destrbuf); + renderer_base destrb(destpf); + + agg::rendering_buffer srcrbuf; + srcrbuf.attach((agg::int8u *)buffer, buffer.dim(1), buffer.dim(0), buffer.dim(1) * 4); + + agg::rect_base region(destx, desty, (int)rect.x2, srcheight - (int)rect.y1); + destrb.copy_from(srcrbuf, ®ion, -destx, -desty); + } - // these three will be overridden below - int destx = 0; - int desty = 0; - int destwidth = 1; - int destheight = 1; - int deststride = 1; + gdk_draw_rgb_32_image(drawable, + gc, + destx, + desty, + destwidth, + destheight, + GDK_RGB_DITHER_NORMAL, + destbufferptr, + deststride); + gdk_gc_destroy(gc); - bool needfree = false; + Py_RETURN_NONE; +} - agg::int8u *destbuffer = NULL; +static PyMethodDef module_methods[] = { + {"agg_to_gtk_drawable", (PyCFunction)Py_agg_to_gtk_drawable, METH_VARARGS, NULL}, + NULL +}; - if (args[2].ptr() == Py_None) - { - //bbox is None; copy the entire image - destbuffer = aggRenderer->pixBuffer; - destwidth = srcwidth; - destheight = srcheight; - deststride = srcstride; - } - else - { - //bbox is not None; copy the image in the bbox - PyObject* clipbox = args[2].ptr(); - double l, b, r, t; - - if (!py_convert_bbox(clipbox, l, b, r, t)) - { - throw Py::TypeError - ("Argument 3 to agg_to_gtk_drawable must be a Bbox object."); - } - - destx = (int)l; - desty = srcheight - (int)t; - destwidth = (int)(r - l); - destheight = (int)(t - b); - deststride = destwidth * 4; - - needfree = true; - destbuffer = new agg::int8u[deststride*destheight]; - if (destbuffer == NULL) - { - throw Py::MemoryError("_gtkagg could not allocate memory for destbuffer"); - } - - agg::rendering_buffer destrbuf; - destrbuf.attach(destbuffer, destwidth, destheight, deststride); - pixfmt destpf(destrbuf); - renderer_base destrb(destpf); - - //destrb.clear(agg::rgba(1, 1, 1, 0)); - - agg::rect_base region(destx, desty, (int)r, srcheight - (int)b); - destrb.copy_from(aggRenderer->renderingBuffer, ®ion, - -destx, -desty); - } +extern "C" { - /*std::cout << desty << " " - << destheight << " " - << srcheight << std::endl;*/ + struct module_state + { + /* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif + }; + +#if PY3K + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_gtkagg", + NULL, + sizeof(struct module_state), + module_methods, + NULL, + NULL, + NULL, + NULL + }; + +#define INITERROR return NULL + + PyMODINIT_FUNC PyInit__gtkagg(void) + +#else +#define INITERROR return + + PyMODINIT_FUNC init_gtkagg(void) +#endif + { + PyObject *m; - //gdk_rgb_init(); - gdk_draw_rgb_32_image(drawable, gc, destx, desty, - destwidth, - destheight, - GDK_RGB_DITHER_NORMAL, - destbuffer, - deststride); +#if PY3K + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("_gtkagg", module_methods, NULL); +#endif - gdk_gc_destroy(gc); - if (needfree) - { - delete [] destbuffer; + if (m == NULL) { + INITERROR; } - return Py::Object(); + init_pygobject(); + init_pygtk(); + import_array(); +#if PY3K + return m; +#endif } -}; - -PyMODINIT_FUNC -init_gtkagg(void) -{ - init_pygobject(); - init_pygtk(); - - import_array(); - //suppress unused warning by creating in two lines - static _gtkagg_module* _gtkagg = NULL; - _gtkagg = new _gtkagg_module; } - - - - - - - diff --git a/src/_image.cpp b/src/_image.cpp index 4114d68e84e0..df670397e37d 100644 --- a/src/_image.cpp +++ b/src/_image.cpp @@ -1,15 +1,8 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ -/* Python API mandates Python.h is included *first* */ -#include "Python.h" -#include +#define NO_IMPORT_ARRAY -#include -#include -#include -#include - -#include "numpy/arrayobject.h" +#include #include "agg_color_rgba.h" #include "agg_conv_transform.h" @@ -18,6 +11,7 @@ #include "agg_pixfmt_rgb.h" #include "agg_pixfmt_rgba.h" #include "agg_rasterizer_scanline_aa.h" +#include "agg_rasterizer_sl_clip.h" #include "agg_renderer_scanline.h" #include "agg_rendering_buffer.h" #include "agg_scanline_bin.h" @@ -27,233 +21,123 @@ #include "agg_span_image_filter_rgb.h" #include "agg_span_image_filter_rgba.h" #include "agg_span_interpolator_linear.h" -#include "agg_rasterizer_sl_clip.h" #include "util/agg_color_conv_rgb8.h" + #include "_image.h" #include "mplutils.h" - typedef agg::pixfmt_rgba32_plain pixfmt; typedef agg::pixfmt_rgba32_pre pixfmt_pre; typedef agg::renderer_base renderer_base; typedef agg::span_interpolator_linear<> interpolator_type; typedef agg::rasterizer_scanline_aa rasterizer; - -Image::Image() : - bufferIn(NULL), rbufIn(NULL), colsIn(0), rowsIn(0), - bufferOut(NULL), rbufOut(NULL), colsOut(0), rowsOut(0), BPP(4), - interpolation(BILINEAR), aspect(ASPECT_FREE), bg(1, 1, 1, 0), resample(true) -{ - _VERBOSE("Image::Image"); +Image::Image() + : bufferIn(NULL), + rbufIn(NULL), + colsIn(0), + rowsIn(0), + bufferOut(NULL), + rbufOut(NULL), + colsOut(0), + rowsOut(0), + BPP(4), + interpolation(BILINEAR), + aspect(ASPECT_FREE), + bg(1, 1, 1, 0), + resample(true) +{ + +} + +Image::Image(unsigned numrows, unsigned numcols, bool isoutput) + : bufferIn(NULL), + rbufIn(NULL), + colsIn(0), + rowsIn(0), + bufferOut(NULL), + rbufOut(NULL), + colsOut(0), + rowsOut(0), + BPP(4), + interpolation(BILINEAR), + aspect(ASPECT_FREE), + bg(1, 1, 1, 0), + resample(true) +{ + if (isoutput) { + rowsOut = numrows; + colsOut = numcols; + unsigned NUMBYTES(numrows * numcols * BPP); + bufferOut = new agg::int8u[NUMBYTES]; + rbufOut = new agg::rendering_buffer; + rbufOut->attach(bufferOut, colsOut, rowsOut, colsOut * BPP); + } else { + rowsIn = numrows; + colsIn = numcols; + unsigned NUMBYTES(numrows * numcols * BPP); + bufferIn = new agg::int8u[NUMBYTES]; + rbufIn = new agg::rendering_buffer; + rbufIn->attach(bufferIn, colsIn, rowsIn, colsIn * BPP); + } } Image::~Image() { - _VERBOSE("Image::~Image"); - delete [] bufferIn; + delete[] bufferIn; bufferIn = NULL; delete rbufIn; rbufIn = NULL; delete rbufOut; rbufOut = NULL; - delete [] bufferOut; + delete[] bufferOut; bufferOut = NULL; } -int -Image::setattr(const char * name, const Py::Object & value) +void Image::apply_rotation(double r) { - _VERBOSE("Image::setattr"); - __dict__[name] = value; - return 0; -} - -Py::Object -Image::getattr(const char * name) -{ - _VERBOSE("Image::getattro"); - if (__dict__.hasKey(name)) return __dict__[name]; - else return getattr_default(name); -} - -char Image::apply_rotation__doc__[] = - "apply_rotation(angle)\n" - "\n" - "Apply the rotation (degrees) to image" - ; -Py::Object -Image::apply_rotation(const Py::Tuple& args) -{ - _VERBOSE("Image::apply_rotation"); - - args.verify_length(1); - double r = Py::Float(args[0]); - - agg::trans_affine M = agg::trans_affine_rotation(r * agg::pi / 180.0); srcMatrix *= M; imageMatrix *= M; - return Py::Object(); } -char Image::flipud_out__doc__[] = - "flipud()\n" - "\n" - "Flip the output image upside down" - ; - -char Image::flipud_in__doc__[] = - "flipud()\n" - "\n" - "Flip the input image upside down" - ; -Py::Object -Image::flipud_in(const Py::Tuple& args) +void Image::set_bg(double r, double g, double b, double a) { - _VERBOSE("Image::flipud_in"); - - args.verify_length(0); - int stride = rbufIn->stride(); - rbufIn->attach(bufferIn, colsIn, rowsIn, -stride); - - return Py::Object(); + bg.r = r; + bg.g = g; + bg.b = b; + bg.a = a; } -char Image::set_bg__doc__[] = - "set_bg(r,g,b,a)\n" - "\n" - "Set the background color" - ; - -Py::Object -Image::set_bg(const Py::Tuple& args) +void Image::apply_scaling(double sx, double sy) { - _VERBOSE("Image::set_bg"); - - args.verify_length(4); - bg.r = Py::Float(args[0]); - bg.g = Py::Float(args[1]); - bg.b = Py::Float(args[2]); - bg.a = Py::Float(args[3]); - return Py::Object(); -} - -char Image::apply_scaling__doc__[] = - "apply_scaling(sx, sy)\n" - "\n" - "Apply the scale factors sx, sy to the transform matrix" - ; - -Py::Object -Image::apply_scaling(const Py::Tuple& args) -{ - _VERBOSE("Image::apply_scaling"); - - args.verify_length(2); - double sx = Py::Float(args[0]); - double sy = Py::Float(args[1]); - - //printf("applying scaling %1.2f, %1.2f\n", sx, sy); agg::trans_affine M = agg::trans_affine_scaling(sx, sy); srcMatrix *= M; imageMatrix *= M; - - return Py::Object(); } -char Image::apply_translation__doc__[] = - "apply_translation(tx, ty)\n" - "\n" - "Apply the translation tx, ty to the transform matrix" - ; - -Py::Object -Image::apply_translation(const Py::Tuple& args) +void Image::apply_translation(double tx, double ty) { - _VERBOSE("Image::apply_translation"); - - args.verify_length(2); - double tx = Py::Float(args[0]); - double ty = Py::Float(args[1]); - - //printf("applying translation %1.2f, %1.2f\n", tx, ty); agg::trans_affine M = agg::trans_affine_translation(tx, ty); srcMatrix *= M; imageMatrix *= M; - - return Py::Object(); } -char Image::as_rgba_str__doc__[] = - "numrows, numcols, s = as_rgba_str()" - "\n" - "Call this function after resize to get the data as string\n" - "The string is a numrows by numcols x 4 (RGBA) unsigned char buffer\n" - ; - -Py::Object -Image::as_rgba_str(const Py::Tuple& args, const Py::Dict& kwargs) +void Image::as_rgba_str(agg::int8u *outbuf) { - _VERBOSE("Image::as_rgba_str"); - - args.verify_length(0); - - std::pair bufpair = _get_output_buffer(); - - #if PY3K - Py::Object ret = Py::asObject(Py_BuildValue("nny#", rowsOut, colsOut, - bufpair.first, colsOut * rowsOut * 4)); - #else - Py::Object ret = Py::asObject(Py_BuildValue("nns#", rowsOut, colsOut, - bufpair.first, colsOut * rowsOut * 4)); - #endif - - if (bufpair.second) delete [] bufpair.first; - return ret; + agg::rendering_buffer rb; + rb.attach(outbuf, colsOut, rowsOut, colsOut * 4); + rb.copy_from(*rbufOut); } - -char Image::color_conv__doc__[] = - "numrows, numcols, buffer = color_conv(format)" - "\n" - "format 0(BGRA) or 1(ARGB)\n" - "Convert image to format and return in a writable buffer\n" - ; -Py::Object -Image::color_conv(const Py::Tuple& args) +void Image::color_conv(int format, agg::int8u *outbuf) { - _VERBOSE("Image::color_conv"); - - args.verify_length(1); - int format = Py::Int(args[0]); - PyObject* py_buffer = NULL; int row_len = colsOut * 4; -#if PY3K - unsigned char* buf = (unsigned char *)malloc(row_len * rowsOut); - if (buf == NULL) - throw Py::MemoryError("Image::color_conv could not allocate memory"); -#else - py_buffer = PyBuffer_New(row_len * rowsOut); - if (py_buffer == NULL) - throw Py::MemoryError("Image::color_conv could not allocate memory"); - - void* buf; - Py_ssize_t buffer_len; - int ret = PyObject_AsWriteBuffer(py_buffer, &buf, &buffer_len); - if (ret != 0) - { - Py_XDECREF(py_buffer); - throw Py::MemoryError("Image::color_conv could not allocate memory"); - } -#endif agg::rendering_buffer rtmp; - rtmp.attach(reinterpret_cast(buf), colsOut, rowsOut, - row_len); + rtmp.attach(outbuf, colsOut, rowsOut, row_len); - switch (format) - { + switch (format) { case 0: agg::color_conv(&rtmp, rbufOut, agg::color_conv_rgba32_to_bgra32()); break; @@ -261,123 +145,24 @@ Image::color_conv(const Py::Tuple& args) agg::color_conv(&rtmp, rbufOut, agg::color_conv_rgba32_to_argb32()); break; default: - Py_XDECREF(py_buffer); - throw Py::ValueError("Image::color_conv unknown format"); - } - -#if PY3K - py_buffer = PyByteArray_FromStringAndSize((char *)buf, row_len * rowsOut); - if (py_buffer == NULL) { - free(buf); + throw "Image::color_conv unknown format"; } -#endif - - PyObject* o = Py_BuildValue("nnN", rowsOut, colsOut, py_buffer); - return Py::asObject(o); } -char Image::buffer_rgba__doc__[] = - "buffer = buffer_rgba()" - "\n" - "Return the image buffer as rgba32\n" - ; -Py::Object -Image::buffer_rgba(const Py::Tuple& args) +void Image::reset_matrix(void) { - //"Return the image object as rgba"; - - _VERBOSE("RendererAgg::buffer_rgba"); - - args.verify_length(0); - int row_len = colsOut * 4; - PyObject* o = Py_BuildValue("nns#", rowsOut, colsOut, - rbufOut, row_len * rowsOut); - return Py::asObject(o); -} - -char Image::reset_matrix__doc__[] = - "reset_matrix()" - "\n" - "Reset the transformation matrix" - ; - -Py::Object -Image::reset_matrix(const Py::Tuple& args) -{ - _VERBOSE("Image::reset_matrix"); - - args.verify_length(0); srcMatrix.reset(); imageMatrix.reset(); - - return Py::Object(); } -char Image::get_matrix__doc__[] = - "(m11,m21,m12,m22,m13,m23) = get_matrix()\n" - "\n" - "Get the affine transformation matrix\n" - " /m11,m12,m13\\\n" - " /m21,m22,m23|\n" - " \\ 0 , 0 , 1 /" - ; - -Py::Object -Image::get_matrix(const Py::Tuple& args) +void Image::resize(int numcols, int numrows, int norm, double radius) { - _VERBOSE("Image::get_matrix"); - - args.verify_length(0); - - double m[6]; - srcMatrix.store_to(m); - Py::Tuple ret(6); - for (int i = 0;i < 6;i++) - { - ret[i] = Py::Float(m[i]); + if (bufferIn == NULL) { + throw "You must first load the image"; } - return ret; -} - -char Image::resize__doc__[] = - "resize(width, height, norm=1, radius=4.0)\n" - "\n" - "Resize the image to width, height using interpolation\n" - "norm and radius are optional args for some of the filters and must be\n" - "passed as kwargs\n" - ; -Py::Object -Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) -{ - _VERBOSE("Image::resize"); - - args.verify_length(2); - - int norm = 1; - if (kwargs.hasKey("norm")) - { - norm = Py::Int(kwargs["norm"]); - } - - double radius = 4.0; - if (kwargs.hasKey("radius")) - { - radius = Py::Float(kwargs["radius"]); - } - - if (bufferIn == NULL) - { - throw Py::RuntimeError("You must first load the image"); - } - - int numcols = Py::Int(args[0]); - int numrows = Py::Int(args[1]); - - if (numcols <= 0 || numrows <= 0) - { - throw Py::RuntimeError( - "Width and height must have positive values"); + if (numcols <= 0 || numrows <= 0) { + throw "Width and height must have positive values"; } colsOut = numcols; @@ -385,11 +170,11 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) size_t NUMBYTES(numrows * numcols * BPP); - delete [] bufferOut; + delete[] bufferOut; bufferOut = new agg::int8u[NUMBYTES]; - if (bufferOut == NULL) //todo: also handle allocation throw + if (bufferOut == NULL) // todo: also handle allocation throw { - throw Py::MemoryError("Image::resize could not allocate memory"); + throw "Image::resize could not allocate memory"; } delete rbufOut; @@ -405,8 +190,6 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) ras.clip_box(0, 0, numcols, numrows); - //srcMatrix *= resizingMatrix; - //imageMatrix *= resizingMatrix; imageMatrix.invert(); interpolator_type interpolator(imageMatrix); @@ -437,26 +220,22 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) pixfmt_pre pixfmtin(*rbufIn); img_accessor_type ia(pixfmtin); - switch (interpolation) - { + switch (interpolation) { - case NEAREST: - { + case NEAREST: { typedef agg::span_image_filter_rgba_nn span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; + typedef agg::renderer_scanline_aa + renderer_type; span_gen_type sg(ia, interpolator); renderer_type ri(rb, sa, sg); agg::render_scanlines(ras, sl, ri); - } - break; + } break; case HANNING: case HAMMING: - case HERMITE: - { + case HERMITE: { agg::image_filter_lut filter; - switch (interpolation) - { + switch (interpolation) { case HANNING: filter.calculate(agg::image_filter_hanning(), norm); break; @@ -467,24 +246,23 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) filter.calculate(agg::image_filter_hermite(), norm); break; } - if (resample) - { + if (resample) { typedef agg::span_image_resample_rgba_affine span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; + typedef agg::renderer_scanline_aa + renderer_type; span_gen_type sg(ia, interpolator, filter); renderer_type ri(rb, sa, sg); agg::render_scanlines(ras, sl, ri); - } - else - { - typedef agg::span_image_filter_rgba_2x2 span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; + } else { + typedef agg::span_image_filter_rgba_2x2 + span_gen_type; + typedef agg::renderer_scanline_aa + renderer_type; span_gen_type sg(ia, interpolator, filter); renderer_type ri(rb, sa, sg); agg::render_scanlines(ras, sl, ri); } - } - break; + } break; case BILINEAR: case BICUBIC: case SPLINE16: @@ -497,11 +275,9 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) case MITCHELL: case SINC: case LANCZOS: - case BLACKMAN: - { + case BLACKMAN: { agg::image_filter_lut filter; - switch (interpolation) - { + switch (interpolation) { case BILINEAR: filter.calculate(agg::image_filter_bilinear(), norm); break; @@ -542,801 +318,85 @@ Image::resize(const Py::Tuple& args, const Py::Dict& kwargs) filter.calculate(agg::image_filter_blackman(radius), norm); break; } - if (resample) - { + if (resample) { typedef agg::span_image_resample_rgba_affine span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; + typedef agg::renderer_scanline_aa + renderer_type; span_gen_type sg(ia, interpolator, filter); renderer_type ri(rb, sa, sg); agg::render_scanlines(ras, sl, ri); - } - else - { + } else { typedef agg::span_image_filter_rgba span_gen_type; - typedef agg::renderer_scanline_aa renderer_type; + typedef agg::renderer_scanline_aa + renderer_type; span_gen_type sg(ia, interpolator, filter); renderer_type ri(rb, sa, sg); agg::render_scanlines(ras, sl, ri); } + } break; } - break; - - } - - return Py::Object(); } - - -char Image::get_interpolation__doc__[] = - "get_interpolation()\n" - "\n" - "Get the interpolation scheme to one of the module constants, " - "one of image.NEAREST, image.BILINEAR, etc..." - ; - -Py::Object -Image::get_interpolation(const Py::Tuple& args) +void Image::clear() { - _VERBOSE("Image::get_interpolation"); - - args.verify_length(0); - return Py::Int((int)interpolation); -} - - -char Image::get_aspect__doc__[] = - "get_aspect()\n" - "\n" - "Get the aspect constraint constants" - ; - -Py::Object -Image::get_aspect(const Py::Tuple& args) -{ - _VERBOSE("Image::get_aspect"); - - args.verify_length(0); - return Py::Int((int)aspect); -} - -char Image::get_size__doc__[] = - "numrows, numcols = get_size()\n" - "\n" - "Get the number or rows and columns of the input image" - ; - -Py::Object -Image::get_size(const Py::Tuple& args) -{ - _VERBOSE("Image::get_size"); - - args.verify_length(0); - - Py::Tuple ret(2); - ret[0] = Py::Int((long)rowsIn); - ret[1] = Py::Int((long)colsIn); - return ret; - -} - -char Image::get_resample__doc__[] = - "get_resample()\n" - "\n" - "Get the resample flag." - ; - -Py::Object -Image::get_resample(const Py::Tuple& args) -{ - _VERBOSE("Image::get_resample"); - - args.verify_length(0); - return Py::Int((int)resample); -} - -char Image::get_size_out__doc__[] = - "numrows, numcols = get_size()\n" - "\n" - "Get the number or rows and columns of the output image" - ; - -Py::Object -Image::get_size_out(const Py::Tuple& args) -{ - _VERBOSE("Image::get_size_out"); - - args.verify_length(0); - - Py::Tuple ret(2); - ret[0] = Py::Int((long)rowsOut); - ret[1] = Py::Int((long)colsOut); - return ret; -} - -//get the output buffer, flipped if necessary. The second element of -//the pair is a bool that indicates whether you need to free the -//memory -std::pair -Image::_get_output_buffer() -{ - _VERBOSE("Image::_get_output_buffer"); - std::pair ret; - bool flipy = rbufOut->stride() < 0; - if (flipy) - { - agg::int8u* buffer = new agg::int8u[rowsOut*colsOut*4]; - agg::rendering_buffer rb; - rb.attach(buffer, colsOut, rowsOut, colsOut*4); - rb.copy_from(*rbufOut); - ret.first = buffer; - ret.second = true; - } - else - { - ret.first = bufferOut; - ret.second = false; - } - return ret; - -} - -char Image::set_interpolation__doc__[] = - "set_interpolation(scheme)\n" - "\n" - "Set the interpolation scheme to one of the module constants, " - "eg, image.NEAREST, image.BILINEAR, etc..." - ; - -Py::Object -Image::set_interpolation(const Py::Tuple& args) -{ - _VERBOSE("Image::set_interpolation"); - - args.verify_length(1); - - size_t method = (long)Py::Int(args[0]); - interpolation = (unsigned)method; - return Py::Object(); -} - -char Image::set_resample__doc__[] = - "set_resample(boolean)\n" - "\n" - "Set the resample flag." - ; - -Py::Object -Image::set_resample(const Py::Tuple& args) -{ - _VERBOSE("Image::set_resample"); - args.verify_length(1); - int flag = Py::Int(args[0]); - resample = (bool)flag; - return Py::Object(); -} - - -char Image::set_aspect__doc__[] = - "set_aspect(scheme)\n" - "\n" - "Set the aspect ration to one of the image module constant." - "eg, one of image.ASPECT_PRESERVE, image.ASPECT_FREE" - ; -Py::Object -Image::set_aspect(const Py::Tuple& args) -{ - _VERBOSE("Image::set_aspect"); - - args.verify_length(1); - size_t method = (long)Py::Int(args[0]); - aspect = (unsigned)method; - return Py::Object(); - -} - -void -Image::init_type() -{ - _VERBOSE("Image::init_type"); - - behaviors().name("Image"); - behaviors().doc("Image"); - behaviors().supportGetattr(); - behaviors().supportSetattr(); - - add_varargs_method("apply_rotation", &Image::apply_rotation, Image::apply_rotation__doc__); - add_varargs_method("apply_scaling", &Image::apply_scaling, Image::apply_scaling__doc__); - add_varargs_method("apply_translation", &Image::apply_translation, Image::apply_translation__doc__); - add_keyword_method("as_rgba_str", &Image::as_rgba_str, Image::as_rgba_str__doc__); - add_varargs_method("color_conv", &Image::color_conv, Image::color_conv__doc__); - add_varargs_method("buffer_rgba", &Image::buffer_rgba, Image::buffer_rgba__doc__); - add_varargs_method("get_aspect", &Image::get_aspect, Image::get_aspect__doc__); - add_varargs_method("get_interpolation", &Image::get_interpolation, Image::get_interpolation__doc__); - add_varargs_method("get_resample", &Image::get_resample, Image::get_resample__doc__); - add_varargs_method("get_size", &Image::get_size, Image::get_size__doc__); - add_varargs_method("get_size_out", &Image::get_size_out, Image::get_size_out__doc__); - add_varargs_method("reset_matrix", &Image::reset_matrix, Image::reset_matrix__doc__); - add_varargs_method("get_matrix", &Image::get_matrix, Image::get_matrix__doc__); - add_keyword_method("resize", &Image::resize, Image::resize__doc__); - add_varargs_method("set_interpolation", &Image::set_interpolation, Image::set_interpolation__doc__); - add_varargs_method("set_resample", &Image::set_resample, Image::set_resample__doc__); - add_varargs_method("set_aspect", &Image::set_aspect, Image::set_aspect__doc__); - add_varargs_method("set_bg", &Image::set_bg, Image::set_bg__doc__); - add_varargs_method("flipud_out", &Image::flipud_out, Image::flipud_out__doc__); - add_varargs_method("flipud_in", &Image::flipud_in, Image::flipud_in__doc__); -} - - - - -char _image_module_from_images__doc__[] = - "from_images(numrows, numcols, seq)\n" - "\n" - "return an image instance with numrows, numcols from a seq of image\n" - "instances using alpha blending. seq is a list of (Image, ox, oy)" - ; -Py::Object -_image_module::from_images(const Py::Tuple& args) -{ - _VERBOSE("_image_module::from_images"); - - args.verify_length(3); - - size_t numrows = (long)Py::Int(args[0]); - size_t numcols = (long)Py::Int(args[1]); - - if (numrows >= 32768 || numcols >= 32768) - { - throw Py::RuntimeError("numrows and numcols must both be less than 32768"); - } - - Py::SeqBase tups = args[2]; - size_t N = tups.length(); - - if (N == 0) - { - throw Py::RuntimeError("Empty list of images"); - } - - Py::Tuple tup; - - size_t ox(0), oy(0), thisx(0), thisy(0); - float alpha; - bool apply_alpha; - - //copy image 0 output buffer into return images output buffer - Image* imo = new Image; - imo->rowsOut = numrows; - imo->colsOut = numcols; - - size_t NUMBYTES(numrows * numcols * imo->BPP); - imo->bufferOut = new agg::int8u[NUMBYTES]; - if (imo->bufferOut == NULL) //todo: also handle allocation throw - { - throw Py::MemoryError("_image_module::from_images could not allocate memory"); - } - - delete imo->rbufOut; - imo->rbufOut = new agg::rendering_buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - - pixfmt pixf(*imo->rbufOut); + pixfmt pixf(*rbufOut); renderer_base rb(pixf); - - rb.clear(agg::rgba(0, 0, 0, 0)); - for (size_t imnum = 0; imnum < N; imnum++) - { - tup = Py::Tuple(tups[imnum]); - Image* thisim = static_cast(tup[0].ptr()); - ox = (long)Py::Int(tup[1]); - oy = (long)Py::Int(tup[2]); - if (tup.size() <= 3 || tup[3].ptr() == Py_None) - { - apply_alpha = false; - } - else - { - apply_alpha = true; - alpha = Py::Float(tup[3]); - } - - bool isflip = (thisim->rbufOut->stride()) < 0; - //std::cout << "from images " << isflip << "; stride=" << thisim->rbufOut->stride() << std::endl; - size_t ind = 0; - for (size_t j = 0; j < thisim->rowsOut; j++) - { - for (size_t i = 0; i < thisim->colsOut; i++) - { - thisx = i + ox; - - if (isflip) - { - thisy = thisim->rowsOut - j + oy; - } - else - { - thisy = j + oy; - } - - if (thisx >= numcols || thisy >= numrows) - { - ind += 4; - continue; - } - - pixfmt::color_type p; - p.r = *(thisim->bufferOut + ind++); - p.g = *(thisim->bufferOut + ind++); - p.b = *(thisim->bufferOut + ind++); - if (apply_alpha) - { - p.a = (pixfmt::value_type) *(thisim->bufferOut + ind++) * alpha; - } - else - { - p.a = *(thisim->bufferOut + ind++); - } - pixf.blend_pixel(thisx, thisy, p, 255); - } - } - } - - return Py::asObject(imo); -} - - -char _image_module_fromarray__doc__[] = - "fromarray(A, isoutput)\n" - "\n" - "Load the image from a numpy array\n" - "By default this function fills the input buffer, which can subsequently\n" - "be resampled using resize. If isoutput=1, fill the output buffer.\n" - "This is used to support raw pixel images w/o resampling" - ; -Py::Object -_image_module::fromarray(const Py::Tuple& args) -{ - _VERBOSE("_image_module::fromarray"); - - args.verify_length(2); - - Py::Object x = args[0]; - int isoutput = Py::Int(args[1]); - PyArrayObject *A = (PyArrayObject *) PyArray_FromObject(x.ptr(), NPY_DOUBLE, 2, 3); - if (A == NULL) - { - throw Py::ValueError("Array must be rank 2 or 3 of doubles"); - } - Py::Object A_obj((PyObject *)A, true); - - Image* imo = new Image; - - imo->rowsIn = PyArray_DIM(A, 0); - imo->colsIn = PyArray_DIM(A, 1); - - size_t NUMBYTES(imo->colsIn * imo->rowsIn * imo->BPP); - agg::int8u *buffer = new agg::int8u[NUMBYTES]; - if (buffer == NULL) //todo: also handle allocation throw - { - throw Py::MemoryError("_image_module::fromarray could not allocate memory"); - } - - if (isoutput) - { - // make the output buffer point to the input buffer - imo->rowsOut = imo->rowsIn; - imo->colsOut = imo->colsIn; - - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - } - else - { - imo->bufferIn = buffer; - imo->rbufIn = new agg::rendering_buffer; - imo->rbufIn->attach(buffer, imo->colsIn, imo->rowsIn, imo->colsIn*imo->BPP); - } - - if (PyArray_NDIM(A) == 2) //assume luminance for now; - { - agg::int8u gray; - for (size_t rownum = 0; rownum < imo->rowsIn; rownum++) - { - for (size_t colnum = 0; colnum < imo->colsIn; colnum++) - { - double val = *(double *)PyArray_GETPTR2(A, rownum, colnum); - - gray = int(255 * val); - *buffer++ = gray; // red - *buffer++ = gray; // green - *buffer++ = gray; // blue - *buffer++ = 255; // alpha - } - } - } - else if (PyArray_NDIM(A) == 3) // assume RGB - { - - if (PyArray_DIM(A, 2) != 3 && PyArray_DIM(A, 2) != 4) - { - throw Py::ValueError(Printf("3rd dimension must be length 3 (RGB) or 4 (RGBA); found %d", - PyArray_DIM(A, 2)).str()); - } - - int rgba = PyArray_DIM(A, 2) == 4; - double r, g, b, alpha; - size_t offset = 0; - - for (size_t rownum = 0; rownum < imo->rowsIn; rownum++) - { - for (size_t colnum = 0; colnum < imo->colsIn; colnum++) - { - r = *(double*)PyArray_GETPTR3(A, rownum, colnum, 0); - g = *(double*)PyArray_GETPTR3(A, rownum, colnum, 1); - b = *(double*)PyArray_GETPTR3(A, rownum, colnum, 2); - - if (rgba) - { - alpha = *(double*)PyArray_GETPTR3(A, rownum, colnum, 3); - } - else - { - alpha = 1.0; - } - - *buffer++ = int(255 * r); // red - *buffer++ = int(255 * g); // green - *buffer++ = int(255 * b); // blue - *buffer++ = int(255 * alpha); // alpha - } - } - } - else // error - { - throw Py::ValueError("Illegal array rank; must be rank; must 2 or 3"); - } - - return Py::asObject(imo); + rb.clear(bg); } -char _image_module_fromarray2__doc__[] = - "fromarray2(A, isoutput)\n" - "\n" - "Load the image from a numpy array\n" - "By default this function fills the input buffer, which can subsequently\n" - "be resampled using resize. If isoutput=1, fill the output buffer.\n" - "This is used to support raw pixel images w/o resampling" - ; -Py::Object -_image_module::fromarray2(const Py::Tuple& args) +void Image::blend_image(Image &im, unsigned ox, unsigned oy, bool apply_alpha, float alpha) { - _VERBOSE("_image_module::fromarray2"); - - args.verify_length(2); - - Py::Object x = args[0]; - int isoutput = Py::Int(args[1]); - PyArrayObject *A = (PyArrayObject *) PyArray_ContiguousFromObject(x.ptr(), NPY_DOUBLE, 2, 3); - if (A == NULL) - { - throw Py::ValueError("Array must be rank 2 or 3 of doubles"); - } - Py::Object A_obj((PyObject*)A, true); - - Image* imo = new Image; - - imo->rowsIn = PyArray_DIM(A, 0); - imo->colsIn = PyArray_DIM(A, 1); - - size_t NUMBYTES(imo->colsIn * imo->rowsIn * imo->BPP); - agg::int8u *buffer = new agg::int8u[NUMBYTES]; - if (buffer == NULL) //todo: also handle allocation throw - { - throw Py::MemoryError("_image_module::fromarray could not allocate memory"); - } - - if (isoutput) - { - // make the output buffer point to the input buffer - imo->rowsOut = imo->rowsIn; - imo->colsOut = imo->colsIn; + unsigned thisx = 0, thisy = 0; - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - } - else - { - imo->bufferIn = buffer; - imo->rbufIn = new agg::rendering_buffer; - imo->rbufIn->attach(buffer, imo->colsIn, imo->rowsIn, imo->colsIn*imo->BPP); - } + pixfmt pixf(*rbufOut); + renderer_base rb(pixf); - if (PyArray_NDIM(A) == 2) //assume luminance for now; - { - agg::int8u gray; - for (size_t row = 0; row < imo->rowsIn; row++) { - for (size_t col = 0; col < imo->colsIn; col++) { - const double val = *(double*)PyArray_GETPTR2(A, row, col); - gray = int(255 * val); - *buffer++ = gray; // red - *buffer++ = gray; // green - *buffer++ = gray; // blue - *buffer++ = 255; // alpha - } + bool isflip = (im.rbufOut->stride()) < 0; + size_t ind = 0; + for (unsigned j = 0; j < im.rowsOut; j++) { + if (isflip) { + thisy = im.rowsOut - j + oy; + } else { + thisy = j + oy; } - } - else if (PyArray_NDIM(A) == 3) // assume RGB - { - if (PyArray_DIM(A, 2) != 3 && PyArray_DIM(A, 2) != 4) - { - throw Py::ValueError(Printf("3rd dimension must be length 3 (RGB) or 4 (RGBA); found %d", - PyArray_DIM(A, 2)).str()); - } + for (unsigned i = 0; i < im.colsOut; i++) { + thisx = i + ox; - int rgba = PyArray_DIM(A, 2) == 4; - double r, g, b, alpha; - for (size_t row = 0; row < imo->rowsIn; row++) { - for (size_t col = 0; col < imo->colsIn; col++) { - r = *(double*)PyArray_GETPTR3(A, row, col, 0); - g = *(double*)PyArray_GETPTR3(A, row, col, 1); - b = *(double*)PyArray_GETPTR3(A, row, col, 2); - if (rgba) - alpha = *(double*)PyArray_GETPTR3(A, row, col, 3); - else - alpha = 1.0; - *buffer++ = int(255 * r); // red - *buffer++ = int(255 * g); // green - *buffer++ = int(255 * b); // blue - *buffer++ = int(255 * alpha); // alpha + if (thisx >= colsOut || thisy >= rowsOut) { + ind += 4; + continue; } - } - } - else // error - { - throw Py::ValueError("Illegal array rank; must be rank; must 2 or 3"); - } - - return Py::asObject(imo); -} - -char _image_module_frombyte__doc__[] = - "frombyte(A, isoutput)\n" - "\n" - "Load the image from a byte array.\n" - "By default this function fills the input buffer, which can subsequently\n" - "be resampled using resize. If isoutput=1, fill the output buffer.\n" - "This is used to support raw pixel images w/o resampling." - ; -Py::Object -_image_module::frombyte(const Py::Tuple& args) -{ - _VERBOSE("_image_module::frombyte"); - - args.verify_length(2); - - Py::Object x = args[0]; - int isoutput = Py::Int(args[1]); - - PyArrayObject *A = (PyArrayObject *) PyArray_FromObject(x.ptr(), NPY_UBYTE, 3, 3); - if (A == NULL) - { - throw Py::ValueError("Array must have 3 dimensions"); - } - Py::Object A_obj((PyObject*)A, true); - - if (PyArray_DIM(A, 2) < 3 || PyArray_DIM(A, 2) > 4) - { - throw Py::ValueError("Array dimension 3 must have size 3 or 4"); - } - - Image* imo = new Image; - - imo->rowsIn = PyArray_DIM(A, 0); - imo->colsIn = PyArray_DIM(A, 1); - - agg::int8u *arrbuf; - agg::int8u *buffer; - agg::int8u *dstbuf; - - arrbuf = reinterpret_cast(PyArray_DATA(A)); - - size_t NUMBYTES(imo->colsIn * imo->rowsIn * imo->BPP); - buffer = dstbuf = new agg::int8u[NUMBYTES]; - if (buffer == NULL) //todo: also handle allocation throw - { - throw Py::MemoryError("_image_module::frombyte could not allocate memory"); - } - - if (PyArray_ISCONTIGUOUS(A)) - { - if (PyArray_DIM(A, 2) == 4) - { - memmove(dstbuf, arrbuf, imo->rowsIn * imo->colsIn * 4); - } - else - { - size_t i = imo->rowsIn * imo->colsIn; - while (i--) - { - *dstbuf++ = *arrbuf++; - *dstbuf++ = *arrbuf++; - *dstbuf++ = *arrbuf++; - *dstbuf++ = 255; - } - } - } - else if ((PyArray_STRIDE(A, 1) == 4) && (PyArray_STRIDE(A, 2) == 1)) - { - const size_t N = imo->colsIn * 4; - const size_t stride = PyArray_STRIDE(A, 0); - for (size_t rownum = 0; rownum < imo->rowsIn; rownum++) - { - memmove(dstbuf, arrbuf, N); - arrbuf += stride; - dstbuf += N; - } - } - else if ((PyArray_STRIDE(A, 1) == 3) && (PyArray_STRIDE(A, 2) == 1)) - { - const size_t stride = PyArray_STRIDE(A, 0) - imo->colsIn * 3; - for (size_t rownum = 0; rownum < imo->rowsIn; rownum++) - { - for (size_t colnum = 0; colnum < imo->colsIn; colnum++) - { - *dstbuf++ = *arrbuf++; - *dstbuf++ = *arrbuf++; - *dstbuf++ = *arrbuf++; - *dstbuf++ = 255; - } - arrbuf += stride; - } - } - else - { - PyArrayIterObject *iter; - iter = (PyArrayIterObject *)PyArray_IterNew((PyObject *)A); - if (PyArray_DIM(A, 2) == 4) - { - while (iter->index < iter->size) { - *dstbuf++ = *((unsigned char *)iter->dataptr); - PyArray_ITER_NEXT(iter); - } - } - else - { - while (iter->index < iter->size) { - *dstbuf++ = *((unsigned char *)iter->dataptr); - PyArray_ITER_NEXT(iter); - *dstbuf++ = *((unsigned char *)iter->dataptr); - PyArray_ITER_NEXT(iter); - *dstbuf++ = *((unsigned char *)iter->dataptr); - PyArray_ITER_NEXT(iter); - *dstbuf++ = 255; + pixfmt::color_type p; + p.r = *(im.bufferOut + ind++); + p.g = *(im.bufferOut + ind++); + p.b = *(im.bufferOut + ind++); + if (apply_alpha) { + p.a = (pixfmt::value_type) * (im.bufferOut + ind++) * alpha; + } else { + p.a = *(im.bufferOut + ind++); } + pixf.blend_pixel(thisx, thisy, p, 255); } - Py_DECREF(iter); - } - - if (isoutput) - { - // make the output buffer point to the input buffer - - imo->rowsOut = imo->rowsIn; - imo->colsOut = imo->colsIn; - - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - - } - else - { - imo->bufferIn = buffer; - imo->rbufIn = new agg::rendering_buffer; - imo->rbufIn->attach(buffer, imo->colsIn, imo->rowsIn, imo->colsIn*imo->BPP); } - - return Py::asObject(imo); -} - -char _image_module_frombuffer__doc__[] = - "frombuffer(buffer, width, height, isoutput)\n" - "\n" - "Load the image from a character buffer\n" - "By default this function fills the input buffer, which can subsequently\n" - "be resampled using resize. If isoutput=1, fill the output buffer.\n" - "This is used to support raw pixel images w/o resampling." - ; -Py::Object -_image_module::frombuffer(const Py::Tuple& args) -{ - _VERBOSE("_image_module::frombuffer"); - - args.verify_length(4); - - PyObject *bufin = args[0].ptr(); - size_t x = (long)Py::Int(args[1]); - size_t y = (long)Py::Int(args[2]); - - if (x >= 32768 || y >= 32768) - { - throw Py::ValueError("x and y must both be less than 32768"); - } - - int isoutput = Py::Int(args[3]); - - if (PyObject_CheckReadBuffer(bufin) != 1) - throw Py::ValueError("First argument must be a buffer."); - - Image* imo = new Image; - - imo->rowsIn = y; - imo->colsIn = x; - Py_ssize_t NUMBYTES(imo->colsIn * imo->rowsIn * imo->BPP); - - Py_ssize_t buflen; - const agg::int8u *rawbuf; - if (PyObject_AsReadBuffer(bufin, reinterpret_cast(&rawbuf), &buflen) != 0) - { - throw Py::ValueError("Cannot get buffer from object."); - } - - // Check buffer is required size. - if (buflen != NUMBYTES) - { - throw Py::ValueError("Buffer length must be width * height * 4."); - } - - // Copy from input buffer to new buffer for agg. - agg::int8u* buffer = new agg::int8u[NUMBYTES]; - if (buffer == NULL) //todo: also handle allocation throw - { - throw Py::MemoryError("_image_module::frombuffer could not allocate memory"); - } - memmove(buffer, rawbuf, NUMBYTES); - - if (isoutput) - { - // make the output buffer point to the input buffer - imo->rowsOut = imo->rowsIn; - imo->colsOut = imo->colsIn; - - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - - } - else - { - imo->bufferIn = buffer; - imo->rbufIn = new agg::rendering_buffer; - imo->rbufIn->attach(buffer, imo->colsIn, imo->rowsIn, imo->colsIn*imo->BPP); - } - - return Py::asObject(imo); } // utilities for irregular grids -void _bin_indices_middle(unsigned int *irows, int nrows, float *ys1, int ny, float dy, float y_min) +void _bin_indices_middle( + unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min) { - int i, j, j_last; - unsigned int * rowstart = irows; - float *ys2 = ys1 + 1; - float *yl = ys1 + ny ; + int i, j, j_last; + unsigned int *rowstart = irows; + const float *ys2 = ys1 + 1; + const float *yl = ys1 + ny; float yo = y_min + dy / 2.0; float ym = 0.5f * (*ys1 + *ys2); // y/rows j = 0; j_last = j; - for (i = 0;i < nrows;i++, yo += dy, rowstart++) - { - while (ys2 != yl && yo > ym) - { + for (i = 0; i < nrows; i++, yo += dy, rowstart++) { + while (ys2 != yl && yo > ym) { ys1 = ys2; ys2 = ys1 + 1; ym = 0.5f * (*ys1 + *ys2); @@ -1347,658 +407,147 @@ void _bin_indices_middle(unsigned int *irows, int nrows, float *ys1, int ny, flo } } -void _bin_indices_middle_linear(float *arows, unsigned int *irows, int nrows, float *y, int ny, float dy, float y_min) +void _bin_indices_middle_linear(float *arows, + unsigned int *irows, + int nrows, + const float *y, + unsigned long ny, + float dy, + float y_min) { int i; int ii = 0; - int iilast = ny - 1; + int iilast = (int)ny - 1; float sc = 1 / dy; - int iy0 = (int)floor(sc * (y[ii] - y_min)); - int iy1 = (int)floor(sc * (y[ii+1] - y_min)); + int iy0 = (int)floor(sc * (y[ii] - y_min)); + int iy1 = (int)floor(sc * (y[ii + 1] - y_min)); float invgap = 1.0f / (iy1 - iy0); - for (i = 0; i < nrows && i <= iy0; i++) - { + for (i = 0; i < nrows && i <= iy0; i++) { irows[i] = 0; arows[i] = 1.0; - //std::cerr<<"i="<= 32768 || cols >= 32768) - { - throw Py::ValueError("rows and cols must both be less than 32768"); - } - - if (bounds.length() != 4) - { - throw Py::TypeError("Incorrect number of bounds (4 expected)"); - } - - float x_min = Py::Float(bounds[0]); - float x_max = Py::Float(bounds[1]); - float y_min = Py::Float(bounds[2]); - float y_max = Py::Float(bounds[3]); - float width = x_max - x_min; - float height = y_max - y_min; - float dx = width / ((float) cols); - float dy = height / ((float) rows); - - // Check we have something to output to - if (rows == 0 || cols == 0) - { - throw Py::ValueError("Cannot scale to zero size"); - } - - PyArrayObject *x = NULL; - PyArrayObject *y = NULL; - PyArrayObject *d = NULL; - unsigned int *rowstarts = NULL; - unsigned int *colstarts = NULL; - float *acols = NULL; - float *arows = NULL; - - // Get numpy arrays - x = (PyArrayObject *) PyArray_ContiguousFromObject(xp.ptr(), NPY_FLOAT, 1, 1); - if (x == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::ValueError("x is of incorrect type (wanted 1D float)"); - } - y = (PyArrayObject *) PyArray_ContiguousFromObject(yp.ptr(), NPY_FLOAT, 1, 1); - if (y == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::ValueError("y is of incorrect type (wanted 1D float)"); - } - d = (PyArrayObject *) PyArray_ContiguousFromObject(dp.ptr(), NPY_UBYTE, 3, 3); - if (d == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::ValueError("data is of incorrect type (wanted 3D UInt8)"); - } - if (PyArray_DIM(d, 2) != 4) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::ValueError("data must be in RGBA format"); - } - - // Check dimensions match - int nx = PyArray_DIM(x, 0); - int ny = PyArray_DIM(y, 0); - if (nx != PyArray_DIM(d, 1) || ny != PyArray_DIM(d, 0)) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::ValueError("data and axis dimensions do not match"); - } - - // Allocate memory for pointer arrays - rowstarts = reinterpret_cast(PyMem_Malloc(sizeof(unsigned int) * rows)); - if (rowstarts == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - colstarts = reinterpret_cast(PyMem_Malloc(sizeof(unsigned int) * cols)); - if (colstarts == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - - // Create output - Image* imo = new Image; - imo->rowsIn = rows; - imo->colsIn = cols; - imo->rowsOut = rows; - imo->colsOut = cols; - size_t NUMBYTES(rows * cols * 4); - agg::int8u *buffer = new agg::int8u[NUMBYTES]; - if (buffer == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::MemoryError("Could not allocate memory for image"); - } - - - // Calculate the pointer arrays to map input x to output x - unsigned int i, j; - unsigned int * colstart = colstarts; - unsigned int * rowstart = rowstarts; - float *xs1 = reinterpret_cast(PyArray_DATA(x)); - float *ys1 = reinterpret_cast(PyArray_DATA(y)); - - - // Copy data to output buffer - unsigned char *start; - unsigned char *inposition; - size_t inrowsize(nx*4); - size_t rowsize(cols*4); - agg::int8u * position = buffer; - agg::int8u * oldposition = NULL; - start = reinterpret_cast(PyArray_DATA(d)); - int s0 = PyArray_STRIDE(d, 0); - int s1 = PyArray_STRIDE(d, 1); - - if (interpolation == Image::NEAREST) - { - _bin_indices_middle(colstart, cols, xs1, nx, dx, x_min); - _bin_indices_middle(rowstart, rows, ys1, ny, dy, y_min); - for (i = 0;i < rows;i++, rowstart++) - { - if (i > 0 && *rowstart == 0) - { - memcpy(position, oldposition, rowsize*sizeof(agg::int8u)); - oldposition = position; - position += rowsize; - } - else - { - oldposition = position; - start += *rowstart * inrowsize; - inposition = start; - for (j = 0, colstart = colstarts;j < cols;j++, position += 4, colstart++) - { - inposition += *colstart * 4; - memcpy(position, inposition, 4*sizeof(agg::int8u)); - } - } - } - } - else if (interpolation == Image::BILINEAR) - { - arows = reinterpret_cast(PyMem_Malloc(sizeof(float) * rows)); - if (arows == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - acols = reinterpret_cast(PyMem_Malloc(sizeof(float) * cols)); - if (acols == NULL) - { - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - - _bin_indices_middle_linear(acols, colstart, cols, xs1, nx, dx, x_min); - _bin_indices_middle_linear(arows, rowstart, rows, ys1, ny, dy, y_min); - double a00, a01, a10, a11, alpha, beta; - - - agg::int8u * start00; - agg::int8u * start01; - agg::int8u * start10; - agg::int8u * start11; - // Copy data to output buffer - for (i = 0; i < rows; i++) - { - for (j = 0; j < cols; j++) - { - alpha = arows[i]; - beta = acols[j]; - - a00 = alpha * beta; - a01 = alpha * (1.0 - beta); - a10 = (1.0 - alpha) * beta; - a11 = 1.0 - a00 - a01 - a10; - - start00 = (agg::int8u *)(start + s0 * rowstart[i] + s1 * colstart[j]); - start01 = start00 + s1; - start10 = start00 + s0; - start11 = start10 + s1; - position[0] = (agg::int8u)(start00[0] * a00 + start01[0] * a01 + start10[0] * a10 + start11[0] * a11); - position[1] = (agg::int8u)(start00[1] * a00 + start01[1] * a01 + start10[1] * a10 + start11[1] * a11); - position[2] = (agg::int8u)(start00[2] * a00 + start01[2] * a01 + start10[2] * a10 + start11[2] * a11); - position[3] = (agg::int8u)(start00[3] * a00 + start01[3] * a01 + start10[3] * a10 + start11[3] * a11); - position += 4; - } - } - - } - - // Attach output buffer to output buffer - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - - _pcolor_cleanup(x, y, d, rowstarts, colstarts, acols, arows); - - return Py::asObject(imo); - -} - -void _pcolor2_cleanup(PyArrayObject* x, PyArrayObject* y, PyArrayObject *d, - PyArrayObject* bg, int *irows, int*jcols) -{ - Py_XDECREF(x); - Py_XDECREF(y); - Py_XDECREF(d); - Py_XDECREF(bg); - if (irows) - { - PyMem_Free(irows); - } - if (jcols) - { - PyMem_Free(jcols); - } -} - - -char __image_module_pcolor2__doc__[] = - "pcolor2(x, y, data, rows, cols, bounds, bg)\n" - "\n" - "Generate a pseudo-color image from data on a non-uniform grid\n" - "specified by its cell boundaries.\n" - "bounds = (x_left, x_right, y_bot, y_top)\n" - "bg = ndarray of 4 uint8 representing background rgba\n" - ; -Py::Object -_image_module::pcolor2(const Py::Tuple& args) -{ - _VERBOSE("_image_module::pcolor2"); - - if (args.length() != 7) - { - throw Py::TypeError("Incorrect number of arguments (6 expected)"); - } - - Py::Object xp = args[0]; - Py::Object yp = args[1]; - Py::Object dp = args[2]; - int rows = Py::Int(args[3]); - int cols = Py::Int(args[4]); - Py::Tuple bounds = args[5]; - Py::Object bgp = args[6]; - - if (rows >= 32768 || cols >= 32768) - { - throw Py::ValueError("rows and cols must both be less than 32768"); - } - - if (bounds.length() != 4) - { - throw Py::TypeError("Incorrect number of bounds (4 expected)"); - } - - double x_left = Py::Float(bounds[0]); - double x_right = Py::Float(bounds[1]); - double y_bot = Py::Float(bounds[2]); - double y_top = Py::Float(bounds[3]); - - // Check we have something to output to - if (rows == 0 || cols == 0) - { - throw Py::ValueError("rows or cols is zero; there are no pixels"); - } - - PyArrayObject* x = NULL; - PyArrayObject* y = NULL; - PyArrayObject* d = NULL; - PyArrayObject* bg = NULL; - int* irows = NULL; - int* jcols = NULL; - - // Get numpy arrays - x = (PyArrayObject *) PyArray_ContiguousFromObject(xp.ptr(), NPY_DOUBLE, 1, 1); - if (x == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("x is of incorrect type (wanted 1D double)"); - } - y = (PyArrayObject *) PyArray_ContiguousFromObject(yp.ptr(), NPY_DOUBLE, 1, 1); - if (y == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("y is of incorrect type (wanted 1D double)"); - } - d = (PyArrayObject *) PyArray_ContiguousFromObject(dp.ptr(), NPY_UBYTE, 3, 3); - if (d == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("data is of incorrect type (wanted 3D uint8)"); - } - if (PyArray_DIM(d, 2) != 4) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("data must be in RGBA format"); - } - - // Check dimensions match - int nx = PyArray_DIM(x, 0); - int ny = PyArray_DIM(y, 0); - if (nx != PyArray_DIM(d, 1) + 1 || ny != PyArray_DIM(d, 0) + 1) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("data and axis bin boundary dimensions are incompatible"); - } - - bg = (PyArrayObject *) PyArray_ContiguousFromObject(bgp.ptr(), NPY_UBYTE, 1, 1); - if (bg == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("bg is of incorrect type (wanted 1D uint8)"); - } - if (PyArray_DIM(bg, 0) != 4) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::ValueError("bg must be in RGBA format"); - } - - // Allocate memory for pointer arrays - irows = reinterpret_cast(PyMem_Malloc(sizeof(int) * rows)); - if (irows == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - jcols = reinterpret_cast(PyMem_Malloc(sizeof(int) * cols)); - if (jcols == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::MemoryError("Cannot allocate memory for lookup table"); - } - - // Create output - Image* imo = new Image; - imo->rowsIn = rows; - imo->rowsOut = rows; - imo->colsIn = cols; - imo->colsOut = cols; - size_t NUMBYTES(rows * cols * 4); - agg::int8u *buffer = new agg::int8u[NUMBYTES]; - if (buffer == NULL) - { - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - throw Py::MemoryError("Could not allocate memory for image"); - } - - // Calculate the pointer arrays to map input x to output x - int i, j; - double *x0 = reinterpret_cast(PyArray_DATA(x)); - double *y0 = reinterpret_cast(PyArray_DATA(y)); - double sx = cols / (x_right - x_left); - double sy = rows / (y_top - y_bot); - _bin_indices(jcols, cols, x0, nx, sx, x_left); - _bin_indices(irows, rows, y0, ny, sy, y_bot); - - // Copy data to output buffer - agg::int8u * position = buffer; - unsigned char *start = reinterpret_cast(PyArray_DATA(d)); - unsigned char *bgptr = reinterpret_cast(PyArray_DATA(bg)); - int s0 = PyArray_STRIDE(d, 0); - int s1 = PyArray_STRIDE(d, 1); - - for (i = 0; i < rows; i++) - { - for (j = 0; j < cols; j++) - { - if (irows[i] == -1 || jcols[j] == -1) - { - memcpy(position, bgptr, 4*sizeof(agg::int8u)); - } - else - { - memcpy(position, (start + s0*irows[i] + s1*jcols[j]), - 4*sizeof(agg::int8u)); - } - position += 4; - } - } - - // Attach output buffer to output buffer - imo->rbufOut = new agg::rendering_buffer; - imo->bufferOut = buffer; - imo->rbufOut->attach(imo->bufferOut, imo->colsOut, imo->rowsOut, imo->colsOut * imo->BPP); - - _pcolor2_cleanup(x, y, d, bg, irows, jcols); - - return Py::asObject(imo); -} - -#if PY3K -PyMODINIT_FUNC -PyInit__image(void) -#else -PyMODINIT_FUNC -init_image(void) -#endif -{ - _VERBOSE("init_image"); - - static _image_module* _image = new _image_module; - - import_array(); - Py::Dict d = _image->moduleDictionary(); - - d["NEAREST"] = Py::Int(Image::NEAREST); - d["BILINEAR"] = Py::Int(Image::BILINEAR); - d["BICUBIC"] = Py::Int(Image::BICUBIC); - d["SPLINE16"] = Py::Int(Image::SPLINE16); - d["SPLINE36"] = Py::Int(Image::SPLINE36); - d["HANNING"] = Py::Int(Image::HANNING); - d["HAMMING"] = Py::Int(Image::HAMMING); - d["HERMITE"] = Py::Int(Image::HERMITE); - d["KAISER"] = Py::Int(Image::KAISER); - d["QUADRIC"] = Py::Int(Image::QUADRIC); - d["CATROM"] = Py::Int(Image::CATROM); - d["GAUSSIAN"] = Py::Int(Image::GAUSSIAN); - d["BESSEL"] = Py::Int(Image::BESSEL); - d["MITCHELL"] = Py::Int(Image::MITCHELL); - d["SINC"] = Py::Int(Image::SINC); - d["LANCZOS"] = Py::Int(Image::LANCZOS); - d["BLACKMAN"] = Py::Int(Image::BLACKMAN); - - d["ASPECT_FREE"] = Py::Int(Image::ASPECT_FREE); - d["ASPECT_PRESERVE"] = Py::Int(Image::ASPECT_PRESERVE); - -#if PY3K - return _image->module().ptr(); -#endif -} diff --git a/src/_image.h b/src/_image.h index aa0de94174cd..4a4a935f800f 100644 --- a/src/_image.h +++ b/src/_image.h @@ -6,182 +6,377 @@ #ifndef _IMAGE_H #define _IMAGE_H -#include -#include "Python.h" + +#include #include "agg_trans_affine.h" #include "agg_rendering_buffer.h" #include "agg_color_rgba.h" -#include "CXX/Extensions.hxx" - - -class Image : public Py::PythonExtension +class Image { -public: + public: Image(); + Image(unsigned numrows, unsigned numcols, bool isoutput); virtual ~Image(); static void init_type(void); - int setattr(const char*, const Py::Object &); - Py::Object getattr(const char * name); - - Py::Object apply_rotation(const Py::Tuple& args); - Py::Object apply_scaling(const Py::Tuple& args); - Py::Object apply_translation(const Py::Tuple& args); - Py::Object as_rgba_str(const Py::Tuple& args, const Py::Dict& kwargs); - Py::Object color_conv(const Py::Tuple& args); - Py::Object buffer_rgba(const Py::Tuple& args); - Py::Object reset_matrix(const Py::Tuple& args); - Py::Object get_matrix(const Py::Tuple& args); - Py::Object resize(const Py::Tuple& args, const Py::Dict& kwargs); - Py::Object get_aspect(const Py::Tuple& args); - Py::Object get_size(const Py::Tuple& args); - Py::Object get_size_out(const Py::Tuple& args); - Py::Object get_interpolation(const Py::Tuple& args); - Py::Object set_interpolation(const Py::Tuple& args); - Py::Object set_aspect(const Py::Tuple& args); - Py::Object set_bg(const Py::Tuple& args); - inline Py::Object flipud_out(const Py::Tuple& args) - { - args.verify_length(0); - if (colsOut <= 0 || rowsOut <= 0) - { - throw Py::RuntimeError( - "Width and height must have positive values"); - } - int stride = rbufOut->stride(); - //std::cout << "flip before: " << rbufOut->stride() << std::endl; - rbufOut->attach(bufferOut, colsOut, rowsOut, -stride); - //std::cout << "flip after: " << rbufOut->stride() << std::endl; - return Py::Object(); - } - - Py::Object flipud_in(const Py::Tuple& args); - Py::Object set_resample(const Py::Tuple& args); - Py::Object get_resample(const Py::Tuple& args); - - - std::pair _get_output_buffer(); - enum {NEAREST, - BILINEAR, - BICUBIC, - SPLINE16, - SPLINE36, - HANNING, - HAMMING, - HERMITE, - KAISER, - QUADRIC, - CATROM, - GAUSSIAN, - BESSEL, - MITCHELL, - SINC, - LANCZOS, - BLACKMAN - }; - - //enum { BICUBIC=0, BILINEAR, BLACKMAN100, BLACKMAN256, BLACKMAN64, + void apply_rotation(double r); + void apply_scaling(double sx, double sy); + void apply_translation(double tx, double ty); + void as_rgba_str(agg::int8u *outbuf); + void color_conv(int format, agg::int8u *outbuf); + void reset_matrix(); + void clear(); + void resize(int numcols, int numrows, int norm, double radius); + void blend_image(Image &im, unsigned ox, unsigned oy, bool apply_alpha, float alpha); + void set_bg(double r, double g, double b, double a); + + enum { + NEAREST, + BILINEAR, + BICUBIC, + SPLINE16, + SPLINE36, + HANNING, + HAMMING, + HERMITE, + KAISER, + QUADRIC, + CATROM, + GAUSSIAN, + BESSEL, + MITCHELL, + SINC, + LANCZOS, + BLACKMAN + }; + + // enum { BICUBIC=0, BILINEAR, BLACKMAN100, BLACKMAN256, BLACKMAN64, // NEAREST, SINC144, SINC256, SINC64, SPLINE16, SPLINE36}; - enum { ASPECT_PRESERVE = 0, ASPECT_FREE}; + enum { + ASPECT_PRESERVE = 0, + ASPECT_FREE + }; agg::int8u *bufferIn; agg::rendering_buffer *rbufIn; - size_t colsIn, rowsIn; + unsigned colsIn, rowsIn; agg::int8u *bufferOut; agg::rendering_buffer *rbufOut; - size_t colsOut, rowsOut; + unsigned colsOut, rowsOut; unsigned BPP; unsigned interpolation, aspect; agg::rgba bg; bool resample; -private: - Py::Dict __dict__; agg::trans_affine srcMatrix, imageMatrix; - static char apply_rotation__doc__[]; - static char apply_scaling__doc__[]; - static char apply_translation__doc__[]; - static char as_rgba_str__doc__[]; - static char color_conv__doc__[]; - static char buffer_rgba__doc__[]; - static char reset_matrix__doc__[]; - static char get_matrix__doc__[]; - static char resize__doc__[]; - static char get_aspect__doc__[]; - static char get_size__doc__[]; - static char get_size_out__doc__[]; - static char get_interpolation__doc__[]; - static char set_interpolation__doc__[]; - static char set_aspect__doc__[]; - static char set_bg__doc__[]; - static char flipud_out__doc__[]; - static char flipud_in__doc__[]; - static char get_resample__doc__[]; - static char set_resample__doc__[]; - + private: // prevent copying - Image(const Image&); - Image& operator=(const Image&); + Image(const Image &); + Image &operator=(const Image &); }; +template +Image *from_grey_array(ArrayType &array, bool isoutput) +{ + Image *im = new Image((unsigned)array.dim(0), (unsigned)array.dim(1), isoutput); + + agg::int8u *buffer; + if (isoutput) { + buffer = im->bufferOut; + } else { + buffer = im->bufferIn; + } + + agg::int8u gray; + for (size_t rownum = 0; rownum < (size_t)array.dim(0); rownum++) { + for (size_t colnum = 0; colnum < (size_t)array.dim(1); colnum++) { + double val = array(rownum, colnum); -/* -class ImageComposite : public Py::PythonExtension { + gray = int(255 * val); + *buffer++ = gray; // red + *buffer++ = gray; // green + *buffer++ = gray; // blue + *buffer++ = 255; // alpha + } + } + return im; } -*/ +template +Image *from_color_array(ArrayType &array, bool isoutput) +{ + Image *im = new Image((unsigned)array.dim(0), (unsigned)array.dim(1), isoutput); + + agg::int8u *buffer; + if (isoutput) { + buffer = im->bufferOut; + } else { + buffer = im->bufferIn; + } + + int rgba = array.dim(2) >= 4; + double r, g, b; + double alpha = 1.0; + + for (size_t rownum = 0; rownum < (size_t)array.dim(0); rownum++) { + for (size_t colnum = 0; colnum < (size_t)array.dim(1); colnum++) { + typename ArrayType::sub_t::sub_t color = array[rownum][colnum]; + + r = color(0); + g = color(1); + b = color(2); + + if (rgba) { + alpha = color(3); + } -// the extension module -class _image_module : public Py::ExtensionModule<_image_module> + *buffer++ = int(255 * r); // red + *buffer++ = int(255 * g); // green + *buffer++ = int(255 * b); // blue + *buffer++ = int(255 * alpha); // alpha + } + } + + return im; +} + +template +Image *frombyte(ArrayType &array, bool isoutput) { -public: - _image_module() : Py::ExtensionModule<_image_module>("_image") - { - Image::init_type(); - - add_varargs_method("fromarray", &_image_module::fromarray, - "fromarray"); - add_varargs_method("fromarray2", &_image_module::fromarray2, - "fromarray2"); - add_varargs_method("frombyte", &_image_module::frombyte, - "frombyte"); - add_varargs_method("frombuffer", &_image_module::frombuffer, - "frombuffer"); - add_varargs_method("from_images", &_image_module::from_images, - "from_images"); - add_varargs_method("pcolor", &_image_module::pcolor, - "pcolor"); - add_varargs_method("pcolor2", &_image_module::pcolor2, - "pcolor2"); - initialize("The _image module"); - } - - ~_image_module() {} - -private: - Py::Object frombyte(const Py::Tuple &args); - Py::Object frombuffer(const Py::Tuple &args); - Py::Object fromarray(const Py::Tuple &args); - Py::Object fromarray2(const Py::Tuple &args); - Py::Object pcolor(const Py::Tuple &args); - Py::Object pcolor2(const Py::Tuple &args); - Py::Object from_images(const Py::Tuple &args); - - static char _image_module_fromarray__doc__[]; - static char _image_module_pcolor__doc__[]; - static char _image_module_pcolor2__doc__[]; - static char _image_module_fromarray2__doc__[]; - static char _image_module_frombyte__doc__[]; - static char _image_module_frombuffer__doc__[]; -}; + Image *im = new Image((unsigned)array.dim(0), (unsigned)array.dim(1), isoutput); + agg::int8u *buffer; + if (isoutput) { + buffer = im->bufferOut; + } else { + buffer = im->bufferIn; + } + int rgba = array.dim(2) >= 4; + agg::int8u r, g, b; + agg::int8u alpha = 255; -#endif + for (size_t rownum = 0; rownum < (size_t)array.dim(0); rownum++) { + for (size_t colnum = 0; colnum < (size_t)array.dim(1); colnum++) { + typename ArrayType::sub_t::sub_t color = array[rownum][colnum]; + r = color(0); + g = color(1); + b = color(2); + + if (rgba) { + alpha = color(3); + } + + *buffer++ = r; // red + *buffer++ = g; // green + *buffer++ = b; // blue + *buffer++ = alpha; // alpha + } + } + + return im; +} + +// utilities for irregular grids +void _bin_indices_middle( + unsigned int *irows, int nrows, const float *ys1, unsigned long ny, float dy, float y_min); +void _bin_indices_middle_linear(float *arows, + unsigned int *irows, + int nrows, + const float *y, + unsigned long ny, + float dy, + float y_min); +void _bin_indices(int *irows, int nrows, const double *y, unsigned long ny, double sc, double offs); +void _bin_indices_linear( + float *arows, int *irows, int nrows, double *y, unsigned long ny, double sc, double offs); +template +Image *pcolor(CoordinateArray &x, + CoordinateArray &y, + ColorArray &d, + unsigned int rows, + unsigned int cols, + float bounds[4], + int interpolation) +{ + if (rows >= 32768 || cols >= 32768) { + throw "rows and cols must both be less than 32768"; + } + + float x_min = bounds[0]; + float x_max = bounds[1]; + float y_min = bounds[2]; + float y_max = bounds[3]; + float width = x_max - x_min; + float height = y_max - y_min; + float dx = width / ((float)cols); + float dy = height / ((float)rows); + + // Check we have something to output to + if (rows == 0 || cols == 0) { + throw "Cannot scale to zero size"; + } + + if (d.dim(2) != 4) { + throw "data must be in RGBA format"; + } + + // Check dimensions match + unsigned long nx = x.dim(0); + unsigned long ny = y.dim(0); + if (nx != d.dim(1) || ny != d.dim(0)) { + throw "data and axis dimensions do not match"; + } + + // Allocate memory for pointer arrays + std::vector rowstarts(rows); + std::vector colstarts(cols); + + // Create output + Image *imo = new Image(rows, cols, true); + + // Calculate the pointer arrays to map input x to output x + unsigned int i, j; + unsigned int *colstart = &colstarts[0]; + unsigned int *rowstart = &rowstarts[0]; + const float *xs1 = x.data(); + const float *ys1 = y.data(); + + // Copy data to output buffer + const unsigned char *start; + const unsigned char *inposition; + size_t inrowsize = nx * 4; + size_t rowsize = cols * 4; + agg::int8u *position = imo->bufferOut; + agg::int8u *oldposition = NULL; + start = d.data(); + + if (interpolation == Image::NEAREST) { + _bin_indices_middle(colstart, cols, xs1, nx, dx, x_min); + _bin_indices_middle(rowstart, rows, ys1, ny, dy, y_min); + for (i = 0; i < rows; i++, rowstart++) { + if (i > 0 && *rowstart == 0) { + memcpy(position, oldposition, rowsize * sizeof(agg::int8u)); + oldposition = position; + position += rowsize; + } else { + oldposition = position; + start += *rowstart * inrowsize; + inposition = start; + for (j = 0, colstart = &colstarts[0]; j < cols; j++, position += 4, colstart++) { + inposition += *colstart * 4; + memcpy(position, inposition, 4 * sizeof(agg::int8u)); + } + } + } + } else if (interpolation == Image::BILINEAR) { + std::vector acols(cols); + std::vector arows(rows); + + _bin_indices_middle_linear(&acols[0], colstart, cols, xs1, nx, dx, x_min); + _bin_indices_middle_linear(&arows[0], rowstart, rows, ys1, ny, dy, y_min); + double a00, a01, a10, a11, alpha, beta; + + // Copy data to output buffer + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + alpha = arows[i]; + beta = acols[j]; + + a00 = alpha * beta; + a01 = alpha * (1.0 - beta); + a10 = (1.0 - alpha) * beta; + a11 = 1.0 - a00 - a01 - a10; + + typename ColorArray::sub_t::sub_t start00 = d[rowstart[i]][colstart[j]]; + typename ColorArray::sub_t::sub_t start01 = d[rowstart[i]][colstart[j] + 1]; + typename ColorArray::sub_t::sub_t start10 = d[rowstart[i] + 1][colstart[j]]; + typename ColorArray::sub_t::sub_t start11 = d[rowstart[i] + 1][colstart[j] + 1]; + for (size_t k = 0; k < 4; ++k) { + position[k] = + start00(k) * a00 + start01(k) * a01 + start10(k) * a10 + start11(k) * a11; + } + position += 4; + } + } + } + + return imo; +} + +template +Image *pcolor2(CoordinateArray &x, + CoordinateArray &y, + ColorArray &d, + unsigned int rows, + unsigned int cols, + float bounds[4], + Color &bg) +{ + double x_left = bounds[0]; + double x_right = bounds[1]; + double y_bot = bounds[2]; + double y_top = bounds[3]; + + // Check we have something to output to + if (rows == 0 || cols == 0) { + throw "rows or cols is zero; there are no pixels"; + } + + if (d.dim(2) != 4) { + throw "data must be in RGBA format"; + } + + // Check dimensions match + unsigned long nx = x.dim(0); + unsigned long ny = y.dim(0); + if (nx != d.dim(1) + 1 || ny != d.dim(0) + 1) { + throw "data and axis bin boundary dimensions are incompatible"; + } + + if (bg.dim(0) != 4) { + throw "bg must be in RGBA format"; + } + + std::vector irows(rows); + std::vector jcols(cols); + + // Create output + Image *imo = new Image(rows, cols, true); + + // Calculate the pointer arrays to map input x to output x + size_t i, j; + const double *x0 = x.data(); + const double *y0 = y.data(); + double sx = cols / (x_right - x_left); + double sy = rows / (y_top - y_bot); + _bin_indices(&jcols[0], cols, x0, nx, sx, x_left); + _bin_indices(&irows[0], rows, y0, ny, sy, y_bot); + + // Copy data to output buffer + agg::int8u *position = imo->bufferOut; + + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + if (irows[i] == -1 || jcols[j] == -1) { + memcpy(position, (const agg::int8u *)bg.data(), 4 * sizeof(agg::int8u)); + } else { + for (size_t k = 0; k < 4; ++k) { + position[k] = d(irows[i], jcols[j], k); + } + } + position += 4; + } + } + + return imo; +} + +#endif diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp new file mode 100644 index 000000000000..6491b993bf65 --- /dev/null +++ b/src/_image_wrapper.cpp @@ -0,0 +1,805 @@ +#include "mplutils.h" +#include "_image.h" +#include "py_converters.h" + +/********************************************************************** + * Image + * */ + +typedef struct +{ + PyObject_HEAD; + Image *x; + Py_ssize_t shape[3]; + Py_ssize_t strides[3]; + Py_ssize_t suboffsets[3]; + PyObject *dict; +} PyImage; + +static PyTypeObject PyImageType; + +static PyObject *PyImage_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyImage *self; + self = (PyImage *)type->tp_alloc(type, 0); + memset(self, 0, sizeof(PyImage)); + self->x = NULL; + self->dict = PyDict_New(); + return (PyObject *)self; +} + +static PyObject *PyImage_cnew(Image *im) +{ + PyImage *self; + self = (PyImage *)PyImageType.tp_alloc(&PyImageType, 0); + self->x = im; + self->dict = PyDict_New(); + return (PyObject *)self; +} + +static int PyImage_init(PyImage *self, PyObject *args, PyObject *kwds) +{ + if (!PyArg_ParseTuple(args, "")) { + return -1; + } + + CALL_CPP_INIT("Image", (self->x = new Image())); + + return 0; +} + +static void PyImage_dealloc(PyImage *self) +{ + delete self->x; + Py_DECREF(self->dict); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +const char *PyImage_apply_rotation__doc__ = + "apply_rotation(angle)\n" + "\n" + "Apply the rotation (degrees) to image"; + +static PyObject *PyImage_apply_rotation(PyImage *self, PyObject *args, PyObject *kwds) +{ + double r; + + if (!PyArg_ParseTuple(args, "d:apply_rotation", &r)) { + return NULL; + } + + CALL_CPP("apply_rotation", (self->x->apply_rotation(r))); + + Py_RETURN_NONE; +} + +const char *PyImage_set_bg__doc__ = + "set_bg(r,g,b,a)\n" + "\n" + "Set the background color"; + +static PyObject *PyImage_set_bg(PyImage *self, PyObject *args, PyObject *kwds) +{ + double r, g, b, a; + + if (!PyArg_ParseTuple(args, "dddd:set_bg", &r, &g, &b, &a)) { + return NULL; + } + + CALL_CPP("set_bg", (self->x->set_bg(r, g, b, a))); + + Py_RETURN_NONE; +} + +const char *PyImage_apply_scaling__doc__ = + "apply_scaling(sx, sy)\n" + "\n" + "Apply the scale factors sx, sy to the transform matrix"; + +static PyObject *PyImage_apply_scaling(PyImage *self, PyObject *args, PyObject *kwds) +{ + double sx, sy; + + if (!PyArg_ParseTuple(args, "dd:apply_scaling", &sx, &sy)) { + return NULL; + } + + CALL_CPP("apply_scaling", (self->x->apply_scaling(sx, sy))); + + Py_RETURN_NONE; +} + +const char *PyImage_apply_translation__doc__ = + "apply_translation(tx, ty)\n" + "\n" + "Apply the translation tx, ty to the transform matrix"; + +static PyObject *PyImage_apply_translation(PyImage *self, PyObject *args, PyObject *kwds) +{ + double tx, ty; + if (!PyArg_ParseTuple(args, "dd:apply_translation", &tx, &ty)) { + return NULL; + } + + CALL_CPP("apply_translation", self->x->apply_translation(tx, ty)); + + Py_RETURN_NONE; +} + +const char *PyImage_as_rgba_str__doc__ = + "numrows, numcols, s = as_rgba_str()" + "\n" + "Call this function after resize to get the data as string\n" + "The string is a numrows by numcols x 4 (RGBA) unsigned char buffer\n"; + +static PyObject *PyImage_as_rgba_str(PyImage *self, PyObject *args, PyObject *kwds) +{ + // TODO: This performs a copy. Use buffer interface when possible + + PyObject *result = PyBytes_FromStringAndSize(NULL, self->x->rowsOut * self->x->colsOut * 4); + if (result == NULL) { + return NULL; + } + + CALL_CPP_CLEANUP("as_rgba_str", + (self->x->as_rgba_str((agg::int8u *)PyBytes_AsString(result))), + Py_DECREF(result)); + + return Py_BuildValue("nnN", self->x->rowsOut, self->x->colsOut, result); +} + +const char *PyImage_color_conv__doc__ = + "numrows, numcols, buffer = color_conv(format)" + "\n" + "format 0(BGRA) or 1(ARGB)\n" + "Convert image to format and return in a writable buffer\n"; + +// TODO: This function is a terrible interface. Change/remove? Only +// used by Cairo backend. + +static PyObject *PyImage_color_conv(PyImage *self, PyObject *args, PyObject *kwds) +{ + int format; + + if (!PyArg_ParseTuple(args, "i:color_conv", &format)) { + return NULL; + } + + PyObject *result = PyBytes_FromStringAndSize(NULL, self->x->rowsOut * self->x->colsOut * 4); + if (result == NULL) { + return NULL; + } + + CALL_CPP_CLEANUP("color_conv", + (self->x->color_conv(format, (agg::int8u *)PyBytes_AsString(result))), + Py_DECREF(result)); + + return Py_BuildValue("nnN", self->x->rowsOut, self->x->colsOut, result); +} + +const char *PyImage_buffer_rgba__doc__ = + "buffer = buffer_rgba()" + "\n" + "Return the image buffer as rgba32\n"; + +static PyObject *PyImage_buffer_rgba(PyImage *self, PyObject *args, PyObject *kwds) +{ +#if PY3K + return Py_BuildValue("nny#", + self->x->rowsOut, + self->x->colsOut, + self->x->rbufOut, + self->x->rowsOut * self->x->colsOut * 4); +#else + PyObject *buffer = + PyBuffer_FromReadWriteMemory(self->x->rbufOut, self->x->rowsOut * self->x->colsOut * 4); + if (buffer == NULL) { + return NULL; + } + + return Py_BuildValue("nnN", self->x->rowsOut, self->x->colsOut, buffer); +#endif +} + +const char *PyImage_reset_matrix__doc__ = + "reset_matrix()" + "\n" + "Reset the transformation matrix"; + +static PyObject *PyImage_reset_matrix(PyImage *self, PyObject *args, PyObject *kwds) +{ + CALL_CPP("reset_matrix", self->x->reset_matrix()); + + Py_RETURN_NONE; +} + +const char *PyImage_get_matrix__doc__ = + "(m11,m21,m12,m22,m13,m23) = get_matrix()\n" + "\n" + "Get the affine transformation matrix\n" + " /m11,m12,m13\\\n" + " /m21,m22,m23|\n" + " \\ 0 , 0 , 1 /"; + +static PyObject *PyImage_get_matrix(PyImage *self, PyObject *args, PyObject *kwds) +{ + double m[6]; + self->x->srcMatrix.store_to(m); + + return Py_BuildValue("dddddd", m[0], m[1], m[2], m[3], m[4], m[5]); +} + +const char *PyImage_resize__doc__ = + "resize(width, height, norm=1, radius=4.0)\n" + "\n" + "Resize the image to width, height using interpolation\n" + "norm and radius are optional args for some of the filters\n"; + +static PyObject *PyImage_resize(PyImage *self, PyObject *args, PyObject *kwds) +{ + double width; + double height; + double norm; + double radius; + const char *names[] = { "width", "height", "norm", "radius", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "dd|dd:resize", (char **)names, &width, &height, &norm, &radius)) { + return NULL; + } + + CALL_CPP("resize", (self->x->resize(width, height, norm, radius))); + + Py_RETURN_NONE; +} + +const char *PyImage_get_interpolation__doc__ = + "get_interpolation()\n" + "\n" + "Get the interpolation scheme to one of the module constants, " + "one of image.NEAREST, image.BILINEAR, etc..."; + +static PyObject *PyImage_get_interpolation(PyImage *self, PyObject *args, PyObject *kwds) +{ + return PyLong_FromLong(self->x->interpolation); +} + +const char *PyImage_set_interpolation__doc__ = + "set_interpolation(scheme)\n" + "\n" + "Set the interpolation scheme to one of the module constants, " + "eg, image.NEAREST, image.BILINEAR, etc..."; + +static PyObject *PyImage_set_interpolation(PyImage *self, PyObject *args, PyObject *kwds) +{ + int method; + + if (!PyArg_ParseTuple(args, "i:set_interpolation", &method)) { + return NULL; + } + + self->x->interpolation = method; + + Py_RETURN_NONE; +} + +const char *PyImage_get_aspect__doc__ = + "get_aspect()\n" + "\n" + "Get the aspect constraint constants"; + +static PyObject *PyImage_get_aspect(PyImage *self, PyObject *args, PyObject *kwds) +{ + return PyLong_FromLong(self->x->aspect); +} + +const char *PyImage_set_aspect__doc__ = + "set_aspect(scheme)\n" + "\n" + "Set the aspect ratio to one of the image module constant." + "eg, one of image.ASPECT_PRESERVE, image.ASPECT_FREE"; + +static PyObject *PyImage_set_aspect(PyImage *self, PyObject *args, PyObject *kwds) +{ + int scheme; + if (!PyArg_ParseTuple(args, "i:set_aspect", &scheme)) { + return NULL; + } + + self->x->aspect = scheme; + + Py_RETURN_NONE; +} + +const char *PyImage_get_size__doc__ = + "numrows, numcols = get_size()\n" + "\n" + "Get the number or rows and columns of the input image"; + +static PyObject *PyImage_get_size(PyImage *self, PyObject *args, PyObject *kwds) +{ + return Py_BuildValue("ii", self->x->rowsIn, self->x->colsIn); +} + +const char *PyImage_get_resample__doc__ = + "get_resample()\n" + "\n" + "Get the resample flag."; + +static PyObject *PyImage_get_resample(PyImage *self, PyObject *args, PyObject *kwds) +{ + if (self->x->resample) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +const char *PyImage_set_resample__doc__ = + "set_resample(boolean)\n" + "\n" + "Set the resample flag."; + +static PyObject *PyImage_set_resample(PyImage *self, PyObject *args, PyObject *kwds) +{ + int resample; + + if (!PyArg_ParseTuple(args, "i:set_resample", &resample)) { + return NULL; + } + + self->x->resample = resample; + + Py_RETURN_NONE; +} + +const char *PyImage_get_size_out__doc__ = + "numrows, numcols = get_size_out()\n" + "\n" + "Get the number or rows and columns of the output image"; + +static PyObject *PyImage_get_size_out(PyImage *self, PyObject *args, PyObject *kwds) +{ + return Py_BuildValue("ii", self->x->rowsOut, self->x->colsOut); +} + +static int PyImage_get_buffer(PyImage *self, Py_buffer *buf, int flags) +{ + Image *im = self->x; + + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = im->bufferOut; + buf->len = im->colsOut * im->rowsOut * 4; + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 3; + self->shape[0] = im->rowsOut; + self->shape[1] = im->colsOut; + self->shape[2] = 4; + buf->shape = self->shape; + self->strides[0] = im->colsOut * 4; + self->strides[1] = 4; + self->strides[2] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject *PyImage_init_type(PyObject *m, PyTypeObject *type) +{ + static PyMethodDef methods[] = { + {"apply_rotation", (PyCFunction)PyImage_apply_rotation, METH_VARARGS, PyImage_apply_rotation__doc__}, + {"set_bg", (PyCFunction)PyImage_set_bg, METH_VARARGS, PyImage_set_bg__doc__}, + {"apply_scaling", (PyCFunction)PyImage_apply_scaling, METH_VARARGS, PyImage_apply_scaling__doc__}, + {"apply_translation", (PyCFunction)PyImage_apply_translation, METH_VARARGS, PyImage_apply_translation__doc__}, + {"as_rgba_str", (PyCFunction)PyImage_as_rgba_str, METH_NOARGS, PyImage_as_rgba_str__doc__}, + {"color_conv", (PyCFunction)PyImage_color_conv, METH_VARARGS, PyImage_color_conv__doc__}, + {"buffer_rgba", (PyCFunction)PyImage_buffer_rgba, METH_NOARGS, PyImage_buffer_rgba__doc__}, + {"reset_matrix", (PyCFunction)PyImage_reset_matrix, METH_NOARGS, PyImage_reset_matrix__doc__}, + {"get_matrix", (PyCFunction)PyImage_get_matrix, METH_NOARGS, PyImage_get_matrix__doc__}, + {"resize", (PyCFunction)PyImage_resize, METH_VARARGS|METH_KEYWORDS, PyImage_resize__doc__}, + {"get_interpolation", (PyCFunction)PyImage_get_interpolation, METH_NOARGS, PyImage_get_interpolation__doc__}, + {"set_interpolation", (PyCFunction)PyImage_set_interpolation, METH_VARARGS, PyImage_set_interpolation__doc__}, + {"get_aspect", (PyCFunction)PyImage_get_aspect, METH_NOARGS, PyImage_get_aspect__doc__}, + {"set_aspect", (PyCFunction)PyImage_set_aspect, METH_VARARGS, PyImage_set_aspect__doc__}, + {"get_size", (PyCFunction)PyImage_get_size, METH_NOARGS, PyImage_get_size__doc__}, + {"get_resample", (PyCFunction)PyImage_get_resample, METH_VARARGS, PyImage_get_resample__doc__}, + {"set_resample", (PyCFunction)PyImage_set_resample, METH_VARARGS, PyImage_set_resample__doc__}, + {"get_size_out", (PyCFunction)PyImage_get_size_out, METH_VARARGS, PyImage_get_size_out__doc__}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + memset(&buffer_procs, 0, sizeof(PyBufferProcs)); + buffer_procs.bf_getbuffer = (getbufferproc)PyImage_get_buffer; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib._image.Image"; + type->tp_basicsize = sizeof(PyImage); + type->tp_dealloc = (destructor)PyImage_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER; + type->tp_methods = methods; + type->tp_new = PyImage_new; + type->tp_init = (initproc)PyImage_init; + type->tp_as_buffer = &buffer_procs; + type->tp_dictoffset = offsetof(PyImage, dict); + + if (PyType_Ready(type) < 0) { + return NULL; + } + + if (PyModule_AddObject(m, "Image", (PyObject *)type)) { + return NULL; + } + + return type; +} + +/********************************************************************** + * Free functions + * */ + +const char *image_from_images__doc__ = + "from_images(numrows, numcols, seq)\n" + "\n" + "return an image instance with numrows, numcols from a seq of image\n" + "instances using alpha blending. seq is a list of (Image, ox, oy)"; + +static PyObject *image_from_images(PyObject *self, PyObject *args, PyObject *kwds) +{ + unsigned int numrows; + unsigned int numcols; + PyObject *images; + size_t numimages; + + if (!PyArg_ParseTuple(args, "IIO:from_images", &numrows, &numcols, &images)) { + return NULL; + } + + if (!PySequence_Check(images)) { + return NULL; + } + + Image *im = new Image(numrows, numcols, true); + im->clear(); + + numimages = PySequence_Size(images); + + for (size_t i = 0; i < numimages; ++i) { + PyObject *entry = PySequence_GetItem(images, i); + if (entry == NULL) { + delete im; + return NULL; + } + + PyObject *subimage; + unsigned int x; + unsigned int y; + PyObject *alphaobj = NULL; + double alpha = 0.0; + + if (!PyArg_ParseTuple(entry, "O!II|O", &PyImageType, &subimage, &x, &y, &alphaobj)) { + Py_DECREF(entry); + delete im; + return NULL; + } + + bool has_alpha = false; + if (alphaobj != NULL && alphaobj != Py_None) { + has_alpha = true; + alpha = PyFloat_AsDouble(alphaobj); + if (PyErr_Occurred()) { + Py_DECREF(entry); + delete im; + return NULL; + } + } + + CALL_CPP("from_images", + (im->blend_image(*((PyImage *)subimage)->x, x, y, has_alpha, alpha))); + + Py_DECREF(entry); + } + + return PyImage_cnew(im); +} + +const char *image_fromarray__doc__ = + "fromarray(A, isoutput)\n" + "\n" + "Load the image from a numpy array\n" + "By default this function fills the input buffer, which can subsequently\n" + "be resampled using resize. If isoutput=1, fill the output buffer.\n" + "This is used to support raw pixel images w/o resampling\n"; + +static PyObject *image_fromarray(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *array; + int isoutput; + const char *names[] = { "array", "isoutput", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|i:fromarray", (char **)names, &array, &isoutput)) { + return NULL; + } + + numpy::array_view color_array; + numpy::array_view grey_array; + Image *result = NULL; + + if (numpy::convert_array(array, &color_array)) { + CALL_CPP("fromarray", result = from_color_array(color_array, isoutput)); + } else if (numpy::convert_array(array, &grey_array)) { + CALL_CPP("fromarray", result = from_grey_array(grey_array, isoutput)); + } else { + PyErr_SetString(PyExc_ValueError, "invalid array"); + return NULL; + } + + return PyImage_cnew(result); +} + +const char *image_frombyte__doc__ = + "frombyte(A, isoutput)\n" + "\n" + "Load the image from a byte array.\n" + "By default this function fills the input buffer, which can subsequently\n" + "be resampled using resize. If isoutput=1, fill the output buffer.\n" + "This is used to support raw pixel images w/o resampling."; + +static PyObject *image_frombyte(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view array; + int isoutput; + const char *names[] = { "array", "isoutput", NULL }; + Image *result; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&|i:frombyte", + (char **)names, + &numpy::convert_array_contiguous, + &array, + &isoutput)) { + return NULL; + } + + CALL_CPP("frombyte", (result = frombyte(array, isoutput))); + + return PyImage_cnew(result); +} + +const char *image_frombuffer__doc__ = + "frombuffer(buffer, width, height, isoutput)\n" + "\n" + "Load the image from a character buffer\n" + "By default this function fills the input buffer, which can subsequently\n" + "be resampled using resize. If isoutput=1, fill the output buffer.\n" + "This is used to support raw pixel images w/o resampling."; + +static PyObject *image_frombuffer(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *buffer; + unsigned x; + unsigned y; + int isoutput; + const char *names[] = { "buffer", "x", "y", "isoutput", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "OII|i:frombuffer", (char **)names, &buffer, &x, &y, &isoutput)) { + return NULL; + } + + const void *rawbuf; + Py_ssize_t buflen; + if (PyObject_AsReadBuffer(buffer, &rawbuf, &buflen) != 0) { + return NULL; + } + + if (buflen != (Py_ssize_t)(y * x * 4)) { + PyErr_SetString(PyExc_ValueError, "Buffer is incorrect length"); + return NULL; + } + + Image *im; + CALL_CPP("frombuffer", (im = new Image(y, x, isoutput))); + + agg::int8u *inbuf = (agg::int8u *)rawbuf; + agg::int8u *outbuf; + if (isoutput) { + outbuf = im->bufferOut; + } else { + outbuf = im->bufferIn; + } + + for (int i = (x * 4) * (y - 1); i >= 0; i -= (x * 4)) { + memmove(outbuf, &inbuf[i], (x * 4)); + outbuf += x * 4; + } + + return PyImage_cnew(im); +} + +const char *image_pcolor__doc__ = + "pcolor(x, y, data, rows, cols, bounds)\n" + "\n" + "Generate a pseudo-color image from data on a non-uniform grid using\n" + "nearest neighbour or linear interpolation.\n" + "bounds = (x_min, x_max, y_min, y_max)\n" + "interpolation = NEAREST or BILINEAR \n"; + +static PyObject *image_pcolor(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view x; + numpy::array_view y; + numpy::array_view d; + unsigned int rows; + unsigned int cols; + float bounds[4]; + int interpolation; + Image *result; + + if (!PyArg_ParseTuple(args, + "O&O&O&II(ffff)i:pcolor", + &numpy::convert_array_contiguous, + &x, + &numpy::convert_array_contiguous, + &y, + &numpy::convert_array_contiguous, + &d, + &rows, + &cols, + &bounds[0], + &bounds[1], + &bounds[2], + &bounds[3], + &interpolation)) { + return NULL; + } + + CALL_CPP("pcolor", (result = pcolor(x, y, d, rows, cols, bounds, interpolation))); + + return PyImage_cnew(result); +} + +const char *image_pcolor2__doc__ = + "pcolor2(x, y, data, rows, cols, bounds, bg)\n" + "\n" + "Generate a pseudo-color image from data on a non-uniform grid\n" + "specified by its cell boundaries.\n" + "bounds = (x_left, x_right, y_bot, y_top)\n" + "bg = ndarray of 4 uint8 representing background rgba\n"; + +static PyObject *image_pcolor2(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view x; + numpy::array_view y; + numpy::array_view d; + unsigned int rows; + unsigned int cols; + float bounds[4]; + numpy::array_view bg; + Image *result; + + if (!PyArg_ParseTuple(args, + "O&O&O&II(ffff)O&:pcolor2", + &numpy::convert_array_contiguous, + &x, + &numpy::convert_array_contiguous, + &y, + &numpy::convert_array, + &d, + &rows, + &cols, + &bounds[0], + &bounds[1], + &bounds[2], + &bounds[3], + &numpy::convert_array, + &bg)) { + return NULL; + } + + CALL_CPP("pcolor2", (result = pcolor2(x, y, d, rows, cols, bounds, bg))); + + return PyImage_cnew(result); +} + +static PyMethodDef module_functions[] = { + {"from_images", (PyCFunction)image_from_images, METH_VARARGS, image_from_images__doc__}, + {"fromarray", (PyCFunction)image_fromarray, METH_VARARGS|METH_KEYWORDS, image_fromarray__doc__}, + {"frombyte", (PyCFunction)image_frombyte, METH_VARARGS|METH_KEYWORDS, image_frombyte__doc__}, + {"frombuffer", (PyCFunction)image_frombuffer, METH_VARARGS|METH_KEYWORDS, image_frombuffer__doc__}, + {"pcolor", (PyCFunction)image_pcolor, METH_VARARGS, image_pcolor__doc__}, + {"pcolor2", (PyCFunction)image_pcolor2, METH_VARARGS, image_pcolor2__doc__}, + {NULL} +}; + +extern "C" { + +struct module_state +{ +/* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif +}; + +#if PY3K +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_image", + NULL, + sizeof(struct module_state), + module_functions, + NULL, + NULL, + NULL, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__image(void) + +#else +#define INITERROR return + +PyMODINIT_FUNC init_image(void) +#endif + +{ + PyObject *m; + +#if PY3K + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("_image", module_functions, NULL); +#endif + + if (m == NULL) { + INITERROR; + } + + import_array(); + + if (!PyImage_init_type(m, &PyImageType)) { + INITERROR; + } + + PyObject *d = PyModule_GetDict(m); + + if (add_dict_int(d, "NEAREST", Image::NEAREST) || + add_dict_int(d, "BILINEAR", Image::BILINEAR) || + add_dict_int(d, "BICUBIC", Image::BICUBIC) || + add_dict_int(d, "SPLINE16", Image::SPLINE16) || + add_dict_int(d, "SPLINE36", Image::SPLINE36) || + add_dict_int(d, "HANNING", Image::HANNING) || + add_dict_int(d, "HAMMING", Image::HAMMING) || + add_dict_int(d, "HERMITE", Image::HERMITE) || + add_dict_int(d, "KAISER", Image::KAISER) || + add_dict_int(d, "QUADRIC", Image::QUADRIC) || + add_dict_int(d, "CATROM", Image::CATROM) || + add_dict_int(d, "GAUSSIAN", Image::GAUSSIAN) || + add_dict_int(d, "BESSEL", Image::BESSEL) || + add_dict_int(d, "MITCHELL", Image::MITCHELL) || + add_dict_int(d, "SINC", Image::SINC) || + add_dict_int(d, "LANCZOS", Image::LANCZOS) || + add_dict_int(d, "BLACKMAN", Image::BLACKMAN) || + + add_dict_int(d, "ASPECT_FREE", Image::ASPECT_FREE) || + add_dict_int(d, "ASPECT_PRESERVE", Image::ASPECT_PRESERVE)) { + INITERROR; + } + + import_array(); + +#if PY3K + return m; +#endif +} + +} // extern "C" diff --git a/src/_macosx.m b/src/_macosx.m index dbebfdc39c97..a5775789e173 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -4546,11 +4546,7 @@ -(void)save_figure:(id)sender { if(states[i]==1) { -#if PY3K PyList_SET_ITEM(list, j, PyLong_FromLong(i)); -#else - PyList_SET_ITEM(list, j, PyInt_FromLong(i)); -#endif j++; } } diff --git a/src/_path.cpp b/src/_path.cpp deleted file mode 100644 index 32eb73601b81..000000000000 --- a/src/_path.cpp +++ /dev/null @@ -1,1813 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#include "agg_py_path_iterator.h" -#include "agg_py_transforms.h" -#include "path_converters.h" - -#include -#include - -#include "CXX/Extensions.hxx" - -#include "agg_conv_contour.h" -#include "agg_conv_curve.h" -#include "agg_conv_stroke.h" -#include "agg_conv_transform.h" -#include "agg_path_storage.h" -#include "agg_trans_affine.h" - -struct XY -{ - double x; - double y; - - XY(double x_, double y_) : x(x_), y(y_) {} -}; - -// the extension module -class _path_module : public Py::ExtensionModule<_path_module> -{ -public: - _path_module() - : Py::ExtensionModule<_path_module>("_path") - { - add_varargs_method("point_in_path", &_path_module::point_in_path, - "point_in_path(x, y, path, trans)"); - add_varargs_method("points_in_path", &_path_module::points_in_path, - "points_in_path(points, path, trans)"); - add_varargs_method("point_on_path", &_path_module::point_on_path, - "point_on_path(x, y, r, path, trans)"); - add_varargs_method("get_path_extents", &_path_module::get_path_extents, - "get_path_extents(path, trans)"); - add_varargs_method("update_path_extents", &_path_module::update_path_extents, - "update_path_extents(path, trans, bbox, minpos)"); - add_varargs_method("get_path_collection_extents", &_path_module::get_path_collection_extents, - "get_path_collection_extents(trans, paths, transforms, offsets, offsetTrans)"); - add_varargs_method("point_in_path_collection", &_path_module::point_in_path_collection, - "point_in_path_collection(x, y, r, trans, paths, transforms, offsets, offsetTrans, filled)"); - add_varargs_method("path_in_path", &_path_module::path_in_path, - "path_in_path(a, atrans, b, btrans)"); - add_varargs_method("clip_path_to_rect", &_path_module::clip_path_to_rect, - "clip_path_to_rect(path, bbox, inside)"); - add_varargs_method("affine_transform", &_path_module::affine_transform, - "affine_transform(vertices, transform)"); - add_varargs_method("count_bboxes_overlapping_bbox", &_path_module::count_bboxes_overlapping_bbox, - "count_bboxes_overlapping_bbox(bbox, bboxes)"); - add_varargs_method("path_intersects_path", &_path_module::path_intersects_path, - "path_intersects_path(p1, p2)"); - add_varargs_method("convert_path_to_polygons", &_path_module::convert_path_to_polygons, - "convert_path_to_polygons(path, trans, width, height)"); - add_varargs_method("cleanup_path", &_path_module::cleanup_path, - "cleanup_path(path, trans, remove_nans, clip, snap, simplify, curves, sketch_params)"); - add_varargs_method("convert_to_svg", &_path_module::convert_to_svg, - "convert_to_svg(path, trans, clip, simplify, precision)"); - initialize("Helper functions for paths"); - } - - virtual ~_path_module() {} - -private: - Py::Object point_in_path(const Py::Tuple& args); - Py::Object points_in_path(const Py::Tuple& args); - Py::Object point_on_path(const Py::Tuple& args); - Py::Object get_path_extents(const Py::Tuple& args); - Py::Object update_path_extents(const Py::Tuple& args); - Py::Object get_path_collection_extents(const Py::Tuple& args); - Py::Object point_in_path_collection(const Py::Tuple& args); - Py::Object path_in_path(const Py::Tuple& args); - Py::Object clip_path_to_rect(const Py::Tuple& args); - Py::Object affine_transform(const Py::Tuple& args); - Py::Object count_bboxes_overlapping_bbox(const Py::Tuple& args); - Py::Object path_intersects_path(const Py::Tuple& args); - Py::Object convert_path_to_polygons(const Py::Tuple& args); - Py::Object cleanup_path(const Py::Tuple& args); - Py::Object convert_to_svg(const Py::Tuple& args); -}; - -// -// The following function was found in the Agg 2.3 examples (interactive_polygon.cpp). -// It has been generalized to work on (possibly curved) polylines, rather than -// just polygons. The original comments have been kept intact. -// -- Michael Droettboom 2007-10-02 -// -//======= Crossings Multiply algorithm of InsideTest ======================== -// -// By Eric Haines, 3D/Eye Inc, erich@eye.com -// -// This version is usually somewhat faster than the original published in -// Graphics Gems IV; by turning the division for testing the X axis crossing -// into a tricky multiplication test this part of the test became faster, -// which had the additional effect of making the test for "both to left or -// both to right" a bit slower for triangles than simply computing the -// intersection each time. The main increase is in triangle testing speed, -// which was about 15% faster; all other polygon complexities were pretty much -// the same as before. On machines where division is very expensive (not the -// case on the HP 9000 series on which I tested) this test should be much -// faster overall than the old code. Your mileage may (in fact, will) vary, -// depending on the machine and the test data, but in general I believe this -// code is both shorter and faster. This test was inspired by unpublished -// Graphics Gems submitted by Joseph Samosky and Mark Haigh-Hutchinson. -// Related work by Samosky is in: -// -// Samosky, Joseph, "SectionView: A system for interactively specifying and -// visualizing sections through three-dimensional medical image data", -// M.S. Thesis, Department of Electrical Engineering and Computer Science, -// Massachusetts Institute of Technology, 1993. -// -// Shoot a test ray along +X axis. The strategy is to compare vertex Y values -// to the testing point's Y and quickly discard edges which are entirely to one -// side of the test ray. Note that CONVEX and WINDING code can be added as -// for the CrossingsTest() code; it is left out here for clarity. -// -// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point -// _point_, returns 1 if inside, 0 if outside. -template -static void -point_in_path_impl(const void* const points_, const size_t s0, - const size_t s1, const size_t n, T& path, - npy_bool* const inside_flag) -{ - int *yflag0; - int *subpath_flag; - int yflag1; - double vtx0, vty0, vtx1, vty1; - double tx, ty; - double sx, sy; - double x, y; - size_t i; - int all_done; - const char *const points = (const char * const)points_; - - yflag0 = (int *)malloc(n * sizeof(int)); - subpath_flag = (int *)malloc(n * sizeof(int)); - - path.rewind(0); - - for (i = 0; i < n; ++i) { - inside_flag[i] = 0; - } - - unsigned code = 0; - do - { - if (code != agg::path_cmd_move_to) - { - code = path.vertex(&x, &y); - if (code == agg::path_cmd_stop || - (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { - continue; - } - } - - sx = vtx0 = vtx1 = x; - sy = vty0 = vty1 = y; - - for (i = 0; i < n; ++i) { - ty = *(double *)(points + s0 * i + s1); - - // get test bit for above/below X axis - yflag0[i] = (vty0 >= ty); - - subpath_flag[i] = 0; - } - - do - { - code = path.vertex(&x, &y); - - // The following cases denote the beginning on a new subpath - if (code == agg::path_cmd_stop || - (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) - { - x = sx; - y = sy; - } - else if (code == agg::path_cmd_move_to) - { - break; - } - - for (i = 0; i < n; ++i) { - tx = *(double *)(points + s0 * i); - ty = *(double *)(points + s0 * i + s1); - - yflag1 = (vty1 >= ty); - // Check if endpoints straddle (are on opposite sides) of - // X axis (i.e. the Y's differ); if so, +X ray could - // intersect this edge. The old test also checked whether - // the endpoints are both to the right or to the left of - // the test point. However, given the faster intersection - // point computation used below, this test was found to be - // a break-even proposition for most polygons and a loser - // for triangles (where 50% or more of the edges which - // survive this test will cross quadrants and so have to - // have the X intersection computed anyway). I credit - // Joseph Samosky with inspiring me to try dropping the - // "both left or both right" part of my code. - if (yflag0[i] != yflag1) { - // Check intersection of pgon segment with +X ray. - // Note if >= point's X; if so, the ray hits it. The - // division operation is avoided for the ">=" test by - // checking the sign of the first vertex wrto the test - // point; idea inspired by Joseph Samosky's and Mark - // Haigh-Hutchinson's different polygon inclusion - // tests. - if (((vty1 - ty) * (vtx0 - vtx1) >= - (vtx1 - tx) * (vty0 - vty1)) == yflag1) { - subpath_flag[i] ^= 1; - } - } - - // Move to the next pair of vertices, retaining info as - // possible. - yflag0[i] = yflag1; - } - - vtx0 = vtx1; - vty0 = vty1; - - vtx1 = x; - vty1 = y; - } - while (code != agg::path_cmd_stop && - (code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); - - all_done = 1; - for (i = 0; i < n; ++i) { - tx = *(double *)(points + s0 * i); - ty = *(double *)(points + s0 * i + s1); - - yflag1 = (vty1 >= ty); - if (yflag0[i] != yflag1) { - if (((vty1 - ty) * (vtx0 - vtx1) >= - (vtx1 - tx) * (vty0 - vty1)) == yflag1) { - subpath_flag[i] ^= 1; - } - } - inside_flag[i] |= subpath_flag[i]; - if (inside_flag[i] == 0) { - all_done = 0; - } - } - - if (all_done) { - goto exit; - } - } - while (code != agg::path_cmd_stop); - - exit: - - free(yflag0); - free(subpath_flag); -} - -inline void -points_in_path(const void* const points, const size_t s0, - const size_t s1, const size_t n, - const double r, PathIterator& path, - const agg::trans_affine& trans, - npy_bool* result) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover no_nans_t; - typedef agg::conv_curve curve_t; - typedef agg::conv_contour contour_t; - - size_t i; - for (i = 0; i < n; ++i) { - result[i] = 0; - } - - if (path.total_vertices() < 3) - { - return; - } - - transformed_path_t trans_path(path, trans); - no_nans_t no_nans_path(trans_path, true, path.has_curves()); - curve_t curved_path(no_nans_path); - contour_t contoured_path(curved_path); - contoured_path.width(r); - point_in_path_impl(points, s0, s1, n, contoured_path, result); -} - -inline bool -point_in_path(const double x, const double y, const double r, - PathIterator& path, const agg::trans_affine& trans) -{ - double points[2]; - npy_bool result; - - points[0] = x; - points[1] = y; - - points_in_path(points, 0, sizeof(double), 1, r, path, trans, &result); - return result; -} - -inline void -points_on_path(const void* const points, const size_t s0, - const size_t s1, const size_t n, - const double r, PathIterator& path, - const agg::trans_affine& trans, - npy_bool* result) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover no_nans_t; - typedef agg::conv_curve curve_t; - typedef agg::conv_stroke stroke_t; - - transformed_path_t trans_path(path, trans); - no_nans_t nan_removed_path(trans_path, true, path.has_curves()); - curve_t curved_path(nan_removed_path); - stroke_t stroked_path(curved_path); - stroked_path.width(r * 2.0); - point_in_path_impl(points, s0, s1, n, stroked_path, result); -} - -inline bool -point_on_path(const double x, const double y, const double r, - PathIterator& path, const agg::trans_affine& trans) -{ - double points[2]; - npy_bool result; - - points[0] = x; - points[1] = y; - - points_on_path(points, 0, sizeof(double), 1, r, path, trans, &result); - return result; -} - -Py::Object -_path_module::point_in_path(const Py::Tuple& args) -{ - double x = Py::Float(args[0]); - double y = Py::Float(args[1]); - double r = Py::Float(args[2]); - PathIterator path(args[3]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[4].ptr(), false); - - if (::point_in_path(x, y, r, path, trans)) { - return Py::Int(1); - } - return Py::Int(0); -} - -Py::Object -_path_module::points_in_path(const Py::Tuple& args) -{ - args.verify_length(4); - - npy_intp n; - PyArrayObject* points_array; - points_array = (PyArrayObject*)PyArray_FromObject(args[0].ptr(), NPY_DOUBLE, 2, 2); - if (points_array == NULL || PyArray_DIM(points_array, 1) != 2) { - throw Py::TypeError( - "Argument 0 to points_in_path must be an Nx2 numpy array"); - - } - double r = Py::Float(args[1]); - PathIterator path(args[2]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[3].ptr(), false); - - n = PyArray_DIM(points_array, 0); - PyObject* result = PyArray_ZEROS(1, &n, NPY_BOOL, 0); - if (result == NULL) { - throw Py::MemoryError("Could not allocate memory for result"); - } - - ::points_in_path(PyArray_DATA(points_array), - PyArray_STRIDE(points_array, 0), - PyArray_STRIDE(points_array, 1), - n, r, path, trans, - (npy_bool *)PyArray_DATA((PyArrayObject*)result)); - Py_DECREF(points_array); - - return Py::Object(result, true);; -} - -Py::Object -_path_module::point_on_path(const Py::Tuple& args) -{ - double x = Py::Float(args[0]); - double y = Py::Float(args[1]); - double r = Py::Float(args[2]); - PathIterator path(args[3]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[4].ptr()); - - if (::point_on_path(x, y, r, path, trans)) - { - return Py::Int(1); - } - return Py::Int(0); -} - -void -update_limits(double x, double y, - double* x0, double* y0, double* x1, double* y1, - double* xm, double* ym) -{ - if (x < *x0) *x0 = x; - if (y < *y0) *y0 = y; - if (x > *x1) *x1 = x; - if (y > *y1) *y1 = y; - /* xm and ym are the minimum positive values in the data, used - by log scaling */ - if (x > 0.0 && x < *xm) *xm = x; - if (y > 0.0 && y < *ym) *ym = y; -} - -void -get_path_extents(PathIterator& path, const agg::trans_affine& trans, - double* x0, double* y0, double* x1, double* y1, - double* xm, double* ym) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removed_t; - typedef agg::conv_curve curve_t; - double x, y; - unsigned code; - - transformed_path_t tpath(path, trans); - nan_removed_t nan_removed(tpath, true, path.has_curves()); - - nan_removed.rewind(0); - - while ((code = nan_removed.vertex(&x, &y)) != agg::path_cmd_stop) - { - if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) - { - continue; - } - update_limits(x, y, x0, y0, x1, y1, xm, ym); - } -} - -Py::Object -_path_module::get_path_extents(const Py::Tuple& args) -{ - args.verify_length(2); - - PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[1].ptr(), false); - - npy_intp extent_dims[] = { 2, 2, 0 }; - double* extents_data = NULL; - double xm, ym; - PyArrayObject* extents = NULL; - try - { - extents = (PyArrayObject*)PyArray_SimpleNew - (2, extent_dims, NPY_DOUBLE); - if (extents == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - extents_data = (double*)PyArray_DATA(extents); - - extents_data[0] = std::numeric_limits::infinity(); - extents_data[1] = std::numeric_limits::infinity(); - extents_data[2] = -std::numeric_limits::infinity(); - extents_data[3] = -std::numeric_limits::infinity(); - /* xm and ym are the minimum positive values in the data, used - by log scaling */ - xm = std::numeric_limits::infinity(); - ym = std::numeric_limits::infinity(); - - ::get_path_extents(path, trans, &extents_data[0], &extents_data[1], - &extents_data[2], &extents_data[3], &xm, &ym); - } - catch (...) - { - Py_XDECREF(extents); - throw; - } - - return Py::Object((PyObject*)extents, true); -} - -Py::Object -_path_module::update_path_extents(const Py::Tuple& args) -{ - args.verify_length(5); - - double x0, y0, x1, y1; - PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix( - args[1].ptr(), false); - - if (!py_convert_bbox(args[2].ptr(), x0, y0, x1, y1)) - { - throw Py::ValueError( - "Must pass Bbox object as arg 3 of update_path_extents"); - } - Py::Object minpos_obj = args[3]; - bool ignore = Py::Boolean(args[4]); - - double xm, ym; - PyArrayObject* input_minpos = NULL; - try - { - input_minpos = (PyArrayObject*)PyArray_FromObject( - minpos_obj.ptr(), NPY_DOUBLE, 1, 1); - if (!input_minpos || PyArray_DIM(input_minpos, 0) != 2) - { - throw Py::TypeError( - "Argument 4 to update_path_extents must be a length-2 numpy array."); - } - xm = *(double*)PyArray_GETPTR1(input_minpos, 0); - ym = *(double*)PyArray_GETPTR1(input_minpos, 1); - } - catch (...) - { - Py_XDECREF(input_minpos); - throw; - } - Py_XDECREF(input_minpos); - - npy_intp extent_dims[] = { 2, 2, 0 }; - double* extents_data = NULL; - npy_intp minpos_dims[] = { 2, 0 }; - double* minpos_data = NULL; - PyArrayObject* extents = NULL; - PyArrayObject* minpos = NULL; - bool changed = false; - - try - { - extents = (PyArrayObject*)PyArray_SimpleNew - (2, extent_dims, NPY_DOUBLE); - if (extents == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - minpos = (PyArrayObject*)PyArray_SimpleNew - (1, minpos_dims, NPY_DOUBLE); - if (minpos == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - - extents_data = (double*)PyArray_DATA(extents); - minpos_data = (double*)PyArray_DATA(minpos); - - if (ignore) - { - extents_data[0] = std::numeric_limits::infinity(); - extents_data[1] = std::numeric_limits::infinity(); - extents_data[2] = -std::numeric_limits::infinity(); - extents_data[3] = -std::numeric_limits::infinity(); - minpos_data[0] = std::numeric_limits::infinity(); - minpos_data[1] = std::numeric_limits::infinity(); - } - else - { - if (x0 > x1) - { - extents_data[0] = std::numeric_limits::infinity(); - extents_data[2] = -std::numeric_limits::infinity(); - } - else - { - extents_data[0] = x0; - extents_data[2] = x1; - } - if (y0 > y1) - { - extents_data[1] = std::numeric_limits::infinity(); - extents_data[3] = -std::numeric_limits::infinity(); - } - else - { - extents_data[1] = y0; - extents_data[3] = y1; - } - minpos_data[0] = xm; - minpos_data[1] = ym; - } - - ::get_path_extents(path, trans, &extents_data[0], &extents_data[1], - &extents_data[2], &extents_data[3], &minpos_data[0], - &minpos_data[1]); - - changed = (extents_data[0] != x0 || - extents_data[1] != y0 || - extents_data[2] != x1 || - extents_data[3] != y1 || - minpos_data[0] != xm || - minpos_data[1] != ym); - - } - catch (...) - { - Py_XDECREF(extents); - Py_XDECREF(minpos); - throw; - } - - Py::Tuple result(3); - result[0] = Py::Object((PyObject*) extents); - result[1] = Py::Object((PyObject*) minpos); - result[2] = Py::Int(changed ? 1 : 0); - - Py_XDECREF(extents); - Py_XDECREF(minpos); - - return result; -} - -Py::Object -_path_module::get_path_collection_extents(const Py::Tuple& args) -{ - args.verify_length(5); - - //segments, trans, clipbox, colors, linewidths, antialiaseds - agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[0].ptr()); - Py::SeqBase paths = args[1]; - Py::SeqBase transforms_obj = args[2]; - Py::Object offsets_obj = args[3]; - agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[4].ptr(), false); - - PyArrayObject* offsets = NULL; - double x0, y0, x1, y1, xm, ym; - - try - { - offsets = (PyArrayObject*)PyArray_FromObject( - offsets_obj.ptr(), NPY_DOUBLE, 0, 2); - if (!offsets || - (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || - (PyArray_NDIM(offsets) == 1 && PyArray_DIM(offsets, 0) != 0)) - { - throw Py::ValueError("Offsets array must be Nx2"); - } - - size_t Npaths = paths.length(); - size_t Noffsets = PyArray_DIM(offsets, 0); - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms_obj.length(), N); - size_t i; - - // Convert all of the transforms up front - typedef std::vector transforms_t; - transforms_t transforms; - transforms.reserve(Ntransforms); - for (i = 0; i < Ntransforms; ++i) - { - agg::trans_affine trans = py_to_agg_transformation_matrix - (transforms_obj[i].ptr(), false); - trans *= master_transform; - transforms.push_back(trans); - } - - // The offset each of those and collect the mins/maxs - x0 = std::numeric_limits::infinity(); - y0 = std::numeric_limits::infinity(); - x1 = -std::numeric_limits::infinity(); - y1 = -std::numeric_limits::infinity(); - xm = std::numeric_limits::infinity(); - ym = std::numeric_limits::infinity(); - agg::trans_affine trans; - - if (transforms.size() <= 1 && paths.size() == 1) - { - PathIterator path(paths[0]); - if (Ntransforms) - { - trans = transforms[0]; - } - else - { - trans = master_transform; - } - - double bx0 = std::numeric_limits::infinity(); - double by0 = std::numeric_limits::infinity(); - double bx1 = -std::numeric_limits::infinity(); - double by1 = -std::numeric_limits::infinity(); - double bxm = std::numeric_limits::infinity(); - double bym = std::numeric_limits::infinity(); - - ::get_path_extents(path, trans, &bx0, &by0, &bx1, &by1, &bxm, &bym); - - for (i = 0; i < Noffsets; ++i) - { - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - update_limits(xo + bx0, yo + by0, &x0, &y0, &x1, &y1, &xm, &ym); - update_limits(xo + bx1, yo + by1, &x0, &y0, &x1, &y1, &xm, &ym); - } - } else { - for (i = 0; i < N; ++i) - { - PathIterator path(paths[i % Npaths]); - if (Ntransforms) - { - trans = transforms[i % Ntransforms]; - } - else - { - trans = master_transform; - } - - if (Noffsets) - { - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - trans *= agg::trans_affine_translation(xo, yo); - } - - ::get_path_extents(path, trans, &x0, &y0, &x1, &y1, &xm, &ym); - } - } - } - catch (...) - { - Py_XDECREF(offsets); - throw; - } - - Py_XDECREF(offsets); - - Py::Tuple result(4); - result[0] = Py::Float(x0); - result[1] = Py::Float(y0); - result[2] = Py::Float(x1); - result[3] = Py::Float(y1); - return result; -} - -Py::Object -_path_module::point_in_path_collection(const Py::Tuple& args) -{ - args.verify_length(10); - - //segments, trans, clipbox, colors, linewidths, antialiaseds - double x = Py::Float(args[0]); - double y = Py::Float(args[1]); - double radius = Py::Float(args[2]); - agg::trans_affine master_transform = py_to_agg_transformation_matrix(args[3].ptr()); - Py::SeqBase paths = args[4]; - Py::SeqBase transforms_obj = args[5]; - Py::SeqBase offsets_obj = args[6]; - agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[7].ptr()); - bool filled = Py::Boolean(args[8]); - std::string offset_position = Py::String(args[9]).encode("utf-8"); - - bool data_offsets = (offset_position == "data"); - - PyArrayObject* offsets = (PyArrayObject*)PyArray_FromObject( - offsets_obj.ptr(), NPY_DOUBLE, 0, 2); - if (!offsets || - (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || - (PyArray_NDIM(offsets) == 1 && PyArray_DIM(offsets, 0) != 0)) - { - Py_XDECREF(offsets); - throw Py::ValueError("Offsets array must be Nx2"); - } - - Py::List result; - - size_t Npaths = paths.length(); - - if (Npaths == 0) { - return result; - } - - size_t Noffsets = PyArray_DIM(offsets, 0); - - size_t N = std::max(Npaths, Noffsets); - size_t Ntransforms = std::min(transforms_obj.length(), N); - size_t i; - - // Convert all of the transforms up front - typedef std::vector transforms_t; - transforms_t transforms; - transforms.reserve(Ntransforms); - for (i = 0; i < Ntransforms; ++i) - { - agg::trans_affine trans = py_to_agg_transformation_matrix - (transforms_obj[i].ptr(), false); - trans *= master_transform; - transforms.push_back(trans); - } - - agg::trans_affine trans; - - for (i = 0; i < N; ++i) - { - PathIterator path(paths[i % Npaths]); - - if (Ntransforms) - { - trans = transforms[i % Ntransforms]; - } - else - { - trans = master_transform; - } - - if (Noffsets) - { - double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); - double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); - offset_trans.transform(&xo, &yo); - if (data_offsets) { - trans = agg::trans_affine_translation(xo, yo) * trans; - } else { - trans *= agg::trans_affine_translation(xo, yo); - } - } - - if (filled) - { - if (::point_in_path(x, y, radius, path, trans)) - result.append(Py::Int((int)i)); - } - else - { - if (::point_on_path(x, y, radius, path, trans)) - result.append(Py::Int((int)i)); - } - } - - return result; -} - -bool -path_in_path(PathIterator& a, const agg::trans_affine& atrans, - PathIterator& b, const agg::trans_affine& btrans) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover no_nans_t; - typedef agg::conv_curve curve_t; - - if (a.total_vertices() < 3) - return false; - - transformed_path_t b_path_trans(b, btrans); - no_nans_t b_no_nans(b_path_trans, true, b.has_curves()); - curve_t b_curved(b_no_nans); - - double x, y; - b_curved.rewind(0); - while (b_curved.vertex(&x, &y) != agg::path_cmd_stop) - { - if (!::point_in_path(x, y, 0.0, a, atrans)) - return false; - } - - return true; -} - -Py::Object -_path_module::path_in_path(const Py::Tuple& args) -{ - args.verify_length(4); - - PathIterator a(args[0]); - agg::trans_affine atrans = py_to_agg_transformation_matrix( - args[1].ptr(), false); - PathIterator b(args[2]); - agg::trans_affine btrans = py_to_agg_transformation_matrix( - args[3].ptr(), false); - - return Py::Int(::path_in_path(a, atrans, b, btrans)); -} - -/** The clip_path_to_rect code here is a clean-room implementation of - the Sutherland-Hodgman clipping algorithm described here: - - http://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm -*/ - -typedef std::vector Polygon; - -namespace clip_to_rect_filters -{ - /* There are four different passes needed to create/remove - vertices (one for each side of the rectangle). The differences - between those passes are encapsulated in these functor classes. - */ - struct bisectx - { - double m_x; - - bisectx(double x) : m_x(x) {} - - inline void - bisect(double sx, double sy, double px, double py, double* bx, - double* by) const - { - *bx = m_x; - double dx = px - sx; - double dy = py - sy; - *by = sy + dy * ((m_x - sx) / dx); - } - }; - - struct xlt : public bisectx - { - xlt(double x) : bisectx(x) {} - - inline bool - is_inside(double x, double y) const - { - return x <= m_x; - } - }; - - struct xgt : public bisectx - { - xgt(double x) : bisectx(x) {} - - inline bool - is_inside(double x, double y) const - { - return x >= m_x; - } - }; - - struct bisecty - { - double m_y; - - bisecty(double y) : m_y(y) {} - - inline void - bisect(double sx, double sy, double px, double py, double* bx, - double* by) const - { - *by = m_y; - double dx = px - sx; - double dy = py - sy; - *bx = sx + dx * ((m_y - sy) / dy); - } - }; - - struct ylt : public bisecty - { - ylt(double y) : bisecty(y) {} - - inline bool - is_inside(double x, double y) const - { - return y <= m_y; - } - }; - - struct ygt : public bisecty - { - ygt(double y) : bisecty(y) {} - - inline bool - is_inside(double x, double y) const - { - return y >= m_y; - } - }; -} - -template -inline void -clip_to_rect_one_step(const Polygon& polygon, Polygon& result, const Filter& filter) -{ - double sx, sy, px, py, bx, by; - bool sinside, pinside; - result.clear(); - - if (polygon.size() == 0) - { - return; - } - - sx = polygon.back().x; - sy = polygon.back().y; - for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) - { - px = i->x; - py = i->y; - - sinside = filter.is_inside(sx, sy); - pinside = filter.is_inside(px, py); - - if (sinside ^ pinside) - { - filter.bisect(sx, sy, px, py, &bx, &by); - result.push_back(XY(bx, by)); - } - - if (pinside) - { - result.push_back(XY(px, py)); - } - - sx = px; - sy = py; - } -} - -template -void -clip_to_rect(Path& path, - double x0, double y0, double x1, double y1, - bool inside, std::vector& results) -{ - double xmin, ymin, xmax, ymax; - if (x0 < x1) - { - xmin = x0; - xmax = x1; - } - else - { - xmin = x1; - xmax = x0; - } - - if (y0 < y1) - { - ymin = y0; - ymax = y1; - } - else - { - ymin = y1; - ymax = y0; - } - - if (!inside) - { - std::swap(xmin, xmax); - std::swap(ymin, ymax); - } - - Polygon polygon1, polygon2; - double x = 0, y = 0; - unsigned code = 0; - path.rewind(0); - - do - { - // Grab the next subpath and store it in polygon1 - polygon1.clear(); - do - { - if (code == agg::path_cmd_move_to) - { - polygon1.push_back(XY(x, y)); - } - - code = path.vertex(&x, &y); - - if (code == agg::path_cmd_stop) - { - break; - } - - if (code != agg::path_cmd_move_to) - { - polygon1.push_back(XY(x, y)); - } - } - while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); - - // The result of each step is fed into the next (note the - // swapping of polygon1 and polygon2 at each step). - clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax)); - clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin)); - clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax)); - clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin)); - - // Empty polygons aren't very useful, so skip them - if (polygon1.size()) - { - results.push_back(polygon1); - } - } - while (code != agg::path_cmd_stop); -} - -Py::Object -_path_module::clip_path_to_rect(const Py::Tuple &args) -{ - args.verify_length(3); - - PathIterator path(args[0]); - Py::Object bbox_obj = args[1]; - bool inside = Py::Boolean(args[2]); - - double x0, y0, x1, y1; - if (!py_convert_bbox(bbox_obj.ptr(), x0, y0, x1, y1)) - { - throw Py::TypeError("Argument 2 to clip_to_rect must be a Bbox object."); - } - - std::vector results; - typedef agg::conv_curve curve_t; - curve_t curve(path); - - ::clip_to_rect(curve, x0, y0, x1, y1, inside, results); - - npy_intp dims[2]; - dims[1] = 2; - PyObject* py_results = PyList_New(results.size()); - if (!py_results) - { - throw Py::RuntimeError("Error creating results list"); - } - - try - { - for (std::vector::const_iterator p = results.begin(); p != results.end(); ++p) - { - size_t size = p->size(); - dims[0] = (npy_intp)size + 1; - PyArrayObject* pyarray = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_DOUBLE); - if (pyarray == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - double* const data = (double*)PyArray_DATA(pyarray); - for (size_t i = 0; i < size; ++i) - { - data[2*i] = (*p)[i].x; - data[2*i+1] = (*p)[i].y; - } - data[2*size] = (*p)[0].x; - data[2*size+1] = (*p)[0].y; - - if (PyList_SetItem(py_results, p - results.begin(), (PyObject *)pyarray) == -1) - { - throw Py::RuntimeError("Error creating results list"); - } - } - } - catch (...) - { - Py_XDECREF(py_results); - throw; - } - - return Py::Object(py_results, true); -} - -Py::Object -_path_module::affine_transform(const Py::Tuple& args) -{ - args.verify_length(2); - - Py::Object vertices_obj = args[0]; - Py::Object transform_obj = args[1]; - - PyArrayObject* vertices = NULL; - PyArrayObject* transform = NULL; - PyArrayObject* result = NULL; - - try - { - vertices = (PyArrayObject*)PyArray_FromObject - (vertices_obj.ptr(), NPY_DOUBLE, 1, 2); - if (!vertices || - (PyArray_NDIM(vertices) == 2 && PyArray_DIM(vertices, 0) != 0 && - PyArray_DIM(vertices, 1) != 2) || - (PyArray_NDIM(vertices) == 1 && - PyArray_DIM(vertices, 0) != 2 && PyArray_DIM(vertices, 0) != 0)) - { - throw Py::ValueError("Invalid vertices array."); - } - - transform = (PyArrayObject*) PyArray_FromObject - (transform_obj.ptr(), NPY_DOUBLE, 2, 2); - if (!transform || - PyArray_DIM(transform, 0) != 3 || - PyArray_DIM(transform, 1) != 3) - { - throw Py::ValueError("Invalid transform."); - } - - double a, b, c, d, e, f; - { - size_t stride0 = PyArray_STRIDE(transform, 0); - size_t stride1 = PyArray_STRIDE(transform, 1); - char* row0 = PyArray_BYTES(transform); - char* row1 = row0 + stride0; - - a = *(double*)(row0); - row0 += stride1; - c = *(double*)(row0); - row0 += stride1; - e = *(double*)(row0); - - b = *(double*)(row1); - row1 += stride1; - d = *(double*)(row1); - row1 += stride1; - f = *(double*)(row1); - } - - result = (PyArrayObject*)PyArray_SimpleNew - (PyArray_NDIM(vertices), PyArray_DIMS(vertices), NPY_DOUBLE); - if (result == NULL) - { - throw Py::MemoryError("Could not allocate memory for path"); - } - if (PyArray_NDIM(vertices) == 2) - { - size_t n = PyArray_DIM(vertices, 0); - char* vertex_in = PyArray_BYTES(vertices); - double* vertex_out = (double*)PyArray_DATA(result); - size_t stride0 = PyArray_STRIDE(vertices, 0); - size_t stride1 = PyArray_STRIDE(vertices, 1); - double x; - double y; - volatile double t0; - volatile double t1; - volatile double t; - - for (size_t i = 0; i < n; ++i) - { - x = *(double*)(vertex_in); - y = *(double*)(vertex_in + stride1); - - t0 = a * x; - t1 = c * y; - t = t0 + t1 + e; - *(vertex_out++) = t; - - t0 = b * x; - t1 = d * y; - t = t0 + t1 + f; - *(vertex_out++) = t; - - vertex_in += stride0; - } - } - else if (PyArray_DIM(vertices, 0) != 0) - { - char* vertex_in = PyArray_BYTES(vertices); - double* vertex_out = (double*)PyArray_DATA(result); - size_t stride0 = PyArray_STRIDE(vertices, 0); - double x; - double y; - x = *(double*)(vertex_in); - y = *(double*)(vertex_in + stride0); - *vertex_out++ = a * x + c * y + e; - *vertex_out++ = b * x + d * y + f; - } - } - catch (...) - { - Py_XDECREF(vertices); - Py_XDECREF(transform); - Py_XDECREF(result); - throw; - } - - Py_XDECREF(vertices); - Py_XDECREF(transform); - - return Py::Object((PyObject*)result, true); -} - -Py::Object -_path_module::count_bboxes_overlapping_bbox(const Py::Tuple& args) -{ - args.verify_length(2); - - Py::Object bbox = args[0]; - Py::SeqBase bboxes = args[1]; - - double ax0, ay0, ax1, ay1; - double bx0, by0, bx1, by1; - long count = 0; - - if (py_convert_bbox(bbox.ptr(), ax0, ay0, ax1, ay1)) - { - if (ax1 < ax0) - { - std::swap(ax0, ax1); - } - if (ay1 < ay0) - { - std::swap(ay0, ay1); - } - - size_t num_bboxes = bboxes.size(); - for (size_t i = 0; i < num_bboxes; ++i) - { - Py::Object bbox_b = bboxes[i]; - if (py_convert_bbox(bbox_b.ptr(), bx0, by0, bx1, by1)) - { - if (bx1 < bx0) - { - std::swap(bx0, bx1); - } - if (by1 < by0) - { - std::swap(by0, by1); - } - if (!((bx1 <= ax0) || - (by1 <= ay0) || - (bx0 >= ax1) || - (by0 >= ay1))) - { - ++count; - } - } - else - { - throw Py::ValueError("Non-bbox object in bboxes list"); - } - } - } - else - { - throw Py::ValueError("First argument to count_bboxes_overlapping_bbox must be a Bbox object."); - } - - return Py::Int(count); -} - -inline bool -segments_intersect(const double& x1, const double& y1, - const double& x2, const double& y2, - const double& x3, const double& y3, - const double& x4, const double& y4) -{ - double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)); - if (den == 0.0) - { - return false; - } - - double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3)); - double n2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3)); - - double u1 = n1 / den; - double u2 = n2 / den; - - return (u1 >= 0.0 && u1 <= 1.0 && - u2 >= 0.0 && u2 <= 1.0); -} - -bool -path_intersects_path(PathIterator& p1, PathIterator& p2) -{ - typedef PathNanRemover no_nans_t; - typedef agg::conv_curve curve_t; - - if (p1.total_vertices() < 2 || p2.total_vertices() < 2) - { - return false; - } - - no_nans_t n1(p1, true, p1.has_curves()); - no_nans_t n2(p2, true, p2.has_curves()); - - curve_t c1(n1); - curve_t c2(n2); - - double x11, y11, x12, y12; - double x21, y21, x22, y22; - - c1.vertex(&x11, &y11); - while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) - { - c2.rewind(0); - c2.vertex(&x21, &y21); - while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) - { - if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) - { - return true; - } - x21 = x22; - y21 = y22; - } - x11 = x12; - y11 = y12; - } - - return false; -} - -Py::Object -_path_module::path_intersects_path(const Py::Tuple& args) -{ - args.verify_length(2, 3); - - PathIterator p1(args[0]); - PathIterator p2(args[1]); - bool filled = false; - - if (args.size() == 3) - { - filled = args[2].isTrue(); - } - - if (!filled) - { - return Py::Int(::path_intersects_path(p1, p2)); - } - else - { - return Py::Int(::path_intersects_path(p1, p2) - || ::path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine()) - || ::path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine())); - } -} - -void -_add_polygon(Py::List& polygons, const std::vector& polygon) -{ - if (polygon.size() == 0) - { - return; - } - npy_intp polygon_dims[] = { static_cast(polygon.size() / 2), 2, 0 }; - PyArrayObject* polygon_array = NULL; - polygon_array = (PyArrayObject*)PyArray_SimpleNew - (2, polygon_dims, NPY_DOUBLE); - if (!polygon_array) - { - throw Py::MemoryError("Error creating polygon array"); - } - double* polygon_data = (double*)PyArray_DATA(polygon_array); - memcpy(polygon_data, &polygon[0], polygon.size() * sizeof(double)); - polygons.append(Py::Object((PyObject*)polygon_array, true)); -} - -Py::Object -_path_module::convert_path_to_polygons(const Py::Tuple& args) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removal_t; - typedef PathClipper clipped_t; - typedef PathSimplifier simplify_t; - typedef agg::conv_curve curve_t; - - typedef std::vector vertices_t; - - args.verify_length(4); - - PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[1].ptr(), false); - double width = Py::Float(args[2]); - double height = Py::Float(args[3]); - - bool do_clip = width != 0.0 && height != 0.0; - - bool simplify = path.should_simplify(); - - transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, true, path.has_curves()); - clipped_t clipped(nan_removed, do_clip, width, height); - simplify_t simplified(clipped, simplify, path.simplify_threshold()); - curve_t curve(simplified); - - Py::List polygons; - vertices_t polygon; - double x, y; - unsigned code; - - polygon.reserve(path.total_vertices() * 2); - - while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) - { - if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) - { - if (polygon.size() >= 2) - { - polygon.push_back(polygon[0]); - polygon.push_back(polygon[1]); - _add_polygon(polygons, polygon); - } - polygon.clear(); - } - else - { - if (code == agg::path_cmd_move_to) - { - _add_polygon(polygons, polygon); - polygon.clear(); - } - polygon.push_back(x); - polygon.push_back(y); - } - } - - _add_polygon(polygons, polygon); - - return polygons; -} - -template -void -__cleanup_path(VertexSource& source, - std::vector& vertices, - std::vector& codes) -{ - unsigned code; - double x, y; - do - { - code = source.vertex(&x, &y); - vertices.push_back(x); - vertices.push_back(y); - codes.push_back((npy_uint8)code); - } - while (code != agg::path_cmd_stop); -} - -void -_cleanup_path(PathIterator& path, const agg::trans_affine& trans, - bool remove_nans, bool do_clip, - const agg::rect_base& rect, - e_snap_mode snap_mode, double stroke_width, - bool do_simplify, bool return_curves, - double sketch_scale, double sketch_length, - double sketch_randomness, - std::vector& vertices, - std::vector& codes) -{ - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removal_t; - typedef PathClipper clipped_t; - typedef PathSnapper snapped_t; - typedef PathSimplifier simplify_t; - typedef agg::conv_curve curve_t; - typedef Sketch sketch_t; - - transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, remove_nans, path.has_curves()); - clipped_t clipped(nan_removed, do_clip, rect); - snapped_t snapped(clipped, snap_mode, path.total_vertices(), stroke_width); - simplify_t simplified(snapped, do_simplify, path.simplify_threshold()); - - vertices.reserve(path.total_vertices() * 2); - codes.reserve(path.total_vertices()); - - if (return_curves && sketch_scale == 0.0) - { - __cleanup_path(simplified, vertices, codes); - } - else - { - curve_t curve(simplified); - sketch_t sketch(curve, sketch_scale, sketch_length, sketch_randomness); - __cleanup_path(sketch, vertices, codes); - } -} - -Py::Object -_path_module::cleanup_path(const Py::Tuple& args) -{ - args.verify_length(9); - - PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[1].ptr(), false); - bool remove_nans = args[2].isTrue(); - - Py::Object clip_obj = args[3]; - bool do_clip; - agg::rect_base clip_rect; - if (clip_obj.isNone()) - { - do_clip = false; - } - else - { - double x1, y1, x2, y2; - Py::Tuple clip_tuple(clip_obj); - x1 = Py::Float(clip_tuple[0]); - y1 = Py::Float(clip_tuple[1]); - x2 = Py::Float(clip_tuple[2]); - y2 = Py::Float(clip_tuple[3]); - clip_rect.init(x1, y1, x2, y2); - do_clip = true; - } - - Py::Object snap_obj = args[4]; - e_snap_mode snap_mode; - if (snap_obj.isNone()) - { - snap_mode = SNAP_AUTO; - } - else if (snap_obj.isTrue()) - { - snap_mode = SNAP_TRUE; - } - else - { - snap_mode = SNAP_FALSE; - } - - double stroke_width = Py::Float(args[5]); - - bool simplify; - Py::Object simplify_obj = args[6]; - if (simplify_obj.isNone()) - { - simplify = path.should_simplify(); - } - else - { - simplify = simplify_obj.isTrue(); - } - - bool return_curves = args[7].isTrue(); - - Py::Object sketch_params = args[8]; - double sketch_scale = 0.0; - double sketch_length = 0.0; - double sketch_randomness = 0.0; - if (sketch_params.ptr() != Py_None) { - Py::Tuple sketch(sketch_params); - sketch_scale = Py::Float(sketch[0]); - sketch_length = Py::Float(sketch[1]); - sketch_randomness = Py::Float(sketch[2]); - } - - std::vector vertices; - std::vector codes; - - _cleanup_path(path, trans, remove_nans, do_clip, clip_rect, snap_mode, - stroke_width, simplify, return_curves, sketch_scale, - sketch_length, sketch_randomness, vertices, codes); - - npy_intp length = codes.size(); - npy_intp dims[] = { length, 2, 0 }; - - PyArrayObject* vertices_obj = NULL; - PyArrayObject* codes_obj = NULL; - Py::Tuple result(2); - try - { - vertices_obj = (PyArrayObject*)PyArray_SimpleNew - (2, dims, NPY_DOUBLE); - if (vertices_obj == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - - codes_obj = (PyArrayObject*)PyArray_SimpleNew - (1, dims, NPY_UINT8); - if (codes_obj == NULL) - { - throw Py::MemoryError("Could not allocate result array"); - } - - memcpy(PyArray_DATA(vertices_obj), &vertices[0], sizeof(double) * 2 * length); - memcpy(PyArray_DATA(codes_obj), &codes[0], sizeof(npy_uint8) * length); - - result[0] = Py::Object((PyObject*)vertices_obj, true); - result[1] = Py::Object((PyObject*)codes_obj, true); - } - catch (...) - { - Py_XDECREF(vertices_obj); - Py_XDECREF(codes_obj); - throw; - } - - return result; -} - -Py::Object -_path_module::convert_to_svg(const Py::Tuple& args) -{ - args.verify_length(5); - - PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[1].ptr(), false); - - Py::Object clip_obj = args[2]; - bool do_clip; - agg::rect_base clip_rect(0, 0, 0, 0); - if (clip_obj.isNone() || !clip_obj.isTrue()) - { - do_clip = false; - } - else - { - double x1, y1, x2, y2; - Py::Tuple clip_tuple(clip_obj); - x1 = Py::Float(clip_tuple[0]); - y1 = Py::Float(clip_tuple[1]); - x2 = Py::Float(clip_tuple[2]); - y2 = Py::Float(clip_tuple[3]); - clip_rect.init(x1, y1, x2, y2); - do_clip = true; - } - - bool simplify; - Py::Object simplify_obj = args[3]; - if (simplify_obj.isNone()) - { - simplify = path.should_simplify(); - } - else - { - simplify = simplify_obj.isTrue(); - } - - int precision = Py::Int(args[4]); - - #if PY_VERSION_HEX < 0x02070000 - char format[64]; - snprintf(format, 64, "%s.%dg", "%", precision); - #endif - - typedef agg::conv_transform transformed_path_t; - typedef PathNanRemover nan_removal_t; - typedef PathClipper clipped_t; - typedef PathSimplifier simplify_t; - - transformed_path_t tpath(path, trans); - nan_removal_t nan_removed(tpath, true, path.has_curves()); - clipped_t clipped(nan_removed, do_clip, clip_rect); - simplify_t simplified(clipped, simplify, path.simplify_threshold()); - - size_t buffersize = path.total_vertices() * (precision + 5) * 4; - char* buffer = (char *)malloc(buffersize); - char* p = buffer; - - const char codes[] = {'M', 'L', 'Q', 'C'}; - const int waits[] = { 1, 1, 2, 3}; - - int wait = 0; - unsigned code; - double x = 0, y = 0; - while ((code = simplified.vertex(&x, &y)) != agg::path_cmd_stop) - { - if (wait == 0) - { - *p++ = '\n'; - - if (code == 0x4f) - { - *p++ = 'z'; - *p++ = '\n'; - continue; - } - - *p++ = codes[code-1]; - wait = waits[code-1]; - } - else - { - *p++ = ' '; - } - - #if PY_VERSION_HEX >= 0x02070000 - char* str; - str = PyOS_double_to_string(x, 'g', precision, 0, NULL); - p += snprintf(p, buffersize - (p - buffer), "%s", str); - PyMem_Free(str); - *p++ = ' '; - str = PyOS_double_to_string(y, 'g', precision, 0, NULL); - p += snprintf(p, buffersize - (p - buffer), "%s", str); - PyMem_Free(str); - #else - char str[64]; - PyOS_ascii_formatd(str, 64, format, x); - p += snprintf(p, buffersize - (p - buffer), "%s", str); - *p++ = ' '; - PyOS_ascii_formatd(str, 64, format, y); - p += snprintf(p, buffersize - (p - buffer), "%s", str); - #endif - - --wait; - } - - #if PY3K - PyObject* result = PyUnicode_FromStringAndSize(buffer, p - buffer); - #else - PyObject* result = PyString_FromStringAndSize(buffer, p - buffer); - #endif - free(buffer); - - return Py::Object(result, true); -} - -PyMODINIT_FUNC -#if PY3K -PyInit__path(void) -#else -init_path(void) -#endif -{ - static _path_module* _path = NULL; - _path = new _path_module; - - import_array(); - - #if PY3K - return _path->module().ptr(); - #endif -} diff --git a/src/_path.h b/src/_path.h new file mode 100644 index 000000000000..77ab85eb52f9 --- /dev/null +++ b/src/_path.h @@ -0,0 +1,978 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef __PATH_H__ +#define __PATH_H__ + +#include +#include +#include + +#include "agg_conv_contour.h" +#include "agg_conv_curve.h" +#include "agg_conv_stroke.h" +#include "agg_conv_transform.h" +#include "agg_path_storage.h" +#include "agg_trans_affine.h" + +#include "path_converters.h" +#include "_backend_agg_basic_types.h" + +struct XY +{ + double x; + double y; + + XY(double x_, double y_) : x(x_), y(y_) + { + } +}; + +// +// The following function was found in the Agg 2.3 examples (interactive_polygon.cpp). +// It has been generalized to work on (possibly curved) polylines, rather than +// just polygons. The original comments have been kept intact. +// -- Michael Droettboom 2007-10-02 +// +//======= Crossings Multiply algorithm of InsideTest ======================== +// +// By Eric Haines, 3D/Eye Inc, erich@eye.com +// +// This version is usually somewhat faster than the original published in +// Graphics Gems IV; by turning the division for testing the X axis crossing +// into a tricky multiplication test this part of the test became faster, +// which had the additional effect of making the test for "both to left or +// both to right" a bit slower for triangles than simply computing the +// intersection each time. The main increase is in triangle testing speed, +// which was about 15% faster; all other polygon complexities were pretty much +// the same as before. On machines where division is very expensive (not the +// case on the HP 9000 series on which I tested) this test should be much +// faster overall than the old code. Your mileage may (in fact, will) vary, +// depending on the machine and the test data, but in general I believe this +// code is both shorter and faster. This test was inspired by unpublished +// Graphics Gems submitted by Joseph Samosky and Mark Haigh-Hutchinson. +// Related work by Samosky is in: +// +// Samosky, Joseph, "SectionView: A system for interactively specifying and +// visualizing sections through three-dimensional medical image data", +// M.S. Thesis, Department of Electrical Engineering and Computer Science, +// Massachusetts Institute of Technology, 1993. +// +// Shoot a test ray along +X axis. The strategy is to compare vertex Y values +// to the testing point's Y and quickly discard edges which are entirely to one +// side of the test ray. Note that CONVEX and WINDING code can be added as +// for the CrossingsTest() code; it is left out here for clarity. +// +// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point +// _point_, returns 1 if inside, 0 if outside. +template +void point_in_path_impl(PointArray &points, PathIterator &path, ResultArray &inside_flag) +{ + int yflag1; + double vtx0, vty0, vtx1, vty1; + double tx, ty; + double sx, sy; + double x, y; + size_t i; + bool all_done; + + size_t n = points.size(); + + std::vector yflag0(n); + std::vector subpath_flag(n); + + path.rewind(0); + + for (i = 0; i < n; ++i) { + inside_flag[i] = 0; + } + + unsigned code = 0; + do { + if (code != agg::path_cmd_move_to) { + code = path.vertex(&x, &y); + if (code == agg::path_cmd_stop || + (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + continue; + } + } + + sx = vtx0 = vtx1 = x; + sy = vty0 = vty1 = y; + + for (i = 0; i < n; ++i) { + ty = points[i][1]; + + // get test bit for above/below X axis + yflag0[i] = (vty0 >= ty); + + subpath_flag[i] = 0; + } + + do { + code = path.vertex(&x, &y); + + // The following cases denote the beginning on a new subpath + if (code == agg::path_cmd_stop || + (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + x = sx; + y = sy; + } else if (code == agg::path_cmd_move_to) { + break; + } + + for (i = 0; i < n; ++i) { + tx = points[i][0]; + ty = points[i][1]; + + yflag1 = (vty1 >= ty); + // Check if endpoints straddle (are on opposite sides) of + // X axis (i.e. the Y's differ); if so, +X ray could + // intersect this edge. The old test also checked whether + // the endpoints are both to the right or to the left of + // the test point. However, given the faster intersection + // point computation used below, this test was found to be + // a break-even proposition for most polygons and a loser + // for triangles (where 50% or more of the edges which + // survive this test will cross quadrants and so have to + // have the X intersection computed anyway). I credit + // Joseph Samosky with inspiring me to try dropping the + // "both left or both right" part of my code. + if (yflag0[i] != yflag1) { + // Check intersection of pgon segment with +X ray. + // Note if >= point's X; if so, the ray hits it. The + // division operation is avoided for the ">=" test by + // checking the sign of the first vertex wrto the test + // point; idea inspired by Joseph Samosky's and Mark + // Haigh-Hutchinson's different polygon inclusion + // tests. + if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) { + subpath_flag[i] ^= 1; + } + } + + // Move to the next pair of vertices, retaining info as + // possible. + yflag0[i] = yflag1; + } + + vtx0 = vtx1; + vty0 = vty1; + + vtx1 = x; + vty1 = y; + } while (code != agg::path_cmd_stop && + (code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); + + all_done = true; + for (i = 0; i < n; ++i) { + tx = points[i][0]; + ty = points[i][1]; + + yflag1 = (vty1 >= ty); + if (yflag0[i] != yflag1) { + if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) { + subpath_flag[i] ^= 1; + } + } + inside_flag[i] |= subpath_flag[i]; + if (inside_flag[i] == 0) { + all_done = false; + } + } + + if (all_done) { + break; + } + } while (code != agg::path_cmd_stop); +} + +template +inline void points_in_path(PointArray &points, + const double r, + PathIterator &path, + const agg::trans_affine &trans, + ResultArray &result) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover no_nans_t; + typedef agg::conv_curve curve_t; + typedef agg::conv_contour contour_t; + + size_t i; + for (i = 0; i < result.size(); ++i) { + result[i] = 0; + } + + if (path.total_vertices() < 3) { + return; + } + + transformed_path_t trans_path(path, trans); + no_nans_t no_nans_path(trans_path, true, path.has_curves()); + curve_t curved_path(no_nans_path); + contour_t contoured_path(curved_path); + contoured_path.width(r); + + point_in_path_impl(points, contoured_path, result); +} + +template +inline bool point_in_path( + double x, double y, const double r, PathIterator &path, const agg::trans_affine &trans) +{ + std::vector point; + std::vector > points; + point.push_back(x); + point.push_back(y); + points.push_back(point); + + std::vector result(1); + result[0] = 0; + + points_in_path(points, r, path, trans, result); + + return (bool)result[0]; +} + +template +void points_on_path(PointArray &points, + const double r, + PathIterator &path, + const agg::trans_affine &trans, + ResultArray result) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover no_nans_t; + typedef agg::conv_curve curve_t; + typedef agg::conv_stroke stroke_t; + + size_t i; + for (i = 0; i < result.size(); ++i) { + result[i] = 0; + } + + transformed_path_t trans_path(path, trans); + no_nans_t nan_removed_path(trans_path, true, path.has_curves()); + curve_t curved_path(nan_removed_path); + stroke_t stroked_path(curved_path); + stroked_path.width(r * 2.0); + point_in_path_impl(points, stroked_path, result); +} + +template +inline bool point_on_path( + double x, double y, const double r, PathIterator &path, const agg::trans_affine &trans) +{ + std::vector point; + std::vector > points; + point.push_back(x); + point.push_back(y); + points.push_back(point); + + std::vector result(1); + result[0] = 0; + + points_on_path(points, r, path, trans, result); + + return (bool)result[0]; +} + +struct extent_limits +{ + double x0; + double y0; + double x1; + double y1; + double xm; + double ym; +}; + +void reset_limits(extent_limits &e) +{ + e.x0 = std::numeric_limits::infinity(); + e.y0 = std::numeric_limits::infinity(); + e.x1 = -std::numeric_limits::infinity(); + e.y1 = -std::numeric_limits::infinity(); + /* xm and ym are the minimum positive values in the data, used + by log scaling */ + e.xm = std::numeric_limits::infinity(); + e.ym = std::numeric_limits::infinity(); +} + +inline void update_limits(double x, double y, extent_limits &e) +{ + if (x < e.x0) + e.x0 = x; + if (y < e.y0) + e.y0 = y; + if (x > e.x1) + e.x1 = x; + if (y > e.y1) + e.y1 = y; + /* xm and ym are the minimum positive values in the data, used + by log scaling */ + if (x > 0.0 && x < e.xm) + e.xm = x; + if (y > 0.0 && y < e.ym) + e.ym = y; +} + +template +void update_path_extents(PathIterator &path, const agg::trans_affine &trans, extent_limits &extents) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removed_t; + double x, y; + unsigned code; + + transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, path.has_curves()); + + nan_removed.rewind(0); + + while ((code = nan_removed.vertex(&x, &y)) != agg::path_cmd_stop) { + if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + continue; + } + update_limits(x, y, extents); + } +} + +template +void get_path_collection_extents(agg::trans_affine &master_transform, + PathGenerator &paths, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + extent_limits &extent) +{ + if (offsets.dim(0) != 0 && offsets.dim(1) != 2) { + throw "Offsets array must be Nx2"; + } + + size_t Npaths = paths.size(); + size_t Noffsets = offsets.size(); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms.size(), N); + size_t i; + + agg::trans_affine trans; + + reset_limits(extent); + + for (i = 0; i < N; ++i) { + typename PathGenerator::path_iterator path(paths(i % Npaths)); + if (Ntransforms) { + typename TransformArray::sub_t subtrans = transforms[i % Ntransforms]; + trans = agg::trans_affine(subtrans(0, 0), + subtrans(1, 0), + subtrans(0, 1), + subtrans(1, 1), + subtrans(0, 2), + subtrans(1, 2)); + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + trans *= agg::trans_affine_translation(xo, yo); + } + + update_path_extents(path, trans, extent); + } +} + +template +void point_in_path_collection(double x, + double y, + double radius, + agg::trans_affine &master_transform, + PathGenerator &paths, + TransformArray &transforms, + OffsetArray &offsets, + agg::trans_affine &offset_trans, + bool filled, + e_offset_position offset_position, + std::vector &result) +{ + size_t Npaths = paths.size(); + + if (Npaths == 0) { + return; + } + + size_t Noffsets = offsets.dim(0); + size_t N = std::max(Npaths, Noffsets); + size_t Ntransforms = std::min(transforms.size(), N); + size_t i; + + agg::trans_affine trans; + + for (i = 0; i < N; ++i) { + typename PathGenerator::path_iterator path = paths(i % Npaths); + + if (Ntransforms) { + typename TransformArray::sub_t subtrans = transforms[i % Ntransforms]; + trans = agg::trans_affine(subtrans(0, 0), + subtrans(1, 0), + subtrans(0, 1), + subtrans(1, 1), + subtrans(0, 2), + subtrans(1, 2)); + } else { + trans = master_transform; + } + + if (Noffsets) { + double xo = offsets(i % Noffsets, 0); + double yo = offsets(i % Noffsets, 1); + offset_trans.transform(&xo, &yo); + if (offset_position == OFFSET_POSITION_DATA) { + trans = agg::trans_affine_translation(xo, yo) * trans; + } else { + trans *= agg::trans_affine_translation(xo, yo); + } + } + + if (filled) { + if (point_in_path(x, y, radius, path, trans)) { + result.push_back(i); + } + } else { + if (point_on_path(x, y, radius, path, trans)) + result.push_back(i); + } + } +} + +template +bool path_in_path(PathIterator1 &a, + const agg::trans_affine &atrans, + PathIterator2 &b, + const agg::trans_affine &btrans) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover no_nans_t; + typedef agg::conv_curve curve_t; + + if (a.total_vertices() < 3) { + return false; + } + + transformed_path_t b_path_trans(b, btrans); + no_nans_t b_no_nans(b_path_trans, true, b.has_curves()); + curve_t b_curved(b_no_nans); + + double x, y; + b_curved.rewind(0); + while (b_curved.vertex(&x, &y) != agg::path_cmd_stop) { + if (!point_in_path(x, y, 0.0, a, atrans)) { + return false; + } + } + + return true; +} + +/** The clip_path_to_rect code here is a clean-room implementation of + the Sutherland-Hodgman clipping algorithm described here: + + http://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm +*/ + +typedef std::vector Polygon; + +namespace clip_to_rect_filters +{ +/* There are four different passes needed to create/remove + vertices (one for each side of the rectangle). The differences + between those passes are encapsulated in these functor classes. +*/ +struct bisectx +{ + double m_x; + + bisectx(double x) : m_x(x) + { + } + + inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + { + *bx = m_x; + double dx = px - sx; + double dy = py - sy; + *by = sy + dy * ((m_x - sx) / dx); + } +}; + +struct xlt : public bisectx +{ + xlt(double x) : bisectx(x) + { + } + + inline bool is_inside(double x, double y) const + { + return x <= m_x; + } +}; + +struct xgt : public bisectx +{ + xgt(double x) : bisectx(x) + { + } + + inline bool is_inside(double x, double y) const + { + return x >= m_x; + } +}; + +struct bisecty +{ + double m_y; + + bisecty(double y) : m_y(y) + { + } + + inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + { + *by = m_y; + double dx = px - sx; + double dy = py - sy; + *bx = sx + dx * ((m_y - sy) / dy); + } +}; + +struct ylt : public bisecty +{ + ylt(double y) : bisecty(y) + { + } + + inline bool is_inside(double x, double y) const + { + return y <= m_y; + } +}; + +struct ygt : public bisecty +{ + ygt(double y) : bisecty(y) + { + } + + inline bool is_inside(double x, double y) const + { + return y >= m_y; + } +}; +} + +template +inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const Filter &filter) +{ + double sx, sy, px, py, bx, by; + bool sinside, pinside; + result.clear(); + + if (polygon.size() == 0) { + return; + } + + sx = polygon.back().x; + sy = polygon.back().y; + for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) { + px = i->x; + py = i->y; + + sinside = filter.is_inside(sx, sy); + pinside = filter.is_inside(px, py); + + if (sinside ^ pinside) { + filter.bisect(sx, sy, px, py, &bx, &by); + result.push_back(XY(bx, by)); + } + + if (pinside) { + result.push_back(XY(px, py)); + } + + sx = px; + sy = py; + } +} + +template +void +clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vector &results) +{ + double xmin, ymin, xmax, ymax; + if (rect.x1 < rect.x2) { + xmin = rect.x1; + xmax = rect.x2; + } else { + xmin = rect.x2; + xmax = rect.x1; + } + + if (rect.y1 < rect.y2) { + ymin = rect.y1; + ymax = rect.y2; + } else { + ymin = rect.y2; + ymax = rect.y1; + } + + if (!inside) { + std::swap(xmin, xmax); + std::swap(ymin, ymax); + } + + typedef agg::conv_curve curve_t; + curve_t curve(path); + + Polygon polygon1, polygon2; + double x = 0, y = 0; + unsigned code = 0; + curve.rewind(0); + + do { + // Grab the next subpath and store it in polygon1 + polygon1.clear(); + do { + if (code == agg::path_cmd_move_to) { + polygon1.push_back(XY(x, y)); + } + + code = curve.vertex(&x, &y); + + if (code == agg::path_cmd_stop) { + break; + } + + if (code != agg::path_cmd_move_to) { + polygon1.push_back(XY(x, y)); + } + } while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); + + // The result of each step is fed into the next (note the + // swapping of polygon1 and polygon2 at each step). + clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax)); + clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin)); + clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax)); + clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin)); + + // Empty polygons aren't very useful, so skip them + if (polygon1.size()) { + results.push_back(polygon1); + } + } while (code != agg::path_cmd_stop); +} + +template +void affine_transform(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result) +{ + if (vertices.dim(0) != 0 && vertices.dim(1) != 2) { + throw "Invalid vertices array."; + } + + size_t n = vertices.dim(0); + double x; + double y; + double t0; + double t1; + double t; + + for (size_t i = 0; i < n; ++i) { + x = vertices(i, 0); + y = vertices(i, 1); + + t0 = trans.sx * x; + t1 = trans.shx * y; + t = t0 + t1 + trans.tx; + result(i, 0) = t; + + t0 = trans.shy * x; + t1 = trans.sy * y; + t = t0 + t1 + trans.ty; + result(i, 1) = t; + } +} + +template +int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes) +{ + agg::rect_d b; + int count = 0; + + if (a.x2 < a.x1) { + std::swap(a.x1, a.x2); + } + if (a.y2 < a.y1) { + std::swap(a.y1, a.y2); + } + + size_t num_bboxes = bboxes.size(); + for (size_t i = 0; i < num_bboxes; ++i) { + typename BBoxArray::sub_t bbox_b = bboxes[i]; + b = agg::rect_d(bbox_b(0, 0), bbox_b(0, 1), bbox_b(1, 0), bbox_b(1, 1)); + + if (b.x2 < b.x1) { + std::swap(b.x1, b.x2); + } + if (b.y2 < b.y1) { + std::swap(b.y1, b.y2); + } + if (!((b.x2 <= a.x1) || (b.y2 <= a.y1) || (b.x1 >= a.x2) || (b.y1 >= a.y2))) { + ++count; + } + } + + return count; +} + +inline bool segments_intersect(const double &x1, + const double &y1, + const double &x2, + const double &y2, + const double &x3, + const double &y3, + const double &x4, + const double &y4) +{ + double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)); + if (den == 0.0) { + return false; + } + + double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3)); + double n2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3)); + + double u1 = n1 / den; + double u2 = n2 / den; + + return (u1 >= 0.0 && u1 <= 1.0 && u2 >= 0.0 && u2 <= 1.0); +} + +template +bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) +{ + typedef PathNanRemover no_nans_t; + typedef agg::conv_curve curve_t; + + if (p1.total_vertices() < 2 || p2.total_vertices() < 2) { + return false; + } + + no_nans_t n1(p1, true, p1.has_curves()); + no_nans_t n2(p2, true, p2.has_curves()); + + curve_t c1(n1); + curve_t c2(n2); + + double x11, y11, x12, y12; + double x21, y21, x22, y22; + + c1.vertex(&x11, &y11); + while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) { + c2.rewind(0); + c2.vertex(&x21, &y21); + while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) { + if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) { + return true; + } + x21 = x22; + y21 = y22; + } + x11 = x12; + y11 = y12; + } + + return false; +} + +template +void convert_path_to_polygons(PathIterator &path, + agg::trans_affine &trans, + double width, + double height, + std::vector &result) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removal_t; + typedef PathClipper clipped_t; + typedef PathSimplifier simplify_t; + typedef agg::conv_curve curve_t; + + bool do_clip = width != 0.0 && height != 0.0; + bool simplify = path.should_simplify(); + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, true, path.has_curves()); + clipped_t clipped(nan_removed, do_clip, width, height); + simplify_t simplified(clipped, simplify, path.simplify_threshold()); + curve_t curve(simplified); + + result.push_back(Polygon()); + Polygon *polygon = &result.back(); + double x, y; + unsigned code; + + while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) { + if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + if (polygon->size() >= 1) { + polygon->push_back((*polygon)[0]); + result.push_back(Polygon()); + polygon = &result.back(); + } + } else { + if (code == agg::path_cmd_move_to && polygon->size() >= 1) { + polygon->push_back((*polygon)[0]); + result.push_back(Polygon()); + polygon = &result.back(); + } + polygon->push_back(XY(x, y)); + } + } + + if (polygon->size() == 0) { + result.pop_back(); + } +} + +template +void +__cleanup_path(VertexSource &source, std::vector &vertices, std::vector &codes) +{ + unsigned code; + double x, y; + do { + code = source.vertex(&x, &y); + vertices.push_back(x); + vertices.push_back(y); + codes.push_back((npy_uint8)code); + } while (code != agg::path_cmd_stop); +} + +template +void cleanup_path(PathIterator &path, + const agg::trans_affine &trans, + bool remove_nans, + bool do_clip, + const agg::rect_base &rect, + e_snap_mode snap_mode, + double stroke_width, + bool do_simplify, + bool return_curves, + SketchParams sketch_params, + std::vector &vertices, + std::vector &codes) +{ + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removal_t; + typedef PathClipper clipped_t; + typedef PathSnapper snapped_t; + typedef PathSimplifier simplify_t; + typedef agg::conv_curve curve_t; + typedef Sketch sketch_t; + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, remove_nans, path.has_curves()); + clipped_t clipped(nan_removed, do_clip, rect); + snapped_t snapped(clipped, snap_mode, path.total_vertices(), stroke_width); + simplify_t simplified(snapped, do_simplify, path.simplify_threshold()); + + vertices.reserve(path.total_vertices() * 2); + codes.reserve(path.total_vertices()); + + if (return_curves && sketch_params.scale == 0.0) { + __cleanup_path(simplified, vertices, codes); + } else { + curve_t curve(simplified); + sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness); + __cleanup_path(sketch, vertices, codes); + } +} + +template +void convert_to_svg(PathIterator &path, + agg::trans_affine &trans, + agg::rect_d &clip_rect, + bool simplify, + int precision, + char *buffer, + size_t *buffersize) +{ +#if PY_VERSION_HEX < 0x02070000 + char format[64]; + snprintf(format, 64, "%s.%dg", "%", precision); +#endif + + typedef agg::conv_transform transformed_path_t; + typedef PathNanRemover nan_removal_t; + typedef PathClipper clipped_t; + typedef PathSimplifier simplify_t; + + bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, true, path.has_curves()); + clipped_t clipped(nan_removed, do_clip, clip_rect); + simplify_t simplified(clipped, simplify, path.simplify_threshold()); + + char *p = buffer; + + const char codes[] = { 'M', 'L', 'Q', 'C' }; + const int waits[] = { 1, 1, 2, 3 }; + + int wait = 0; + unsigned code; + double x = 0, y = 0; + while ((code = simplified.vertex(&x, &y)) != agg::path_cmd_stop) { + if (wait == 0) { + *p++ = '\n'; + + if (code == 0x4f) { + *p++ = 'z'; + *p++ = '\n'; + continue; + } + + *p++ = codes[code - 1]; + wait = waits[code - 1]; + } else { + *p++ = ' '; + } + +#if PY_VERSION_HEX >= 0x02070000 + char *str; + str = PyOS_double_to_string(x, 'g', precision, 0, NULL); + p += snprintf(p, *buffersize - (p - buffer), "%s", str); + PyMem_Free(str); + *p++ = ' '; + str = PyOS_double_to_string(y, 'g', precision, 0, NULL); + p += snprintf(p, *buffersize - (p - buffer), "%s", str); + PyMem_Free(str); +#else + char str[64]; + PyOS_ascii_formatd(str, 64, format, x); + p += snprintf(p, *buffersize - (p - buffer), "%s", str); + *p++ = ' '; + PyOS_ascii_formatd(str, 64, format, y); + p += snprintf(p, *buffersize - (p - buffer), "%s", str); +#endif + + --wait; + } + + *p = '\0'; + *buffersize = p - buffer; +} + +#endif diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp new file mode 100644 index 000000000000..d898a1684c82 --- /dev/null +++ b/src/_path_wrapper.cpp @@ -0,0 +1,713 @@ +#include "_path.h" + +#include "py_converters.h" +#include "py_adaptors.h" + +PyObject *convert_polygon_vector(std::vector &polygons) +{ + PyObject *pyresult = PyList_New(polygons.size()); + + for (size_t i = 0; i < polygons.size(); ++i) { + Polygon poly = polygons[i]; + npy_intp dims[] = {(npy_intp)poly.size() + 1, 2 }; + numpy::array_view subresult(dims); + + /* Make last point same as first. */ + memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2); + subresult(poly.size(), 0) = poly[0].x; + subresult(poly.size(), 1) = poly[0].y; + + if (PyList_SetItem(pyresult, i, subresult.pyobj())) { + Py_DECREF(pyresult); + return NULL; + } + } + + return pyresult; +} + +const char *Py_point_in_path__doc__ = "point_in_path(x, y, radius, path, trans)"; + +static PyObject *Py_point_in_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + double x, y, r; + py::PathIterator path; + agg::trans_affine trans; + bool result; + + if (!PyArg_ParseTuple(args, + "dddO&O&:point_in_path", + &x, + &y, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + CALL_CPP("point_in_path", (result = point_in_path(x, y, r, path, trans))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_points_in_path__doc__ = "points_in_path(points, radius, path, trans)"; + +static PyObject *Py_points_in_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view points; + double r; + py::PathIterator path; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&dO&O&:points_in_path", + &numpy::convert_array, + &points, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + npy_intp dims[] = { points.dim(0) }; + numpy::array_view results(dims); + + CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results))); + + return results.pyobj(); +} + +const char *Py_point_on_path__doc__ = "point_on_path(x, y, radius, path, trans)"; + +static PyObject *Py_point_on_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + double x, y, r; + py::PathIterator path; + agg::trans_affine trans; + bool result; + + if (!PyArg_ParseTuple(args, + "dddO&O&:point_on_path", + &x, + &y, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + CALL_CPP("point_on_path", (result = point_on_path(x, y, r, path, trans))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_points_on_path__doc__ = "points_on_path(points, radius, path, trans)"; + +static PyObject *Py_points_on_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view points; + double r; + py::PathIterator path; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&dO&O&:points_on_path", + &numpy::convert_array, + &points, + &r, + &convert_path, + &path, + &convert_trans_affine, + &trans)) { + return NULL; + } + + npy_intp dims[] = { points.dim(0) }; + numpy::array_view results(dims); + + CALL_CPP("points_on_path", (points_on_path(points, r, path, trans, results))); + + return results.pyobj(); +} + +const char *Py_get_path_extents__doc__ = "get_path_extents(path, trans)"; + +static PyObject *Py_get_path_extents(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + + if (!PyArg_ParseTuple( + args, "O&O&:get_path_extents", &convert_path, &path, &convert_trans_affine, &trans)) { + return NULL; + } + + extent_limits e; + + CALL_CPP("get_path_extents", (reset_limits(e))); + CALL_CPP("get_path_extents", (update_path_extents(path, trans, e))); + + npy_intp dims[] = { 2, 2 }; + numpy::array_view extents(dims); + extents(0, 0) = e.x0; + extents(0, 1) = e.y0; + extents(1, 0) = e.x1; + extents(1, 1) = e.y1; + + return extents.pyobj(); +} + +const char *Py_update_path_extents__doc__ = + "update_path_extents(path, trans, rect, minpos, ignore)"; + +static PyObject *Py_update_path_extents(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + agg::rect_d rect; + numpy::array_view minpos; + bool ignore; + bool changed; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&i:update_path_extents", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &convert_rect, + &rect, + &numpy::convert_array, + &minpos, + &ignore)) { + return NULL; + } + + if (minpos.dim(0) != 2) { + PyErr_SetString(PyExc_ValueError, "minpos must be of length 2"); + } + + extent_limits e; + + if (ignore) { + CALL_CPP("update_path_extents", reset_limits(e)); + } else { + if (rect.x1 > rect.x2) { + e.x0 = std::numeric_limits::infinity(); + e.x1 = -std::numeric_limits::infinity(); + } else { + e.x0 = rect.x1; + e.x1 = rect.x2; + } + if (rect.y1 > rect.y2) { + e.y0 = std::numeric_limits::infinity(); + e.y1 = -std::numeric_limits::infinity(); + } else { + e.y0 = rect.y1; + e.y1 = rect.y2; + } + e.xm = minpos(0); + e.ym = minpos(1); + } + + CALL_CPP("update_path_extents", (update_path_extents(path, trans, e))); + + changed = (e.x0 != rect.x1 || e.y0 != rect.y1 || e.x1 != rect.x2 || e.y1 != rect.y2 || + e.xm != minpos(0) || e.ym != minpos(1)); + + npy_intp extentsdims[] = { 2, 2 }; + numpy::array_view outextents(extentsdims); + outextents(0, 0) = e.x0; + outextents(0, 1) = e.y0; + outextents(1, 0) = e.x1; + outextents(1, 1) = e.y1; + + npy_intp minposdims[] = { 2 }; + numpy::array_view outminpos(minposdims); + outminpos(0) = e.xm; + outminpos(1) = e.ym; + + return Py_BuildValue( + "NNO", outextents.pyobj(), outminpos.pyobj(), changed ? Py_True : Py_False); +} + +const char *Py_get_path_collection_extents__doc__ = "get_path_collection_extents("; + +static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, PyObject *kwds) +{ + agg::trans_affine master_transform; + PyObject *pathsobj; + numpy::array_view transforms; + numpy::array_view offsets; + agg::trans_affine offset_trans; + extent_limits e; + + if (!PyArg_ParseTuple(args, + "O&OO&O&O&:get_path_collection_extents", + &convert_trans_affine, + &master_transform, + &pathsobj, + &numpy::convert_array, + &transforms, + &numpy::convert_array, + &offsets, + &convert_trans_affine, + &offset_trans)) { + return NULL; + } + + try + { + py::PathGenerator paths(pathsobj); + + CALL_CPP("get_path_collection_extents", + (get_path_collection_extents( + master_transform, paths, transforms, offsets, offset_trans, e))); + } + catch (py::exception &e) + { + return NULL; + } + + npy_intp dims[] = { 2, 2 }; + numpy::array_view extents(dims); + extents(0, 0) = e.x0; + extents(0, 1) = e.y0; + extents(1, 0) = e.x1; + extents(1, 1) = e.y1; + + return extents.pyobj(); +} + +const char *Py_point_in_path_collection__doc__ = + "point_in_path_collection(x, y, radius, master_transform, paths, transforms, offsets, " + "offset_trans, filled, offset_position)"; + +static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args, PyObject *kwds) +{ + double x, y, radius; + agg::trans_affine master_transform; + PyObject *pathsobj; + numpy::array_view transforms; + numpy::array_view offsets; + agg::trans_affine offset_trans; + int filled; + e_offset_position offset_position; + std::vector result; + + if (!PyArg_ParseTuple(args, + "dddO&OO&O&O&iO&:point_in_path_collection", + &x, + &y, + &radius, + &convert_trans_affine, + &master_transform, + &pathsobj, + &numpy::convert_array, + &transforms, + &numpy::convert_array, + &offsets, + &convert_trans_affine, + &offset_trans, + &filled, + &convert_offset_position, + &offset_position)) { + return NULL; + } + + try + { + py::PathGenerator paths(pathsobj); + + CALL_CPP("point_in_path_collection", + (point_in_path_collection(x, + y, + radius, + master_transform, + paths, + transforms, + offsets, + offset_trans, + filled, + offset_position, + result))); + } + catch (py::exception &e) + { + return NULL; + } + + npy_intp dims[] = {(npy_intp)result.size() }; + numpy::array_view pyresult(dims); + memcpy(pyresult.data(), &result[0], result.size() * sizeof(size_t)); + return pyresult.pyobj(); +} + +const char *Py_path_in_path__doc__ = "path_in_path(path_a, trans_a, path_b, trans_b)"; + +static PyObject *Py_path_in_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator a; + agg::trans_affine atrans; + py::PathIterator b; + agg::trans_affine btrans; + bool result; + + if (!PyArg_ParseTuple(args, + "O&O&O&O&:path_in_path", + &convert_path, + &a, + &convert_trans_affine, + &atrans, + &convert_path, + &b, + &convert_trans_affine, + &btrans)) { + return NULL; + } + + CALL_CPP("path_in_path", (result = path_in_path(a, atrans, b, btrans))); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_clip_path_to_rect__doc__ = "clip_path_to_rect(path, rect, inside)"; + +static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::rect_d rect; + int inside; + std::vector result; + + if (!PyArg_ParseTuple(args, + "O&O&i:clip_path_to_rect", + &convert_path, + &path, + &convert_rect, + &rect, + &inside)) { + return NULL; + } + + CALL_CPP("clip_path_to_rect", (clip_path_to_rect(path, rect, inside, result))); + + return convert_polygon_vector(result); +} + +const char *Py_affine_transform__doc__ = "affine_transform(points, trans)"; + +static PyObject *Py_affine_transform(PyObject *self, PyObject *args, PyObject *kwds) +{ + numpy::array_view vertices; + agg::trans_affine trans; + + if (!PyArg_ParseTuple(args, + "O&O&:affine_transform", + &numpy::convert_array, + &vertices, + &convert_trans_affine, + &trans)) { + return NULL; + } + + npy_intp dims[] = { vertices.dim(0), 2 }; + numpy::array_view result(dims); + + CALL_CPP("affine_transform", (affine_transform(vertices, trans, result))); + + return result.pyobj(); +} + +const char *Py_count_bboxes_overlapping_bbox__doc__ = "count_bboxes_overlapping_bbox(bbox, bboxes)"; + +static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args, PyObject *kwds) +{ + agg::rect_d bbox; + numpy::array_view bboxes; + int result; + + if (!PyArg_ParseTuple(args, + "O&O&:count_bboxes_overlapping_bbox", + &convert_rect, + &bbox, + &numpy::convert_array, + &bboxes)) { + return NULL; + } + + CALL_CPP("count_bboxes_overlapping_bbox", + (result = count_bboxes_overlapping_bbox(bbox, bboxes))); + + return PyLong_FromLong(result); +} + +const char *Py_path_intersects_path__doc__ = "path_intersects_path(path1, path2, filled=False)"; + +static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator p1; + py::PathIterator p2; + bool filled = false; + const char *names[] = { "p1", "p2", "filled", NULL }; + bool result; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&O&i:path_intersects_path", + (char **)names, + &convert_path, + &p1, + &convert_path, + &p2, + &filled)) { + return NULL; + } + + CALL_CPP("path_intersects_path", (result = path_intersects_path(p1, p2))); + if (filled) { + if (!result) { + CALL_CPP("path_intersects_path", + (result = path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine()))); + } + if (!result) { + CALL_CPP("path_intersects_path", + (result = path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine()))); + } + } + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +const char *Py_convert_path_to_polygons__doc__ = + "convert_path_to_polygons(path, trans, width=0, height=0)"; + +static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + double width = 0.0, height = 0.0; + std::vector result; + + if (!PyArg_ParseTuple(args, + "O&O&|dd:convert_path_to_polygons", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &width, + &height)) { + return NULL; + } + + CALL_CPP("convert_path_to_polygons", + (convert_path_to_polygons(path, trans, width, height, result))); + + return convert_polygon_vector(result); +} + +const char *Py_cleanup_path__doc__ = + "cleanup_path(path, trans, remove_nans, clip_rect, snap_mode, stroke_width, simplify, " + "return_curves, sketch)"; + +static PyObject *Py_cleanup_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + int remove_nans; + agg::rect_d clip_rect; + e_snap_mode snap_mode; + double stroke_width; + PyObject *simplifyobj; + bool simplify = false; + int return_curves; + SketchParams sketch; + + if (!PyArg_ParseTuple(args, + "O&O&iO&O&dOiO&:cleanup_path", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &remove_nans, + &convert_rect, + &clip_rect, + &convert_snap, + &snap_mode, + &stroke_width, + &simplifyobj, + &return_curves, + &convert_sketch_params, + &sketch)) { + return NULL; + } + + if (simplifyobj == Py_None) { + simplify = path.should_simplify(); + } else if (PyObject_IsTrue(simplifyobj)) { + simplify = true; + } + + bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2); + + std::vector vertices; + std::vector codes; + + CALL_CPP("cleanup_path", + (cleanup_path(path, + trans, + remove_nans, + do_clip, + clip_rect, + snap_mode, + stroke_width, + simplify, + return_curves, + sketch, + vertices, + codes))); + + size_t length = codes.size(); + + npy_intp vertices_dims[] = {(npy_intp)length, 2 }; + numpy::array_view pyvertices(vertices_dims); + + npy_intp codes_dims[] = {(npy_intp)length }; + numpy::array_view pycodes(codes_dims); + + memcpy(pyvertices.data(), &vertices[0], sizeof(double) * 2 * length); + memcpy(pycodes.data(), &codes[0], sizeof(unsigned char) * length); + + return Py_BuildValue("NN", pyvertices.pyobj(), pycodes.pyobj()); +} + +const char *Py_convert_to_svg__doc__ = "convert_to_svg(path, trans, cliprect, simplify, precision)"; + +static PyObject *Py_convert_to_svg(PyObject *self, PyObject *args, PyObject *kwds) +{ + py::PathIterator path; + agg::trans_affine trans; + agg::rect_d cliprect; + PyObject *simplifyobj; + bool simplify = false; + int precision; + + if (!PyArg_ParseTuple(args, + "O&O&O&Oi:convert_to_svg", + &convert_path, + &path, + &convert_trans_affine, + &trans, + &convert_rect, + &cliprect, + &simplifyobj, + &precision)) { + return NULL; + } + + if (simplifyobj == Py_None) { + simplify = path.should_simplify(); + } else if (PyObject_IsTrue(simplifyobj)) { + simplify = true; + } + + size_t buffersize = path.total_vertices() * (precision + 5) * 4; + std::string buffer; + buffer.reserve(buffersize); + + CALL_CPP("convert_to_svg", + (convert_to_svg(path, trans, cliprect, simplify, precision, &buffer[0], &buffersize))); + + return PyUnicode_DecodeASCII(&buffer[0], buffersize, ""); +} + +extern "C" { + + static PyMethodDef module_functions[] = { + {"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__}, + {"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__}, + {"point_on_path", (PyCFunction)Py_point_on_path, METH_VARARGS, Py_point_on_path__doc__}, + {"points_on_path", (PyCFunction)Py_points_on_path, METH_VARARGS, Py_points_on_path__doc__}, + {"get_path_extents", (PyCFunction)Py_get_path_extents, METH_VARARGS, Py_get_path_extents__doc__}, + {"update_path_extents", (PyCFunction)Py_update_path_extents, METH_VARARGS, Py_update_path_extents__doc__}, + {"get_path_collection_extents", (PyCFunction)Py_get_path_collection_extents, METH_VARARGS, Py_get_path_collection_extents__doc__}, + {"point_in_path_collection", (PyCFunction)Py_point_in_path_collection, METH_VARARGS, Py_point_in_path_collection__doc__}, + {"path_in_path", (PyCFunction)Py_path_in_path, METH_VARARGS, Py_path_in_path__doc__}, + {"clip_path_to_rect", (PyCFunction)Py_clip_path_to_rect, METH_VARARGS, Py_clip_path_to_rect__doc__}, + {"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__}, + {"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__}, + {"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__}, + {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS, Py_convert_path_to_polygons__doc__}, + {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__}, + {"convert_to_svg", (PyCFunction)Py_convert_to_svg, METH_VARARGS, Py_convert_to_svg__doc__}, + {NULL, NULL, NULL, NULL} + }; + + struct module_state + { + /* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif + }; + +#if PY3K + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_path", + NULL, + sizeof(struct module_state), + module_functions, + NULL, + NULL, + NULL, + NULL + }; + +#define INITERROR return NULL + PyMODINIT_FUNC PyInit__path(void) +#else +#define INITERROR return + PyMODINIT_FUNC init_path(void) +#endif + { + PyObject *m; +#if PY3K + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("_path", module_functions, NULL); +#endif + + if (m == NULL) { + INITERROR; + } + + import_array(); + +#if PY3K + return m; +#endif + } +} diff --git a/src/_png.cpp b/src/_png.cpp index 265620fa8809..6d76f5d05e10 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -1,11 +1,17 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ +// this code is heavily adapted from the paint license, which is in +// the file paint.license (BSD compatible) included in this +// distribution. TODO, add license file to MANIFEST.in and CVS + /* For linux, png.h must be imported before Python.h because png.h needs to be the one to define setjmp. Undefining _POSIX_C_SOURCE and _XOPEN_SOURCE stops a couple of harmless warnings. */ +#include + #ifdef __linux__ # include # ifdef _POSIX_C_SOURCE @@ -23,62 +29,26 @@ # include #endif -// TODO: Un CXX-ify this module -#include "CXX/Extensions.hxx" -#include "numpy/arrayobject.h" -#include "mplutils.h" - #include "file_compat.h" +#include "numpy_cpp.h" +#include "mplutils.h" // As reported in [3082058] build _png.so on aix #ifdef _AIX #undef jmpbuf #endif -// the extension module -class _png_module : public Py::ExtensionModule<_png_module> -{ -public: - _png_module() - : Py::ExtensionModule<_png_module>("_png") - { - add_varargs_method("write_png", &_png_module::write_png, - "write_png(buffer, width, height, fileobj, dpi=None)"); - add_varargs_method("read_png", &_png_module::read_png_float, - "read_png(fileobj)"); - add_varargs_method("read_png_float", &_png_module::read_png_float, - "read_png_float(fileobj)"); - add_varargs_method("read_png_uint8", &_png_module::read_png_uint8, - "read_png_uint8(fileobj)"); - add_varargs_method("read_png_int", &_png_module::read_png_int, - "read_png_int(fileobj)"); - initialize("Module to write PNG files"); - } - - virtual ~_png_module() {} - -private: - Py::Object write_png(const Py::Tuple& args); - Py::Object read_png_uint8(const Py::Tuple& args); - Py::Object read_png_float(const Py::Tuple& args); - Py::Object read_png_int(const Py::Tuple& args); - PyObject* _read_png(const Py::Object& py_fileobj, const bool float_result, int result_bit_depth = -1); -}; - static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length) { - PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr); - PyObject* write_method = PyObject_GetAttrString(py_file_obj, "write"); - PyObject* result = NULL; - if (write_method) - { - #if PY3K - result = PyObject_CallFunction(write_method, (char *)"y#", data, - length); - #else - result = PyObject_CallFunction(write_method, (char *)"s#", data, - length); - #endif + PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); + PyObject *write_method = PyObject_GetAttrString(py_file_obj, "write"); + PyObject *result = NULL; + if (write_method) { +#if PY3K + result = PyObject_CallFunction(write_method, (char *)"y#", data, length); +#else + result = PyObject_CallFunction(write_method, (char *)"s#", data, length); +#endif } Py_XDECREF(write_method); Py_XDECREF(result); @@ -86,214 +56,166 @@ static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t lengt static void flush_png_data(png_structp png_ptr) { - PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr); - PyObject* flush_method = PyObject_GetAttrString(py_file_obj, "flush"); - PyObject* result = NULL; - if (flush_method) - { + PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); + PyObject *flush_method = PyObject_GetAttrString(py_file_obj, "flush"); + PyObject *result = NULL; + if (flush_method) { result = PyObject_CallFunction(flush_method, (char *)""); } Py_XDECREF(flush_method); Py_XDECREF(result); } -// this code is heavily adapted from the paint license, which is in -// the file paint.license (BSD compatible) included in this -// distribution. TODO, add license file to MANIFEST.in and CVS -Py::Object _png_module::write_png(const Py::Tuple& args) +const char *Py_write_png__doc__ = "write_png(buffer, file, dpi=0)"; + +static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) { - args.verify_length(4, 5); + numpy::array_view buffer; + PyObject *filein; + double dpi = 0; + const char *names[] = { "buffer", "file", "dpi", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&O|d:write_png", + (char **)names, + &numpy::convert_array, + &buffer, + &filein, + &dpi)) { + return NULL; + } + + png_uint_32 width = (png_uint_32)buffer.dim(1); + png_uint_32 height = (png_uint_32)buffer.dim(0); + std::vector row_pointers(height); + for (png_uint_32 row = 0; row < (png_uint_32)height; ++row) { + row_pointers[row] = (png_bytep)buffer[row].data(); + } FILE *fp = NULL; - mpl_off_t offset; + mpl_off_t offset = 0; bool close_file = false; bool close_dup_file = false; - Py::Object buffer_obj = Py::Object(args[0]); - PyObject* buffer = buffer_obj.ptr(); - if (!PyObject_CheckReadBuffer(buffer)) - { - throw Py::TypeError("First argument must be an rgba buffer."); - } - - const void* pixBufferPtr = NULL; - Py_ssize_t pixBufferLength = 0; - if (PyObject_AsReadBuffer(buffer, &pixBufferPtr, &pixBufferLength)) - { - throw Py::ValueError("Couldn't get data from read buffer."); - } + PyObject *py_file = NULL; - png_byte* pixBuffer = (png_byte*)pixBufferPtr; - int width = (int)Py::Int(args[1]); - int height = (int)Py::Int(args[2]); + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + struct png_color_8_struct sig_bit; - if (pixBufferLength < width * height * 4) - { - throw Py::ValueError("Buffer and width, height don't seem to match."); + if (buffer.dim(2) != 4) { + PyErr_SetString(PyExc_ValueError, "Buffer must be RGBA NxMx4 array"); + goto exit; } - Py::Object py_fileobj = Py::Object(args[3]); - PyObject* py_file = NULL; - if (py_fileobj.isString()) - { - if ((py_file = mpl_PyFile_OpenFile(py_fileobj.ptr(), (char *)"wb")) == NULL) { - throw Py::Exception(); + if (PyBytes_Check(filein) || PyUnicode_Check(filein)) { + if ((py_file = mpl_PyFile_OpenFile(filein, (char *)"wb")) == NULL) { + goto exit; } close_file = true; - } - else - { - py_file = py_fileobj.ptr(); + } else { + py_file = filein; } - if ((fp = mpl_PyFile_Dup(py_file, (char *)"wb", &offset))) - { + if ((fp = mpl_PyFile_Dup(py_file, (char *)"wb", &offset))) { close_dup_file = true; - } - else - { + } else { PyErr_Clear(); - PyObject* write_method = PyObject_GetAttrString( - py_file, "write"); - if (!(write_method && PyCallable_Check(write_method))) - { + PyObject *write_method = PyObject_GetAttrString(py_file, "write"); + if (!(write_method && PyCallable_Check(write_method))) { Py_XDECREF(write_method); - throw Py::TypeError( - "Object does not appear to be a 8-bit string path or " - "a Python file-like object"); + PyErr_SetString(PyExc_TypeError, + "Object does not appear to be a 8-bit string path or " + "a Python file-like object"); + goto exit; } Py_XDECREF(write_method); } - png_bytep *row_pointers = NULL; - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - - try - { - struct png_color_8_struct sig_bit; - png_uint_32 row; - - row_pointers = new png_bytep[height]; - for (row = 0; row < (png_uint_32)height; ++row) - { - row_pointers[row] = pixBuffer + row * width * 4; - } + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Could not create write struct"); + goto exit; + } - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr == NULL) - { - throw Py::RuntimeError("Could not create write struct"); - } + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Could not create info struct"); + goto exit; + } - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) - { - throw Py::RuntimeError("Could not create info struct"); - } + if (setjmp(png_jmpbuf(png_ptr))) { + PyErr_SetString(PyExc_RuntimeError, "Error setting jumps"); + goto exit; + } - if (setjmp(png_jmpbuf(png_ptr))) - { - throw Py::RuntimeError("Error building image"); - } + if (fp) { + png_init_io(png_ptr, fp); + } else { + png_set_write_fn(png_ptr, (void *)py_file, &write_png_data, &flush_png_data); + } + png_set_IHDR(png_ptr, + info_ptr, + width, + height, + 8, + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); - if (fp) - { - png_init_io(png_ptr, fp); - } - else - { - png_set_write_fn(png_ptr, (void*)py_file, - &write_png_data, &flush_png_data); - } - png_set_IHDR(png_ptr, info_ptr, - width, height, 8, - PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - // Save the dpi of the image in the file - if (args.size() == 5) - { - double dpi = Py::Float(args[4]); - size_t dots_per_meter = (size_t)(dpi / (2.54 / 100.0)); - png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); - } + // Save the dpi of the image in the file + if (dpi > 0.0) { + png_uint_32 dots_per_meter = (png_uint_32)(dpi / (2.54 / 100.0)); + png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); + } - // this a a color image! - sig_bit.gray = 0; - sig_bit.red = 8; - sig_bit.green = 8; - sig_bit.blue = 8; - /* if the image has an alpha channel then */ - sig_bit.alpha = 8; - png_set_sBIT(png_ptr, info_ptr, &sig_bit); + // this a a color image! + sig_bit.gray = 0; + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + /* if the image has an alpha channel then */ + sig_bit.alpha = 8; + png_set_sBIT(png_ptr, info_ptr, &sig_bit); - png_write_info(png_ptr, info_ptr); - png_write_image(png_ptr, row_pointers); - png_write_end(png_ptr, info_ptr); - } - catch (...) - { - if (png_ptr && info_ptr) - { - png_destroy_write_struct(&png_ptr, &info_ptr); - } - delete [] row_pointers; + png_write_info(png_ptr, info_ptr); + png_write_image(png_ptr, &row_pointers[0]); + png_write_end(png_ptr, info_ptr); - if (close_dup_file) - { - if (mpl_PyFile_DupClose(py_file, fp, offset)) { - throw Py::RuntimeError("Error closing dupe file handle"); - } - } +exit: - if (close_file) - { - mpl_PyFile_CloseFile(py_file); - Py_DECREF(py_file); - } - /* Changed calls to png_destroy_write_struct to follow - http://www.libpng.org/pub/png/libpng-manual.txt. - This ensures the info_ptr memory is released. - */ - throw; + if (png_ptr && info_ptr) { + png_destroy_write_struct(&png_ptr, &info_ptr); } - png_destroy_write_struct(&png_ptr, &info_ptr); - delete [] row_pointers; - if (close_dup_file) - { - if (mpl_PyFile_DupClose(py_file, fp, offset)) { - throw Py::RuntimeError("Error closing dupe file handle"); - } + if (close_dup_file) { + mpl_PyFile_DupClose(py_file, fp, offset); } - if (close_file) - { + if (close_file) { mpl_PyFile_CloseFile(py_file); Py_DECREF(py_file); } if (PyErr_Occurred()) { - throw Py::Exception(); + return NULL; } else { - return Py::Object(); + Py_RETURN_NONE; } } -static void _read_png_data(PyObject* py_file_obj, png_bytep data, png_size_t length) +static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t length) { - PyObject* read_method = PyObject_GetAttrString(py_file_obj, "read"); - PyObject* result = NULL; + PyObject *read_method = PyObject_GetAttrString(py_file_obj, "read"); + PyObject *result = NULL; char *buffer; Py_ssize_t bufflen; - if (read_method) - { + if (read_method) { result = PyObject_CallFunction(read_method, (char *)"i", length); } - if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) - { - if (bufflen == (Py_ssize_t)length) - { + if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { + if (bufflen == (Py_ssize_t)length) { memcpy(data, buffer, length); } } @@ -303,271 +225,214 @@ static void _read_png_data(PyObject* py_file_obj, png_bytep data, png_size_t len static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length) { - PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr); + PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); _read_png_data(py_file_obj, data, length); } -PyObject* -_png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, - int result_bit_depth) +static PyObject *_read_png(PyObject *filein, bool float_result) { - png_byte header[8]; // 8 is the maximum size that can be checked - FILE* fp = NULL; - mpl_off_t offset; + png_byte header[8]; // 8 is the maximum size that can be checked + FILE *fp = NULL; + mpl_off_t offset = 0; bool close_file = false; bool close_dup_file = false; PyObject *py_file = NULL; - - if (py_fileobj.isString()) - { - if ((py_file = mpl_PyFile_OpenFile(py_fileobj.ptr(), (char *)"rb")) == NULL) { - throw Py::Exception(); + PyArrayObject *A = NULL; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + int num_dims; + std::vector row_pointers; + png_uint_32 width = 0; + png_uint_32 height = 0; + int bit_depth; + + if (PyBytes_Check(filein) || PyUnicode_Check(filein)) { + if ((py_file = mpl_PyFile_OpenFile(filein, (char *)"rb")) == NULL) { + goto exit; } close_file = true; } else { - py_file = py_fileobj.ptr(); + py_file = filein; } - if ((fp = mpl_PyFile_Dup(py_file, "rb", &offset))) - { + if ((fp = mpl_PyFile_Dup(py_file, (char *)"rb", &offset))) { close_dup_file = true; - } - else - { + } else { PyErr_Clear(); - PyObject* read_method = PyObject_GetAttrString(py_file, "read"); - if (!(read_method && PyCallable_Check(read_method))) - { + PyObject *read_method = PyObject_GetAttrString(py_file, "read"); + if (!(read_method && PyCallable_Check(read_method))) { Py_XDECREF(read_method); - throw Py::TypeError( - "Object does not appear to be a 8-bit string path or a Python " - "file-like object"); + PyErr_SetString(PyExc_TypeError, + "Object does not appear to be a 8-bit string path or " + "a Python file-like object"); + goto exit; } Py_XDECREF(read_method); } - if (fp) - { - if (fread(header, 1, 8, fp) != 8) - { - throw Py::RuntimeError( - "_image_module::readpng: error reading PNG header"); + if (fp) { + if (fread(header, 1, 8, fp) != 8) { + PyErr_SetString(PyExc_IOError, "error reading PNG header"); + goto exit; } - } - else - { + } else { _read_png_data(py_file, header, 8); } - if (png_sig_cmp(header, 0, 8)) - { - throw Py::RuntimeError( - "_image_module::readpng: file not recognized as a PNG file"); + + if (png_sig_cmp(header, 0, 8)) { + PyErr_SetString(PyExc_ValueError, "invalid PNG header"); + goto exit; } /* initialize stuff */ - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ptr) - { - throw Py::RuntimeError( - "_image_module::readpng: png_create_read_struct failed"); + if (!png_ptr) { + PyErr_SetString(PyExc_RuntimeError, "png_create_read_struct failed"); + goto exit; } - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) - { - throw Py::RuntimeError( - "_image_module::readpng: png_create_info_struct failed"); + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + PyErr_SetString(PyExc_RuntimeError, "png_create_info_struct failed"); + goto exit; } - if (setjmp(png_jmpbuf(png_ptr))) - { - throw Py::RuntimeError( - "_image_module::readpng: error during init_io"); + if (setjmp(png_jmpbuf(png_ptr))) { + PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); + goto exit; } - if (fp) - { + if (fp) { png_init_io(png_ptr, fp); - } - else - { - png_set_read_fn(png_ptr, (void*)py_file, &read_png_data); + } else { + png_set_read_fn(png_ptr, (void *)py_file, &read_png_data); } png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); - png_uint_32 width = png_get_image_width(png_ptr, info_ptr); - png_uint_32 height = png_get_image_height(png_ptr, info_ptr); + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); - int bit_depth = png_get_bit_depth(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); // Unpack 1, 2, and 4-bit images - if (bit_depth < 8) + if (bit_depth < 8) { png_set_packing(png_ptr); + } // If sig bits are set, shift data png_color_8p sig_bit; if ((png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE) && - png_get_sBIT(png_ptr, info_ptr, &sig_bit)) - { + png_get_sBIT(png_ptr, info_ptr, &sig_bit)) { png_set_shift(png_ptr, sig_bit); } // Convert big endian to little - if (bit_depth == 16) - { + if (bit_depth == 16) { png_set_swap(png_ptr); } // Convert palletes to full RGB - if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) - { + if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); bit_depth = 8; } // If there's an alpha channel convert gray to RGB - if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) - { + if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); } png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); - /* read file */ - if (setjmp(png_jmpbuf(png_ptr))) - { - throw Py::RuntimeError( - "_image_module::readpng: error during read_image"); - } - - png_bytep *row_pointers = new png_bytep[height]; - png_uint_32 row; - - for (row = 0; row < height; row++) - { - row_pointers[row] = new png_byte[png_get_rowbytes(png_ptr,info_ptr)]; + row_pointers.resize(height); + for (png_uint_32 row = 0; row < height; row++) { + row_pointers[row] = new png_byte[png_get_rowbytes(png_ptr, info_ptr)]; } - png_read_image(png_ptr, row_pointers); + png_read_image(png_ptr, &row_pointers[0]); npy_intp dimensions[3]; - dimensions[0] = height; //numrows - dimensions[1] = width; //numcols - if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) - { - dimensions[2] = 4; //RGBA images - } - else if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) - { - dimensions[2] = 3; //RGB images - } - else - { - dimensions[2] = 1; //Greyscale images + dimensions[0] = height; // numrows + dimensions[1] = width; // numcols + if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) { + dimensions[2] = 4; // RGBA images + } else if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) { + dimensions[2] = 3; // RGB images + } else { + dimensions[2] = 1; // Greyscale images } - //For gray, return an x by y array, not an x by y by 1 - int num_dims = (png_get_color_type(png_ptr, info_ptr) - & PNG_COLOR_MASK_COLOR) ? 3 : 2; + // For gray, return an x by y array, not an x by y by 1 + num_dims = (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) ? 3 : 2; - PyArrayObject *A = NULL; if (float_result) { double max_value = (1 << bit_depth) - 1; - A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions, NPY_FLOAT); - - if (A == NULL) - { - throw Py::MemoryError("Could not allocate image array"); + A = (PyArrayObject *)PyArray_SimpleNew(num_dims, dimensions, NPY_FLOAT); + if (A == NULL) { + goto exit; } - const npy_intp ystride = PyArray_STRIDE(A, 0); - const npy_intp xstride = PyArray_STRIDE(A, 1); - const npy_intp pstride = PyArray_STRIDE(A, 2); - char* target_row = PyArray_BYTES(A); - for (png_uint_32 y = 0; y < height; y++, target_row += ystride) - { - png_byte* row = row_pointers[y]; - char* target_pixel = target_row; - for (png_uint_32 x = 0; x < width; x++, target_pixel += xstride) - { - if (bit_depth == 16) - { - png_uint_16* ptr = &reinterpret_cast(row)[x * dimensions[2]]; - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(float*)(target_pixel + p * pstride) = (float)(ptr[p]) / max_value; + for (png_uint_32 y = 0; y < height; y++) { + png_byte *row = row_pointers[y]; + for (png_uint_32 x = 0; x < width; x++) { + size_t offset = y * A->strides[0] + x * A->strides[1]; + if (bit_depth == 16) { + png_uint_16 *ptr = &reinterpret_cast(row)[x * dimensions[2]]; + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(float *)(A->data + offset + p *A->strides[2]) = + (float)(ptr[p]) / max_value; } - } - else - { - png_byte* ptr = &(row[x * dimensions[2]]); - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(float*)(target_pixel + p * pstride) = (float)(ptr[p]) / max_value; + } else { + png_byte *ptr = &(row[x * dimensions[2]]); + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(float *)(A->data + offset + p *A->strides[2]) = + (float)(ptr[p]) / max_value; } } } } } else { - if (result_bit_depth < 0) { - result_bit_depth = bit_depth; - } - - if (result_bit_depth == 8) { - A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions, NPY_UBYTE); - } else if (result_bit_depth == 16) { - A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions, NPY_UINT16); + if (bit_depth == 8) { + A = (PyArrayObject *)PyArray_SimpleNew(num_dims, dimensions, NPY_UBYTE); + } else if (bit_depth == 16) { + A = (PyArrayObject *)PyArray_SimpleNew(num_dims, dimensions, NPY_UINT16); } else { - throw Py::RuntimeError( - "_image_module::readpng: image has unknown bit depth"); + PyErr_SetString(PyExc_RuntimeError, "image has unknown bit depth"); + goto exit; } - if (A == NULL) - { - throw Py::MemoryError("Could not allocate image array"); + if (A == NULL) { + goto exit; } - const npy_intp ystride = PyArray_STRIDE(A, 0); - const npy_intp xstride = PyArray_STRIDE(A, 1); - const npy_intp pstride = PyArray_STRIDE(A, 2); - char* target_row = PyArray_BYTES(A); - for (png_uint_32 y = 0; y < height; y++, target_row += ystride) - { - png_byte* row = row_pointers[y]; - char* target_pixel = target_row; - for (png_uint_32 x = 0; x < width; x++, target_pixel += xstride) - { - if (bit_depth == 16) - { - png_uint_16* ptr = &reinterpret_cast(row)[x * dimensions[2]]; - - if (result_bit_depth == 16) { - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(png_uint_16*)(target_pixel + p*pstride) = ptr[p]; + for (png_uint_32 y = 0; y < height; y++) { + png_byte *row = row_pointers[y]; + for (png_uint_32 x = 0; x < width; x++) { + size_t offset = y * A->strides[0] + x * A->strides[1]; + if (bit_depth == 16) { + png_uint_16 *ptr = &reinterpret_cast(row)[x * dimensions[2]]; + + if (bit_depth == 16) { + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(png_uint_16 *)(A->data + offset + p *A->strides[2]) = ptr[p]; } } else { - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(png_byte*)(target_pixel + p*pstride) = ptr[p] >> 8; + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(png_byte *)(A->data + offset + p *A->strides[2]) = ptr[p] >> 8; } } - } - else - { - png_byte* ptr = &(row[x * dimensions[2]]); - if (result_bit_depth == 16) { - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(png_uint_16*)(target_pixel + p*pstride) = ptr[p]; + } else { + png_byte *ptr = &(row[x * dimensions[2]]); + if (bit_depth == 16) { + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(png_uint_16 *)(A->data + offset + p *A->strides[2]) = ptr[p]; } } else { - for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) - { - *(png_byte*)(target_pixel + p*pstride) = ptr[p]; + for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++) { + *(png_byte *)(A->data + offset + p *A->strides[2]) = ptr[p]; } } } @@ -575,73 +440,113 @@ _png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, } } - //free the png memory + // free the png memory png_read_end(png_ptr, info_ptr); + +exit: + if (png_ptr && info_ptr) { #ifndef png_infopp_NULL - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); #else - png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); #endif - if (close_dup_file) - { - if (mpl_PyFile_DupClose(py_file, fp, offset)) { - throw Py::RuntimeError("Error closing dupe file handle"); - } } - if (close_file) - { + if (close_dup_file) { + mpl_PyFile_DupClose(py_file, fp, offset); + } + + if (close_file) { mpl_PyFile_CloseFile(py_file); Py_DECREF(py_file); } - for (row = 0; row < height; row++) - { - delete [] row_pointers[row]; + for (png_uint_32 row = 0; row < height; row++) { + delete[] row_pointers[row]; } - delete [] row_pointers; if (PyErr_Occurred()) { - Py_DECREF((PyObject *)A); + Py_XDECREF((PyObject *)A); return NULL; } else { return (PyObject *)A; } } -Py::Object -_png_module::read_png_float(const Py::Tuple& args) -{ - args.verify_length(1); - return Py::asObject(_read_png(args[0], true)); -} +const char *Py_read_png_float__doc__ = "read_png_float(file)"; -Py::Object -_png_module::read_png_uint8(const Py::Tuple& args) +static PyObject *Py_read_png_float(PyObject *self, PyObject *args, PyObject *kwds) { - throw Py::RuntimeError("read_png_uint8 is deprecated. Use read_png_int instead."); + return _read_png(args, true); } -Py::Object -_png_module::read_png_int(const Py::Tuple& args) +const char *Py_read_png_int__doc__ = "read_png_int(file)"; + +static PyObject *Py_read_png_int(PyObject *self, PyObject *args, PyObject *kwds) { - args.verify_length(1); - return Py::asObject(_read_png(args[0], false)); + return _read_png(args, false); } -PyMODINIT_FUNC +const char *Py_read_png__doc__ = "read_png(file)"; + +static PyMethodDef module_methods[] = { + {"write_png", (PyCFunction)Py_write_png, METH_VARARGS|METH_KEYWORDS, Py_write_png__doc__}, + {"read_png", (PyCFunction)Py_read_png_float, METH_O, Py_read_png__doc__}, + {"read_png_float", (PyCFunction)Py_read_png_float, METH_O, Py_read_png_float__doc__}, + {"read_png_int", (PyCFunction)Py_read_png_int, METH_O, Py_read_png_int__doc__}, + {NULL} +}; + +extern "C" { + + struct module_state + { + /* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif + }; + #if PY3K -PyInit__png(void) + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_png", + NULL, + sizeof(struct module_state), + module_methods, + NULL, + NULL, + NULL, + NULL + }; + +#define INITERROR return NULL + + PyMODINIT_FUNC PyInit__png(void) + #else -init_png(void) +#define INITERROR return + + PyMODINIT_FUNC init_png(void) #endif -{ - import_array(); - static _png_module* _png = NULL; - _png = new _png_module; + { + PyObject *m; #if PY3K - return _png->module().ptr(); + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("_png", module_methods, NULL); #endif + + if (m == NULL) { + INITERROR; + } + + import_array(); + +#if PY3K + return m; +#endif + } } diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 60fe2a0e3a40..827e0db0deb0 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -17,8 +17,8 @@ #include #include "agg_basics.h" -#include "_backend_agg.h" -#include "agg_py_transforms.h" +#include "_backend_agg_wrapper.h" +#include "py_converters.h" extern "C" { @@ -42,103 +42,89 @@ extern "C" typedef struct { - PyObject_HEAD - Tcl_Interp* interp; + PyObject_HEAD; + Tcl_Interp *interp; } TkappObject; -static int -PyAggImagePhoto(ClientData clientdata, Tcl_Interp* interp, - int argc, char **argv) +static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv) { Tk_PhotoHandle photo; Tk_PhotoImageBlock block; - PyObject* aggo; + PyObject *aggo; // vars for blitting - PyObject* bboxo; + PyObject *bboxo; size_t aggl, bboxl; bool has_bbox; agg::int8u *destbuffer; - double l, b, r, t; int destx, desty, destwidth, destheight, deststride; //unsigned long tmp_ptr; long mode; long nval; - if (Tk_MainWindow(interp) == NULL) - { + if (Tk_MainWindow(interp) == NULL) { // Will throw a _tkinter.TclError with "this isn't a Tk application" return TCL_ERROR; } - if (argc != 5) - { - Tcl_AppendResult(interp, "usage: ", argv[0], - " destPhoto srcImage", (char *) NULL); + if (argc != 5) { + Tcl_AppendResult(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); return TCL_ERROR; } /* get Tcl PhotoImage handle */ photo = Tk_FindPhoto(interp, argv[1]); - if (photo == NULL) - { - Tcl_AppendResult(interp, "destination photo must exist", (char *) NULL); + if (photo == NULL) { + Tcl_AppendResult(interp, "destination photo must exist", (char *)NULL); return TCL_ERROR; } /* get array (or object that can be converted to array) pointer */ - if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) - { - Tcl_AppendResult(interp, "error casting pointer", (char *) NULL); + if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) { + Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } - aggo = (PyObject*)aggl; - //aggo = (PyObject*)atol(argv[2]); - - //std::stringstream agg_ptr_ss; - //agg_ptr_ss.str(argv[2]); - //agg_ptr_ss >> tmp_ptr; - //aggo = (PyObject*)tmp_ptr; - RendererAgg *aggRenderer = (RendererAgg *)aggo; + aggo = (PyObject *)aggl; + + // TODO: This is really brittle and will break when RendererAgg + // comes in multiple flavors + RendererAgg *aggRenderer = ((PyRendererAgg *)(aggo))->x; int srcheight = (int)aggRenderer->get_height(); /* XXX insert aggRenderer type check */ /* get array mode (0=mono, 1=rgb, 2=rgba) */ mode = atol(argv[3]); - if ((mode != 0) && (mode != 1) && (mode != 2)) - { - Tcl_AppendResult(interp, "illegal image mode", (char *) NULL); + if ((mode != 0) && (mode != 1) && (mode != 2)) { + Tcl_AppendResult(interp, "illegal image mode", (char *)NULL); return TCL_ERROR; } /* check for bbox/blitting */ - if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) - { - Tcl_AppendResult(interp, "error casting pointer", (char *) NULL); + if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) { + Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } - bboxo = (PyObject*)bboxl; - - //bboxo = (PyObject*)atol(argv[4]); - //std::stringstream bbox_ptr_ss; - //bbox_ptr_ss.str(argv[4]); - //bbox_ptr_ss >> tmp_ptr; - //bboxo = (PyObject*)tmp_ptr; - if (py_convert_bbox(bboxo, l, b, r, t)) - { + bboxo = (PyObject *)bboxl; + + if (bboxo != NULL && bboxo != Py_None) { + agg::rect_d rect; + if (!convert_rect(bboxo, &rect)) { + return TCL_ERROR; + } + has_bbox = true; - destx = (int)l; - desty = srcheight - (int)t; - destwidth = (int)(r - l); - destheight = (int)(t - b); + destx = (int)rect.x1; + desty = srcheight - (int)rect.y2; + destwidth = (int)(rect.x2 - rect.x1); + destheight = (int)(rect.y2 - rect.y1); deststride = 4 * destwidth; - destbuffer = new agg::int8u[deststride*destheight]; - if (destbuffer == NULL) - { - throw Py::MemoryError("_tkagg could not allocate memory for destbuffer"); + destbuffer = new agg::int8u[deststride * destheight]; + if (destbuffer == NULL) { + Tcl_AppendResult(interp, "could not allocate memory", (char *)NULL); + return TCL_ERROR; } agg::rendering_buffer destrbuf; @@ -146,12 +132,9 @@ PyAggImagePhoto(ClientData clientdata, Tcl_Interp* interp, pixfmt destpf(destrbuf); renderer_base destrb(destpf); - agg::rect_base region(destx, desty, (int)r, srcheight - (int)b); - destrb.copy_from(aggRenderer->renderingBuffer, ®ion, - -destx, -desty); - } - else - { + agg::rect_base region(destx, desty, (int)rect.x2, srcheight - (int)rect.y1); + destrb.copy_from(aggRenderer->renderingBuffer, ®ion, -destx, -desty); + } else { has_bbox = false; destbuffer = NULL; destx = desty = destwidth = destheight = deststride = 0; @@ -159,47 +142,38 @@ PyAggImagePhoto(ClientData clientdata, Tcl_Interp* interp, /* setup tkblock */ block.pixelSize = 1; - if (mode == 0) - { + if (mode == 0) { block.offset[0] = block.offset[1] = block.offset[2] = 0; nval = 1; - } - else - { + } else { block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; - if (mode == 1) - { + if (mode == 1) { block.offset[3] = 0; block.pixelSize = 3; nval = 3; - } - else - { + } else { block.offset[3] = 3; block.pixelSize = 4; nval = 4; } } - if (has_bbox) - { - block.width = destwidth; + if (has_bbox) { + block.width = destwidth; block.height = destheight; block.pitch = deststride; block.pixelPtr = destbuffer; Tk_PhotoPutBlock(photo, &block, destx, desty, destwidth, destheight); - delete [] destbuffer; + delete[] destbuffer; - } - else - { - block.width = aggRenderer->get_width(); + } else { + block.width = aggRenderer->get_width(); block.height = aggRenderer->get_height(); - block.pitch = block.width * nval; - block.pixelPtr = aggRenderer->pixBuffer; + block.pitch = (int)block.width * nval; + block.pixelPtr = aggRenderer->pixBuffer; /* Clear current contents */ Tk_PhotoBlank(photo); @@ -210,75 +184,60 @@ PyAggImagePhoto(ClientData clientdata, Tcl_Interp* interp, return TCL_OK; } - -static PyObject * -_pyobj_addr(PyObject* self, PyObject* args) +static PyObject *_pyobj_addr(PyObject *self, PyObject *args) { PyObject *pyobj; - if (!PyArg_ParseTuple(args, "O", &pyobj)) - { + if (!PyArg_ParseTuple(args, "O", &pyobj)) { return NULL; } - return Py_BuildValue("n", (Py_ssize_t) pyobj); + return Py_BuildValue("n", (Py_ssize_t)pyobj); } -static PyObject* -_tkinit(PyObject* self, PyObject* args) +static PyObject *_tkinit(PyObject *self, PyObject *args) { - Tcl_Interp* interp; - TkappObject* app; + Tcl_Interp *interp; + TkappObject *app; Py_ssize_t arg; int is_interp; - if (!PyArg_ParseTuple(args, "ni", &arg, &is_interp)) - { + if (!PyArg_ParseTuple(args, "ni", &arg, &is_interp)) { return NULL; } - if (is_interp) - { - interp = (Tcl_Interp*) arg; - } - else - { + if (is_interp) { + interp = (Tcl_Interp *)arg; + } else { /* Do it the hard way. This will break if the TkappObject layout changes */ - app = (TkappObject*) arg; + app = (TkappObject *)arg; interp = app->interp; } /* This will bomb if interp is invalid... */ - Tcl_CreateCommand(interp, "PyAggImagePhoto", - (Tcl_CmdProc *) PyAggImagePhoto, - (ClientData) 0, (Tcl_CmdDeleteProc*) NULL); + Tcl_CreateCommand(interp, + "PyAggImagePhoto", + (Tcl_CmdProc *)PyAggImagePhoto, + (ClientData)0, + (Tcl_CmdDeleteProc *)NULL); Py_INCREF(Py_None); return Py_None; } -static PyMethodDef functions[] = -{ +static PyMethodDef functions[] = { /* Tkinter interface stuff */ - {"_pyobj_addr", (PyCFunction)_pyobj_addr, 1}, - {"tkinit", (PyCFunction)_tkinit, 1}, - {NULL, NULL} /* sentinel */ + { "_pyobj_addr", (PyCFunction)_pyobj_addr, 1 }, { "tkinit", (PyCFunction)_tkinit, 1 }, + { NULL, NULL } /* sentinel */ }; #if PY3K -static PyModuleDef _tkagg_module = { - PyModuleDef_HEAD_INIT, - "_tkagg", - "", - -1, - functions, - NULL, NULL, NULL, NULL -}; +static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, + NULL, NULL, NULL, NULL }; -PyMODINIT_FUNC -PyInit__tkagg(void) +PyMODINIT_FUNC PyInit__tkagg(void) { - PyObject* m; + PyObject *m; m = PyModule_Create(&_tkagg_module); @@ -287,12 +246,10 @@ PyInit__tkagg(void) return m; } #else -PyMODINIT_FUNC -init_tkagg(void) +PyMODINIT_FUNC init_tkagg(void) { import_array(); Py_InitModule("_tkagg", functions); } #endif - diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp index 42f4b4ec4594..e0aa4611d28d 100644 --- a/src/_ttconv.cpp +++ b/src/_ttconv.cpp @@ -10,23 +10,19 @@ #include #include "ttconv/pprdrv.h" +#include "py_exceptions.h" #include #include -class PythonExceptionOccurred -{ - -}; - /** * An implementation of TTStreamWriter that writes to a Python * file-like object. */ class PythonFileWriter : public TTStreamWriter { - PyObject* _write_method; + PyObject *_write_method; -public: + public: PythonFileWriter() { _write_method = NULL; @@ -37,41 +33,38 @@ class PythonFileWriter : public TTStreamWriter Py_XDECREF(_write_method); } - void set(PyObject* write_method) + void set(PyObject *write_method) { Py_XDECREF(_write_method); _write_method = write_method; Py_XINCREF(_write_method); } - virtual void write(const char* a) + virtual void write(const char *a) { - PyObject* result = NULL; - if (_write_method) - { - PyObject* decoded = NULL; + PyObject *result = NULL; + if (_write_method) { + PyObject *decoded = NULL; decoded = PyUnicode_DecodeLatin1(a, strlen(a), ""); if (decoded == NULL) { - throw PythonExceptionOccurred(); + throw py::exception(); } - result = PyObject_CallFunction(_write_method, "O", decoded); + result = PyObject_CallFunction(_write_method, (char *)"O", decoded); Py_DECREF(decoded); - if (! result) - { - throw PythonExceptionOccurred(); + if (!result) { + throw py::exception(); } Py_DECREF(result); } } }; -int fileobject_to_PythonFileWriter(PyObject* object, void* address) +int fileobject_to_PythonFileWriter(PyObject *object, void *address) { - PythonFileWriter* file_writer = (PythonFileWriter*)address; + PythonFileWriter *file_writer = (PythonFileWriter *)address; - PyObject* write_method = PyObject_GetAttrString(object, "write"); - if (write_method == NULL || ! PyCallable_Check(write_method)) - { + PyObject *write_method = PyObject_GetAttrString(object, "write"); + if (write_method == NULL || !PyCallable_Check(write_method)) { PyErr_SetString(PyExc_TypeError, "Expected a file-like object with a write method."); return 0; } @@ -82,30 +75,27 @@ int fileobject_to_PythonFileWriter(PyObject* object, void* address) return 1; } -int pyiterable_to_vector_int(PyObject* object, void* address) +int pyiterable_to_vector_int(PyObject *object, void *address) { - std::vector* result = (std::vector*)address; + std::vector *result = (std::vector *)address; - PyObject* iterator = PyObject_GetIter(object); - if (! iterator) - { + PyObject *iterator = PyObject_GetIter(object); + if (!iterator) { return 0; } - PyObject* item; - while ((item = PyIter_Next(iterator))) - { - #if PY3K + PyObject *item; + while ((item = PyIter_Next(iterator))) { +#if PY3K long value = PyLong_AsLong(item); - #else +#else long value = PyInt_AsLong(item); - #endif +#endif Py_DECREF(item); - if (value == -1 && PyErr_Occurred()) - { + if (value == -1 && PyErr_Occurred()) { return 0; } - result->push_back(value); + result->push_back((int)value); } Py_DECREF(iterator); @@ -113,38 +103,32 @@ int pyiterable_to_vector_int(PyObject* object, void* address) return 1; } -static PyObject* -convert_ttf_to_ps(PyObject* self, PyObject* args, PyObject* kwds) +static PyObject *convert_ttf_to_ps(PyObject *self, PyObject *args, PyObject *kwds) { - const char* filename; - PythonFileWriter output; - int fonttype; - std::vector glyph_ids; - - static const char *kwlist[] = - { - "filename", "output", "fonttype", "glyph_ids", NULL - }; - if (! PyArg_ParseTupleAndKeywords - (args, kwds, - #if PY_MAJOR_VERSION == 3 - "yO&i|O&:convert_ttf_to_ps", - #else - "sO&i|O&:convert_ttf_to_ps", - #endif - (char**)kwlist, - &filename, - fileobject_to_PythonFileWriter, - &output, - &fonttype, - pyiterable_to_vector_int, - &glyph_ids)) - { + const char *filename; + PythonFileWriter output; + int fonttype; + std::vector glyph_ids; + + static const char *kwlist[] = { "filename", "output", "fonttype", "glyph_ids", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, + kwds, +#if PY_MAJOR_VERSION == 3 + "yO&i|O&:convert_ttf_to_ps", +#else + "sO&i|O&:convert_ttf_to_ps", +#endif + (char **)kwlist, + &filename, + fileobject_to_PythonFileWriter, + &output, + &fonttype, + pyiterable_to_vector_int, + &glyph_ids)) { return NULL; } - if (fonttype != 3 && fonttype != 42) - { + if (fonttype != 3 && fonttype != 42) { PyErr_SetString(PyExc_ValueError, "fonttype must be either 3 (raw Postscript) or 42 " "(embedded Truetype)"); @@ -155,12 +139,12 @@ convert_ttf_to_ps(PyObject* self, PyObject* args, PyObject* kwds) { insert_ttfont(filename, output, (font_type_enum)fonttype, glyph_ids); } - catch (TTException& e) + catch (TTException &e) { PyErr_SetString(PyExc_RuntimeError, e.getMessage()); return NULL; } - catch (PythonExceptionOccurred&) + catch (const py::exception &) { return NULL; } @@ -176,58 +160,53 @@ convert_ttf_to_ps(PyObject* self, PyObject* args, PyObject* kwds) class PythonDictionaryCallback : public TTDictionaryCallback { - PyObject* _dict; + PyObject *_dict; -public: - PythonDictionaryCallback(PyObject* dict) + public: + PythonDictionaryCallback(PyObject *dict) { _dict = dict; } - virtual void add_pair(const char* a, const char* b) + virtual void add_pair(const char *a, const char *b) { assert(a != NULL); assert(b != NULL); - PyObject* value = PyBytes_FromString(b); - if (!value) - { - throw PythonExceptionOccurred(); + PyObject *value = PyBytes_FromString(b); + if (!value) { + throw py::exception(); } - if (PyDict_SetItemString(_dict, a, value)) - { + if (PyDict_SetItemString(_dict, a, value)) { Py_DECREF(value); - throw PythonExceptionOccurred(); + throw py::exception(); } Py_DECREF(value); } }; -static PyObject* -py_get_pdf_charprocs(PyObject* self, PyObject* args, PyObject* kwds) +static PyObject *py_get_pdf_charprocs(PyObject *self, PyObject *args, PyObject *kwds) { - const char* filename; - std::vector glyph_ids; - PyObject* result; + const char *filename; + std::vector glyph_ids; + PyObject *result; static const char *kwlist[] = { "filename", "glyph_ids", NULL }; - if (! PyArg_ParseTupleAndKeywords - (args, kwds, - #if PY_MAJOR_VERSION == 3 - "y|O&:get_pdf_charprocs", - #else - "s|O&:get_pdf_charprocs", - #endif - (char **)kwlist, - &filename, - pyiterable_to_vector_int, - &glyph_ids)) - { + if (!PyArg_ParseTupleAndKeywords(args, + kwds, +#if PY_MAJOR_VERSION == 3 + "y|O&:get_pdf_charprocs", +#else + "s|O&:get_pdf_charprocs", +#endif + (char **)kwlist, + &filename, + pyiterable_to_vector_int, + &glyph_ids)) { return NULL; } result = PyDict_New(); - if (!result) - { + if (!result) { return NULL; } @@ -237,13 +216,13 @@ py_get_pdf_charprocs(PyObject* self, PyObject* args, PyObject* kwds) { ::get_pdf_charprocs(filename, glyph_ids, dict); } - catch (TTException& e) + catch (TTException &e) { Py_DECREF(result); PyErr_SetString(PyExc_RuntimeError, e.getMessage()); return NULL; } - catch (PythonExceptionOccurred&) + catch (const py::exception &) { Py_DECREF(result); return NULL; @@ -295,7 +274,7 @@ static PyMethodDef ttconv_methods[] = {0, 0, 0, 0} /* Sentinel */ }; -static const char* module_docstring = +static const char *module_docstring = "Module to handle converting and subsetting TrueType " "fonts to Postscript Type 3, Postscript Type 42 and " "Pdf Type 3 fonts."; diff --git a/src/agg_py_path_iterator.h b/src/agg_py_path_iterator.h deleted file mode 100644 index 07aeb4325cc0..000000000000 --- a/src/agg_py_path_iterator.h +++ /dev/null @@ -1,137 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#ifndef __AGG_PY_PATH_ITERATOR_H__ -#define __AGG_PY_PATH_ITERATOR_H__ - -#include "CXX/Objects.hxx" -#include "numpy/arrayobject.h" -#include "agg_path_storage.h" - -/* - This file contains a vertex source to adapt Python Numpy arrays to - Agg paths. It works as an iterator, and converts on-the-fly without - the need for a full copy of the data. - */ - -/************************************************************ - PathIterator acts as a bridge between Numpy and Agg. Given a pair of - Numpy arrays, vertices and codes, it iterates over those vertices and - codes, using the standard Agg vertex source interface: - - unsigned vertex(double* x, double* y) - */ -class PathIterator -{ - /* We hold references to the Python objects, not just the - underlying data arrays, so that Python reference counting can - work. - */ - Py::Object m_vertices; - Py::Object m_codes; - - size_t m_iterator; - size_t m_total_vertices; - - /* This class doesn't actually do any simplification, but we - store the value here, since it is obtained from the Python object. - */ - bool m_should_simplify; - double m_simplify_threshold; - -public: - /* path_obj is an instance of the class Path as defined in path.py */ - inline PathIterator(const Py::Object& path_obj) : - m_vertices(), m_codes(), m_iterator(0), m_should_simplify(false), - m_simplify_threshold(1.0 / 9.0) - { - Py::Object vertices_obj = path_obj.getAttr("vertices"); - Py::Object codes_obj = path_obj.getAttr("codes"); - Py::Object should_simplify_obj = path_obj.getAttr("should_simplify"); - Py::Object simplify_threshold_obj = path_obj.getAttr("simplify_threshold"); - - PyObject* vertices_arr = PyArray_FromObject(vertices_obj.ptr(), NPY_DOUBLE, 2, 2); - if (!vertices_arr) - { - throw Py::ValueError("Invalid vertices array."); - } - - m_vertices = Py::Object(vertices_arr, true); - if (PyArray_DIM((PyArrayObject*)m_vertices.ptr(), 1) != 2) - { - throw Py::ValueError("Invalid vertices array."); - } - - if (codes_obj.ptr() != Py_None) - { - PyObject* codes_arr = PyArray_FromObject(codes_obj.ptr(), NPY_UINT8, 1, 1); - - if (!codes_arr) - { - throw Py::ValueError("Invalid codes array."); - } - - m_codes = Py::Object(codes_arr, true); - if (PyArray_DIM((PyArrayObject*)m_codes.ptr(), 0) != - PyArray_DIM((PyArrayObject*)m_vertices.ptr(), 0)) - { - throw Py::ValueError("Codes array is wrong length"); - } - } - - m_should_simplify = should_simplify_obj.isTrue(); - m_total_vertices = PyArray_DIM((PyArrayObject*)m_vertices.ptr(), 0); - m_simplify_threshold = Py::Float(simplify_threshold_obj); - } - - ~PathIterator() - { - - } - - inline unsigned vertex(double* x, double* y) - { - if (m_iterator >= m_total_vertices) return agg::path_cmd_stop; - - const size_t idx = m_iterator++; - - char* pair = (char*)PyArray_GETPTR2((PyArrayObject*)m_vertices.ptr(), idx, 0); - *x = *(double*)pair; - *y = *(double*)(pair + PyArray_STRIDE((PyArrayObject*)m_vertices.ptr(), 1)); - - if (!m_codes.isNone()) - { - return (unsigned)(*(char *)PyArray_GETPTR1((PyArrayObject*)m_codes.ptr(), idx)); - } - else - { - return idx == 0 ? agg::path_cmd_move_to : agg::path_cmd_line_to; - } - } - - inline void rewind(unsigned path_id) - { - m_iterator = path_id; - } - - inline unsigned total_vertices() - { - return m_total_vertices; - } - - inline bool should_simplify() - { - return m_should_simplify; - } - - inline double simplify_threshold() - { - return m_simplify_threshold; - } - - inline bool has_curves() - { - return !m_codes.isNone(); - } -}; - -#endif // __AGG_PY_PATH_ITERATOR_H__ diff --git a/src/agg_py_transforms.cpp b/src/agg_py_transforms.cpp deleted file mode 100644 index c9a1b501a7df..000000000000 --- a/src/agg_py_transforms.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#include - -#define NO_IMPORT_ARRAY -#include "numpy/arrayobject.h" - -#include "CXX/Objects.hxx" -#include "agg_trans_affine.h" - -/** A helper function to convert from a Numpy affine transformation matrix - * to an agg::trans_affine. If errors = false then an Identity transform is returned. - */ -agg::trans_affine -py_to_agg_transformation_matrix(PyObject* obj, bool errors = true) -{ - PyArrayObject* matrix = NULL; - - /** If None either raise a TypeError or return an agg identity transform. */ - if (obj == Py_None) - { - if (errors) - { - throw Py::TypeError("Cannot convert None to an affine transform."); - } - - return agg::trans_affine(); - } - - /** Try turning the object into an affine transform matrix. */ - try - { - matrix = (PyArrayObject*) PyArray_FromObject(obj, NPY_DOUBLE, 2, 2); - if (!matrix) { - PyErr_Clear(); - throw std::exception(); - } - } - catch (...) - { - Py_XDECREF(matrix); - if (errors) - { - throw Py::TypeError("Unable to get an affine transform matrix from the given object."); - } - - return agg::trans_affine(); - } - - /** Try turning the matrix into an agg transform. */ - try - { - if (PyArray_NDIM(matrix) == 2 || PyArray_DIM(matrix, 0) == 3 || PyArray_DIM(matrix, 1) == 3) - { - size_t stride0 = PyArray_STRIDE(matrix, 0); - size_t stride1 = PyArray_STRIDE(matrix, 1); - char* row0 = PyArray_BYTES(matrix); - char* row1 = row0 + stride0; - - double a = *(double*)(row0); - row0 += stride1; - double c = *(double*)(row0); - row0 += stride1; - double e = *(double*)(row0); - - double b = *(double*)(row1); - row1 += stride1; - double d = *(double*)(row1); - row1 += stride1; - double f = *(double*)(row1); - - Py_XDECREF(matrix); - - return agg::trans_affine(a, b, c, d, e, f); - } - - throw std::exception(); - } - catch (...) - { - if (errors) - { - Py_XDECREF(matrix); - throw Py::TypeError("Invalid affine transformation matrix."); - } - } - - Py_XDECREF(matrix); - return agg::trans_affine(); -} - -bool -py_convert_bbox(PyObject* bbox_obj, double& l, double& b, double& r, double& t) -{ - PyArrayObject* bbox = NULL; - - if (bbox_obj == Py_None) - return false; - - try - { - bbox = (PyArrayObject*) PyArray_FromObject(bbox_obj, NPY_DOUBLE, 2, 2); - - if (!bbox || PyArray_NDIM(bbox) != 2 || PyArray_DIM(bbox, 0) != 2 || PyArray_DIM(bbox, 1) != 2) - { - throw Py::TypeError - ("Expected a bbox array"); - } - - l = *(double*)PyArray_GETPTR2(bbox, 0, 0); - b = *(double*)PyArray_GETPTR2(bbox, 0, 1); - r = *(double*)PyArray_GETPTR2(bbox, 1, 0); - t = *(double*)PyArray_GETPTR2(bbox, 1, 1); - - Py_XDECREF(bbox); - bbox = NULL; - return true; - } - catch (...) - { - Py_XDECREF(bbox); - bbox = NULL; - throw; - } - - return false; -} diff --git a/src/agg_py_transforms.h b/src/agg_py_transforms.h deleted file mode 100644 index db66a2ff4d79..000000000000 --- a/src/agg_py_transforms.h +++ /dev/null @@ -1,17 +0,0 @@ -/* -*- mode: c++; c-basic-offset: 4 -*- */ - -#ifndef __AGG_PY_TRANSFORMS_H__ -#define __AGG_PY_TRANSFORMS_H__ - -#include "agg_trans_affine.h" - -/** A helper function to convert from a Numpy affine transformation matrix - * to an agg::trans_affine. - */ -agg::trans_affine -py_to_agg_transformation_matrix(PyObject* obj, bool errors = true); - -bool -py_convert_bbox(PyObject* bbox_obj, double& l, double& b, double& r, double& t); - -#endif // __AGG_PY_TRANSFORMS_H__ diff --git a/src/array.h b/src/array.h new file mode 100644 index 000000000000..349b900066ab --- /dev/null +++ b/src/array.h @@ -0,0 +1,80 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +/* Utilities to create scalars and empty arrays that behave like the + Numpy array wrappers in numpy_cpp.h */ + +#ifndef _SCALAR_H_ +#define _SCALAR_H_ + +namespace array +{ + +template +class scalar +{ + public: + T m_value; + + scalar(const T value) : m_value(value) + { + } + + T &operator()(int i, int j = 0, int k = 0) + { + return m_value; + } + + const T &operator()(int i, int j = 0, int k = 0) const + { + return m_value; + } + + int dim(size_t i) + { + return 1; + } + + size_t size() + { + return 1; + } +}; + +template +class empty +{ + public: + typedef empty sub_t; + + empty() + { + } + + T &operator()(int i, int j = 0, int k = 0) + { + throw "Accessed empty array"; + } + + const T &operator()(int i, int j = 0, int k = 0) const + { + throw "Accessed empty array"; + } + + sub_t operator[](int i) const + { + return empty(); + } + + int dim(size_t i) const + { + return 0; + } + + size_t size() const + { + return 0; + } +}; +} + +#endif diff --git a/src/file_compat.h b/src/file_compat.h index 2f62965f26f5..a8f04eaeddca 100644 --- a/src/file_compat.h +++ b/src/file_compat.h @@ -10,12 +10,11 @@ #ifdef __cplusplus extern "C" { #endif - #if defined(_MSC_VER) && defined(_WIN64) && (_MSC_VER > 1400) #include - #define npy_fseek _fseeki64 - #define npy_ftell _ftelli64 - #define npy_lseek _lseeki64 + #define mpl_fseek _fseeki64 + #define mpl_ftell _ftelli64 + #define mpl_lseek _lseeki64 #define mpl_off_t npy_int64 #if NPY_SIZEOF_INT == 8 @@ -28,9 +27,9 @@ extern "C" { #error Unsupported size for type off_t #endif #else - #define npy_fseek fseek - #define npy_ftell ftell - #define npy_lseek lseek + #define mpl_fseek fseek + #define mpl_ftell ftell + #define mpl_lseek lseek #define mpl_off_t off_t #if NPY_SIZEOF_INT == NPY_SIZEOF_SHORT @@ -54,8 +53,7 @@ extern "C" { /* * Get a FILE* handle to the file represented by the Python object */ -static NPY_INLINE FILE* -mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) +static NPY_INLINE FILE *mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) { int fd, fd2; PyObject *ret, *os; @@ -63,7 +61,7 @@ mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) FILE *handle; /* Flush first to ensure things end up in the file in the correct order */ - ret = PyObject_CallMethod(file, "flush", ""); + ret = PyObject_CallMethod(file, (char *)"flush", (char *)""); if (ret == NULL) { return NULL; } @@ -79,7 +77,7 @@ mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) if (os == NULL) { return NULL; } - ret = PyObject_CallMethod(os, "dup", "i", fd); + ret = PyObject_CallMethod(os, (char *)"dup", (char *)"i", fd); Py_DECREF(os); if (ret == NULL) { return NULL; @@ -87,26 +85,25 @@ mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) fd2 = PyNumber_AsSsize_t(ret, NULL); Py_DECREF(ret); - /* Convert to FILE* handle */ +/* Convert to FILE* handle */ #ifdef _WIN32 handle = _fdopen(fd2, mode); #else handle = fdopen(fd2, mode); #endif if (handle == NULL) { - PyErr_SetString(PyExc_IOError, - "Getting a FILE* from a Python file object failed"); + PyErr_SetString(PyExc_IOError, "Getting a FILE* from a Python file object failed"); } /* Record the original raw file handle position */ - *orig_pos = npy_ftell(handle); + *orig_pos = mpl_ftell(handle); if (*orig_pos == -1) { // handle is a stream, so we don't have to worry about this return handle; } /* Seek raw handle to the Python-side position */ - ret = PyObject_CallMethod(file, "tell", ""); + ret = PyObject_CallMethod(file, (char *)"tell", (char *)""); if (ret == NULL) { fclose(handle); return NULL; @@ -117,7 +114,7 @@ mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) fclose(handle); return NULL; } - if (npy_fseek(handle, pos, SEEK_SET) == -1) { + if (mpl_fseek(handle, pos, SEEK_SET) == -1) { PyErr_SetString(PyExc_IOError, "seeking file failed"); return NULL; } @@ -127,14 +124,13 @@ mpl_PyFile_Dup(PyObject *file, char *mode, mpl_off_t *orig_pos) /* * Close the dup-ed file handle, and seek the Python one to the current position */ -static NPY_INLINE int -mpl_PyFile_DupClose(PyObject *file, FILE* handle, mpl_off_t orig_pos) +static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_t orig_pos) { int fd; PyObject *ret; mpl_off_t position; - position = npy_ftell(handle); + position = mpl_ftell(handle); /* Close the FILE* handle */ fclose(handle); @@ -145,14 +141,14 @@ mpl_PyFile_DupClose(PyObject *file, FILE* handle, mpl_off_t orig_pos) if (fd == -1) { return -1; } - if (npy_lseek(fd, orig_pos, SEEK_SET) != -1) { + if (mpl_lseek(fd, orig_pos, SEEK_SET) != -1) { if (position == -1) { PyErr_SetString(PyExc_IOError, "obtaining file position failed"); return -1; } /* Seek Python-side handle to the FILE* handle position */ - ret = PyObject_CallMethod(file, "seek", MPL_OFF_T_PYFMT "i", position, 0); + ret = PyObject_CallMethod(file, (char *)"seek", (char *)(MPL_OFF_T_PYFMT "i"), position, 0); if (ret == NULL) { return -1; } @@ -161,8 +157,7 @@ mpl_PyFile_DupClose(PyObject *file, FILE* handle, mpl_off_t orig_pos) return 0; } -static NPY_INLINE int -mpl_PyFile_Check(PyObject *file) +static NPY_INLINE int mpl_PyFile_Check(PyObject *file) { int fd; fd = PyObject_AsFileDescriptor(file); @@ -175,29 +170,39 @@ mpl_PyFile_Check(PyObject *file) #else -#define mpl_PyFile_Dup(file, mode, orig_pos_p) PyFile_AsFile(file) -#define mpl_PyFile_DupClose(file, handle, orig_pos) (0) -#define mpl_PyFile_Check PyFile_Check +static NPY_INLINE FILE *mpl_PyFile_Dup(PyObject *file, const char *mode, mpl_off_t *orig_pos) +{ + return PyFile_AsFile(file); +} + +static NPY_INLINE int mpl_PyFile_DupClose(PyObject *file, FILE *handle, mpl_off_t orig_pos) +{ + // deliberately nothing + return 0; +} + +static NPY_INLINE int mpl_PyFile_Check(PyObject *file) +{ + return PyFile_Check(file); +} #endif -static NPY_INLINE PyObject* -mpl_PyFile_OpenFile(PyObject *filename, const char *mode) +static NPY_INLINE PyObject *mpl_PyFile_OpenFile(PyObject *filename, const char *mode) { PyObject *open; open = PyDict_GetItemString(PyEval_GetBuiltins(), "open"); if (open == NULL) { return NULL; } - return PyObject_CallFunction(open, (char*)"Os", filename, mode); + return PyObject_CallFunction(open, (char *)"Os", filename, mode); } -static NPY_INLINE int -mpl_PyFile_CloseFile(PyObject *file) +static NPY_INLINE int mpl_PyFile_CloseFile(PyObject *file) { PyObject *ret; - ret = PyObject_CallMethod(file, (char*)"close", NULL); + ret = PyObject_CallMethod(file, (char *)"close", NULL); if (ret == NULL) { return -1; } diff --git a/src/ft2font.cpp b/src/ft2font.cpp index aa5ba19038dc..e7cebfaa6864 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -1,19 +1,9 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ +#define NO_IMPORT_ARRAY + #include "ft2font.h" #include "mplutils.h" -#include - -#include "file_compat.h" - -#include "numpy/arrayobject.h" - -/* - By definition, FT_FIXED as 2 16bit values stored in a single long. - We cast to long to ensure the correct Py::Int convertor is called - */ -#define FIXED_MAJOR(val) (long) ((val & 0xffff000) >> 16) -#define FIXED_MINOR(val) (long) (val & 0xffff) /** To improve the hinting of the fonts, this code uses a hack @@ -40,80 +30,56 @@ you have disabled hints). */ - FT_Library _ft2Library; -FT2Image::FT2Image(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds) : - Py::PythonClass< FT2Image >(self, args, kwds), - _isDirty(true), - _buffer(NULL), - _width(0), _height(0) +FT2Image::FT2Image() : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) { - _VERBOSE("FT2Image::FT2Image"); - - args.verify_length(2); - int width = Py::Int(args[0]); - int height = Py::Int(args[1]); - - resize(width, height); } -FT2Image::~FT2Image() { - delete [] _buffer; - _buffer = NULL; +FT2Image::FT2Image(unsigned long width, unsigned long height) + : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0) +{ + resize(width, height); } -Py::PythonClassObject FT2Image::factory(int width, int height) +FT2Image::~FT2Image() { - Py::Callable class_type(type()); - Py::Tuple args(2); - args[0] = Py::Int(width); - args[1] = Py::Int(height); - Py::PythonClassObject o = Py::PythonClassObject( - class_type.apply(args, Py::Dict())); - return o; + delete[] m_buffer; } -void -FT2Image::resize(long width, long height) +void FT2Image::resize(long width, long height) { - if (width < 0) - { + if (width < 0) { width = 1; } - if (height < 0) - { + if (height < 0) { height = 1; } size_t numBytes = width * height; - if ((unsigned long)width != _width || (unsigned long)height != _height) - { - if (numBytes > _width*_height) - { - delete [] _buffer; - _buffer = NULL; - _buffer = new unsigned char [numBytes]; + if ((unsigned long)width != m_width || (unsigned long)height != m_height) { + if (numBytes > m_width * m_height) { + delete[] m_buffer; + m_buffer = NULL; + m_buffer = new unsigned char[numBytes]; } - _width = (unsigned long)width; - _height = (unsigned long)height; + m_width = (unsigned long)width; + m_height = (unsigned long)height; } - memset(_buffer, 0, numBytes); + if (numBytes) { + memset(m_buffer, 0, numBytes); + } - _isDirty = true; + m_dirty = true; } -void -FT2Image::draw_bitmap(FT_Bitmap* bitmap, - FT_Int x, - FT_Int y) +void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y) { - _VERBOSE("FT2Image::draw_bitmap"); - FT_Int image_width = (FT_Int)_width; - FT_Int image_height = (FT_Int)_height; - FT_Int char_width = bitmap->width; + FT_Int image_width = (FT_Int)m_width; + FT_Int image_height = (FT_Int)m_height; + FT_Int char_width = bitmap->width; FT_Int char_height = bitmap->rows; FT_Int x1 = CLAMP(x, 0, image_width); @@ -125,18 +91,16 @@ FT2Image::draw_bitmap(FT_Bitmap* bitmap, FT_Int y_offset = y1 - MAX(0, -y); if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { - for (FT_Int i = y1; i < y2; ++i) - { - unsigned char* dst = _buffer + (i * image_width + x1); - unsigned char* src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start); + for (FT_Int i = y1; i < y2; ++i) { + unsigned char *dst = m_buffer + (i * image_width + x1); + unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start); for (FT_Int j = x1; j < x2; ++j, ++dst, ++src) *dst |= *src; } } else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { - for (FT_Int i = y1; i < y2; ++i) - { - unsigned char* dst = _buffer + (i * image_width + x1); - unsigned char* src = bitmap->buffer + ((i - y_offset) * bitmap->pitch); + for (FT_Int i = y1; i < y2; ++i) { + unsigned char *dst = m_buffer + (i * image_width + x1); + unsigned char *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch); for (FT_Int j = x1; j < x2; ++j, ++dst) { int x = (j - x1 + x_start); int val = *(src + (x >> 3)) & (1 << (7 - (x & 0x7))); @@ -144,409 +108,101 @@ FT2Image::draw_bitmap(FT_Bitmap* bitmap, } } } else { - throw Py::Exception("Unknown pixel mode"); + throw "Unknown pixel mode"; } - _isDirty = true; + m_dirty = true; } -void -FT2Image::write_bitmap(FILE *fh) const +void FT2Image::draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1) { - for (size_t i = 0; i < _height; i++) - { - for (size_t j = 0; j < _width; ++j) - { - if (_buffer[j + i*_width]) - { - fputc('#', fh); - } - else - { - fputc(' ', fh); - } - } - fputc('\n', fh); + if (x0 > m_width || x1 > m_width || y0 > m_height || y1 > m_height) { + throw "Rect coords outside image bounds"; } -} -char FT2Image::write_bitmap__doc__[] = - "write_bitmap(fname)\n" - "\n" - "Write the bitmap to file fname\n" - ; -Py::Object -FT2Image::py_write_bitmap(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::write_bitmap"); - PyObject *py_file; - FILE *fh; - mpl_off_t offset; - - args.verify_length(1); - - if ((py_file = mpl_PyFile_OpenFile(args[0].ptr(), (char *)"wb")) == NULL) { - throw Py::Exception(); + size_t top = y0 * m_width; + size_t bottom = y1 * m_width; + for (size_t i = x0; i < x1 + 1; ++i) { + m_buffer[i + top] = 255; + m_buffer[i + bottom] = 255; } - fh = mpl_PyFile_Dup(py_file, (char *)"wb", &offset); - - write_bitmap(fh); - - if (mpl_PyFile_DupClose(py_file, fh, offset)) - { - throw Py::Exception(); + for (size_t j = y0 + 1; j < y1; ++j) { + m_buffer[x0 + j * m_width] = 255; + m_buffer[x1 + j * m_width] = 255; } - mpl_PyFile_CloseFile(py_file); - Py_DECREF(py_file); - return Py::Object(); + m_dirty = true; } -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_write_bitmap) void -FT2Image::draw_rect(unsigned long x0, unsigned long y0, - unsigned long x1, unsigned long y1) -{ - if (x0 > _width || x1 > _width || - y0 > _height || y1 > _height) - { - throw Py::ValueError("Rect coords outside image bounds"); - } - - size_t top = y0 * _width; - size_t bottom = y1 * _width; - for (size_t i = x0; i < x1 + 1; ++i) - { - _buffer[i + top] = 255; - _buffer[i + bottom] = 255; - } - - for (size_t j = y0 + 1; j < y1; ++j) - { - _buffer[x0 + j*_width] = 255; - _buffer[x1 + j*_width] = 255; - } - - _isDirty = true; -} - -char FT2Image::draw_rect__doc__[] = - "draw_rect(x0, y0, x1, y1)\n" - "\n" - "Draw a rect to the image.\n" - "\n" - ; -Py::Object -FT2Image::py_draw_rect(const Py::Tuple & args) +FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1) { - _VERBOSE("FT2Image::draw_rect"); - - args.verify_length(4); + x0 = std::min(x0, m_width); + y0 = std::min(y0, m_height); + x1 = std::min(x1, m_width); + y1 = std::min(y1, m_height); - long x0 = Py::Int(args[0]); - long y0 = Py::Int(args[1]); - long x1 = Py::Int(args[2]); - long y1 = Py::Int(args[3]); - - draw_rect(x0, y0, x1, y1); - - return Py::Object(); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_draw_rect) - -void -FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, - unsigned long x1, unsigned long y1) -{ - x0 = std::min(x0, _width); - y0 = std::min(y0, _height); - x1 = std::min(x1, _width); - y1 = std::min(y1, _height); - - for (size_t j = y0; j < y1 + 1; j++) - { - for (size_t i = x0; i < x1 + 1; i++) - { - _buffer[i + j*_width] = 255; + for (size_t j = y0; j < y1 + 1; j++) { + for (size_t i = x0; i < x1 + 1; i++) { + m_buffer[i + j * m_width] = 255; } } - _isDirty = true; -} - -char FT2Image::draw_rect_filled__doc__[] = - "draw_rect_filled(x0, y0, x1, y1)\n" - "\n" - "Draw a filled rect to the image.\n" - "\n" - ; -Py::Object -FT2Image::py_draw_rect_filled(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::draw_rect_filled"); - - args.verify_length(4); - - long x0 = Py::Int(args[0]); - long y0 = Py::Int(args[1]); - long x1 = Py::Int(args[2]); - long y1 = Py::Int(args[3]); - - draw_rect_filled(x0, y0, x1, y1); - - return Py::Object(); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_draw_rect_filled) - -char FT2Image::as_str__doc__[] = - "s = image.as_str()\n" - "\n" - "Return the image buffer as a string\n" - "\n" - ; -Py::Object -FT2Image::py_as_str(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::as_str"); - args.verify_length(0); - - return Py::asObject - (PyBytes_FromStringAndSize((const char *)_buffer, _width*_height)); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_as_str) - -char FT2Image::as_rgba_str__doc__[] = - "s = image.as_rgba_str()\n" - "\n" - "Return the image buffer as a RGBA string\n" - "\n" - ; -Py::Object -FT2Image::py_as_rgba_str(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::as_str"); - args.verify_length(0); - - Py_ssize_t size = _width*_height*4; - PyObject* result = PyBytes_FromStringAndSize(NULL, size); - - unsigned char *src = _buffer; - unsigned char *src_end = src + (_width * _height); - unsigned char *dst = (unsigned char *)PyBytes_AS_STRING(result); - - while (src != src_end) - { - *dst++ = 0; - *dst++ = 0; - *dst++ = 0; - *dst++ = *src++; - } - - return Py::asObject(result); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_as_rgba_str) - -/* TODO: This could take a color as an argument, but for - now it defaults to black on white background */ -char FT2Image::as_rgb_str__doc__[] = - "s = image.as_rgb_str()\n" - "\n" - "Return the image buffer as a RGB string\n" - "\n" - ; -Py::Object -FT2Image::py_as_rgb_str(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::as_str"); - args.verify_length(0); - - Py_ssize_t size = _width*_height*3; - PyObject* result = PyBytes_FromStringAndSize(NULL, size); - - unsigned char *src = _buffer; - unsigned char *src_end = src + (_width * _height); - unsigned char *dst = (unsigned char *)PyBytes_AS_STRING(result); - - while (src != src_end) - { - unsigned char tmp = 255 - *src++; - *dst++ = tmp; - *dst++ = tmp; - *dst++ = tmp; - } - - return Py::asObject(result); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_as_rgb_str) - -char FT2Image::as_array__doc__[] = - "x = image.as_array()\n" - "\n" - "Return the image buffer as a width x height numpy array of ubyte \n" - "\n" - ; -Py::Object -FT2Image::py_as_array(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::as_array"); - args.verify_length(0); - - npy_intp dimensions[2]; - dimensions[0] = get_height(); //numrows - dimensions[1] = get_width(); //numcols - - - PyArrayObject *A = (PyArrayObject *) PyArray_SimpleNewFromData(2, dimensions, NPY_UBYTE, _buffer); - - return Py::asObject((PyObject*)A); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_as_array) - -Py::Object -FT2Image::py_get_width(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::get_width"); - args.verify_length(0); - - return Py::Int((long)get_width()); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_get_width) - -Py::Object -FT2Image::py_get_height(const Py::Tuple & args) -{ - _VERBOSE("FT2Image::get_height"); - args.verify_length(0); - - return Py::Int((long)get_height()); -} -PYCXX_VARARGS_METHOD_DECL(FT2Image, py_get_height) - -Py::PythonClassObject Glyph::factory( - const FT_Face& face, const FT_Glyph& glyph, size_t ind, long hinting_factor) -{ - Py::Callable class_type(type()); - Py::PythonClassObject obj = Py::PythonClassObject( - class_type.apply(Py::Tuple(), Py::Dict())); - Glyph* o = obj.getCxxObject(); - - o->glyphInd = ind; - FT_BBox bbox; - FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &bbox); - - o->setattro("width", Py::Int(face->glyph->metrics.width / hinting_factor)); - o->setattro("height", Py::Int(face->glyph->metrics.height)); - o->setattro("horiBearingX", Py::Int(face->glyph->metrics.horiBearingX / hinting_factor)); - o->setattro("horiBearingY", Py::Int(face->glyph->metrics.horiBearingY)); - o->setattro("horiAdvance", Py::Int(face->glyph->metrics.horiAdvance)); - o->setattro("linearHoriAdvance", Py::Int(face->glyph->linearHoriAdvance / hinting_factor)); - o->setattro("vertBearingX", Py::Int(face->glyph->metrics.vertBearingX)); - - o->setattro("vertBearingY", Py::Int(face->glyph->metrics.vertBearingY)); - o->setattro("vertAdvance", Py::Int(face->glyph->metrics.vertAdvance)); - - Py::Tuple abbox(4); - - abbox[0] = Py::Int(bbox.xMin); - abbox[1] = Py::Int(bbox.yMin); - abbox[2] = Py::Int(bbox.xMax); - abbox[3] = Py::Int(bbox.yMax); - o->setattro("bbox", abbox); - - return obj; -} - -Glyph::~Glyph() -{ - _VERBOSE("Glyph::~Glyph"); + m_dirty = true; } -int -Glyph::setattro(const Py::String &name, const Py::Object &value) -{ - _VERBOSE("Glyph::setattr"); - __dict__[name] = value; - return 0; -} - -Py::Object -Glyph::getattro(const Py::String &name) -{ - _VERBOSE("Glyph::getattr"); - if (__dict__.hasKey(name)) return __dict__[name]; - else return genericGetAttro(name); -} - -inline double conv(int v) +inline double conv(long v) { return double(v) / 64.0; } - -char FT2Font::get_path__doc__[] = - "get_path()\n" - "\n" - "Get the path data from the currently loaded glyph as a tuple of vertices, codes.\n" - ; -Py::Object -FT2Font::get_path() +int FT2Font::get_path_count() { - //get the glyph as a path, a list of (COMMAND, *args) as desribed in matplotlib.path + // get the glyph as a path, a list of (COMMAND, *args) as desribed in matplotlib.path // this code is from agg's decompose_ft_outline with minor modifications if (!face->glyph) { - throw Py::ValueError("No glyph loaded"); + throw "No glyph loaded"; } - enum {STOP = 0, - MOVETO = 1, - LINETO = 2, - CURVE3 = 3, - CURVE4 = 4, - ENDPOLY = 0x4f}; - FT_Outline& outline = face->glyph->outline; - bool flip_y = false; //todo, pass me as kwarg + FT_Outline &outline = face->glyph->outline; - FT_Vector v_last; - FT_Vector v_control; - FT_Vector v_start; + FT_Vector v_last; + FT_Vector v_control; + FT_Vector v_start; - FT_Vector* point; - FT_Vector* limit; - char* tags; + FT_Vector *point; + FT_Vector *limit; + char *tags; - int n; // index of contour in outline - int first; // index of first point in contour - char tag; // current point's state - int count; + int n; // index of contour in outline + int first; // index of first point in contour + char tag; // current point's state + int count; count = 0; first = 0; - for (n = 0; n < outline.n_contours; n++) - { - int last; // index of last point in contour + for (n = 0; n < outline.n_contours; n++) { + int last; // index of last point in contour bool starts_with_last; - last = outline.contours[n]; + last = outline.contours[n]; limit = outline.points + last; v_start = outline.points[first]; - v_last = outline.points[last]; + v_last = outline.points[last]; v_control = v_start; point = outline.points + first; - tags = outline.tags + first; - tag = FT_CURVE_TAG(tags[0]); + tags = outline.tags + first; + tag = FT_CURVE_TAG(tags[0]); // A contour cannot start with a cubic control point! - if (tag == FT_CURVE_TAG_CUBIC) - { - throw Py::RuntimeError("A contour cannot start with a cubic control point"); - } - else if (tag == FT_CURVE_TAG_CONIC) - { + if (tag == FT_CURVE_TAG_CUBIC) { + throw "A contour cannot start with a cubic control point"; + } else if (tag == FT_CURVE_TAG_CONIC) { starts_with_last = true; } else { starts_with_last = false; @@ -554,8 +210,7 @@ FT2Font::get_path() count++; - while (point < limit) - { + while (point < limit) { if (!starts_with_last) { point++; tags++; @@ -563,32 +218,28 @@ FT2Font::get_path() starts_with_last = false; tag = FT_CURVE_TAG(tags[0]); - switch (tag) - { - case FT_CURVE_TAG_ON: // emit a single line_to + switch (tag) { + case FT_CURVE_TAG_ON: // emit a single line_to { count++; continue; } - case FT_CURVE_TAG_CONIC: // consume conic arcs + case FT_CURVE_TAG_CONIC: // consume conic arcs { Count_Do_Conic: - if (point < limit) - { + if (point < limit) { point++; tags++; tag = FT_CURVE_TAG(tags[0]); - if (tag == FT_CURVE_TAG_ON) - { + if (tag == FT_CURVE_TAG_ON) { count += 2; continue; } - if (tag != FT_CURVE_TAG_CONIC) - { - throw Py::RuntimeError("Invalid font"); + if (tag != FT_CURVE_TAG_CONIC) { + throw "Invalid font"; } count += 2; @@ -601,18 +252,16 @@ FT2Font::get_path() goto Count_Close; } - default: // FT_CURVE_TAG_CUBIC + default: // FT_CURVE_TAG_CUBIC { - if (point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC) - { - throw Py::RuntimeError("Invalid font"); + if (point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC) { + throw "Invalid font"; } point += 2; - tags += 2; + tags += 2; - if (point <= limit) - { + if (point <= limit) { count += 3; continue; } @@ -630,50 +279,45 @@ FT2Font::get_path() first = last + 1; } - PyArrayObject* vertices = NULL; - PyArrayObject* codes = NULL; - Py::Tuple result(2); + return count; +} + +void FT2Font::get_path(double *outpoints, unsigned char *outcodes) +{ + FT_Outline &outline = face->glyph->outline; + bool flip_y = false; // todo, pass me as kwarg - npy_intp vertices_dims[2] = {count, 2}; - vertices = (PyArrayObject*)PyArray_SimpleNew( - 2, vertices_dims, NPY_DOUBLE); - if (vertices == NULL) { - throw; - } - npy_intp codes_dims[1] = {count}; - codes = (PyArrayObject*)PyArray_SimpleNew( - 1, codes_dims, NPY_UINT8); - if (codes == NULL) { - throw; - } + FT_Vector v_last; + FT_Vector v_control; + FT_Vector v_start; - result[0] = Py::Object((PyObject*)vertices, true); - result[1] = Py::Object((PyObject*)codes, true); + FT_Vector *point; + FT_Vector *limit; + char *tags; - double* outpoints = (double *)PyArray_DATA(vertices); - unsigned char* outcodes = (unsigned char *)PyArray_DATA(codes); + int n; // index of contour in outline + int first; // index of first point in contour + char tag; // current point's state first = 0; - for (n = 0; n < outline.n_contours; n++) - { - int last; // index of last point in contour + for (n = 0; n < outline.n_contours; n++) { + int last; // index of last point in contour bool starts_with_last; - last = outline.contours[n]; + last = outline.contours[n]; limit = outline.points + last; v_start = outline.points[first]; - v_last = outline.points[last]; + v_last = outline.points[last]; v_control = v_start; point = outline.points + first; - tags = outline.tags + first; - tag = FT_CURVE_TAG(tags[0]); + tags = outline.tags + first; + tag = FT_CURVE_TAG(tags[0]); double x, y; - if (tag != FT_CURVE_TAG_ON) - { + if (tag != FT_CURVE_TAG_ON) { x = conv(v_last.x); y = flip_y ? -conv(v_last.y) : conv(v_last.y); starts_with_last = true; @@ -687,8 +331,7 @@ FT2Font::get_path() *(outpoints++) = y; *(outcodes++) = MOVETO; - while (point < limit) - { + while (point < limit) { if (!starts_with_last) { point++; tags++; @@ -696,9 +339,8 @@ FT2Font::get_path() starts_with_last = false; tag = FT_CURVE_TAG(tags[0]); - switch (tag) - { - case FT_CURVE_TAG_ON: // emit a single line_to + switch (tag) { + case FT_CURVE_TAG_ON: // emit a single line_to { double x = conv(point->x); double y = flip_y ? -conv(point->y) : conv(point->y); @@ -708,14 +350,13 @@ FT2Font::get_path() continue; } - case FT_CURVE_TAG_CONIC: // consume conic arcs + case FT_CURVE_TAG_CONIC: // consume conic arcs { v_control.x = point->x; v_control.y = point->y; Do_Conic: - if (point < limit) - { + if (point < limit) { FT_Vector vec; FT_Vector v_middle; @@ -726,8 +367,7 @@ FT2Font::get_path() vec.x = point->x; vec.y = point->y; - if (tag == FT_CURVE_TAG_ON) - { + if (tag == FT_CURVE_TAG_ON) { double xctl = conv(v_control.x); double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y); double xto = conv(vec.x); @@ -773,7 +413,7 @@ FT2Font::get_path() goto Close; } - default: // FT_CURVE_TAG_CUBIC + default: // FT_CURVE_TAG_CUBIC { FT_Vector vec1, vec2; @@ -783,10 +423,9 @@ FT2Font::get_path() vec2.y = point[1].y; point += 2; - tags += 2; + tags += 2; - if (point <= limit) - { + if (point <= limit) { FT_Vector vec; vec.x = point->x; @@ -839,313 +478,123 @@ FT2Font::get_path() Close: first = last + 1; } - - if (outcodes - (unsigned char *)PyArray_DATA(codes) != count) { - throw Py::RuntimeError("Font path size doesn't match"); - } - - return result; } -PYCXX_NOARGS_METHOD_DECL(FT2Font, get_path) -FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds) : - Py::PythonClass(self, args, kwds), - face(NULL), - image() +FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face(NULL) { - FT_Open_Args open_args; - - /* This string is only used for error messages, so encode it in something - * that we'll always be able to print. */ - std::string facefile = Py::String(args[0]).encode("unicode_escape"); - - args.verify_length(1); - - clear(Py::Tuple(0)); - - memset(&stream, 0, sizeof(FT_StreamRec)); - mem = NULL; - mem_size = 0; - - if (make_open_args(args[0].ptr(), &open_args)) { - /* make_open_args sets the Python exception for us. */ - throw Py::Exception(); - } + clear(); int error = FT_Open_Face(_ft2Library, &open_args, 0, &face); - if (error == FT_Err_Unknown_File_Format) - { - std::ostringstream s; - s << "Could not load facefile " << facefile << "; Unknown_File_Format" << std::endl; - throw Py::RuntimeError(s.str()); - } - else if (error == FT_Err_Cannot_Open_Resource) - { - std::ostringstream s; - s << "Could not open facefile " << facefile << "; Cannot_Open_Resource" << std::endl; - throw Py::RuntimeError(s.str()); - } - else if (error == FT_Err_Invalid_File_Format) - { - std::ostringstream s; - s << "Could not open facefile " << facefile << "; Invalid_File_Format" << std::endl; - throw Py::RuntimeError(s.str()); - } - else if (error) - { - std::ostringstream s; - s << "Could not open facefile " << facefile << "; freetype error code " << error << std::endl; - throw Py::RuntimeError(s.str()); + if (error == FT_Err_Unknown_File_Format) { + throw "Can not load face. Unknown file format."; + } else if (error == FT_Err_Cannot_Open_Resource) { + throw "Can not load face. Can not open resource."; + } else if (error == FT_Err_Invalid_File_Format) { + throw "Can not load face. Invalid file format."; + } else if (error) { + throw "Can not load face."; } // set a default fontsize 12 pt at 72dpi - hinting_factor = 8; - if (kwds.hasKey("hinting_factor")) - { - hinting_factor = Py::Long(kwds["hinting_factor"]); + hinting_factor = hinting_factor_; + + error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * (unsigned int)hinting_factor, 72); + if (error) { + throw "Could not set the fontsize"; } - error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * hinting_factor, 72); static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); - if (error) - { - std::ostringstream s; - s << "Could not set the fontsize for facefile " << facefile << std::endl; - throw Py::RuntimeError(s.str()); - } - - // set some face props as attributes - //small memory leak fixed after 2.1.8 - //fields can be null so we have to check this first - - const char* ps_name = FT_Get_Postscript_Name(face); - if (ps_name == NULL) - { - ps_name = "UNAVAILABLE"; - } - - const char* family_name = face->family_name; - if (family_name == NULL) - { - family_name = "UNAVAILABLE"; - } - - const char* style_name = face->style_name; - if (style_name == NULL) - { - style_name = "UNAVAILABLE"; - } - - setattro("postscript_name", Py::String(ps_name)); - setattro("num_faces", Py::Int(face->num_faces)); - setattro("family_name", Py::String(family_name)); - setattro("style_name", Py::String(style_name)); - setattro("face_flags", Py::Int(face->face_flags)); - setattro("style_flags", Py::Int(face->style_flags)); - setattro("num_glyphs", Py::Int(face->num_glyphs)); - setattro("num_fixed_sizes", Py::Int(face->num_fixed_sizes)); - setattro("num_charmaps", Py::Int(face->num_charmaps)); - - int scalable = FT_IS_SCALABLE(face); - - setattro("scalable", Py::Int(scalable)); - - if (scalable) - { - setattro("units_per_EM", Py::Int(face->units_per_EM)); - - Py::Tuple bbox(4); - bbox[0] = Py::Int(face->bbox.xMin); - bbox[1] = Py::Int(face->bbox.yMin); - bbox[2] = Py::Int(face->bbox.xMax); - bbox[3] = Py::Int(face->bbox.yMax); - setattro("bbox", bbox); - setattro("ascender", Py::Int(face->ascender)); - setattro("descender", Py::Int(face->descender)); - setattro("height", Py::Int(face->height)); - setattro("max_advance_width", Py::Int(face->max_advance_width)); - setattro("max_advance_height", Py::Int(face->max_advance_height)); - setattro("underline_position", Py::Int(face->underline_position)); - setattro("underline_thickness", Py::Int(face->underline_thickness)); - } - - setattro("fname", args[0]); - - _VERBOSE("FT2Font::FT2Font done"); } FT2Font::~FT2Font() { - _VERBOSE("FT2Font::~FT2Font"); + for (size_t i = 0; i < glyphs.size(); i++) { + FT_Done_Glyph(glyphs[i]); + } if (face) { FT_Done_Face(face); - - for (size_t i = 0; i < glyphs.size(); i++) - { - FT_Done_Glyph(glyphs[i]); - } - } - - if (stream.descriptor.pointer != NULL) { - PyMem_Free(stream.descriptor.pointer); } } -int -FT2Font::setattro(const Py::String &name, const Py::Object &value) -{ - _VERBOSE("FT2Font::setattr"); - __dict__[name] = value; - return 1; -} - -Py::Object -FT2Font::getattro(const Py::String &name) -{ - _VERBOSE("FT2Font::getattr"); - if (__dict__.hasKey(name)) return __dict__[name]; - else return genericGetAttro(name); -} - -char FT2Font::clear__doc__[] = - "clear()\n" - "\n" - "Clear all the glyphs, reset for a new set_text" - ; - -Py::Object -FT2Font::clear(const Py::Tuple & args) +void FT2Font::clear() { - _VERBOSE("FT2Font::clear"); - args.verify_length(0); - angle = 0.0; pen.x = 0; pen.y = 0; - for (size_t i = 0; i < glyphs.size(); i++) - { + for (size_t i = 0; i < glyphs.size(); i++) { FT_Done_Glyph(glyphs[i]); } glyphs.clear(); - - return Py::Object(); } -PYCXX_VARARGS_METHOD_DECL(FT2Font, clear) -char FT2Font::set_size__doc__[] = - "set_size(ptsize, dpi)\n" - "\n" - "Set the point size and dpi of the text.\n" - ; - -Py::Object -FT2Font::set_size(const Py::Tuple & args) +void FT2Font::set_size(double ptsize, double dpi) { - _VERBOSE("FT2Font::set_size"); - args.verify_length(2); - - double ptsize = Py::Float(args[0]); - double dpi = Py::Float(args[1]); - - int error = FT_Set_Char_Size(face, (long)(ptsize * 64), 0, - (unsigned int)dpi * hinting_factor, - (unsigned int)dpi); + int error = FT_Set_Char_Size( + face, (long)(ptsize * 64), 0, (unsigned int)(dpi * hinting_factor), (unsigned int)dpi); static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); - if (error) - { - throw Py::RuntimeError("Could not set the fontsize"); + if (error) { + throw "Could not set the fontsize"; } - return Py::Object(); } -PYCXX_VARARGS_METHOD_DECL(FT2Font, set_size) -char FT2Font::set_charmap__doc__[] = - "set_charmap(i)\n" - "\n" - "Make the i-th charmap current\n" - ; - -Py::Object -FT2Font::set_charmap(const Py::Tuple & args) +void FT2Font::set_charmap(int i) { - _VERBOSE("FT2Font::set_charmap"); - args.verify_length(1); - - int i = Py::Int(args[0]); - if (i >= face->num_charmaps) - { - throw Py::ValueError("i exceeds the available number of char maps"); + if (i >= face->num_charmaps) { + throw "i exceeds the available number of char maps"; } FT_CharMap charmap = face->charmaps[i]; - if (FT_Set_Charmap(face, charmap)) - { - throw Py::ValueError("Could not set the charmap"); + if (FT_Set_Charmap(face, charmap)) { + throw "Could not set the charmap"; } - return Py::Object(); } -PYCXX_VARARGS_METHOD_DECL(FT2Font, set_charmap) -char FT2Font::select_charmap__doc__[] = - "select_charmap(i)\n" - "\n" - "select charmap i where i is one of the FT_Encoding number\n" - ; - -Py::Object -FT2Font::select_charmap(const Py::Tuple & args) +void FT2Font::select_charmap(unsigned long i) { - _VERBOSE("FT2Font::set_charmap"); - args.verify_length(1); - - unsigned long i = Py::Long(args[0]); - //if (FT_Select_Charmap( face, FT_ENCODING_ADOBE_CUSTOM )) - if (FT_Select_Charmap(face, (FT_Encoding) i)) - { - throw Py::ValueError("Could not set the charmap"); + if (FT_Select_Charmap(face, (FT_Encoding)i)) { + throw "Could not set the charmap"; } - return Py::Object(); } -PYCXX_VARARGS_METHOD_DECL(FT2Font, select_charmap) -FT_BBox -FT2Font::compute_string_bbox() +FT_BBox FT2Font::compute_string_bbox() { - _VERBOSE("FT2Font::compute_string_bbox"); - FT_BBox bbox; /* initialize string bbox to "empty" values */ bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; int right_side = 0; - for (size_t n = 0; n < glyphs.size(); n++) - { + for (size_t n = 0; n < glyphs.size(); n++) { FT_BBox glyph_bbox; FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_subpixels, &glyph_bbox); - if (glyph_bbox.xMin < bbox.xMin) bbox.xMin = glyph_bbox.xMin; - if (glyph_bbox.yMin < bbox.yMin) bbox.yMin = glyph_bbox.yMin; - if (glyph_bbox.xMin == glyph_bbox.xMax) - { - right_side += glyphs[n]->advance.x >> 10; - if (right_side > bbox.xMax) bbox.xMax = right_side; + if (glyph_bbox.xMin < bbox.xMin) { + bbox.xMin = glyph_bbox.xMin; } - else - { - if (glyph_bbox.xMax > bbox.xMax) bbox.xMax = glyph_bbox.xMax; + if (glyph_bbox.yMin < bbox.yMin) { + bbox.yMin = glyph_bbox.yMin; + } + if (glyph_bbox.xMin == glyph_bbox.xMax) { + right_side += glyphs[n]->advance.x >> 10; + if (right_side > bbox.xMax) { + bbox.xMax = right_side; + } + } else { + if (glyph_bbox.xMax > bbox.xMax) { + bbox.xMax = glyph_bbox.xMax; + } } - if (glyph_bbox.yMax > bbox.yMax) bbox.yMax = glyph_bbox.yMax; + if (glyph_bbox.yMax > bbox.yMax) + bbox.yMax = glyph_bbox.yMax; } /* check that we really grew the string bbox */ - if (bbox.xMin > bbox.xMax) - { + if (bbox.xMin > bbox.xMax) { bbox.xMin = 0; bbox.yMin = 0; bbox.xMax = 0; @@ -1154,84 +603,26 @@ FT2Font::compute_string_bbox() return bbox; } -char FT2Font::get_kerning__doc__[] = - "dx = get_kerning(left, right, mode)\n" - "\n" - "Get the kerning between left char and right glyph indices\n" - "mode is a kerning mode constant\n" - " KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n" - " KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n" - " KERNING_UNSCALED - Return the kerning vector in original font units\n" - ; -Py::Object -FT2Font::get_kerning(const Py::Tuple & args) +int FT2Font::get_kerning(int left, int right, int mode) { - _VERBOSE("FT2Font::get_kerning"); - args.verify_length(3); - int left = Py::Int(args[0]); - int right = Py::Int(args[1]); - int mode = Py::Int(args[2]); - - - if (!FT_HAS_KERNING(face)) - { - return Py::Int(0); + if (!FT_HAS_KERNING(face)) { + return 0; } FT_Vector delta; - if (!FT_Get_Kerning(face, left, right, mode, &delta)) - { - return Py::Int(delta.x / hinting_factor); - } - else - { - return Py::Int(0); - + if (!FT_Get_Kerning(face, left, right, mode, &delta)) { + return (int)(delta.x / hinting_factor); + } else { + return 0; } } -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_kerning) - -char FT2Font::set_text__doc__[] = - "set_text(s, angle)\n" - "\n" - "Set the text string and angle.\n" - "You must call this before draw_glyphs_to_bitmap\n" - "A sequence of x,y positions is returned"; -Py::Object -FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs) +void FT2Font::set_text( + size_t N, uint32_t *codepoints, double angle, FT_UInt32 flags, std::vector &xys) { - _VERBOSE("FT2Font::set_text"); - args.verify_length(2); - - - Py::String text(args[0]); - std::string stdtext = ""; - Py_UNICODE* pcode = NULL; - size_t N = 0; - if (PyUnicode_Check(text.ptr())) - { - pcode = PyUnicode_AsUnicode(text.ptr()); - N = PyUnicode_GetSize(text.ptr()); - } - else - { - stdtext = text.as_std_string(); - N = stdtext.size(); - } - - - angle = Py::Float(args[1]); - - angle = angle / 360.0 * 2 * 3.14159; + angle = angle / 360.0 * 2 * M_PI; - long flags = FT_LOAD_FORCE_AUTOHINT; - if (kwargs.hasKey("flags")) - { - flags = Py::Long(kwargs["flags"]); - } - - //this computes width and height in subpixels so we have to divide by 64 + // this computes width and height in subpixels so we have to divide by 64 matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); @@ -1244,36 +635,20 @@ FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs) pen.x = 0; pen.y = 0; - Py::Tuple xys(N); - for (unsigned int n = 0; n < N; n++) - { + for (unsigned int n = 0; n < N; n++) { std::string thischar("?"); FT_UInt glyph_index; - - if (pcode == NULL) - { - // plain ol string - thischar = stdtext[n]; - glyph_index = FT_Get_Char_Index(face, stdtext[n]); - } - else - { - //unicode - glyph_index = FT_Get_Char_Index(face, pcode[n]); - } + glyph_index = FT_Get_Char_Index(face, codepoints[n]); // retrieve kerning distance and move pen position - if (use_kerning && previous && glyph_index) - { + if (use_kerning && previous && glyph_index) { FT_Vector delta; - FT_Get_Kerning(face, previous, glyph_index, - FT_KERNING_DEFAULT, &delta); + FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); pen.x += delta.x / hinting_factor; } error = FT_Load_Glyph(face, glyph_index, flags); - if (error) - { + if (error) { std::cerr << "\tcould not load glyph for " << thischar << std::endl; continue; } @@ -1284,18 +659,15 @@ FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs) FT_Glyph thisGlyph; error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (error) - { + if (error) { std::cerr << "\tcould not get glyph for " << thischar << std::endl; continue; } // ignore errors, jump to next glyph FT_Glyph_Transform(thisGlyph, 0, &pen); - Py::Tuple xy(2); - xy[0] = Py::Float(pen.x); - xy[1] = Py::Float(pen.y); - xys[n] = xy; + xys.push_back(pen.x); + xys.push_back(pen.y); pen.x += face->glyph->advance.x; previous = glyph_index; @@ -1303,1208 +675,156 @@ FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs) } // now apply the rotation - for (unsigned int n = 0; n < glyphs.size(); n++) - { + for (unsigned int n = 0; n < glyphs.size(); n++) { FT_Glyph_Transform(glyphs[n], &matrix, 0); } - - _VERBOSE("FT2Font::set_text done"); - return xys; } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, set_text) - -char FT2Font::get_num_glyphs__doc__[] = - "get_num_glyphs()\n" - "\n" - "Return the number of loaded glyphs\n" - ; -Py::Object -FT2Font::get_num_glyphs(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_num_glyphs"); - args.verify_length(0); - return Py::Int((long)glyphs.size()); -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_num_glyphs) - -char FT2Font::load_char__doc__[] = - "load_char(charcode, flags=LOAD_FORCE_AUTOHINT)\n" - "\n" - "Load character with charcode in current fontfile and set glyph.\n" - "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" - "Return value is a Glyph object, with attributes\n" - " width # glyph width\n" - " height # glyph height\n" - " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" - " horiBearingX # left side bearing in horizontal layouts\n" - " horiBearingY # top side bearing in horizontal layouts\n" - " horiAdvance # advance width for horizontal layout\n" - " vertBearingX # left side bearing in vertical layouts\n" - " vertBearingY # top side bearing in vertical layouts\n" - " vertAdvance # advance height for vertical layout\n" - ; -Py::Object -FT2Font::load_char(const Py::Tuple & args, const Py::Dict & kwargs) +void FT2Font::load_char(long charcode, FT_UInt32 flags) { - _VERBOSE("FT2Font::load_char"); - //load a char using the unsigned long charcode - - args.verify_length(1); - long charcode = Py::Long(args[0]), flags = Py::Long(FT_LOAD_FORCE_AUTOHINT); - if (kwargs.hasKey("flags")) - { - flags = Py::Long(kwargs["flags"]); - } - int error = FT_Load_Char(face, (unsigned long)charcode, flags); - if (error) - { - throw Py::RuntimeError(Printf("Could not load charcode %d", charcode).str()); + if (error) { + throw "Could not load charcode"; } FT_Glyph thisGlyph; error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (error) - { - throw Py::RuntimeError(Printf("Could not get glyph for char %d", charcode).str()); + if (error) { + throw "Could not get glyph"; } - size_t num = glyphs.size(); //the index into the glyphs list glyphs.push_back(thisGlyph); - return Glyph::factory(face, thisGlyph, num, hinting_factor); } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_char) - -char FT2Font::load_glyph__doc__[] = - "load_glyph(glyphindex, flags=LOAD_FORCE_AUTOHINT)\n" - "\n" - "Load character with glyphindex in current fontfile and set glyph.\n" - "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" - "Return value is a Glyph object, with attributes\n" - " width # glyph width\n" - " height # glyph height\n" - " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" - " horiBearingX # left side bearing in horizontal layouts\n" - " horiBearingY # top side bearing in horizontal layouts\n" - " horiAdvance # advance width for horizontal layout\n" - " vertBearingX # left side bearing in vertical layouts\n" - " vertBearingY # top side bearing in vertical layouts\n" - " vertAdvance # advance height for vertical layout\n" - ; -Py::Object -FT2Font::load_glyph(const Py::Tuple & args, const Py::Dict & kwargs) -{ - _VERBOSE("FT2Font::load_glyph"); - //load a char using the unsigned long charcode - - args.verify_length(1); - long glyph_index = Py::Long(args[0]), flags = Py::Long(FT_LOAD_FORCE_AUTOHINT); - if (kwargs.hasKey("flags")) - { - flags = Py::Long(kwargs["flags"]); - } +void FT2Font::load_glyph(FT_UInt glyph_index, FT_UInt32 flags) +{ int error = FT_Load_Glyph(face, glyph_index, flags); - if (error) - { - throw Py::RuntimeError(Printf("Could not load glyph index %d", glyph_index).str()); + if (error) { + throw "Could not load glyph"; } FT_Glyph thisGlyph; error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (error) - { - throw Py::RuntimeError(Printf("Could not get glyph for glyph index %d", glyph_index).str()); + if (error) { + throw "Could not load glyph"; } - size_t num = glyphs.size(); //the index into the glyphs list glyphs.push_back(thisGlyph); - return Glyph::factory(face, thisGlyph, num, hinting_factor); } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_glyph) - -char FT2Font::get_width_height__doc__[] = - "w, h = get_width_height()\n" - "\n" - "Get the width and height in 26.6 subpixels of the current string set by set_text\n" - "The rotation of the string is accounted for. To get width and height\n" - "in pixels, divide these values by 64\n" - ; -Py::Object -FT2Font::get_width_height(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_width_height"); - args.verify_length(0); +void FT2Font::get_width_height(long *width, long *height) +{ FT_BBox bbox = compute_string_bbox(); - Py::Tuple ret(2); - ret[0] = Py::Int(bbox.xMax - bbox.xMin); - ret[1] = Py::Int(bbox.yMax - bbox.yMin); - return ret; + *width = bbox.xMax - bbox.xMin; + *height = bbox.yMax - bbox.yMin; } -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_width_height) - -char FT2Font::get_descent__doc__[] = - "d = get_descent()\n" - "\n" - "Get the descent of the current string set by set_text in 26.6 subpixels.\n" - "The rotation of the string is accounted for. To get the descent\n" - "in pixels, divide this value by 64.\n" - ; -Py::Object -FT2Font::get_descent(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_descent"); - args.verify_length(0); +long FT2Font::get_descent() +{ FT_BBox bbox = compute_string_bbox(); - return Py::Int(- bbox.yMin);; + return -bbox.yMin; } -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_descent) - -char FT2Font::draw_glyphs_to_bitmap__doc__[] = - "draw_glyphs_to_bitmap()\n" - "\n" - "Draw the glyphs that were loaded by set_text to the bitmap\n" - "The bitmap size will be automatically set to include the glyphs\n" - ; -Py::Object -FT2Font::draw_glyphs_to_bitmap(const Py::Tuple &args, const Py::Dict &kwargs) -{ - - _VERBOSE("FT2Font::draw_glyphs_to_bitmap"); - args.verify_length(0); - - long antialiased = 1; - if (kwargs.hasKey("antialiased")) - { - antialiased = Py::Long(kwargs["antialiased"]); - } +void FT2Font::draw_glyphs_to_bitmap(bool antialiased) +{ FT_BBox string_bbox = compute_string_bbox(); size_t width = (string_bbox.xMax - string_bbox.xMin) / 64 + 2; size_t height = (string_bbox.yMax - string_bbox.yMin) / 64 + 2; - image = FT2Image::factory(width, height); - FT2Image* image_cxx = Py::PythonClassObject(image).getCxxObject(); + image.resize(width, height); - for (size_t n = 0; n < glyphs.size(); n++) - { + for (size_t n = 0; n < glyphs.size(); n++) { FT_BBox bbox; FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_pixels, &bbox); error = FT_Glyph_To_Bitmap( - &glyphs[n], - antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, - 0, - 1); - if (error) - { - throw Py::RuntimeError("Could not convert glyph to bitmap"); + &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); + if (error) { + throw "Could not convert glyph to bitmap"; } FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; // now, draw to our target surface (convert position) - //bitmap left and top in pixel, string bbox in subpixel + // bitmap left and top in pixel, string bbox in subpixel FT_Int x = (FT_Int)(bitmap->left - (string_bbox.xMin / 64.)); FT_Int y = (FT_Int)((string_bbox.yMax / 64.) - bitmap->top + 1); - image_cxx->draw_bitmap(&bitmap->bitmap, x, y); + image.draw_bitmap(&bitmap->bitmap, x, y); } - - return Py::Object(); } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, draw_glyphs_to_bitmap) - -char FT2Font::get_xys__doc__[] = - "get_xys()\n" - "\n" - "Get the xy locations of the current glyphs\n" - ; -Py::Object -FT2Font::get_xys(const Py::Tuple &args, const Py::Dict &kwargs) -{ - _VERBOSE("FT2Font::get_xys"); - args.verify_length(0); - - long antialiased = 1; - if (kwargs.hasKey("antialiased")) - { - antialiased = Py::Long(kwargs["antialiased"]); - } +void FT2Font::get_xys(bool antialiased, std::vector &xys) +{ FT_BBox string_bbox = compute_string_bbox(); - Py::Tuple xys(glyphs.size()); - for (size_t n = 0; n < glyphs.size(); n++) - { + for (size_t n = 0; n < glyphs.size(); n++) { FT_BBox bbox; FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_pixels, &bbox); error = FT_Glyph_To_Bitmap( - &glyphs[n], - antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, - 0, - 1); - if (error) - { - throw Py::RuntimeError("Could not convert glyph to bitmap"); + &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); + if (error) { + throw "Could not convert glyph to bitmap"; } FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n]; - - //bitmap left and top in pixel, string bbox in subpixel + // bitmap left and top in pixel, string bbox in subpixel FT_Int x = (FT_Int)(bitmap->left - string_bbox.xMin / 64.); FT_Int y = (FT_Int)(string_bbox.yMax / 64. - bitmap->top + 1); - //make sure the index is non-neg + // make sure the index is non-neg x = x < 0 ? 0 : x; y = y < 0 ? 0 : y; - Py::Tuple xy(2); - xy[0] = Py::Float(x); - xy[1] = Py::Float(y); - xys[n] = xy; + xys.push_back(x); + xys.push_back(y); } - - return xys; } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, get_xys) - -char FT2Font::draw_glyph_to_bitmap__doc__[] = - "draw_glyph_to_bitmap(bitmap, x, y, glyph)\n" - "\n" - "Draw a single glyph to the bitmap at pixel locations x,y\n" - "Note it is your responsibility to set up the bitmap manually\n" - "with set_bitmap_size(w,h) before this call is made.\n" - "\n" - "If you want automatic layout, use set_text in combinations with\n" - "draw_glyphs_to_bitmap. This function is intended for people who\n" - "want to render individual glyphs at precise locations, eg, a\n" - "a glyph returned by load_char\n"; - -Py::Object -FT2Font::draw_glyph_to_bitmap(const Py::Tuple &args, const Py::Dict &kwargs) -{ - _VERBOSE("FT2Font::draw_glyph_to_bitmap"); - args.verify_length(4); - FT2Image* im = Py::PythonClassObject(args[0]).getCxxObject(); - - double xd = Py::Float(args[1]); - double yd = Py::Float(args[2]); - long x = (long)xd; - long y = (long)yd; +void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased) +{ FT_Vector sub_offset; sub_offset.x = 0; // int((xd - (double)x) * 64.0); sub_offset.y = 0; // int((yd - (double)y) * 64.0); - Glyph* glyph = Py::PythonClassObject(args[3]).getCxxObject(); - - long antialiased = 1; - if (kwargs.hasKey("antialiased")) - { - antialiased = Py::Long(kwargs["antialiased"]); - } - - if (glyph->glyphInd >= glyphs.size()) - { - throw Py::ValueError("glyph num is out of range"); + if (glyphInd >= glyphs.size()) { + throw "glyph num is out of range"; } - error = FT_Glyph_To_Bitmap( - &glyphs[glyph->glyphInd], - antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, - &sub_offset, // additional translation - 1 //destroy image - ); - if (error) - { - throw Py::RuntimeError("Could not convert glyph to bitmap"); + error = FT_Glyph_To_Bitmap(&glyphs[glyphInd], + antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, + &sub_offset, // additional translation + 1 // destroy image + ); + if (error) { + throw "Could not convert glyph to bitmap"; } - FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyph->glyphInd]; + FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd]; - im->draw_bitmap(&bitmap->bitmap, x + bitmap->left, y); - return Py::Object(); + im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y); } -PYCXX_KEYWORDS_METHOD_DECL(FT2Font, draw_glyph_to_bitmap) - -char FT2Font::get_glyph_name__doc__[] = - "get_glyph_name(index)\n" - "\n" - "Retrieves the ASCII name of a given glyph in a face.\n" - ; -Py::Object -FT2Font::get_glyph_name(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_glyph_name"); - args.verify_length(1); - - char buffer[128]; - FT_UInt glyph_number = (FT_UInt)(unsigned long long)Py::Int(args[0]); - if (!FT_HAS_GLYPH_NAMES(face)) - { +void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer) +{ + if (!FT_HAS_GLYPH_NAMES(face)) { /* Note that this generated name must match the name that is generated by ttconv in ttfont_CharStrings_getname. */ PyOS_snprintf(buffer, 128, "uni%08x", glyph_number); } else { - if (FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) - { - throw Py::RuntimeError("Could not get glyph names."); - } - } - - return Py::String(buffer); -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_glyph_name) - -char FT2Font::get_charmap__doc__[] = - "get_charmap()\n" - "\n" - "Returns a dictionary that maps the character codes of the selected charmap\n" - "(Unicode by default) to their corresponding glyph indices.\n" - ; -Py::Object -FT2Font::get_charmap(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_charmap"); - args.verify_length(0); - - FT_UInt index; - Py::Dict charmap; - - //std::cout << "asd" << face->charmaps[1]->encoding << std::endl; - FT_ULong code = FT_Get_First_Char(face, &index); - while (index != 0) - { - charmap[Py::Long((long) code)] = Py::Int((int) index); - code = FT_Get_Next_Char(face, code, &index); - } - return charmap; -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_charmap) - -// ID Platform Encoding -// 0 Unicode Reserved (set to 0) -// 1 Macintoch The Script Manager code -// 2 ISO ISO encoding -// 3 Microsoft Microsoft encoding -// 240-255 User-defined Reserved for all nonregistered platforms - -// Code ISO encoding scheme -// 0 7-bit ASCII -// 1 ISO 10646 -// 2 ISO 8859-1 - -// Code Language Code Language Code -// 0 English 10 Hebrew 20 Urdu -// 1 French 11 Japanese 21 Hindi -// 2 German 12 Arabic 22 Thai -// 3 Italian 13 Finnish -// 4 Dutch 14 Greek -// 5 Swedish 15 Icelandic -// 6 Spanish 16 Maltese -// 7 Danish 17 Turkish -// 8 Portuguese 18 Yugoslavian -// 9 Norwegian 19 Chinese - -// Code Meaning Description -// 0 Copyright notice e.g. "Copyright Apple Computer, Inc. 1992 -// 1 Font family name e.g. "New York" -// 2 Font style e.g. "Bold" -// 3 Font identification e.g. "Apple Computer New York Bold Ver 1" -// 4 Full font name e.g. "New York Bold" -// 5 Version string e.g. "August 10, 1991, 1.08d21" -// 6 Postscript name e.g. "Times-Bold" -// 7 Trademark -// 8 Designer e.g. "Apple Computer" - -char FT2Font::get_sfnt__doc__[] = - "get_sfnt(name)\n" - "\n" - "Get all values from the SFNT names table. Result is a dictionary whose" - "key is the platform-ID, ISO-encoding-scheme, language-code, and" - "description.\n" - /* - "The font name identifier codes are:\n" - "\n" - " 0 Copyright notice e.g. Copyright Apple Computer, Inc. 1992\n" - " 1 Font family name e.g. New York\n" - " 2 Font style e.g. Bold\n" - " 3 Font identification e.g. Apple Computer New York Bold Ver 1\n" - " 4 Full font name e.g. New York Bold\n" - " 5 Version string e.g. August 10, 1991, 1.08d21\n" - " 6 Postscript name e.g. Times-Bold\n" - " 7 Trademark \n" - " 8 Designer e.g. Apple Computer\n" - " 11 URL e.g. http://www.apple.com\n" - " 13 Copyright license \n" - */ - ; -Py::Object -FT2Font::get_sfnt(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_sfnt"); - args.verify_length(0); - - if (!(face->face_flags & FT_FACE_FLAG_SFNT)) - { - throw Py::RuntimeError("No SFNT name table"); - } - - size_t count = FT_Get_Sfnt_Name_Count(face); - - Py::Dict names; - for (size_t j = 0; j < count; j++) - { - FT_SfntName sfnt; - FT_Error error = FT_Get_Sfnt_Name(face, j, &sfnt); - - if (error) - { - throw Py::RuntimeError("Could not get SFNT name"); - } - - Py::Tuple key(4); - key[0] = Py::Int(sfnt.platform_id); - key[1] = Py::Int(sfnt.encoding_id); - key[2] = Py::Int(sfnt.language_id); - key[3] = Py::Int(sfnt.name_id); - - names[key] = Py::asObject - (PyBytes_FromStringAndSize( - (const char *)sfnt.string, sfnt.string_len)); - } - return names; -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_sfnt) - -char FT2Font::get_name_index__doc__[] = - "get_name_index(name)\n" - "\n" - "Returns the glyph index of a given glyph name.\n" - "The glyph index 0 means `undefined character code'.\n" - ; -Py::Object -FT2Font::get_name_index(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_name_index"); - args.verify_length(1); - std::string glyphname = Py::String(args[0]).encode("ascii"); - - return Py::Long((long) - FT_Get_Name_Index(face, (FT_String *) glyphname.c_str())); -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_name_index) - -char FT2Font::get_ps_font_info__doc__[] = - "get_ps_font_info()\n" - "\n" - "Return the information in the PS Font Info structure.\n" - ; -Py::Object -FT2Font::get_ps_font_info(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_ps_font_info"); - args.verify_length(0); - PS_FontInfoRec fontinfo; - - FT_Error error = FT_Get_PS_Font_Info(face, &fontinfo); - if (error) - { - Py::RuntimeError("Could not get PS font info"); - return Py::Object(); - } - - Py::Tuple info(9); - info[0] = Py::String(fontinfo.version ? fontinfo.version : ""); - info[1] = Py::String(fontinfo.notice ? fontinfo.notice : ""); - info[2] = Py::String(fontinfo.full_name ? fontinfo.full_name : ""); - info[3] = Py::String(fontinfo.family_name ? fontinfo.family_name : ""); - info[4] = Py::String(fontinfo.weight ? fontinfo.weight : ""); - info[5] = Py::Long(fontinfo.italic_angle); - info[6] = Py::Int(fontinfo.is_fixed_pitch); - info[7] = Py::Int(fontinfo.underline_position); - info[8] = Py::Int(fontinfo.underline_thickness); - return info; -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_ps_font_info) - -char FT2Font::get_sfnt_table__doc__[] = - "get_sfnt_table(name)\n" - "\n" - "Return one of the following SFNT tables: head, maxp, OS/2, hhea, " - "vhea, post, or pclt.\n" - ; -Py::Object -FT2Font::get_sfnt_table(const Py::Tuple & args) -{ - _VERBOSE("FT2Font::get_sfnt_table"); - args.verify_length(1); - std::string tagname = Py::String(args[0]).encode("ascii"); - - int tag; - const char *tags[] = {"head", "maxp", "OS/2", "hhea", - "vhea", "post", "pclt", NULL - }; - - for (tag = 0; tags[tag] != NULL; tag++) - { - if (strcmp(tagname.c_str(), tags[tag]) == 0) - { - break; - } - } - - void *table = FT_Get_Sfnt_Table(face, (FT_Sfnt_Tag) tag); - if (!table) - { - return Py::Object(); - } - - switch (tag) - { - case 0: - { - char head_dict[] = "{s:(h,h), s:(h,h), s:l, s:l, s:i, s:i," - "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:i, s:i, s:h, s:h, s:h}"; - TT_Header *t = (TT_Header *)table; - return Py::asObject(Py_BuildValue(head_dict, - "version", - FIXED_MAJOR(t->Table_Version), - FIXED_MINOR(t->Table_Version), - "fontRevision", - FIXED_MAJOR(t->Font_Revision), - FIXED_MINOR(t->Font_Revision), - "checkSumAdjustment", t->CheckSum_Adjust, - "magicNumber" , t->Magic_Number, - "flags", (unsigned)t->Flags, - "unitsPerEm", (unsigned)t->Units_Per_EM, - "created", t->Created[0], t->Created[1], - "modified", t->Modified[0], t->Modified[1], - "xMin", t->xMin, - "yMin", t->yMin, - "xMax", t->xMax, - "yMax", t->yMax, - "macStyle", (unsigned)t->Mac_Style, - "lowestRecPPEM", (unsigned)t->Lowest_Rec_PPEM, - "fontDirectionHint", t->Font_Direction, - "indexToLocFormat", t->Index_To_Loc_Format, - "glyphDataFormat", t->Glyph_Data_Format)); - } - case 1: - { - char maxp_dict[] = "{s:(h,h), s:i, s:i, s:i, s:i, s:i, s:i," - "s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}"; - TT_MaxProfile *t = (TT_MaxProfile *)table; - return Py::asObject(Py_BuildValue(maxp_dict, - "version", - FIXED_MAJOR(t->version), - FIXED_MINOR(t->version), - "numGlyphs", (unsigned)t->numGlyphs, - "maxPoints", (unsigned)t->maxPoints, - "maxContours", (unsigned)t->maxContours, - "maxComponentPoints", - (unsigned)t->maxCompositePoints, - "maxComponentContours", - (unsigned)t->maxCompositeContours, - "maxZones", (unsigned)t->maxZones, - "maxTwilightPoints", (unsigned)t->maxTwilightPoints, - "maxStorage", (unsigned)t->maxStorage, - "maxFunctionDefs", (unsigned)t->maxFunctionDefs, - "maxInstructionDefs", - (unsigned)t->maxInstructionDefs, - "maxStackElements", (unsigned)t->maxStackElements, - "maxSizeOfInstructions", - (unsigned)t->maxSizeOfInstructions, - "maxComponentElements", - (unsigned)t->maxComponentElements, - "maxComponentDepth", - (unsigned)t->maxComponentDepth)); - } - case 2: - { - #if PY3K - char os_2_dict[] = "{s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(llll)," - "s:y#, s:h, s:h, s:h}"; - #else - char os_2_dict[] = "{s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:s#, s:(llll)," - "s:s#, s:h, s:h, s:h}"; - #endif - TT_OS2 *t = (TT_OS2 *)table; - return Py::asObject(Py_BuildValue(os_2_dict, - "version", (unsigned)t->version, - "xAvgCharWidth", t->xAvgCharWidth, - "usWeightClass", (unsigned)t->usWeightClass, - "usWidthClass", (unsigned)t->usWidthClass, - "fsType", t->fsType, - "ySubscriptXSize", t->ySubscriptXSize, - "ySubscriptYSize", t->ySubscriptYSize, - "ySubscriptXOffset", t->ySubscriptXOffset, - "ySubscriptYOffset", t->ySubscriptYOffset, - "ySuperscriptXSize", t->ySuperscriptXSize, - "ySuperscriptYSize", t->ySuperscriptYSize, - "ySuperscriptXOffset", t->ySuperscriptXOffset, - "ySuperscriptYOffset", t->ySuperscriptYOffset, - "yStrikeoutSize", t->yStrikeoutSize, - "yStrikeoutPosition", t->yStrikeoutPosition, - "sFamilyClass", t->sFamilyClass, - "panose", t->panose, 10, - "ulCharRange", - (unsigned long) t->ulUnicodeRange1, - (unsigned long) t->ulUnicodeRange2, - (unsigned long) t->ulUnicodeRange3, - (unsigned long) t->ulUnicodeRange4, - "achVendID", t->achVendID, 4, - "fsSelection", (unsigned)t->fsSelection, - "fsFirstCharIndex", (unsigned)t->usFirstCharIndex, - "fsLastCharIndex", (unsigned)t->usLastCharIndex)); - } - case 3: - { - char hhea_dict[] = "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:i}"; - TT_HoriHeader *t = (TT_HoriHeader *)table; - return Py::asObject(Py_BuildValue(hhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "ascent", t->Ascender, - "descent", t->Descender, - "lineGap", t->Line_Gap, - "advanceWidthMax", (unsigned)t->advance_Width_Max, - "minLeftBearing", t->min_Left_Side_Bearing, - "minRightBearing", t->min_Right_Side_Bearing, - "xMaxExtent", t->xMax_Extent, - "caretSlopeRise", t->caret_Slope_Rise, - "caretSlopeRun", t->caret_Slope_Run, - "caretOffset", t->caret_Offset, - "metricDataFormat", t->metric_Data_Format, - "numOfLongHorMetrics", - (unsigned)t->number_Of_HMetrics)); - } - case 4: - { - char vhea_dict[] = "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:i}"; - TT_VertHeader *t = (TT_VertHeader *)table; - return Py::asObject(Py_BuildValue(vhea_dict, - "version", - FIXED_MAJOR(t->Version), - FIXED_MINOR(t->Version), - "vertTypoAscender", t->Ascender, - "vertTypoDescender", t->Descender, - "vertTypoLineGap", t->Line_Gap, - "advanceHeightMax", (unsigned)t->advance_Height_Max, - "minTopSideBearing", t->min_Top_Side_Bearing, - "minBottomSizeBearing", t->min_Bottom_Side_Bearing, - "yMaxExtent", t->yMax_Extent, - "caretSlopeRise", t->caret_Slope_Rise, - "caretSlopeRun", t->caret_Slope_Run, - "caretOffset", t->caret_Offset, - "metricDataFormat", t->metric_Data_Format, - "numOfLongVerMetrics", - (unsigned)t->number_Of_VMetrics)); - } - case 5: - { - TT_Postscript *t = (TT_Postscript *)table; - Py::Dict post; - Py::Tuple format(2), angle(2); - format[0] = Py::Int(FIXED_MAJOR(t->FormatType)); - format[1] = Py::Int(FIXED_MINOR(t->FormatType)); - post["format"] = format; - angle[0] = Py::Int(FIXED_MAJOR(t->italicAngle)); - angle[1] = Py::Int(FIXED_MINOR(t->italicAngle)); - post["italicAngle"] = angle; - post["underlinePosition"] = Py::Int(t->underlinePosition); - post["underlineThickness"] = Py::Int(t->underlineThickness); - post["isFixedPitch"] = Py::Long((long) t->isFixedPitch); - post["minMemType42"] = Py::Long((long) t->minMemType42); - post["maxMemType42"] = Py::Long((long) t->maxMemType42); - post["minMemType1"] = Py::Long((long) t->minMemType1); - post["maxMemType1"] = Py::Long((long) t->maxMemType1); - return post; - } - case 6: - { - TT_PCLT *t = (TT_PCLT *)table; - Py::Dict pclt; - Py::Tuple version(2); - version[0] = Py::Int(FIXED_MAJOR(t->Version)); - version[1] = Py::Int(FIXED_MINOR(t->Version)); - pclt["version"] = version; - pclt["fontNumber"] = Py::Long((long) t->FontNumber); - pclt["pitch"] = Py::Int((short) t->Pitch); - pclt["xHeight"] = Py::Int((short) t->xHeight); - pclt["style"] = Py::Int((short) t->Style); - pclt["typeFamily"] = Py::Int((short) t->TypeFamily); - pclt["capHeight"] = Py::Int((short) t->CapHeight); - pclt["symbolSet"] = Py::Int((short) t->SymbolSet); - #if PY3K - pclt["typeFace"] = Py::String((char *) t->TypeFace, 16, "latin-1"); - pclt["characterComplement"] = Py::Bytes((char *) t->CharacterComplement, 8); - #else - pclt["typeFace"] = Py::String((char *) t->TypeFace, 16); - pclt["characterComplement"] = Py::String((char *) t->CharacterComplement, 8); - #endif - // pclt["filename"] = Py::String((char *) t->FileName, 6); - pclt["strokeWeight"] = Py::Int((int) t->StrokeWeight); - pclt["widthType"] = Py::Int((int) t->WidthType); - pclt["serifStyle"] = Py::Int((int) t->SerifStyle); - return pclt; - } - default: - return Py::Object(); - } -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_sfnt_table) - -char FT2Font::get_image__doc__ [] = - "get_image()\n" - "\n" - "Returns the underlying image buffer for this font object.\n"; -Py::Object -FT2Font::get_image(const Py::Tuple &args) -{ - args.verify_length(0); - if (!image.isNone()) - { - return image; - } - throw Py::RuntimeError("You must call .set_text() before .get_image()"); -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, get_image) - -char FT2Font::attach_file__doc__ [] = - "attach_file(filename)\n" - "\n" - "Attach a file with extra information on the font\n" - "(in practice, an AFM file with the metrics of a Type 1 font).\n" - "Throws an exception if unsuccessful.\n"; -Py::Object -FT2Font::attach_file(const Py::Tuple &args) -{ - FT_Open_Args open_args; - - args.verify_length(1); - - std::string filename = Py::String(args[0]).encode("utf-8"); - - if (make_open_args(args[0].ptr(), &open_args)) - { - /* make_open_args sets the Python exception for us. */ - throw Py::Exception(); - } - - FT_Error error = FT_Attach_Stream(face, &open_args); - - if (error) - { - std::ostringstream s; - s << "Could not attach file " << filename - << " (freetype error code " << error << ")" << std::endl; - throw Py::RuntimeError(s.str()); - } - return Py::Object(); -} -PYCXX_VARARGS_METHOD_DECL(FT2Font, attach_file) - - -typedef struct -{ - PyObject *py_file; - FILE *fp; - int close_file; - mpl_off_t offset; -} py_file_def; - - -static unsigned long read_from_file_callback( - FT_Stream stream, unsigned long offset, unsigned char *buffer, - unsigned long count) { - - py_file_def *def = (py_file_def *)stream->descriptor.pointer; - - if (fseek(def->fp, offset, SEEK_SET) == -1) { - return 0; - } - - if (count > 0) { - return fread(buffer, 1, count, def->fp); - } - - return 0; -} - - -static void close_file_callback(FT_Stream stream) -{ - py_file_def *def = (py_file_def *)stream->descriptor.pointer; - - if (mpl_PyFile_DupClose(def->py_file, def->fp, def->offset)) - { - throw Py::Exception(); - } - - if (def->close_file) { - mpl_PyFile_CloseFile(def->py_file); - } - - Py_DECREF(def->py_file); -} - - -int -FT2Font::make_open_args(PyObject *py_file_arg, FT_Open_Args *open_args) -{ - PyObject *py_file = NULL; - int close_file = 0; - FILE *fp; - PyObject *data = NULL; - char *data_ptr; - Py_ssize_t data_len; - py_file_def *stream_info = NULL; - long file_size; - FT_Byte *new_memory; - mpl_off_t offset = 0; - - int result = -1; - - memset((void *)open_args, 0, sizeof(FT_Open_Args)); - - if (PyBytes_Check(py_file_arg) || PyUnicode_Check(py_file_arg)) { - if ((py_file = mpl_PyFile_OpenFile(py_file_arg, (char *)"rb")) == NULL) { - goto exit; - } - close_file = 1; - } else { - Py_INCREF(py_file_arg); - py_file = py_file_arg; - } - - if ((fp = mpl_PyFile_Dup(py_file, (char *)"rb", &offset))) { - stream_info = (py_file_def *)PyMem_Malloc(sizeof(py_file_def)); - if (stream_info == NULL) { - goto exit; - } - memset(stream_info, 0, sizeof(py_file_def)); - - Py_INCREF(py_file); - stream_info->py_file = py_file; - stream_info->close_file = close_file; - stream_info->fp = fp; - stream_info->offset = offset; - fseek(fp, 0, SEEK_END); - file_size = ftell(fp); - fseek(fp, 0, SEEK_SET); - - stream.base = NULL; - stream.size = (unsigned long)file_size; - stream.pos = 0; - stream.descriptor.pointer = stream_info; - stream.read = &read_from_file_callback; - stream.close = &close_file_callback; - - open_args->flags = FT_OPEN_STREAM; - open_args->stream = &stream; - } else { - if (PyObject_HasAttrString(py_file_arg, "read") && - (data = PyObject_CallMethod(py_file_arg, (char*)"read", (char*)""))) { - if (PyBytes_AsStringAndSize(data, &data_ptr, &data_len)) { - goto exit; - } - - if (mem) { - free(mem); - } - mem = (FT_Byte *)PyMem_Malloc(mem_size + data_len); - if (mem == NULL) { - goto exit; - } - new_memory = mem + mem_size; - mem_size += data_len; - - memcpy(new_memory, data_ptr, data_len); - open_args->flags = FT_OPEN_MEMORY; - open_args->memory_base = new_memory; - open_args->memory_size = data_len; - open_args->stream = NULL; - } else { - PyErr_SetString( - PyExc_TypeError, - "First argument must be a path or file object reading bytes"); - goto exit; + if (FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) { + throw "Could not get glyph names."; } } - - result = 0; - - exit: - - Py_XDECREF(py_file); - Py_XDECREF(data); - - return result; -} - -void -FT2Image::init_type(void) -{ - _VERBOSE("FT2Image::init_type"); - behaviors().name("FT2Image"); - behaviors().doc("FT2Image"); - - PYCXX_ADD_VARARGS_METHOD(write_bitmap, py_write_bitmap, - FT2Image::write_bitmap__doc__); - PYCXX_ADD_VARARGS_METHOD(draw_rect, py_draw_rect, - FT2Image::draw_rect__doc__); - PYCXX_ADD_VARARGS_METHOD(draw_rect_filled, py_draw_rect_filled, - FT2Image::draw_rect_filled__doc__); - PYCXX_ADD_VARARGS_METHOD(as_array, py_as_array, - FT2Image::as_array__doc__); - PYCXX_ADD_VARARGS_METHOD(as_str, py_as_str, - FT2Image::as_str__doc__); - PYCXX_ADD_VARARGS_METHOD(as_rgb_str, py_as_rgb_str, - FT2Image::as_rgb_str__doc__); - PYCXX_ADD_VARARGS_METHOD(as_rgba_str, py_as_rgba_str, - FT2Image::as_rgba_str__doc__); - PYCXX_ADD_VARARGS_METHOD(get_width, py_get_width, - "Returns the width of the image"); - PYCXX_ADD_VARARGS_METHOD(get_height, py_get_height, - "Returns the height of the image"); - - behaviors().readyType(); -} - -void -Glyph::init_type() -{ - _VERBOSE("Glyph::init_type"); - behaviors().name("Glyph"); - behaviors().doc("Glyph"); - behaviors().supportGetattro(); - behaviors().supportSetattro(); - behaviors().readyType(); -} - -void -FT2Font::init_type() -{ - _VERBOSE("FT2Font::init_type"); - behaviors().name("FT2Font"); - behaviors().doc("FT2Font"); - behaviors().supportGetattro(); - behaviors().supportSetattro(); - - PYCXX_ADD_VARARGS_METHOD(clear, clear, - FT2Font::clear__doc__); - PYCXX_ADD_KEYWORDS_METHOD(draw_glyph_to_bitmap, draw_glyph_to_bitmap, - FT2Font::draw_glyph_to_bitmap__doc__); - PYCXX_ADD_KEYWORDS_METHOD(draw_glyphs_to_bitmap, draw_glyphs_to_bitmap, - FT2Font::draw_glyphs_to_bitmap__doc__); - PYCXX_ADD_KEYWORDS_METHOD(get_xys, get_xys, - FT2Font::get_xys__doc__); - - PYCXX_ADD_VARARGS_METHOD(get_num_glyphs, get_num_glyphs, - FT2Font::get_num_glyphs__doc__); - PYCXX_ADD_KEYWORDS_METHOD(load_char, load_char, - FT2Font::load_char__doc__); - PYCXX_ADD_KEYWORDS_METHOD(load_glyph, load_glyph, - FT2Font::load_glyph__doc__); - PYCXX_ADD_KEYWORDS_METHOD(set_text, set_text, - FT2Font::set_text__doc__); - PYCXX_ADD_VARARGS_METHOD(set_size, set_size, - FT2Font::set_size__doc__); - PYCXX_ADD_VARARGS_METHOD(set_charmap, set_charmap, - FT2Font::set_charmap__doc__); - PYCXX_ADD_VARARGS_METHOD(select_charmap, select_charmap, - FT2Font::select_charmap__doc__); - - PYCXX_ADD_VARARGS_METHOD(get_width_height, get_width_height, - FT2Font::get_width_height__doc__); - PYCXX_ADD_VARARGS_METHOD(get_descent, get_descent, - FT2Font::get_descent__doc__); - PYCXX_ADD_VARARGS_METHOD(get_glyph_name, get_glyph_name, - FT2Font::get_glyph_name__doc__); - PYCXX_ADD_VARARGS_METHOD(get_charmap, get_charmap, - FT2Font::get_charmap__doc__); - PYCXX_ADD_VARARGS_METHOD(get_kerning, get_kerning, - FT2Font::get_kerning__doc__); - PYCXX_ADD_VARARGS_METHOD(get_sfnt, get_sfnt, - FT2Font::get_sfnt__doc__); - PYCXX_ADD_VARARGS_METHOD(get_name_index, get_name_index, - FT2Font::get_name_index__doc__); - PYCXX_ADD_VARARGS_METHOD(get_ps_font_info, get_ps_font_info, - FT2Font::get_ps_font_info__doc__); - PYCXX_ADD_VARARGS_METHOD(get_sfnt_table, get_sfnt_table, - FT2Font::get_sfnt_table__doc__); - PYCXX_ADD_VARARGS_METHOD(get_image, get_image, - FT2Font::get_image__doc__); - PYCXX_ADD_VARARGS_METHOD(attach_file, attach_file, - FT2Font::attach_file__doc__); - PYCXX_ADD_NOARGS_METHOD(get_path, get_path, - FT2Font::get_path__doc__); - - behaviors().readyType(); -} - -//todo add module docs strings - -char ft2font__doc__[] = - "ft2font\n" - "\n" - "Methods:\n" - " FT2Font(ttffile)\n" - "Face Constants\n" - " SCALABLE scalable\n" - " FIXED_SIZES \n" - " FIXED_WIDTH \n" - " SFNT \n" - " HORIZONTAL \n" - " VERTICAL \n" - " KERNING \n" - " FAST_GLYPHS \n" - " MULTIPLE_MASTERS \n" - " GLYPH_NAMES \n" - " EXTERNAL_STREAM \n" - "Style Constants\n" - " ITALIC \n" - " BOLD \n" - ; - -/* Function of no arguments returning new FT2Font object */ -char ft2font_new__doc__[] = - "FT2Font(ttffile)\n" - "\n" - "Create a new FT2Font object\n" - "The following global font attributes are defined:\n" - " num_faces number of faces in file\n" - " face_flags face flags (int type); see the ft2font constants\n" - " style_flags style flags (int type); see the ft2font constants\n" - " num_glyphs number of glyphs in the face\n" - " family_name face family name\n" - " style_name face syle name\n" - " num_fixed_sizes number of bitmap in the face\n" - " scalable face is scalable\n" - "\n" - "The following are available, if scalable is true:\n" - " bbox face global bounding box (xmin, ymin, xmax, ymax)\n" - " units_per_EM number of font units covered by the EM\n" - " ascender ascender in 26.6 units\n" - " descender descender in 26.6 units\n" - " height height in 26.6 units; used to compute a default\n" - " line spacing (baseline-to-baseline distance)\n" - " max_advance_width maximum horizontal cursor advance for all glyphs\n" - " max_advance_height same for vertical layout\n" - " underline_position vertical position of the underline bar\n" - " underline_thickness vertical thickness of the underline\n" - " postscript_name PostScript name of the font\n" - ; - -ft2font_module::ft2font_module() - : Py::ExtensionModule("ft2font") -{ - FT2Image::init_type(); - Glyph::init_type(); - FT2Font::init_type(); - - initialize("The ft2font module"); - - Py::Dict d(moduleDictionary()); - Py::Object ft2font_type(FT2Font::type()); - d["FT2Font"] = ft2font_type; - Py::Object ft2image_type(FT2Image::type()); - d["FT2Image"] = ft2image_type; } -ft2font_module::~ft2font_module() +long FT2Font::get_name_index(char *name) { - FT_Done_FreeType(_ft2Library); -} - -PyMODINIT_FUNC -#if PY3K -PyInit_ft2font(void) -#else -initft2font(void) -#endif -{ - static ft2font_module* ft2font = new ft2font_module; - Py::Dict d = ft2font->moduleDictionary(); - d["SCALABLE"] = Py::Int(FT_FACE_FLAG_SCALABLE); - d["FIXED_SIZES"] = Py::Int(FT_FACE_FLAG_FIXED_SIZES); - d["FIXED_WIDTH"] = Py::Int(FT_FACE_FLAG_FIXED_WIDTH); - d["SFNT"] = Py::Int(FT_FACE_FLAG_SFNT); - d["HORIZONTAL"] = Py::Int(FT_FACE_FLAG_HORIZONTAL); - d["VERTICAL"] = Py::Int(FT_FACE_FLAG_SCALABLE); - d["KERNING"] = Py::Int(FT_FACE_FLAG_KERNING); - d["FAST_GLYPHS"] = Py::Int(FT_FACE_FLAG_FAST_GLYPHS); - d["MULTIPLE_MASTERS"] = Py::Int(FT_FACE_FLAG_MULTIPLE_MASTERS); - d["GLYPH_NAMES"] = Py::Int(FT_FACE_FLAG_GLYPH_NAMES); - d["EXTERNAL_STREAM"] = Py::Int(FT_FACE_FLAG_EXTERNAL_STREAM); - d["ITALIC"] = Py::Int(FT_STYLE_FLAG_ITALIC); - d["BOLD"] = Py::Int(FT_STYLE_FLAG_BOLD); - d["KERNING_DEFAULT"] = Py::Int(FT_KERNING_DEFAULT); - d["KERNING_UNFITTED"] = Py::Int(FT_KERNING_UNFITTED); - d["KERNING_UNSCALED"] = Py::Int(FT_KERNING_UNSCALED); - - d["LOAD_DEFAULT"] = Py::Long(FT_LOAD_DEFAULT); - d["LOAD_NO_SCALE"] = Py::Long(FT_LOAD_NO_SCALE); - d["LOAD_NO_HINTING"] = Py::Long(FT_LOAD_NO_HINTING); - d["LOAD_RENDER"] = Py::Long(FT_LOAD_RENDER); - d["LOAD_NO_BITMAP"] = Py::Long(FT_LOAD_NO_BITMAP); - d["LOAD_VERTICAL_LAYOUT"] = Py::Long(FT_LOAD_VERTICAL_LAYOUT); - d["LOAD_FORCE_AUTOHINT"] = Py::Long(FT_LOAD_FORCE_AUTOHINT); - d["LOAD_CROP_BITMAP"] = Py::Long(FT_LOAD_CROP_BITMAP); - d["LOAD_PEDANTIC"] = Py::Long(FT_LOAD_PEDANTIC); - d["LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH"] = - Py::Long(FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH); - d["LOAD_NO_RECURSE"] = Py::Long(FT_LOAD_NO_RECURSE); - d["LOAD_IGNORE_TRANSFORM"] = Py::Long(FT_LOAD_IGNORE_TRANSFORM); - d["LOAD_MONOCHROME"] = Py::Long(FT_LOAD_MONOCHROME); - d["LOAD_LINEAR_DESIGN"] = Py::Long(FT_LOAD_LINEAR_DESIGN); - // These need casting because large-valued numeric literals could - // be either longs or unsigned longs: - d["LOAD_NO_AUTOHINT"] = Py::Long((unsigned long)FT_LOAD_NO_AUTOHINT); - d["LOAD_TARGET_NORMAL"] = Py::Long((unsigned long)FT_LOAD_TARGET_NORMAL); - d["LOAD_TARGET_LIGHT"] = Py::Long((unsigned long)FT_LOAD_TARGET_LIGHT); - d["LOAD_TARGET_MONO"] = Py::Long((unsigned long)FT_LOAD_TARGET_MONO); - d["LOAD_TARGET_LCD"] = Py::Long((unsigned long)FT_LOAD_TARGET_LCD); - d["LOAD_TARGET_LCD_V"] = Py::Long((unsigned long)FT_LOAD_TARGET_LCD_V); - - //initialize library - int error = FT_Init_FreeType(&_ft2Library); - - if (error) - { - throw Py::RuntimeError("Could not find initialize the freetype2 library"); - } - - { - FT_Int major, minor, patch; - char version_string[64]; - - FT_Library_Version(_ft2Library, &major, &minor, &patch); - sprintf(version_string, "%d.%d.%d", major, minor, patch); - - d["__freetype_version__"] = Py::String(version_string); - } - - import_array(); - - #if PY3K - return ft2font->module().ptr(); - #endif + return FT_Get_Name_Index(face, (FT_String *)name); } diff --git a/src/ft2font.h b/src/ft2font.h index d2aa06245bcd..bef5e9ab27de 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -3,16 +3,10 @@ /* A python interface to freetype2 */ #ifndef _FT2FONT_H #define _FT2FONT_H -#include "CXX/Extensions.hxx" -#include "CXX/Objects.hxx" -#include #include -#include -#include -#include +#include -extern "C" -{ +extern "C" { #include #include FT_FREETYPE_H #include FT_GLYPH_H @@ -21,129 +15,110 @@ extern "C" #include FT_TRUETYPE_TABLES_H } +/* + By definition, FT_FIXED as 2 16bit values stored in a single long. + */ +#define FIXED_MAJOR(val) (long)((val & 0xffff000) >> 16) +#define FIXED_MINOR(val) (long)(val & 0xffff) + // the freetype string rendered into a width, height buffer -class FT2Image : public Py::PythonClass +class FT2Image { -public: - FT2Image(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds); + public: + FT2Image(); + FT2Image(unsigned long width, unsigned long height); virtual ~FT2Image(); - static Py::PythonClassObject factory(int width, int height); - - static void init_type(); - void draw_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y); - void write_bitmap(FILE* fp) const; - void draw_rect(unsigned long x0, unsigned long y0, - unsigned long x1, unsigned long y1); - void draw_rect_filled(unsigned long x0, unsigned long y0, - unsigned long x1, unsigned long y1); + void resize(long width, long height); + void draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y); + void write_bitmap(FILE *fp) const; + void draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); + void draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1); - unsigned int get_width() const + unsigned char *get_buffer() { - return _width; - }; - unsigned int get_height() const + return m_buffer; + } + unsigned long get_width() { - return _height; - }; - const unsigned char *const get_buffer() const + return m_width; + } + unsigned long get_height() { - return _buffer; - }; - - static char write_bitmap__doc__ []; - Py::Object py_write_bitmap(const Py::Tuple & args); - static char draw_rect__doc__ []; - Py::Object py_draw_rect(const Py::Tuple & args); - static char draw_rect_filled__doc__ []; - Py::Object py_draw_rect_filled(const Py::Tuple & args); - static char as_array__doc__ []; - Py::Object py_as_array(const Py::Tuple & args); - static char as_str__doc__ []; - Py::Object py_as_str(const Py::Tuple & args); - static char as_rgb_str__doc__ []; - Py::Object py_as_rgb_str(const Py::Tuple & args); - static char as_rgba_str__doc__ []; - Py::Object py_as_rgba_str(const Py::Tuple & args); - Py::Object py_get_width(const Py::Tuple & args); - Py::Object py_get_height(const Py::Tuple & args); - -private: - bool _isDirty; - unsigned char *_buffer; - unsigned long _width; - unsigned long _height; + return m_height; + } - void resize(long width, long height); + private: + bool m_dirty; + unsigned char *m_buffer; + unsigned long m_width; + unsigned long m_height; // prevent copying - FT2Image(const FT2Image&); - FT2Image& operator=(const FT2Image&); + FT2Image(const FT2Image &); + FT2Image &operator=(const FT2Image &); }; -class Glyph : public Py::PythonClass -{ -public: - Glyph(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds) : - Py::PythonClass(self, args, kwds) { } - virtual ~Glyph(); - static Py::PythonClassObject factory(const FT_Face&, const FT_Glyph&, size_t, long); - int setattro(const Py::String &name, const Py::Object &value); - Py::Object getattro(const Py::String &name); - static void init_type(void); - size_t glyphInd; -private: - Py::Dict __dict__; +extern FT_Library _ft2Library; - // prevent copying - Glyph(const Glyph&); - Glyph& operator=(const Glyph&); -}; - -class FT2Font : public Py::PythonClass +class FT2Font { -public: - FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds); + public: + FT2Font(FT_Open_Args &open_args, long hinting_factor); virtual ~FT2Font(); - static void init_type(void); - Py::Object clear(const Py::Tuple & args); - Py::Object set_size(const Py::Tuple & args); - Py::Object set_charmap(const Py::Tuple & args); - Py::Object select_charmap(const Py::Tuple & args); - Py::Object set_text(const Py::Tuple & args, const Py::Dict & kwargs); - Py::Object get_kerning(const Py::Tuple & args); - Py::Object get_num_glyphs(const Py::Tuple & args); - Py::Object load_char(const Py::Tuple & args, const Py::Dict & kws); - Py::Object load_glyph(const Py::Tuple & args, const Py::Dict & kws); - Py::Object get_width_height(const Py::Tuple & args); - Py::Object get_descent(const Py::Tuple & args); - Py::Object draw_rect_filled(const Py::Tuple & args); - Py::Object get_xys(const Py::Tuple & args, const Py::Dict & kws); - Py::Object draw_glyphs_to_bitmap(const Py::Tuple & args, const Py::Dict & kws); - Py::Object draw_glyph_to_bitmap(const Py::Tuple & args, const Py::Dict & kws); - Py::Object get_glyph_name(const Py::Tuple & args); - Py::Object get_charmap(const Py::Tuple & args); - Py::Object get_sfnt(const Py::Tuple & args); - Py::Object get_name_index(const Py::Tuple & args); - Py::Object get_ps_font_info(const Py::Tuple & args); - Py::Object get_sfnt_table(const Py::Tuple & args); - Py::Object get_image(const Py::Tuple & args); - Py::Object attach_file(const Py::Tuple & args); - int setattro(const Py::String &name, const Py::Object &value); - Py::Object getattro(const Py::String &name); - Py::Object get_path(); - Py::Object image; - -private: - Py::Dict __dict__; - FT_Face face; - FT_Matrix matrix; /* transformation matrix */ - FT_Vector pen; /* untransformed origin */ - FT_Error error; - FT_StreamRec stream; - FT_Byte * mem; - size_t mem_size; + void clear(); + void set_size(double ptsize, double dpi); + void set_charmap(int i); + void select_charmap(unsigned long i); + void set_text( + size_t N, uint32_t *codepoints, double angle, FT_UInt32 flags, std::vector &xys); + int get_kerning(int left, int right, int mode); + void load_char(long charcode, FT_UInt32 flags); + void load_glyph(FT_UInt glyph_index, FT_UInt32 flags); + void get_width_height(long *width, long *height); + long get_descent(); + // TODO: Since we know the size of the array upfront, we probably don't + // need to dynamically allocate like this + void get_xys(bool antialiased, std::vector &xys); + void draw_glyphs_to_bitmap(bool antialiased); + void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); + void get_glyph_name(unsigned int glyph_number, char *buffer); + long get_name_index(char *name); + int get_path_count(); + void get_path(double *outpoints, unsigned char *outcodes); + + FT_Face &get_face() + { + return face; + } + FT2Image &get_image() + { + return image; + } + FT_Glyph &get_last_glyph() + { + return glyphs.back(); + } + size_t get_last_glyph_index() + { + return glyphs.size() - 1; + } + size_t get_num_glyphs() + { + return glyphs.size(); + } + long get_hinting_factor() + { + return hinting_factor; + } + + private: + FT2Image image; + FT_Face face; + FT_Matrix matrix; /* transformation matrix */ + FT_Vector pen; /* untransformed origin */ + FT_Error error; std::vector glyphs; std::vector pos; double angle; @@ -154,49 +129,9 @@ class FT2Font : public Py::PythonClass FT_BBox compute_string_bbox(); void set_scalable_attributes(); - int make_open_args(PyObject *fileobj, FT_Open_Args *open_args); - - static char clear__doc__ []; - static char set_size__doc__ []; - static char set_charmap__doc__ []; - static char select_charmap__doc__ []; - static char set_text__doc__ []; - static char get_glyph__doc__ []; - static char get_num_glyphs__doc__ []; - static char load_char__doc__ []; - static char load_glyph__doc__ []; - static char get_width_height__doc__ []; - static char get_descent__doc__ []; - static char get_kerning__doc__ []; - static char draw_glyphs_to_bitmap__doc__ []; - static char get_xys__doc__ []; - static char draw_glyph_to_bitmap__doc__ []; - static char get_glyph_name__doc__[]; - static char get_charmap__doc__[]; - static char get_sfnt__doc__ []; - static char get_name_index__doc__[]; - static char get_ps_font_info__doc__[]; - static char get_sfnt_table__doc__[]; - static char get_image__doc__[]; - static char attach_file__doc__[]; - static char get_path__doc__[]; - - // prevent copying - FT2Font(const FT2Font&); - FT2Font& operator=(const FT2Font&); -}; - -// the extension module -class ft2font_module : public Py::ExtensionModule -{ -public: - ft2font_module(); - virtual ~ft2font_module(); - -private: // prevent copying - ft2font_module(const ft2font_module&); - ft2font_module operator=(const ft2font_module&); + FT2Font(const FT2Font &); + FT2Font &operator=(const FT2Font &); }; #endif diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp new file mode 100644 index 000000000000..5e53f977c029 --- /dev/null +++ b/src/ft2font_wrapper.cpp @@ -0,0 +1,1728 @@ +#include "mplutils.h" +#include "ft2font.h" +#include "file_compat.h" +#include "py_exceptions.h" +#include "numpy_cpp.h" + +// From Python +#include + +static PyObject *convert_xys_to_array(std::vector &xys) +{ + npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 }; + return PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, &xys[0]); +} + +/********************************************************************** + * FT2Image + * */ + +typedef struct +{ + PyObject_HEAD; + FT2Image *x; + Py_ssize_t shape[2]; + Py_ssize_t strides[2]; + Py_ssize_t suboffsets[2]; +} PyFT2Image; + +static PyObject *PyFT2Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyFT2Image *self; + self = (PyFT2Image *)type->tp_alloc(type, 0); + self->x = NULL; + return (PyObject *)self; +} + +static int PyFT2Image_init(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + double width; + double height; + + if (!PyArg_ParseTuple(args, "dd:FT2Image", &width, &height)) { + return -1; + } + + CALL_CPP_INIT("FT2Image", (self->x = new FT2Image(width, height))); + + return 0; +} + +static void PyFT2Image_dealloc(PyFT2Image *self) +{ + delete self->x; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +const char *PyFT2Image_draw_rect__doc__ = + "draw_rect(x0, y0, x1, y1)\n" + "\n" + "Draw a rect to the image.\n" + "\n"; + +static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + double x0, y0, x1, y1; + + if (!PyArg_ParseTuple(args, "dddd:draw_rect", &x0, &y0, &x1, &y1)) { + return NULL; + } + + CALL_CPP("draw_rect", (self->x->draw_rect(x0, y0, x1, y1))); + + Py_RETURN_NONE; +} + +const char *PyFT2Image_draw_rect_filled__doc__ = + "draw_rect_filled(x0, y0, x1, y1)\n" + "\n" + "Draw a filled rect to the image.\n" + "\n"; + +static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + double x0, y0, x1, y1; + + if (!PyArg_ParseTuple(args, "dddd:draw_rect_filled", &x0, &y0, &x1, &y1)) { + return NULL; + } + + CALL_CPP("draw_rect_filled", (self->x->draw_rect_filled(x0, y0, x1, y1))); + + Py_RETURN_NONE; +} + +const char *PyFT2Image_as_str__doc__ = + "s = image.as_str()\n" + "\n" + "Return the image buffer as a string\n" + "\n"; + +static PyObject *PyFT2Image_as_str(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + // TODO: Use a buffer to avoid the copy + return PyBytes_FromStringAndSize((const char *)self->x->get_buffer(), + self->x->get_width() * self->x->get_height()); +} + +const char *PyFT2Image_as_rgba_str__doc__ = + "s = image.as_rgba_str()\n" + "\n" + "Return the image buffer as a RGBA string\n" + "\n"; + +static PyObject *PyFT2Image_as_rgba_str(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width(), 4 }; + numpy::array_view result(dims); + + unsigned char *src = self->x->get_buffer(); + unsigned char *end = src + (self->x->get_width() * self->x->get_height()); + unsigned char *dst = result.data(); + + while (src != end) { + *dst++ = 0; + *dst++ = 0; + *dst++ = 0; + *dst++ = *src++; + } + + return result.pyobj(); +} + +const char *PyFT2Image_as_array__doc__ = + "x = image.as_array()\n" + "\n" + "Return the image buffer as a width x height numpy array of ubyte \n" + "\n"; + +static PyObject *PyFT2Image_as_array(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + npy_intp dims[] = {(npy_intp)self->x->get_height(), (npy_intp)self->x->get_width() }; + return PyArray_SimpleNewFromData(2, dims, PyArray_UBYTE, self->x->get_buffer()); +} + +static PyObject *PyFT2Image_get_width(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + return PyLong_FromLong(self->x->get_width()); +} + +static PyObject *PyFT2Image_get_height(PyFT2Image *self, PyObject *args, PyObject *kwds) +{ + return PyLong_FromLong(self->x->get_height()); +} + +static int PyFT2Image_get_buffer(PyFT2Image *self, Py_buffer *buf, int flags) +{ + FT2Image *im = self->x; + + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = im->get_buffer(); + buf->len = im->get_width() * im->get_height(); + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 2; + self->shape[0] = im->get_height(); + self->shape[1] = im->get_width(); + buf->shape = self->shape; + self->strides[0] = im->get_width(); + self->strides[1] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject PyFT2ImageType; + +static PyTypeObject *PyFT2Image_init_type(PyObject *m, PyTypeObject *type) +{ + static PyMethodDef methods[] = { + {"draw_rect", (PyCFunction)PyFT2Image_draw_rect, METH_VARARGS, PyFT2Image_draw_rect__doc__}, + {"draw_rect_filled", (PyCFunction)PyFT2Image_draw_rect_filled, METH_VARARGS, PyFT2Image_draw_rect_filled__doc__}, + {"as_str", (PyCFunction)PyFT2Image_as_str, METH_NOARGS, PyFT2Image_as_str__doc__}, + {"as_rgba_str", (PyCFunction)PyFT2Image_as_rgba_str, METH_NOARGS, PyFT2Image_as_rgba_str__doc__}, + {"as_array", (PyCFunction)PyFT2Image_as_array, METH_NOARGS, PyFT2Image_as_array__doc__}, + {"get_width", (PyCFunction)PyFT2Image_get_width, METH_NOARGS, NULL}, + {"get_height", (PyCFunction)PyFT2Image_get_height, METH_NOARGS, NULL}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + memset(&buffer_procs, 0, sizeof(PyBufferProcs)); + buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib.ft2font.FT2Image"; + type->tp_basicsize = sizeof(PyFT2Image); + type->tp_dealloc = (destructor)PyFT2Image_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER; + type->tp_methods = methods; + type->tp_new = PyFT2Image_new; + type->tp_init = (initproc)PyFT2Image_init; + type->tp_as_buffer = &buffer_procs; + + if (PyType_Ready(type) < 0) { + return NULL; + } + + if (PyModule_AddObject(m, "FT2Image", (PyObject *)type)) { + return NULL; + } + + return type; +} + +/********************************************************************** + * Glyph + * */ + +typedef struct +{ + PyObject_HEAD; + size_t glyphInd; + long width; + long height; + long horiBearingX; + long horiBearingY; + long horiAdvance; + long linearHoriAdvance; + long vertBearingX; + long vertBearingY; + long vertAdvance; + FT_BBox bbox; +} PyGlyph; + +static PyTypeObject PyGlyphType; + +static PyObject * +PyGlyph_new(const FT_Face &face, const FT_Glyph &glyph, size_t ind, long hinting_factor) +{ + PyGlyph *self; + self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0); + + self->glyphInd = ind; + + FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); + + self->width = face->glyph->metrics.width / hinting_factor; + self->height = face->glyph->metrics.height; + self->horiBearingX = face->glyph->metrics.horiBearingX / hinting_factor; + self->horiBearingY = face->glyph->metrics.horiBearingY; + self->horiAdvance = face->glyph->metrics.horiAdvance; + self->linearHoriAdvance = face->glyph->linearHoriAdvance / hinting_factor; + self->vertBearingX = face->glyph->metrics.vertBearingX; + self->vertBearingY = face->glyph->metrics.vertBearingY; + self->vertAdvance = face->glyph->metrics.vertAdvance; + + return (PyObject *)self; +} + +static void PyGlyph_dealloc(PyGlyph *self) +{ + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *PyGlyph_get_bbox(PyGlyph *self, void *closure) +{ + return Py_BuildValue( + "iiii", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax); +} + +static PyTypeObject *PyGlyph_init_type(PyObject *m, PyTypeObject *type) +{ + static PyMemberDef members[] = { + {(char *)"width", T_INT, offsetof(PyGlyph, width), READONLY, (char *)""}, + {(char *)"height", T_INT, offsetof(PyGlyph, height), READONLY, (char *)""}, + {(char *)"horiBearingX", T_INT, offsetof(PyGlyph, horiBearingX), READONLY, (char *)""}, + {(char *)"horiBearingY", T_INT, offsetof(PyGlyph, horiBearingY), READONLY, (char *)""}, + {(char *)"horiAdvance", T_INT, offsetof(PyGlyph, horiAdvance), READONLY, (char *)""}, + {(char *)"linearHoriAdvance", T_INT, offsetof(PyGlyph, linearHoriAdvance), READONLY, (char *)""}, + {(char *)"vertBearingX", T_INT, offsetof(PyGlyph, vertBearingX), READONLY, (char *)""}, + {(char *)"vertBearingY", T_INT, offsetof(PyGlyph, vertBearingY), READONLY, (char *)""}, + {(char *)"vertAdvance", T_INT, offsetof(PyGlyph, vertAdvance), READONLY, (char *)""}, + {NULL} + }; + + static PyGetSetDef getset[] = { + {(char *)"bbox", (getter)PyGlyph_get_bbox, NULL, NULL, NULL}, + {NULL} + }; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib.ft2font.Glyph"; + type->tp_basicsize = sizeof(PyGlyph); + type->tp_dealloc = (destructor)PyGlyph_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + type->tp_members = members; + type->tp_getset = getset; + + if (PyType_Ready(type) < 0) { + return NULL; + } + + /* Don't need to add to module, since you can't create glyphs + directly from Python */ + + return type; +} + +/********************************************************************** + * FT2Font + * */ + +typedef struct +{ + PyObject_HEAD FT2Font *x; + PyObject *fname; + PyObject *py_file; + FILE *fp; + int close_file; + mpl_off_t offset; + FT_StreamRec stream; + FT_Byte *mem; + size_t mem_size; + Py_ssize_t shape[2]; + Py_ssize_t strides[2]; + Py_ssize_t suboffsets[2]; +} PyFT2Font; + +static unsigned long read_from_file_callback(FT_Stream stream, + unsigned long offset, + unsigned char *buffer, + unsigned long count) +{ + + PyFT2Font *def = (PyFT2Font *)stream->descriptor.pointer; + + if (fseek(def->fp, offset, SEEK_SET) == -1) { + return 0; + } + + if (count > 0) { + return fread(buffer, 1, count, def->fp); + } + + return 0; +} + +static void close_file_callback(FT_Stream stream) +{ + PyFT2Font *def = (PyFT2Font *)stream->descriptor.pointer; + + if (mpl_PyFile_DupClose(def->py_file, def->fp, def->offset)) { + throw "Couldn't close file"; + } + + if (def->close_file) { + mpl_PyFile_CloseFile(def->py_file); + } + + Py_DECREF(def->py_file); + def->py_file = NULL; +} + +static int convert_open_args(PyFT2Font *self, PyObject *py_file_arg, FT_Open_Args *open_args) +{ + PyObject *py_file = NULL; + int close_file = 0; + FILE *fp; + PyObject *data = NULL; + char *data_ptr; + Py_ssize_t data_len; + long file_size; + FT_Byte *new_memory; + mpl_off_t offset = 0; + + int result = 0; + + memset((void *)open_args, 0, sizeof(FT_Open_Args)); + + if (PyBytes_Check(py_file_arg) || PyUnicode_Check(py_file_arg)) { + if ((py_file = mpl_PyFile_OpenFile(py_file_arg, (char *)"rb")) == NULL) { + goto exit; + } + close_file = 1; + } else { + Py_INCREF(py_file_arg); + py_file = py_file_arg; + } + + if ((fp = mpl_PyFile_Dup(py_file, (char *)"rb", &offset))) { + Py_INCREF(py_file); + self->py_file = py_file; + self->close_file = close_file; + self->fp = fp; + self->offset = offset; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + self->stream.base = NULL; + self->stream.size = (unsigned long)file_size; + self->stream.pos = 0; + self->stream.descriptor.pointer = self; + self->stream.read = &read_from_file_callback; + self->stream.close = &close_file_callback; + + open_args->flags = FT_OPEN_STREAM; + open_args->stream = &self->stream; + } else { + if (PyObject_HasAttrString(py_file_arg, "read") && + (data = PyObject_CallMethod(py_file_arg, (char *)"read", (char *)""))) { + if (PyBytes_AsStringAndSize(data, &data_ptr, &data_len)) { + goto exit; + } + + if (self->mem) { + free(self->mem); + } + self->mem = (FT_Byte *)malloc((self->mem_size + data_len) * sizeof(FT_Byte)); + if (self->mem == NULL) { + goto exit; + } + new_memory = self->mem + self->mem_size; + self->mem_size += data_len; + + memcpy(new_memory, data_ptr, data_len); + open_args->flags = FT_OPEN_MEMORY; + open_args->memory_base = new_memory; + open_args->memory_size = data_len; + open_args->stream = NULL; + } else { + PyErr_SetString(PyExc_TypeError, + "First argument must be a path or file object reading bytes"); + goto exit; + } + } + + result = 1; + +exit: + + Py_XDECREF(py_file); + Py_XDECREF(data); + + return result; +} + +static PyTypeObject PyFT2FontType; + +static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyFT2Font *self; + self = (PyFT2Font *)type->tp_alloc(type, 0); + self->x = NULL; + self->py_file = NULL; + self->fp = NULL; + self->close_file = 0; + self->offset = 0; + memset(&self->stream, 0, sizeof(FT_StreamRec)); + return (PyObject *)self; +} + +const char *PyFT2Font_init__doc__ = + "FT2Font(ttffile)\n" + "\n" + "Create a new FT2Font object\n" + "The following global font attributes are defined:\n" + " num_faces number of faces in file\n" + " face_flags face flags (int type); see the ft2font constants\n" + " style_flags style flags (int type); see the ft2font constants\n" + " num_glyphs number of glyphs in the face\n" + " family_name face family name\n" + " style_name face syle name\n" + " num_fixed_sizes number of bitmap in the face\n" + " scalable face is scalable\n" + "\n" + "The following are available, if scalable is true:\n" + " bbox face global bounding box (xmin, ymin, xmax, ymax)\n" + " units_per_EM number of font units covered by the EM\n" + " ascender ascender in 26.6 units\n" + " descender descender in 26.6 units\n" + " height height in 26.6 units; used to compute a default\n" + " line spacing (baseline-to-baseline distance)\n" + " max_advance_width maximum horizontal cursor advance for all glyphs\n" + " max_advance_height same for vertical layout\n" + " underline_position vertical position of the underline bar\n" + " underline_thickness vertical thickness of the underline\n" + " postscript_name PostScript name of the font\n"; + +static void PyFT2Font_fail(PyFT2Font *self) +{ + free(self->mem); + self->mem = NULL; + Py_XDECREF(self->py_file); + self->py_file = NULL; +} + +static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *fname; + FT_Open_Args open_args; + long hinting_factor = 8; + const char *names[] = { "filename", "hinting_factor", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|l:FT2Font", (char **)names, &fname, &hinting_factor)) { + return -1; + } + + if (!convert_open_args(self, fname, &open_args)) { + return -1; + } + + CALL_CPP_FULL( + "FT2Font", (self->x = new FT2Font(open_args, hinting_factor)), PyFT2Font_fail(self), -1); + + Py_INCREF(fname); + self->fname = fname; + + return 0; +} + +static void PyFT2Font_dealloc(PyFT2Font *self) +{ + delete self->x; + free(self->mem); + Py_XDECREF(self->py_file); + Py_XDECREF(self->fname); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +const char *PyFT2Font_clear__doc__ = + "clear()\n" + "\n" + "Clear all the glyphs, reset for a new set_text"; + +static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + CALL_CPP("clear", (self->x->clear())); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_set_size__doc__ = + "set_size(ptsize, dpi)\n" + "\n" + "Set the point size and dpi of the text.\n"; + +static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + double ptsize; + double dpi; + + if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { + return NULL; + } + + CALL_CPP("set_size", (self->x->set_size(ptsize, dpi))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_set_charmap__doc__ = + "set_charmap(i)\n" + "\n" + "Make the i-th charmap current\n"; + +static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + int i; + + if (!PyArg_ParseTuple(args, "i:set_charmap", &i)) { + return NULL; + } + + CALL_CPP("set_charmap", (self->x->set_charmap(i))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_select_charmap__doc__ = + "select_charmap(i)\n" + "\n" + "select charmap i where i is one of the FT_Encoding number\n"; + +static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + unsigned long i; + + if (!PyArg_ParseTuple(args, "k:select_charmap", &i)) { + return NULL; + } + + CALL_CPP("select_charmap", self->x->select_charmap(i)); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_kerning__doc__ = + "dx = get_kerning(left, right, mode)\n" + "\n" + "Get the kerning between left char and right glyph indices\n" + "mode is a kerning mode constant\n" + " KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n" + " KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n" + " KERNING_UNSCALED - Return the kerning vector in original font units\n"; + +static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + int left, right, mode, result; + + if (!PyArg_ParseTuple(args, "iii:get_kerning", &left, &right, &mode)) { + return NULL; + } + + CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode))); + + return PyLong_FromLong(result); +} + +const char *PyFT2Font_set_text__doc__ = + "set_text(s, angle)\n" + "\n" + "Set the text string and angle.\n" + "You must call this before draw_glyphs_to_bitmap\n" + "A sequence of x,y positions is returned"; + +static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *textobj; + double angle = 0.0; + FT_UInt32 flags = FT_LOAD_FORCE_AUTOHINT; + std::vector xys; + const char *names[] = { "string", "angle", "flags", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|dI:set_text", (char **)names, &textobj, &angle, &flags)) { + return NULL; + } + + std::vector codepoints; + size_t size; + + if (PyUnicode_Check(textobj)) { + size = PyUnicode_GET_SIZE(textobj); + codepoints.resize(size); + Py_UNICODE *unistr = PyUnicode_AsUnicode(textobj); + for (size_t i = 0; i < size; ++i) { + codepoints[i] = unistr[i]; + } + } else if (PyBytes_Check(textobj)) { + size = PyBytes_Size(textobj); + codepoints.resize(size); + char *bytestr = PyBytes_AsString(textobj); + for (size_t i = 0; i < size; ++i) { + codepoints[i] = bytestr[i]; + } + } else { + PyErr_SetString(PyExc_TypeError, "String must be unicode or bytes"); + return NULL; + } + + CALL_CPP("set_text", self->x->set_text(size, &codepoints[0], angle, flags, xys)); + + return convert_xys_to_array(xys); +} + +const char *PyFT2Font_get_num_glyphs__doc__ = + "get_num_glyphs()\n" + "\n" + "Return the number of loaded glyphs\n"; + +static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + return PyLong_FromLong(self->x->get_num_glyphs()); +} + +const char *PyFT2Font_load_char__doc__ = + "load_char(charcode, flags=LOAD_FORCE_AUTOHINT)\n" + "\n" + "Load character with charcode in current fontfile and set glyph.\n" + "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" + "Return value is a Glyph object, with attributes\n" + " width # glyph width\n" + " height # glyph height\n" + " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" + " horiBearingX # left side bearing in horizontal layouts\n" + " horiBearingY # top side bearing in horizontal layouts\n" + " horiAdvance # advance width for horizontal layout\n" + " vertBearingX # left side bearing in vertical layouts\n" + " vertBearingY # top side bearing in vertical layouts\n" + " vertAdvance # advance height for vertical layout\n"; + +static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + long charcode; + FT_UInt32 flags = FT_LOAD_FORCE_AUTOHINT; + const char *names[] = { "charcode", "flags", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "k|I:load_char", (char **)names, &charcode, &flags)) { + return NULL; + } + + CALL_CPP("load_char", (self->x->load_char(charcode, flags))); + + return PyGlyph_new(self->x->get_face(), + self->x->get_last_glyph(), + self->x->get_last_glyph_index(), + self->x->get_hinting_factor()); +} + +const char *PyFT2Font_load_glyph__doc__ = + "load_glyph(glyphindex, flags=LOAD_FORCE_AUTOHINT)\n" + "\n" + "Load character with glyphindex in current fontfile and set glyph.\n" + "The flags argument can be a bitwise-or of the LOAD_XXX constants.\n" + "Return value is a Glyph object, with attributes\n" + " width # glyph width\n" + " height # glyph height\n" + " bbox # the glyph bbox (xmin, ymin, xmax, ymax)\n" + " horiBearingX # left side bearing in horizontal layouts\n" + " horiBearingY # top side bearing in horizontal layouts\n" + " horiAdvance # advance width for horizontal layout\n" + " vertBearingX # left side bearing in vertical layouts\n" + " vertBearingY # top side bearing in vertical layouts\n" + " vertAdvance # advance height for vertical layout\n"; + +static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + FT_UInt glyph_index; + FT_UInt32 flags = FT_LOAD_FORCE_AUTOHINT; + const char *names[] = { "glyph_index", "flags", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "I|I:load_glyph", (char **)names, &glyph_index, &flags)) { + return NULL; + } + + CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags))); + + return PyGlyph_new(self->x->get_face(), + self->x->get_last_glyph(), + self->x->get_last_glyph_index(), + self->x->get_hinting_factor()); +} + +const char *PyFT2Font_get_width_height__doc__ = + "w, h = get_width_height()\n" + "\n" + "Get the width and height in 26.6 subpixels of the current string set by set_text\n" + "The rotation of the string is accounted for. To get width and height\n" + "in pixels, divide these values by 64\n"; + +static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + long width, height; + + CALL_CPP("get_width_height", (self->x->get_width_height(&width, &height))); + + return Py_BuildValue("ll", width, height); +} + +const char *PyFT2Font_get_descent__doc__ = + "d = get_descent()\n" + "\n" + "Get the descent of the current string set by set_text in 26.6 subpixels.\n" + "The rotation of the string is accounted for. To get the descent\n" + "in pixels, divide this value by 64.\n"; + +static PyObject *PyFT2Font_get_descent(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + long descent; + + CALL_CPP("get_descent", (descent = self->x->get_descent())); + + return PyLong_FromLong(descent); +} + +const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = + "draw_glyphs_to_bitmap()\n" + "\n" + "Draw the glyphs that were loaded by set_text to the bitmap\n" + "The bitmap size will be automatically set to include the glyphs\n"; + +static PyObject *PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + int antialiased = 1; + const char *names[] = { "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "|i:draw_glyphs_to_bitmap", (char **)names, &antialiased)) { + return NULL; + } + + CALL_CPP("draw_glyphs_to_bitmap", (self->x->draw_glyphs_to_bitmap(antialiased))); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_xys__doc__ = + "get_xys()\n" + "\n" + "Get the xy locations of the current glyphs\n"; + +static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + int antialiased = 1; + std::vector xys; + const char *names[] = { "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:get_xys", (char **)names, &antialiased)) { + return NULL; + } + + CALL_CPP("get_xys", (self->x->get_xys(antialiased, xys))); + + return convert_xys_to_array(xys); +} + +const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = + "draw_glyph_to_bitmap(bitmap, x, y, glyph)\n" + "\n" + "Draw a single glyph to the bitmap at pixel locations x,y\n" + "Note it is your responsibility to set up the bitmap manually\n" + "with set_bitmap_size(w,h) before this call is made.\n" + "\n" + "If you want automatic layout, use set_text in combinations with\n" + "draw_glyphs_to_bitmap. This function is intended for people who\n" + "want to render individual glyphs at precise locations, eg, a\n" + "a glyph returned by load_char\n"; + +static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyFT2Image *image; + double xd, yd; + PyGlyph *glyph; + int antialiased = 1; + const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O!ddO!|i:draw_glyph_to_bitmap", + (char **)names, + &PyFT2ImageType, + &image, + &xd, + &yd, + &PyGlyphType, + &glyph, + &antialiased)) { + return NULL; + } + + CALL_CPP("draw_glyph_to_bitmap", + self->x->draw_glyph_to_bitmap(*(image->x), xd, yd, glyph->glyphInd, antialiased)); + + Py_RETURN_NONE; +} + +const char *PyFT2Font_get_glyph_name__doc__ = + "get_glyph_name(index)\n" + "\n" + "Retrieves the ASCII name of a given glyph in a face.\n"; + +static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + unsigned int glyph_number; + char buffer[128]; + + if (!PyArg_ParseTuple(args, "i:get_glyph_name", &glyph_number)) { + return NULL; + } + + CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer))); + + return PyUnicode_FromString(buffer); +} + +const char *PyFT2Font_get_charmap__doc__ = + "get_charmap()\n" + "\n" + "Returns a dictionary that maps the character codes of the selected charmap\n" + "(Unicode by default) to their corresponding glyph indices.\n"; + +static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *charmap; + + charmap = PyDict_New(); + if (charmap == NULL) { + return NULL; + } + + FT_UInt index; + FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index); + while (index != 0) { + PyObject *key; + PyObject *val; + + key = PyLong_FromLong(code); + if (key == NULL) { + Py_DECREF(charmap); + return NULL; + } + + val = PyLong_FromLong(index); + if (val == NULL) { + Py_DECREF(key); + Py_DECREF(charmap); + return NULL; + } + + if (PyDict_SetItem(charmap, key, val)) { + Py_DECREF(key); + Py_DECREF(val); + Py_DECREF(charmap); + return NULL; + } + + Py_DECREF(key); + Py_DECREF(val); + + code = FT_Get_Next_Char(self->x->get_face(), code, &index); + } + + return charmap; +} + +const char *PyFT2Font_get_sfnt__doc__ = + "get_sfnt(name)\n" + "\n" + "Get all values from the SFNT names table. Result is a dictionary whose" + "key is the platform-ID, ISO-encoding-scheme, language-code, and" + "description.\n"; + +static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *names; + + if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { + PyErr_SetString(PyExc_ValueError, "No SFNT name table"); + return NULL; + } + + size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face()); + + names = PyDict_New(); + if (names == NULL) { + return NULL; + } + + for (FT_UInt j = 0; j < count; ++j) { + FT_SfntName sfnt; + FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt); + + if (error) { + Py_DECREF(names); + PyErr_SetString(PyExc_ValueError, "Could not get SFNT name"); + return NULL; + } + + PyObject *key = Py_BuildValue( + "iiii", sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); + if (key == NULL) { + Py_DECREF(names); + return NULL; + } + + PyObject *val = PyBytes_FromStringAndSize((const char *)sfnt.string, sfnt.string_len); + if (val == NULL) { + Py_DECREF(key); + Py_DECREF(names); + return NULL; + } + + if (PyDict_SetItem(names, key, val)) { + Py_DECREF(key); + Py_DECREF(val); + Py_DECREF(names); + return NULL; + } + + Py_DECREF(key); + Py_DECREF(val); + } + + return names; +} + +const char *PyFT2Font_get_name_index__doc__ = + "get_name_index(name)\n" + "\n" + "Returns the glyph index of a given glyph name.\n" + "The glyph index 0 means `undefined character code'.\n"; + +static PyObject *PyFT2Font_get_name_index(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + char *glyphname; + long name_index; + + if (!PyArg_ParseTuple(args, "es:get_name_index", "ascii", &glyphname)) { + return NULL; + } + + CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname)); + + return PyLong_FromLong(name_index); +} + +const char *PyFT2Font_get_ps_font_info__doc__ = + "get_ps_font_info()\n" + "\n" + "Return the information in the PS Font Info structure.\n"; + +static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PS_FontInfoRec fontinfo; + + FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo); + if (error) { + PyErr_SetString(PyExc_ValueError, "Could not get PS font info"); + return NULL; + } + + return Py_BuildValue("sssssliii", + fontinfo.version ? fontinfo.version : "", + fontinfo.notice ? fontinfo.notice : "", + fontinfo.full_name ? fontinfo.full_name : "", + fontinfo.family_name ? fontinfo.family_name : "", + fontinfo.weight ? fontinfo.weight : "", + fontinfo.italic_angle, + fontinfo.is_fixed_pitch, + fontinfo.underline_position, + fontinfo.underline_thickness); +} + +const char *PyFT2Font_get_sfnt_table__doc__ = + "get_sfnt_table(name)\n" + "\n" + "Return one of the following SFNT tables: head, maxp, OS/2, hhea, " + "vhea, post, or pclt.\n"; + +static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + char *tagname; + + if (!PyArg_ParseTuple(args, "es:get_sfnt_table", "ascii", &tagname)) { + return NULL; + } + + int tag; + const char *tags[] = { "head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt", NULL }; + + for (tag = 0; tags[tag] != NULL; tag++) { + if (strncmp(tagname, tags[tag], 5) == 0) { + break; + } + } + + void *table = FT_Get_Sfnt_Table(self->x->get_face(), (FT_Sfnt_Tag)tag); + if (!table) { + Py_RETURN_NONE; + } + + switch (tag) { + case 0: { + char head_dict[] = + "{s:(h,h), s:(h,h), s:l, s:l, s:i, s:i," + "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:i, s:i, s:h, s:h, s:h}"; + TT_Header *t = (TT_Header *)table; + return Py_BuildValue(head_dict, + "version", + FIXED_MAJOR(t->Table_Version), + FIXED_MINOR(t->Table_Version), + "fontRevision", + FIXED_MAJOR(t->Font_Revision), + FIXED_MINOR(t->Font_Revision), + "checkSumAdjustment", + t->CheckSum_Adjust, + "magicNumber", + t->Magic_Number, + "flags", + (unsigned)t->Flags, + "unitsPerEm", + (unsigned)t->Units_Per_EM, + "created", + t->Created[0], + t->Created[1], + "modified", + t->Modified[0], + t->Modified[1], + "xMin", + t->xMin, + "yMin", + t->yMin, + "xMax", + t->xMax, + "yMax", + t->yMax, + "macStyle", + (unsigned)t->Mac_Style, + "lowestRecPPEM", + (unsigned)t->Lowest_Rec_PPEM, + "fontDirectionHint", + t->Font_Direction, + "indexToLocFormat", + t->Index_To_Loc_Format, + "glyphDataFormat", + t->Glyph_Data_Format); + } + case 1: { + char maxp_dict[] = + "{s:(h,h), s:i, s:i, s:i, s:i, s:i, s:i," + "s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}"; + TT_MaxProfile *t = (TT_MaxProfile *)table; + return Py_BuildValue(maxp_dict, + "version", + FIXED_MAJOR(t->version), + FIXED_MINOR(t->version), + "numGlyphs", + (unsigned)t->numGlyphs, + "maxPoints", + (unsigned)t->maxPoints, + "maxContours", + (unsigned)t->maxContours, + "maxComponentPoints", + (unsigned)t->maxCompositePoints, + "maxComponentContours", + (unsigned)t->maxCompositeContours, + "maxZones", + (unsigned)t->maxZones, + "maxTwilightPoints", + (unsigned)t->maxTwilightPoints, + "maxStorage", + (unsigned)t->maxStorage, + "maxFunctionDefs", + (unsigned)t->maxFunctionDefs, + "maxInstructionDefs", + (unsigned)t->maxInstructionDefs, + "maxStackElements", + (unsigned)t->maxStackElements, + "maxSizeOfInstructions", + (unsigned)t->maxSizeOfInstructions, + "maxComponentElements", + (unsigned)t->maxComponentElements, + "maxComponentDepth", + (unsigned)t->maxComponentDepth); + } + case 2: { +#if PY3K + char os_2_dict[] = + "{s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(llll)," + "s:y#, s:h, s:h, s:h}"; +#else + char os_2_dict[] = + "{s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:s#, s:(llll)," + "s:s#, s:h, s:h, s:h}"; +#endif + TT_OS2 *t = (TT_OS2 *)table; + return Py_BuildValue(os_2_dict, + "version", + (unsigned)t->version, + "xAvgCharWidth", + t->xAvgCharWidth, + "usWeightClass", + (unsigned)t->usWeightClass, + "usWidthClass", + (unsigned)t->usWidthClass, + "fsType", + t->fsType, + "ySubscriptXSize", + t->ySubscriptXSize, + "ySubscriptYSize", + t->ySubscriptYSize, + "ySubscriptXOffset", + t->ySubscriptXOffset, + "ySubscriptYOffset", + t->ySubscriptYOffset, + "ySuperscriptXSize", + t->ySuperscriptXSize, + "ySuperscriptYSize", + t->ySuperscriptYSize, + "ySuperscriptXOffset", + t->ySuperscriptXOffset, + "ySuperscriptYOffset", + t->ySuperscriptYOffset, + "yStrikeoutSize", + t->yStrikeoutSize, + "yStrikeoutPosition", + t->yStrikeoutPosition, + "sFamilyClass", + t->sFamilyClass, + "panose", + t->panose, + 10, + "ulCharRange", + (unsigned long)t->ulUnicodeRange1, + (unsigned long)t->ulUnicodeRange2, + (unsigned long)t->ulUnicodeRange3, + (unsigned long)t->ulUnicodeRange4, + "achVendID", + t->achVendID, + 4, + "fsSelection", + (unsigned)t->fsSelection, + "fsFirstCharIndex", + (unsigned)t->usFirstCharIndex, + "fsLastCharIndex", + (unsigned)t->usLastCharIndex); + } + case 3: { + char hhea_dict[] = + "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:i}"; + TT_HoriHeader *t = (TT_HoriHeader *)table; + return Py_BuildValue(hhea_dict, + "version", + FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version), + "ascent", + t->Ascender, + "descent", + t->Descender, + "lineGap", + t->Line_Gap, + "advanceWidthMax", + (unsigned)t->advance_Width_Max, + "minLeftBearing", + t->min_Left_Side_Bearing, + "minRightBearing", + t->min_Right_Side_Bearing, + "xMaxExtent", + t->xMax_Extent, + "caretSlopeRise", + t->caret_Slope_Rise, + "caretSlopeRun", + t->caret_Slope_Run, + "caretOffset", + t->caret_Offset, + "metricDataFormat", + t->metric_Data_Format, + "numOfLongHorMetrics", + (unsigned)t->number_Of_HMetrics); + } + case 4: { + char vhea_dict[] = + "{s:(h,h), s:h, s:h, s:h, s:i, s:h, s:h, s:h," + "s:h, s:h, s:h, s:h, s:i}"; + TT_VertHeader *t = (TT_VertHeader *)table; + return Py_BuildValue(vhea_dict, + "version", + FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version), + "vertTypoAscender", + t->Ascender, + "vertTypoDescender", + t->Descender, + "vertTypoLineGap", + t->Line_Gap, + "advanceHeightMax", + (unsigned)t->advance_Height_Max, + "minTopSideBearing", + t->min_Top_Side_Bearing, + "minBottomSizeBearing", + t->min_Bottom_Side_Bearing, + "yMaxExtent", + t->yMax_Extent, + "caretSlopeRise", + t->caret_Slope_Rise, + "caretSlopeRun", + t->caret_Slope_Run, + "caretOffset", + t->caret_Offset, + "metricDataFormat", + t->metric_Data_Format, + "numOfLongVerMetrics", + (unsigned)t->number_Of_VMetrics); + } + case 5: { + char post_dict[] = "{s:(h,h), s:(h,h), s:h, s:h, s:k, s:k, s:k, s:k, s:k}"; + TT_Postscript *t = (TT_Postscript *)table; + return Py_BuildValue(post_dict, + "format", + FIXED_MAJOR(t->FormatType), + FIXED_MINOR(t->FormatType), + "italicAngle", + FIXED_MAJOR(t->italicAngle), + FIXED_MINOR(t->italicAngle), + "underlinePosition", + t->underlinePosition, + "underlineThickness", + t->underlineThickness, + "isFixedPitch", + t->isFixedPitch, + "minMemType42", + t->minMemType42, + "maxMemType42", + t->maxMemType42, + "minMemType1", + t->minMemType1, + "maxMemType1", + t->maxMemType1); + } + case 6: { + char pclt_dict[] = + "{s:(h,h), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:s, s:s, s:b, s:b, " + "s:b}"; + TT_PCLT *t = (TT_PCLT *)table; + return Py_BuildValue(pclt_dict, + "version", + FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version), + "fontNumber", + t->FontNumber, + "pitch", + t->Pitch, + "xHeight", + t->xHeight, + "style", + t->Style, + "typeFamily", + t->TypeFamily, + "capHeight", + t->CapHeight, + "symbolSet", + t->SymbolSet, + "typeFace", + t->TypeFace, + "characterComplement", + t->CharacterComplement, + "strokeWeight", + t->StrokeWeight, + "widthType", + t->WidthType, + "serifStyle", + t->SerifStyle); + } + default: + Py_RETURN_NONE; + } +} + +const char *PyFT2Font_get_path__doc__ = + "get_path()\n" + "\n" + "Get the path data from the currently loaded glyph as a tuple of vertices, " + "codes.\n"; + +static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + int count; + + CALL_CPP("get_path", (count = self->x->get_path_count())); + + npy_intp vertices_dims[2] = { count, 2 }; + numpy::array_view vertices(vertices_dims); + + npy_intp codes_dims[1] = { count }; + numpy::array_view codes(codes_dims); + + self->x->get_path(vertices.data(), codes.data()); + + return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); +} + +const char *PyFT2Font_get_image__doc__ = + "get_image()\n" + "\n" + "Returns the underlying image buffer for this font object.\n"; + +static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + FT2Image &im = self->x->get_image(); + npy_intp dims[] = {(npy_intp)im.get_height(), (npy_intp)im.get_width() }; + return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, im.get_buffer()); +} + +static PyObject *PyFT2Font_postscript_name(PyFT2Font *self, void *closure) +{ + const char *ps_name = FT_Get_Postscript_Name(self->x->get_face()); + if (ps_name == NULL) { + ps_name = "UNAVAILABLE"; + } + + return PyUnicode_FromString(ps_name); +} + +static PyObject *PyFT2Font_num_faces(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_faces); +} + +static PyObject *PyFT2Font_family_name(PyFT2Font *self, void *closure) +{ + const char *name = self->x->get_face()->family_name; + if (name == NULL) { + name = "UNAVAILABLE"; + } + return PyUnicode_FromString(name); +} + +static PyObject *PyFT2Font_style_name(PyFT2Font *self, void *closure) +{ + const char *name = self->x->get_face()->style_name; + if (name == NULL) { + name = "UNAVAILABLE"; + } + return PyUnicode_FromString(name); +} + +static PyObject *PyFT2Font_face_flags(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->face_flags); +} + +static PyObject *PyFT2Font_style_flags(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->style_flags); +} + +static PyObject *PyFT2Font_num_glyphs(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_glyphs); +} + +static PyObject *PyFT2Font_num_fixed_sizes(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_fixed_sizes); +} + +static PyObject *PyFT2Font_num_charmaps(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->num_charmaps); +} + +static PyObject *PyFT2Font_scalable(PyFT2Font *self, void *closure) +{ + if (FT_IS_SCALABLE(self->x->get_face())) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyObject *PyFT2Font_units_per_EM(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->units_per_EM); +} + +static PyObject *PyFT2Font_get_bbox(PyFT2Font *self, void *closure) +{ + FT_BBox *bbox = &(self->x->get_face()->bbox); + + return Py_BuildValue("iiii", bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); +} + +static PyObject *PyFT2Font_ascender(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->ascender); +} + +static PyObject *PyFT2Font_descender(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->descender); +} + +static PyObject *PyFT2Font_height(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->height); +} + +static PyObject *PyFT2Font_max_advance_width(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->max_advance_width); +} + +static PyObject *PyFT2Font_max_advance_height(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->max_advance_height); +} + +static PyObject *PyFT2Font_underline_position(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->underline_position); +} + +static PyObject *PyFT2Font_underline_thickness(PyFT2Font *self, void *closure) +{ + return PyLong_FromLong(self->x->get_face()->underline_thickness); +} + +static PyObject *PyFT2Font_fname(PyFT2Font *self, void *closure) +{ + if (self->fname) { + Py_INCREF(self->fname); + return self->fname; + } + + Py_RETURN_NONE; +} + +static int PyFT2Font_get_buffer(PyFT2Font *self, Py_buffer *buf, int flags) +{ + FT2Image &im = self->x->get_image(); + + Py_INCREF(self); + buf->obj = (PyObject *)self; + buf->buf = im.get_buffer(); + buf->len = im.get_width() * im.get_height(); + buf->readonly = 0; + buf->format = (char *)"B"; + buf->ndim = 2; + self->shape[0] = im.get_height(); + self->shape[1] = im.get_width(); + buf->shape = self->shape; + self->strides[0] = im.get_width(); + self->strides[1] = 1; + buf->strides = self->strides; + buf->suboffsets = NULL; + buf->itemsize = 1; + buf->internal = NULL; + + return 1; +} + +static PyTypeObject *PyFT2Font_init_type(PyObject *m, PyTypeObject *type) +{ + static PyGetSetDef getset[] = { + {(char *)"postscript_name", (getter)PyFT2Font_postscript_name, NULL, NULL, NULL}, + {(char *)"num_faces", (getter)PyFT2Font_num_faces, NULL, NULL, NULL}, + {(char *)"family_name", (getter)PyFT2Font_family_name, NULL, NULL, NULL}, + {(char *)"style_name", (getter)PyFT2Font_style_name, NULL, NULL, NULL}, + {(char *)"face_flags", (getter)PyFT2Font_face_flags, NULL, NULL, NULL}, + {(char *)"style_flags", (getter)PyFT2Font_style_flags, NULL, NULL, NULL}, + {(char *)"num_glyphs", (getter)PyFT2Font_num_glyphs, NULL, NULL, NULL}, + {(char *)"num_fixed_sizes", (getter)PyFT2Font_num_fixed_sizes, NULL, NULL, NULL}, + {(char *)"num_charmaps", (getter)PyFT2Font_num_charmaps, NULL, NULL, NULL}, + {(char *)"scalable", (getter)PyFT2Font_scalable, NULL, NULL, NULL}, + {(char *)"units_per_EM", (getter)PyFT2Font_units_per_EM, NULL, NULL, NULL}, + {(char *)"bbox", (getter)PyFT2Font_get_bbox, NULL, NULL, NULL}, + {(char *)"ascender", (getter)PyFT2Font_ascender, NULL, NULL, NULL}, + {(char *)"descender", (getter)PyFT2Font_descender, NULL, NULL, NULL}, + {(char *)"height", (getter)PyFT2Font_height, NULL, NULL, NULL}, + {(char *)"max_advance_width", (getter)PyFT2Font_max_advance_width, NULL, NULL, NULL}, + {(char *)"max_advance_height", (getter)PyFT2Font_max_advance_height, NULL, NULL, NULL}, + {(char *)"underline_position", (getter)PyFT2Font_underline_position, NULL, NULL, NULL}, + {(char *)"underline_thickness", (getter)PyFT2Font_underline_thickness, NULL, NULL, NULL}, + {(char *)"fname", (getter)PyFT2Font_fname, NULL, NULL, NULL}, + {NULL} + }; + + static PyMethodDef methods[] = { + {"clear", (PyCFunction)PyFT2Font_clear, METH_NOARGS, PyFT2Font_clear__doc__}, + {"set_size", (PyCFunction)PyFT2Font_set_size, METH_VARARGS, PyFT2Font_set_size__doc__}, + {"set_charmap", (PyCFunction)PyFT2Font_set_charmap, METH_VARARGS, PyFT2Font_set_charmap__doc__}, + {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__}, + {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__}, + {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__}, + {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__}, + {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__}, + {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__}, + {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__}, + {"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__}, + {"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__}, + {"get_xys", (PyCFunction)PyFT2Font_get_xys, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_xys__doc__}, + {"draw_glyph_to_bitmap", (PyCFunction)PyFT2Font_draw_glyph_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyph_to_bitmap__doc__}, + {"get_glyph_name", (PyCFunction)PyFT2Font_get_glyph_name, METH_VARARGS, PyFT2Font_get_glyph_name__doc__}, + {"get_charmap", (PyCFunction)PyFT2Font_get_charmap, METH_NOARGS, PyFT2Font_get_charmap__doc__}, + {"get_sfnt", (PyCFunction)PyFT2Font_get_sfnt, METH_NOARGS, PyFT2Font_get_sfnt__doc__}, + {"get_name_index", (PyCFunction)PyFT2Font_get_name_index, METH_VARARGS, PyFT2Font_get_name_index__doc__}, + {"get_ps_font_info", (PyCFunction)PyFT2Font_get_ps_font_info, METH_NOARGS, PyFT2Font_get_ps_font_info__doc__}, + {"get_sfnt_table", (PyCFunction)PyFT2Font_get_sfnt_table, METH_VARARGS, PyFT2Font_get_sfnt_table__doc__}, + {"get_path", (PyCFunction)PyFT2Font_get_path, METH_NOARGS, PyFT2Font_get_path__doc__}, + {"get_image", (PyCFunction)PyFT2Font_get_image, METH_NOARGS, PyFT2Font_get_path__doc__}, + {NULL} + }; + + static PyBufferProcs buffer_procs; + memset(&buffer_procs, 0, sizeof(PyBufferProcs)); + buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer; + + memset(type, 0, sizeof(PyTypeObject)); + type->tp_name = "matplotlib.ft2font.FT2Font"; + type->tp_doc = PyFT2Font_init__doc__; + type->tp_basicsize = sizeof(PyFT2Font); + type->tp_dealloc = (destructor)PyFT2Font_dealloc; + type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER; + type->tp_methods = methods; + type->tp_getset = getset; + type->tp_new = PyFT2Font_new; + type->tp_init = (initproc)PyFT2Font_init; + type->tp_as_buffer = &buffer_procs; + + if (PyType_Ready(type) < 0) { + return NULL; + } + + if (PyModule_AddObject(m, "FT2Font", (PyObject *)type)) { + return NULL; + } + + return type; +} + +extern "C" { + +struct module_state +{ +/* The Sun compiler can't handle empty structs */ +#if defined(__SUNPRO_C) || defined(_MSC_VER) + int _dummy; +#endif +}; + +#if PY3K +static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ft2font", NULL, + sizeof(struct module_state), NULL, NULL, + NULL, NULL, NULL }; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit_ft2font(void) + +#else +#define INITERROR return + +PyMODINIT_FUNC initft2font(void) +#endif + +{ + PyObject *m; + +#if PY3K + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("ft2font", NULL, NULL); +#endif + + if (m == NULL) { + INITERROR; + } + + if (!PyFT2Image_init_type(m, &PyFT2ImageType)) { + INITERROR; + } + + if (!PyGlyph_init_type(m, &PyGlyphType)) { + INITERROR; + } + + if (!PyFT2Font_init_type(m, &PyFT2FontType)) { + INITERROR; + } + + PyObject *d = PyModule_GetDict(m); + + if (add_dict_int(d, "SCALABLE", FT_FACE_FLAG_SCALABLE) || + add_dict_int(d, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) || + add_dict_int(d, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) || + add_dict_int(d, "SFNT", FT_FACE_FLAG_SFNT) || + add_dict_int(d, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) || + add_dict_int(d, "VERTICAL", FT_FACE_FLAG_SCALABLE) || + add_dict_int(d, "KERNING", FT_FACE_FLAG_KERNING) || + add_dict_int(d, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) || + add_dict_int(d, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) || + add_dict_int(d, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) || + add_dict_int(d, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) || + add_dict_int(d, "ITALIC", FT_STYLE_FLAG_ITALIC) || + add_dict_int(d, "BOLD", FT_STYLE_FLAG_BOLD) || + add_dict_int(d, "KERNING_DEFAULT", FT_KERNING_DEFAULT) || + add_dict_int(d, "KERNING_UNFITTED", FT_KERNING_UNFITTED) || + add_dict_int(d, "KERNING_UNSCALED", FT_KERNING_UNSCALED) || + add_dict_int(d, "LOAD_DEFAULT", FT_LOAD_DEFAULT) || + add_dict_int(d, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) || + add_dict_int(d, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) || + add_dict_int(d, "LOAD_RENDER", FT_LOAD_RENDER) || + add_dict_int(d, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) || + add_dict_int(d, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) || + add_dict_int(d, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) || + add_dict_int(d, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) || + add_dict_int(d, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) || + add_dict_int(d, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) || + add_dict_int(d, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) || + add_dict_int(d, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) || + add_dict_int(d, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) || + add_dict_int(d, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) || + add_dict_int(d, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) || + add_dict_int(d, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) || + add_dict_int(d, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) || + add_dict_int(d, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) || + add_dict_int(d, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) || + add_dict_int(d, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) { + INITERROR; + } + + // initialize library + int error = FT_Init_FreeType(&_ft2Library); + + if (error) { + PyErr_SetString(PyExc_RuntimeError, "Could not find initialize the freetype2 library"); + INITERROR; + } + + { + FT_Int major, minor, patch; + char version_string[64]; + + FT_Library_Version(_ft2Library, &major, &minor, &patch); + sprintf(version_string, "%d.%d.%d", major, minor, patch); + if (PyModule_AddStringConstant(m, "__freetype_version__", version_string)) { + INITERROR; + } + } + + import_array(); + +#if PY3K + return m; +#endif +} + +} // extern "C" diff --git a/src/mplutils.cpp b/src/mplutils.cpp index febb97ac81a0..bc09db52aa9e 100644 --- a/src/mplutils.cpp +++ b/src/mplutils.cpp @@ -1,34 +1,21 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ -#include -#include -#include #include "mplutils.h" -void _VERBOSE(const std::string& s) +int add_dict_int(PyObject *dict, const char *key, long val) { -#ifdef VERBOSE - std::cout << s << std::endl; -#endif -} - - -Printf::Printf(const char *fmt, ...) - : buffer(new char[1024]) // some reasonably large number -{ - va_list ap; - va_start(ap, fmt); - vsprintf(buffer, fmt, ap); - va_end(ap); // look ma - I rememberd it this time -} + PyObject *valobj; + valobj = PyLong_FromLong(val); + if (valobj == NULL) { + return 1; + } -Printf::~Printf() -{ - delete [] buffer; -} + if (PyDict_SetItemString(dict, (char *)key, valobj)) { + Py_DECREF(valobj); + return 1; + } + Py_DECREF(valobj); -std::ostream &operator<<(std::ostream &o, const Printf &p) -{ - return o << p.buffer; + return 0; } diff --git a/src/mplutils.h b/src/mplutils.h index 35f6b1b1a72b..d09f810709ad 100644 --- a/src/mplutils.h +++ b/src/mplutils.h @@ -15,25 +15,23 @@ #ifndef _MPLUTILS_H #define _MPLUTILS_H -#include - #include #include #include +#include + #if PY_MAJOR_VERSION >= 3 #define PY3K 1 +#define Py_TPFLAGS_HAVE_NEWBUFFER 0 #else #define PY3K 0 #endif -void _VERBOSE(const std::string&); +#undef CLAMP +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) - -#undef CLAMP -#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) - -#undef MAX +#undef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) inline double mpl_round(double v) @@ -41,23 +39,19 @@ inline double mpl_round(double v) return (double)(int)(v + ((v >= 0.0) ? 0.5 : -0.5)); } -class Printf -{ -private : - char *buffer; - // prevent copying - Printf(const Printf&); - Printf& operator=(const Printf&); -public : - Printf(const char *, ...); - ~Printf(); - std::string str() - { - return buffer; - } - friend std::ostream &operator <<(std::ostream &, const Printf &); +enum { + STOP = 0, + MOVETO = 1, + LINETO = 2, + CURVE3 = 3, + CURVE4 = 4, + ENDPOLY = 0x4f }; +const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 }; + +extern "C" int add_dict_int(PyObject *dict, const char *key, long val); + #if defined(_MSC_VER) && (_MSC_VER == 1400) /* Required by libpng and zlib */ @@ -65,18 +59,20 @@ public : /* std::max and std::min are missing in Windows Server 2003 R2 Platform SDK compiler. See matplotlib bug #3067191 */ -namespace std { - - template inline T max(const T& a, const T& b) - { - return (a > b) ? a : b; - } +namespace std +{ - template inline T min(const T& a, const T& b) - { - return (a < b) ? a : b; - } +template +inline T max(const T &a, const T &b) +{ + return (a > b) ? a : b; +} +template +inline T min(const T &a, const T &b) +{ + return (a < b) ? a : b; +} } #endif diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h new file mode 100644 index 000000000000..1f96c3fc00eb --- /dev/null +++ b/src/numpy_cpp.h @@ -0,0 +1,480 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +#ifndef _NUMPY_CPP_H_ +#define _NUMPY_CPP_H_ + +/*************************************************************************** + * This file is based on original work by Mark Wiebe, available at: + * + * http://github.com/mwiebe/numpy-cpp + * + * However, the needs of matplotlib wrappers, such as treating an + * empty array as having the correct dimensions, have made this rather + * matplotlib-specific, so it's no longer compatible with the + * original. + */ + +#include "py_exceptions.h" + +#include + +#include +#include + +namespace numpy +{ + +// Type traits for the NumPy types +template +struct type_num_of; + +// This is dodgy - need sizeof(bool) == 1 consistently for this to be valid... +// template <> struct type_num_of { +// enum {value = NPY_BOOL}; +//}; +template <> +struct type_num_of +{ + enum { + value = NPY_BYTE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_UBYTE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_SHORT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_USHORT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_INT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_UINT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_LONG + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_ULONG + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_LONGLONG + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_ULONGLONG + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_FLOAT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_DOUBLE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_LONGDOUBLE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_CFLOAT + }; +}; +template <> +struct type_num_of > +{ + enum { + value = NPY_CFLOAT + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_CDOUBLE + }; +}; +template <> +struct type_num_of > +{ + enum { + value = NPY_CDOUBLE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_CLONGDOUBLE + }; +}; +template <> +struct type_num_of > +{ + enum { + value = NPY_CLONGDOUBLE + }; +}; +template <> +struct type_num_of +{ + enum { + value = NPY_OBJECT + }; +}; +template +struct type_num_of +{ + enum { + value = type_num_of::value + }; +}; +template +struct type_num_of +{ + enum { + value = type_num_of::value + }; +}; + +template +struct is_const +{ + enum { + value = false + }; +}; +template +struct is_const +{ + enum { + value = true + }; +}; + +namespace detail +{ +template