From: <md...@us...> - 2007-11-20 21:00:22
|
Revision: 4398 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4398&view=rev Author: mdboom Date: 2007-11-20 13:00:20 -0800 (Tue, 20 Nov 2007) Log Message: ----------- Support mixed-mode rendering the PDF backend. This allows some things to be rendered as vectors and some as rasters. At the moment, mostly as a proof-of-concept, all quadmeshes are rendered as rasters with the PDF backend. Also make PDF backend resolution independent. Modified Paths: -------------- branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/backends/backend_pdf.py branches/transforms/lib/matplotlib/collections.py Added Paths: ----------- branches/transforms/lib/matplotlib/backends/backend_mixed.py Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-11-20 20:21:53 UTC (rev 4397) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-11-20 21:00:20 UTC (rev 4398) @@ -9,6 +9,7 @@ import numpy as npy import matplotlib.cbook as cbook import matplotlib.colors as colors +import matplotlib._image as _image import matplotlib.path as path import matplotlib.transforms as transforms import matplotlib.widgets as widgets @@ -328,7 +329,13 @@ def strip_math(self, s): return cbook.strip_math(s) + def start_rasterizing(self): + pass + def stop_rasterizing(self): + pass + + class GraphicsContextBase: """An abstract base class that provides color, line styles, etc... """ Added: branches/transforms/lib/matplotlib/backends/backend_mixed.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_mixed.py (rev 0) +++ branches/transforms/lib/matplotlib/backends/backend_mixed.py 2007-11-20 21:00:20 UTC (rev 4398) @@ -0,0 +1,142 @@ +from matplotlib._image import frombuffer +from matplotlib.backends.backend_agg import RendererAgg + +class MixedModeRenderer(object): + """ + A helper class to implement a renderer that switches between + vector and raster drawing. An example may be a PDF writer, where + most things are drawn with PDF vector commands, but some very + complex objects, such as quad meshes, are rasterised and then + output as images. + """ + def __init__(self, width, height, dpi, vector_renderer, raster_renderer_class=None): + """ + width: The width of the canvas in logical units + + height: The height of the canvas in logical units + + dpi: The dpi of the canvas + + vector_renderer: An instance of a subclass of RendererBase + that will be used for the vector drawing. + + raster_renderer_class: The renderer class to use for the + raster drawing. If not provided, this will use the Agg + backend (which is currently the only viable option anyway.) + """ + if raster_renderer_class is None: + raster_renderer_class = RendererAgg + + self._raster_renderer_class = raster_renderer_class + self._width = width + self._height = height + self._dpi = dpi + + assert not vector_renderer.option_image_nocomposite() + self._vector_renderer = vector_renderer + vector_renderer.start_rasterizing = self.start_rasterizing + vector_renderer.stop_rasterizing = self.stop_rasterizing + + self._raster_renderer = None + self._rasterizing = False + + self._renderer = self._vector_renderer + + def start_rasterizing(self): + """ + Enter "raster" mode. All subsequent drawing commands (until + stop_rasterizing is called) will be drawn with the raster + backend. + + If start_rasterizing is called multiple times before + stop_rasterizing is called, this method has no effect. + """ + if not self._rasterizing: + self._raster_renderer = self._raster_renderer_class( + self._width*self._dpi, self._height*self._dpi, self._dpi) + self._raster_renderer.start_rasterizing = self.start_rasterizing + self._raster_renderer.stop_rasterizing = self.stop_rasterizing + self._renderer = self._raster_renderer + self._rasterizing = True + + def stop_rasterizing(self): + """ + Exit "raster" mode. All of the drawing that was done since + the last start_rasterizing command will be copied to the + vector backend by calling draw_image. + + If stop_rasterizing is called multiple times before + start_rasterizing is called, this method has no effect. + """ + if self._rasterizing: + width, height = self._width * self._dpi, self._height * self._dpi + buffer = self._raster_renderer.buffer_rgba(0, 0) + image = frombuffer(buffer, width, height, True) + image.is_grayscale = False + + self._renderer = self._vector_renderer + self._renderer.draw_image(0, 0, image, None) + self._raster_renderer = None + self._rasterizing = False + + def get_canvas_width_height(self): + 'return the canvas width and height in display coords' + return self._width, self._height + + # The rest of this methods simply delegate to the currently active + # rendering backend. + + def open_group(self, *args, **kwargs): + return self._renderer.open_group(*args, **kwargs) + + def close_group(self, *args, **kwargs): + return self._renderer.close_group(*args, **kwargs) + + def draw_path(self, *args, **kwargs): + return self._renderer.draw_path(*args, **kwargs) + + def draw_markers(self, *args, **kwargs): + return self._renderer.draw_markers(*args, **kwargs) + + def draw_path_collection(self, *args, **kwargs): + return self._renderer.draw_path_collection(*args, **kwargs) + + def draw_quad_mesh(self, *args, **kwargs): + return self._renderer.draw_quad_mesh(*args, **kwargs) + + def get_image_magnification(self, *args, **kwargs): + return self._renderer.get_image_magnification(*args, **kwargs) + + def draw_image(self, *args, **kwargs): + return self._renderer.draw_image(*args, **kwargs) + + def draw_tex(self, *args, **kwargs): + return self._renderer.draw_tex(*args, **kwargs) + + def draw_text(self, *args, **kwargs): + return self._renderer.draw_text(*args, **kwargs) + + def flipy(self, *args, **kwargs): + return self._renderer.flipy(*args, **kwargs) + + def option_image_nocomposite(self, *args, **kwargs): + return self._vector_renderer.option_image_nocomposite(*args, **kwargs) + + def get_texmanager(self, *args, **kwargs): + return self._renderer.get_texmanager(*args, **kwargs) + + def get_text_width_height_descent(self, *args, **kwargs): + return self._renderer.get_text_width_height_descent(*args, **kwargs) + + def new_gc(self, *args, **kwargs): + return self._renderer.new_gc(*args, **kwargs) + + def points_to_pixels(self, *args, **kwargs): + return self._renderer.points_to_pixels(*args, **kwargs) + + def strip_math(self, *args, **kwargs): + return self._renderer(*args, **kwargs) + + def finalize(self, *args, **kwargs): + return self._renderer.finalize(*args, **kwargs) + Modified: branches/transforms/lib/matplotlib/backends/backend_pdf.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_pdf.py 2007-11-20 20:21:53 UTC (rev 4397) +++ branches/transforms/lib/matplotlib/backends/backend_pdf.py 2007-11-20 21:00:20 UTC (rev 4398) @@ -20,10 +20,11 @@ from sets import Set import matplotlib -from matplotlib import __version__, rcParams, agg, get_data_path +from matplotlib import __version__, rcParams, get_data_path from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ FigureManagerBase, FigureCanvasBase +from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.cbook import Bunch, enumerate, is_string_like, reverse_dict, \ get_realpath_and_stat, is_writable_file_like from matplotlib.figure import Figure @@ -327,8 +328,9 @@ class PdfFile: """PDF file with one page.""" - def __init__(self, width, height, filename): + def __init__(self, width, height, dpi, filename): self.width, self.height = width, height + self.dpi = dpi self.nextObject = 1 # next free object id self.xrefTable = [ [0, 65535, 'the zero object'] ] self.passed_in_file_object = False @@ -379,7 +381,7 @@ thePage = { 'Type': Name('Page'), 'Parent': pagesObject, 'Resources': resourceObject, - 'MediaBox': [ 0, 0, 72*width, 72*height ], + 'MediaBox': [ 0, 0, dpi*width, dpi*height ], 'Contents': contentObject } self.writeObject(thePageObject, thePage) @@ -1003,8 +1005,9 @@ rgba = npy.fromstring(s, npy.uint8) rgba.shape = (h, w, 4) - rgb = rgba[:,:,:4] - return h, w, rgb.tostring() + rgb = rgba[:,:,:3] + a = rgba[:,:,3:] + return h, w, rgb.tostring(), a.tostring() def _gray(self, im, rc=0.3, gc=0.59, bc=0.11): rgbat = im.as_rgba_str() @@ -1022,20 +1025,36 @@ img.flipud_out() if img.is_grayscale: height, width, data = self._gray(img) - colorspace = Name('DeviceGray') + self.beginStream( + pair[1].id, + self.reserveObject('length of image stream'), + {'Type': Name('XObject'), 'Subtype': Name('Image'), + 'Width': width, 'Height': height, + 'ColorSpace': Name('DeviceGray'), 'BitsPerComponent': 8 }) + self.currentstream.write(data) # TODO: predictors (i.e., output png) + self.endStream() else: - height, width, data = self._rgb(img) - colorspace = Name('DeviceRGB') + height, width, data, adata = self._rgb(img) + smaskObject = self.reserveObject("smask") + stream = self.beginStream( + smaskObject.id, + self.reserveObject('length of smask stream'), + {'Type': Name('XObject'), 'Subtype': Name('Image'), + 'Width': width, 'Height': height, + 'ColorSpace': Name('DeviceGray'), 'BitsPerComponent': 8 }) + self.currentstream.write(adata) # TODO: predictors (i.e., output png) + self.endStream() - self.beginStream( - pair[1].id, - self.reserveObject('length of image stream'), - {'Type': Name('XObject'), 'Subtype': Name('Image'), - 'Width': width, 'Height': height, - 'ColorSpace': colorspace, 'BitsPerComponent': 8 }) - self.currentstream.write(data) # TODO: predictors (i.e., output png) - self.endStream() - + self.beginStream( + pair[1].id, + self.reserveObject('length of image stream'), + {'Type': Name('XObject'), 'Subtype': Name('Image'), + 'Width': width, 'Height': height, + 'ColorSpace': Name('DeviceRGB'), 'BitsPerComponent': 8, + 'SMask': smaskObject}) + self.currentstream.write(data) # TODO: predictors (i.e., output png) + self.endStream() + img.flipud_out() def markerObject(self, path, trans, fillp, lw): @@ -1152,7 +1171,7 @@ self.afm_font_cache = {} self.file.used_characters = self.used_characters = {} self.mathtext_parser = MathTextParser("Pdf") - self.image_magnification = dpi/72.0 + self.dpi = dpi self.tex_font_map = None def finalize(self): @@ -1194,19 +1213,16 @@ stat_key, (realpath, Set())) used_characters[1].update(set) - def get_image_magnification(self): - return self.image_magnification - def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None): #print >>sys.stderr, "draw_image called" # MGDTODO: Support clippath here gc = self.new_gc() - gc.set_clip_rectangle(bbox.bounds) + if bbox is not None: + gc.set_clip_rectangle(bbox) self.check_gc(gc) h, w = im.get_size_out() - h, w = h/self.image_magnification, w/self.image_magnification imob = self.file.imageObject(im) self.file.output(Op.gsave, w, 0, 0, h, x, y, Op.concat_matrix, imob, Op.use_xobject, Op.grestore) @@ -1246,7 +1262,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): # TODO: fix positioning and encoding width, height, descent, glyphs, rects, used_characters = \ - self.mathtext_parser.parse(s, 72, prop) + self.mathtext_parser.parse(s, self.dpi, prop) self.merge_used_characters(used_characters) # When using Type 3 fonts, we can't use character codes higher @@ -1311,7 +1327,7 @@ texmanager = self.get_texmanager() fontsize = prop.get_size_in_points() dvifile = texmanager.make_dvi(s, fontsize) - dvi = dviread.Dvi(dvifile, 72) + dvi = dviread.Dvi(dvifile, self.dpi) page = iter(dvi).next() dvi.close() @@ -1539,13 +1555,13 @@ texmanager = self.get_texmanager() fontsize = prop.get_size_in_points() dvifile = texmanager.make_dvi(s, fontsize) - dvi = dviread.Dvi(dvifile, 72) + dvi = dviread.Dvi(dvifile, self.dpi) page = iter(dvi).next() dvi.close() return page.width, page.height, page.descent if ismath: w, h, d, glyphs, rects, used_characters = \ - self.mathtext_parser.parse(s, 72, prop) + self.mathtext_parser.parse(s, self.dpi, prop) elif rcParams['pdf.use14corefonts']: font = self._get_font_afm(prop) @@ -1583,14 +1599,14 @@ font = FT2Font(str(filename)) self.truetype_font_cache[key] = font font.clear() - font.set_size(prop.get_size_in_points(), 72.0) + font.set_size(prop.get_size_in_points(), self.dpi) return font def flipy(self): return False def get_canvas_width_height(self): - return self.file.width / 72.0, self.file.height / 72.0 + return self.file.width / self.dpi, self.file.height / self.dpi def new_gc(self): return GraphicsContextPdf(self.file) @@ -1830,11 +1846,12 @@ return 'pdf' def print_pdf(self, filename, **kwargs): - dpi = kwargs.get('dpi', None) - self.figure.set_dpi(72) # Override the dpi kwarg + dpi = kwargs.get('dpi', 72) + self.figure.set_dpi(dpi) # Override the dpi kwarg width, height = self.figure.get_size_inches() - file = PdfFile(width, height, filename) - renderer = RendererPdf(file, dpi) + file = PdfFile(width, height, dpi, filename) + renderer = MixedModeRenderer( + width, height, dpi, RendererPdf(file, dpi)) self.figure.draw(renderer) renderer.finalize() file.close() Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-11-20 20:21:53 UTC (rev 4397) +++ branches/transforms/lib/matplotlib/collections.py 2007-11-20 21:00:20 UTC (rev 4398) @@ -453,11 +453,13 @@ offsets = transOffset.transform_non_affine(offsets) transOffset = transOffset.get_affine() + renderer.start_rasterizing() renderer.draw_quad_mesh( transform.frozen(), self.clipbox, clippath, clippath_trans, self._meshWidth, self._meshHeight, self._coordinates, offsets, transOffset, self._facecolors, self._antialiased, self._showedges) + renderer.stop_rasterizing() renderer.close_group(self.__class__.__name__) class PolyCollection(Collection): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |