Esempio n. 1
0
class RendererHTMLCanvas(RendererBase):
    def __init__(self, ctx, width, height, dpi, fig):
        super().__init__()
        self.fig = fig
        self.ctx = ctx
        self.width = width
        self.height = height
        self.ctx.width = self.width
        self.ctx.height = self.height
        self.dpi = dpi
        self.fontd = maxdict(50)
        self.mathtext_parser = MathTextParser("bitmap")

    def new_gc(self):
        return GraphicsContextHTMLCanvas(renderer=self)

    def points_to_pixels(self, points):
        return (points / 72.0) * self.dpi

    def _matplotlib_color_to_CSS(self,
                                 color,
                                 alpha,
                                 alpha_overrides,
                                 is_RGB=True):
        if not is_RGB:
            R, G, B, alpha = colorConverter.to_rgba(color)
            color = (R, G, B)

        if (len(color) == 4) and (alpha is None):
            alpha = color[3]

        if alpha is None:
            CSS_color = rgb2hex(color[:3])

        else:
            R = int(color[0] * 255)
            G = int(color[1] * 255)
            B = int(color[2] * 255)
            if len(color) == 3 or alpha_overrides:
                CSS_color = f"""rgba({R:d}, {G:d}, {B:d}, {alpha:.3g})"""
            else:
                CSS_color = """rgba({:d}, {:d}, {:d}, {:.3g})""".format(
                    R, G, B, color[3])

        return CSS_color

    def _set_style(self, gc, rgbFace=None):
        if rgbFace is not None:
            self.ctx.fillStyle = self._matplotlib_color_to_CSS(
                rgbFace, gc.get_alpha(), gc.get_forced_alpha())

        if gc.get_capstyle():
            self.ctx.lineCap = _capstyle_d[gc.get_capstyle()]

        self.ctx.strokeStyle = self._matplotlib_color_to_CSS(
            gc.get_rgb(), gc.get_alpha(), gc.get_forced_alpha())

        self.ctx.lineWidth = self.points_to_pixels(gc.get_linewidth())

    def _path_helper(self, ctx, path, transform, clip=None):
        ctx.beginPath()
        for points, code in path.iter_segments(transform,
                                               remove_nans=True,
                                               clip=clip):
            if code == Path.MOVETO:
                ctx.moveTo(points[0], points[1])
            elif code == Path.LINETO:
                ctx.lineTo(points[0], points[1])
            elif code == Path.CURVE3:
                ctx.quadraticCurveTo(*points)
            elif code == Path.CURVE4:
                ctx.bezierCurveTo(*points)
            elif code == Path.CLOSEPOLY:
                ctx.closePath()

    def draw_path(self, gc, path, transform, rgbFace=None):
        self._set_style(gc, rgbFace)
        if rgbFace is None and gc.get_hatch() is None:
            figure_clip = (0, 0, self.width, self.height)

        else:
            figure_clip = None

        transform += Affine2D().scale(1, -1).translate(0, self.height)
        self._path_helper(self.ctx, path, transform, figure_clip)

        if rgbFace is not None:
            self.ctx.fill()
            self.ctx.fillStyle = "#000000"

        if gc.stroke:
            self.ctx.stroke()

    def draw_markers(self,
                     gc,
                     marker_path,
                     marker_trans,
                     path,
                     trans,
                     rgbFace=None):
        super().draw_markers(gc, marker_path, marker_trans, path, trans,
                             rgbFace)

    def draw_image(self, gc, x, y, im, transform=None):
        im = np.flipud(im)
        h, w, d = im.shape
        y = self.ctx.height - y - h
        im = np.ravel(np.uint8(np.reshape(im, (h * w * d, -1)))).tobytes()
        pixels_proxy = create_proxy(im)
        pixels_buf = pixels_proxy.getBuffer("u8clamped")
        img_data = ImageData.new(pixels_buf.data, w, h)
        self.ctx.save()
        in_memory_canvas = document.createElement("canvas")
        in_memory_canvas.width = w
        in_memory_canvas.height = h
        in_memory_canvas_context = in_memory_canvas.getContext("2d")
        in_memory_canvas_context.putImageData(img_data, 0, 0)
        self.ctx.drawImage(in_memory_canvas, x, y, w, h)
        self.ctx.restore()
        pixels_proxy.destroy()
        pixels_buf.release()

    def _get_font(self, prop):
        key = hash(prop)
        font_value = self.fontd.get(key)
        if font_value is None:
            fname = findfont(prop)
            font_value = self.fontd.get(fname)
            if font_value is None:
                font = FT2Font(str(fname))
                font_file_name = fname[fname.rfind("/") + 1:]
                font_value = font, font_file_name
                self.fontd[fname] = font_value
            self.fontd[key] = font_value
        font, font_file_name = font_value
        font.clear()
        font.set_size(prop.get_size_in_points(), self.dpi)
        return font, font_file_name

    def get_text_width_height_descent(self, s, prop, ismath):
        if ismath:
            image, d = self.mathtext_parser.parse(s, self.dpi, prop)
            w, h = image.get_width(), image.get_height()
        else:
            font, _ = self._get_font(prop)
            font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
            w, h = font.get_width_height()
            w /= 64.0
            h /= 64.0
            d = font.get_descent() / 64.0
        return w, h, d

    def _draw_math_text(self, gc, x, y, s, prop, angle):
        rgba, descent = self.mathtext_parser.to_rgba(s, gc.get_rgb(), self.dpi,
                                                     prop.get_size_in_points())
        height, width, _ = rgba.shape
        angle = math.radians(angle)
        if angle != 0:
            self.ctx.save()
            self.ctx.translate(x, y)
            self.ctx.rotate(-angle)
            self.ctx.translate(-x, -y)
        self.draw_image(gc, x, -y - descent, np.flipud(rgba))
        if angle != 0:
            self.ctx.restore()

    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
        def _load_font_into_web(loaded_face):
            document.fonts.add(loaded_face)
            window.font_counter += 1
            self.fig.draw_idle()

        if ismath:
            self._draw_math_text(gc, x, y, s, prop, angle)
            return
        angle = math.radians(angle)
        width, height, descent = self.get_text_width_height_descent(
            s, prop, ismath)
        x -= math.sin(angle) * descent
        y -= math.cos(angle) * descent - self.ctx.height
        font_size = self.points_to_pixels(prop.get_size_in_points())

        _, font_file_name = self._get_font(prop)

        font_face_arguments = (
            prop.get_name(),
            f"url({_base_fonts_url + font_file_name})",
        )

        # The following snippet loads a font into the browser's
        # environment if it wasn't loaded before. This check is necessary
        # to help us avoid loading the same font multiple times. Further,
        # it helps us to avoid the infinite loop of
        # load font --> redraw --> load font --> redraw --> ....

        if font_face_arguments not in _font_set:
            _font_set.add(font_face_arguments)
            f = FontFace.new(*font_face_arguments)
            f.load().then(_load_font_into_web)

        font_property_string = "{} {} {:.3g}px {}, {}".format(
            prop.get_style(),
            prop.get_weight(),
            font_size,
            prop.get_name(),
            prop.get_family()[0],
        )
        if angle != 0:
            self.ctx.save()
            self.ctx.translate(x, y)
            self.ctx.rotate(-angle)
            self.ctx.translate(-x, -y)
        self.ctx.font = font_property_string
        self.ctx.fillStyle = self._matplotlib_color_to_CSS(
            gc.get_rgb(), gc.get_alpha(), gc.get_forced_alpha())
        self.ctx.fillText(s, x, y)
        self.ctx.fillStyle = "#000000"
        if angle != 0:
            self.ctx.restore()
Esempio n. 2
0
class RendererH5Canvas(RendererBase):
    """The renderer handles drawing/rendering operations."""
    fontd = maxdict(50)

    def __init__(self, width, height, ctx, dpi=72):
        self.width = width
        self.height = height
        self.dpi = dpi
        self.ctx = ctx
        self._image_count = 0
         # used to uniquely label each image created in this figure...
         # define the js context
        self.ctx.width = width
        self.ctx.height = height
        #self.ctx.textAlign = "center";
        self.ctx.textBaseline = "alphabetic"
        self.flip = Affine2D().scale(1, -1).translate(0, height)
        self.mathtext_parser = MathTextParser('bitmap')
        self._path_time = 0
        self._text_time = 0
        self._marker_time = 0
        self._sub_time = 0
        self._last_clip = None
        self._last_clip_path = None
        self._clip_count = 0

    def _set_style(self, gc, rgbFace=None):
        ctx = self.ctx
        if rgbFace is not None:
            ctx.fillStyle = mpl_to_css_color(rgbFace, gc.get_alpha())
        ctx.strokeStyle = mpl_to_css_color(gc.get_rgb(), gc.get_alpha())
        if gc.get_capstyle():
            ctx.lineCap = _capstyle_d[gc.get_capstyle()]
        ctx.lineWidth = self.points_to_pixels(gc.get_linewidth())

    def _path_to_h5(self, ctx, path, transform, clip=None, stroke=True, dashes=(None, None)):
        """Iterate over a path and produce h5 drawing directives."""
        transform = transform + self.flip
        ctx.beginPath()
        current_point = None
        dash_offset, dash_pattern = dashes
        if dash_pattern is not None:
            dash_offset = self.points_to_pixels(dash_offset)
            dash_pattern = tuple([self.points_to_pixels(dash) for dash in dash_pattern])
        for points, code in path.iter_segments(transform, clip=clip):
            # Shift all points by half a pixel, so that integer coordinates are aligned with pixel centers instead of edges
            # This prevents lines that are one pixel wide and aligned with the pixel grid from being rendered as a two-pixel wide line
            # This happens because HTML Canvas defines (0, 0) as the *top left* of a pixel instead of the center,
            # which causes all integer-valued coordinates to fall exactly between pixels
            points += 0.5
            if code == Path.MOVETO:
                ctx.moveTo(points[0], points[1])
                current_point = (points[0], points[1])
            elif code == Path.LINETO:
                t = time.time()
                if (dash_pattern is None) or (current_point is None):
                    ctx.lineTo(points[0], points[1])
                else:
                    dash_offset = ctx.dashedLine(current_point[0], current_point[1], points[0], points[1], (dash_offset, dash_pattern))
                self._sub_time += time.time() - t
                current_point = (points[0], points[1])
            elif code == Path.CURVE3:
                ctx.quadraticCurveTo(*points)
                current_point = (points[2], points[3])
            elif code == Path.CURVE4:
                ctx.bezierCurveTo(*points)
                current_point = (points[4], points[5])
            else:
                pass
        if stroke: ctx.stroke()

    def _do_path_clip(self, ctx, clip):
        self._clip_count += 1
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(clip[0],clip[1])
        ctx.lineTo(clip[2],clip[1])
        ctx.lineTo(clip[2],clip[3])
        ctx.lineTo(clip[0],clip[3])
        ctx.clip()

    def draw_path(self, gc, path, transform, rgbFace=None):
        t = time.time()
        self._set_style(gc, rgbFace)
        clip = self._get_gc_clip_svg(gc)
        clippath, cliptrans = gc.get_clip_path()
        ctx = self.ctx
        if clippath is not None and self._last_clip_path != clippath:
            ctx.restore()
            ctx.save()
            self._path_to_h5(ctx, clippath, cliptrans, None, stroke=False)
            ctx.clip()
            self._last_clip_path = clippath
        if self._last_clip != clip and clip is not None and clippath is None:
            ctx.restore()
            self._do_path_clip(ctx, clip)
            self._last_clip = clip
        if clip is None and clippath is None and (self._last_clip is not None or self._last_clip_path is not None): self._reset_clip()
        if rgbFace is None and gc.get_hatch() is None:
            figure_clip = (0, 0, self.width, self.height)
        else:
            figure_clip = None
        self._path_to_h5(ctx, path, transform, figure_clip, dashes=gc.get_dashes())
        if rgbFace is not None:
            ctx.fill()
            ctx.fillStyle = '#000000'
        self._path_time += time.time() - t

    def _get_gc_clip_svg(self, gc):
        cliprect = gc.get_clip_rectangle()
        if cliprect is not None:
            x, y, w, h = cliprect.bounds
            y = self.height-(y+h)
            return (x,y,x+w,y+h)
        return None

    def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None):
        t = time.time()
        for vertices, codes in path.iter_segments(trans, simplify=False):
            if len(vertices):
                x,y = vertices[-2:]
                self._set_style(gc, rgbFace)
                clip = self._get_gc_clip_svg(gc)
                ctx = self.ctx
                self._path_to_h5(ctx, marker_path, marker_trans + Affine2D().translate(x, y), clip)
                if rgbFace is not None:
                    ctx.fill()
                    ctx.fillStyle = '#000000'
        self._marker_time += time.time() - t

    def _slipstream_png(self, x, y, im_buffer, width, height):
        """Insert image directly into HTML canvas as base64-encoded PNG."""
        # Shift x, y (top left corner) to the nearest CSS pixel edge, to prevent resampling and consequent image blurring
        x = math.floor(x + 0.5)
        y = math.floor(y + 1.5)
        # Write the image into a WebPNG object
        f = WebPNG()
        _png.write_png(im_buffer, width, height, f)
        # Write test PNG as file as well
        #_png.write_png(im_buffer, width, height, 'canvas_image_%d.png' % (self._image_count,))
        # Extract the base64-encoded PNG and send it to the canvas
        uname = str(uuid.uuid1()).replace("-","") #self.ctx._context_name + str(self._image_count)
         # try to use a unique image name
        enc = "var canvas_image_%s = 'data:image/png;base64,%s';" % (uname, f.get_b64())
        s = "function imageLoaded_%s(ev) {\nim = ev.target;\nim_left_to_load_%s -=1;\nif (im_left_to_load_%s == 0) frame_body_%s();\n}\ncanv_im_%s = new Image();\ncanv_im_%s.onload = imageLoaded_%s;\ncanv_im_%s.src = canvas_image_%s;\n" % \
            (uname, self.ctx._context_name, self.ctx._context_name, self.ctx._context_name, uname, uname, uname, uname, uname)
        self.ctx.add_header(enc)
        self.ctx.add_header(s)
         # Once the base64 encoded image has been received, draw it into the canvas
        self.ctx.write("%s.drawImage(canv_im_%s, %g, %g, %g, %g);" % (self.ctx._context_name, uname, x, y, width, height))
         # draw the image as loaded into canv_im_%d...
        self._image_count += 1

    def _reset_clip(self):
        self.ctx.restore()
        self._last_clip = None
        self._last_clip_path = None

     #<1.0.0: def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None):
     #1.0.0 and up: def draw_image(self, gc, x, y, im, clippath=None):
     #API for draw image changed between 0.99 and 1.0.0
    def draw_image(self, *args, **kwargs):
        x, y, im = args[:3]
        try:
            h,w = im.get_size_out()
        except AttributeError:
            x, y, im = args[1:4]
            h,w = im.get_size_out()
        clippath = (kwargs.has_key('clippath') and kwargs['clippath'] or None)
        if self._last_clip is not None or self._last_clip_path is not None: self._reset_clip()
        if clippath is not None:
            self._path_to_h5(self.ctx,clippath, clippath_trans, stroke=False)
            self.ctx.save()
            self.ctx.clip()
        (x,y) = self.flip.transform((x,y))
        im.flipud_out()
        rows, cols, im_buffer = im.as_rgba_str()
        self._slipstream_png(x, (y-h), im_buffer, cols, rows)
        if clippath is not None:
            self.ctx.restore()

    def _get_font(self, prop):
        key = hash(prop)
        font = self.fontd.get(key)
        if font is None:
            fname = findfont(prop)
            font = self.fontd.get(fname)
            if font is None:
                font = FT2Font(str(fname))
                self.fontd[fname] = font
            self.fontd[key] = font
        font.clear()
        font.set_size(prop.get_size_in_points(), self.dpi)
        return font

    def draw_tex(self, gc, x, y, s, prop, angle, ismath=False):
        logger.error("Tex support is currently not implemented. Text element '%s' will not be displayed..." % s)

    def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
        if self._last_clip is not None or self._last_clip_path is not None: self._reset_clip()
        t = time.time()
        if ismath:
            self._draw_mathtext(gc, x, y, s, prop, angle)
            return
        angle = math.radians(angle)
        width, height, descent = self.get_text_width_height_descent(s, prop, ismath)
        x -= math.sin(angle) * descent
        y -= math.cos(angle) * descent
        ctx = self.ctx
        if angle != 0:
            ctx.save()
            ctx.translate(x, y)
            ctx.rotate(-angle)
            ctx.translate(-x, -y)
        font_size = self.points_to_pixels(prop.get_size_in_points())
        font_str = '%s %s %.3gpx %s, %s' % (prop.get_style(), prop.get_weight(), font_size, prop.get_name(), prop.get_family()[0])
        ctx.font = font_str
        # Set the text color, draw the text and reset the color to black afterwards
        ctx.fillStyle = mpl_to_css_color(gc.get_rgb(), gc.get_alpha())
        ctx.fillText(unicode(s), x, y)
        ctx.fillStyle = '#000000'
        if angle != 0:
            ctx.restore()
        self._text_time = time.time() - t

    def _draw_mathtext(self, gc, x, y, s, prop, angle):
        """Draw math text using matplotlib.mathtext."""
        # Render math string as an image at the configured DPI, and get the image dimensions and baseline depth
        rgba, descent = self.mathtext_parser.to_rgba(s, color=gc.get_rgb(), dpi=self.dpi, fontsize=prop.get_size_in_points())
        height, width, tmp = rgba.shape
        angle = math.radians(angle)
        # Shift x, y (top left corner) to the nearest CSS pixel edge, to prevent resampling and consequent image blurring
        x = math.floor(x + 0.5)
        y = math.floor(y + 1.5)
        ctx = self.ctx
        if angle != 0:
            ctx.save()
            ctx.translate(x, y)
            ctx.rotate(-angle)
            ctx.translate(-x, -y)
        # Insert math text image into stream, and adjust x, y reference point to be at top left of image
        self._slipstream_png(x, y - height, rgba.tostring(), width, height)
        if angle != 0:
            ctx.restore()

    def flipy(self):
        return True

    def get_canvas_width_height(self):
        return self.width, self.height

    def get_text_width_height_descent(self, s, prop, ismath):
        if ismath:
            image, d = self.mathtext_parser.parse(s, self.dpi, prop)
            w, h = image.get_width(), image.get_height()
        else:
            font = self._get_font(prop)
            font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
            w, h = font.get_width_height()
            w /= 64.0  # convert from subpixels
            h /= 64.0
            d = font.get_descent() / 64.0
        return w, h, d

    def new_gc(self):
        return GraphicsContextH5Canvas()

    def points_to_pixels(self, points):
        # The standard desktop-publishing (Postscript) point is 1/72 of an inch
        return points/72.0 * self.dpi
Esempio n. 3
0
class RendererH5Canvas(RendererBase):
    """The renderer handles drawing/rendering operations."""
    fontd = maxdict(50)

    def __init__(self, width, height, ctx, dpi=72):
        self.width = width
        self.height = height
        self.dpi = dpi
        self.ctx = ctx
        self._image_count = 0
        # used to uniquely label each image created in this figure...
        # define the js context
        self.ctx.width = width
        self.ctx.height = height
        #self.ctx.textAlign = "center";
        self.ctx.textBaseline = "alphabetic"
        self.flip = Affine2D().scale(1, -1).translate(0, height)
        self.mathtext_parser = MathTextParser('bitmap')
        self._path_time = 0
        self._text_time = 0
        self._marker_time = 0
        self._sub_time = 0
        self._last_clip = None
        self._last_clip_path = None
        self._clip_count = 0

    def _set_style(self, gc, rgbFace=None):
        ctx = self.ctx
        if rgbFace is not None:
            ctx.fillStyle = mpl_to_css_color(rgbFace, gc.get_alpha())
        ctx.strokeStyle = mpl_to_css_color(gc.get_rgb(), gc.get_alpha())
        if gc.get_capstyle():
            ctx.lineCap = _capstyle_d[gc.get_capstyle()]
        ctx.lineWidth = self.points_to_pixels(gc.get_linewidth())

    def _path_to_h5(self,
                    ctx,
                    path,
                    transform,
                    clip=None,
                    stroke=True,
                    dashes=(None, None)):
        """Iterate over a path and produce h5 drawing directives."""
        transform = transform + self.flip
        ctx.beginPath()
        current_point = None
        dash_offset, dash_pattern = dashes
        if dash_pattern is not None:
            dash_offset = self.points_to_pixels(dash_offset)
            dash_pattern = tuple(
                [self.points_to_pixels(dash) for dash in dash_pattern])
        for points, code in path.iter_segments(transform, clip=clip):
            # Shift all points by half a pixel, so that integer coordinates are aligned with pixel centers instead of edges
            # This prevents lines that are one pixel wide and aligned with the pixel grid from being rendered as a two-pixel wide line
            # This happens because HTML Canvas defines (0, 0) as the *top left* of a pixel instead of the center,
            # which causes all integer-valued coordinates to fall exactly between pixels
            points += 0.5
            if code == Path.MOVETO:
                ctx.moveTo(points[0], points[1])
                current_point = (points[0], points[1])
            elif code == Path.LINETO:
                t = time.time()
                if (dash_pattern is None) or (current_point is None):
                    ctx.lineTo(points[0], points[1])
                else:
                    dash_offset = ctx.dashedLine(current_point[0],
                                                 current_point[1], points[0],
                                                 points[1],
                                                 (dash_offset, dash_pattern))
                self._sub_time += time.time() - t
                current_point = (points[0], points[1])
            elif code == Path.CURVE3:
                ctx.quadraticCurveTo(*points)
                current_point = (points[2], points[3])
            elif code == Path.CURVE4:
                ctx.bezierCurveTo(*points)
                current_point = (points[4], points[5])
            else:
                pass
        if stroke: ctx.stroke()

    def _do_path_clip(self, ctx, clip):
        self._clip_count += 1
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(clip[0], clip[1])
        ctx.lineTo(clip[2], clip[1])
        ctx.lineTo(clip[2], clip[3])
        ctx.lineTo(clip[0], clip[3])
        ctx.clip()

    def draw_path(self, gc, path, transform, rgbFace=None):
        t = time.time()
        self._set_style(gc, rgbFace)
        clip = self._get_gc_clip_svg(gc)
        clippath, cliptrans = gc.get_clip_path()
        ctx = self.ctx
        if clippath is not None and self._last_clip_path != clippath:
            ctx.restore()
            ctx.save()
            self._path_to_h5(ctx, clippath, cliptrans, None, stroke=False)
            ctx.clip()
            self._last_clip_path = clippath
        if self._last_clip != clip and clip is not None and clippath is None:
            ctx.restore()
            self._do_path_clip(ctx, clip)
            self._last_clip = clip
        if clip is None and clippath is None and (self._last_clip is not None
                                                  or self._last_clip_path
                                                  is not None):
            self._reset_clip()
        if rgbFace is None and gc.get_hatch() is None:
            figure_clip = (0, 0, self.width, self.height)
        else:
            figure_clip = None
        self._path_to_h5(ctx,
                         path,
                         transform,
                         figure_clip,
                         dashes=gc.get_dashes())
        if rgbFace is not None:
            ctx.fill()
            ctx.fillStyle = '#000000'
        self._path_time += time.time() - t

    def _get_gc_clip_svg(self, gc):
        cliprect = gc.get_clip_rectangle()
        if cliprect is not None:
            x, y, w, h = cliprect.bounds
            y = self.height - (y + h)
            return (x, y, x + w, y + h)
        return None

    def draw_markers(self,
                     gc,
                     marker_path,
                     marker_trans,
                     path,
                     trans,
                     rgbFace=None):
        t = time.time()
        for vertices, codes in path.iter_segments(trans, simplify=False):
            if len(vertices):
                x, y = vertices[-2:]
                self._set_style(gc, rgbFace)
                clip = self._get_gc_clip_svg(gc)
                ctx = self.ctx
                self._path_to_h5(ctx, marker_path,
                                 marker_trans + Affine2D().translate(x, y),
                                 clip)
                if rgbFace is not None:
                    ctx.fill()
                    ctx.fillStyle = '#000000'
        self._marker_time += time.time() - t

    def _slipstream_png(self, x, y, im_buffer, width, height):
        """Insert image directly into HTML canvas as base64-encoded PNG."""
        # Shift x, y (top left corner) to the nearest CSS pixel edge, to prevent resampling and consequent image blurring
        x = math.floor(x + 0.5)
        y = math.floor(y + 1.5)
        # Write the image into a WebPNG object
        f = WebPNG()
        _png.write_png(im_buffer, width, height, f)
        # Write test PNG as file as well
        #_png.write_png(im_buffer, width, height, 'canvas_image_%d.png' % (self._image_count,))
        # Extract the base64-encoded PNG and send it to the canvas
        uname = str(uuid.uuid1()).replace(
            "-", "")  #self.ctx._context_name + str(self._image_count)
        # try to use a unique image name
        enc = "var canvas_image_%s = 'data:image/png;base64,%s';" % (
            uname, f.get_b64())
        s = "function imageLoaded_%s(ev) {\nim = ev.target;\nim_left_to_load_%s -=1;\nif (im_left_to_load_%s == 0) frame_body_%s();\n}\ncanv_im_%s = new Image();\ncanv_im_%s.onload = imageLoaded_%s;\ncanv_im_%s.src = canvas_image_%s;\n" % \
            (uname, self.ctx._context_name, self.ctx._context_name, self.ctx._context_name, uname, uname, uname, uname, uname)
        self.ctx.add_header(enc)
        self.ctx.add_header(s)
        # Once the base64 encoded image has been received, draw it into the canvas
        self.ctx.write("%s.drawImage(canv_im_%s, %g, %g, %g, %g);" %
                       (self.ctx._context_name, uname, x, y, width, height))
        # draw the image as loaded into canv_im_%d...
        self._image_count += 1

    def _reset_clip(self):
        self.ctx.restore()
        self._last_clip = None
        self._last_clip_path = None

    #<1.0.0: def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None):
    #1.0.0 and up: def draw_image(self, gc, x, y, im, clippath=None):
    #API for draw image changed between 0.99 and 1.0.0
    def draw_image(self, *args, **kwargs):
        x, y, im = args[:3]
        try:
            h, w = im.get_size_out()
        except AttributeError:
            x, y, im = args[1:4]
            h, w = im.get_size_out()
        clippath = (kwargs.has_key('clippath') and kwargs['clippath'] or None)
        if self._last_clip is not None or self._last_clip_path is not None:
            self._reset_clip()
        if clippath is not None:
            self._path_to_h5(self.ctx, clippath, clippath_trans, stroke=False)
            self.ctx.save()
            self.ctx.clip()
        (x, y) = self.flip.transform((x, y))
        im.flipud_out()
        rows, cols, im_buffer = im.as_rgba_str()
        self._slipstream_png(x, (y - h), im_buffer, cols, rows)
        if clippath is not None:
            self.ctx.restore()

    def _get_font(self, prop):
        key = hash(prop)
        font = self.fontd.get(key)
        if font is None:
            fname = findfont(prop)
            font = self.fontd.get(fname)
            if font is None:
                font = FT2Font(str(fname))
                self.fontd[fname] = font
            self.fontd[key] = font
        font.clear()
        font.set_size(prop.get_size_in_points(), self.dpi)
        return font

    def draw_tex(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
        logger.error(
            "Tex support is currently not implemented. Text element '%s' will not be displayed..."
            % s)

    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
        if self._last_clip is not None or self._last_clip_path is not None:
            self._reset_clip()
        t = time.time()
        if ismath:
            self._draw_mathtext(gc, x, y, s, prop, angle)
            return
        angle = math.radians(angle)
        width, height, descent = self.get_text_width_height_descent(
            s, prop, ismath)
        x -= math.sin(angle) * descent
        y -= math.cos(angle) * descent
        ctx = self.ctx
        if angle != 0:
            ctx.save()
            ctx.translate(x, y)
            ctx.rotate(-angle)
            ctx.translate(-x, -y)
        font_size = self.points_to_pixels(prop.get_size_in_points())
        font_str = '%s %s %.3gpx %s, %s' % (prop.get_style(), prop.get_weight(
        ), font_size, prop.get_name(), prop.get_family()[0])
        ctx.font = font_str
        # Set the text color, draw the text and reset the color to black afterwards
        ctx.fillStyle = mpl_to_css_color(gc.get_rgb(), gc.get_alpha())
        ctx.fillText(unicode(s), x, y)
        ctx.fillStyle = '#000000'
        if angle != 0:
            ctx.restore()
        self._text_time = time.time() - t

    def _draw_mathtext(self, gc, x, y, s, prop, angle):
        """Draw math text using matplotlib.mathtext."""
        # Render math string as an image at the configured DPI, and get the image dimensions and baseline depth
        rgba, descent = self.mathtext_parser.to_rgba(
            s,
            color=gc.get_rgb(),
            dpi=self.dpi,
            fontsize=prop.get_size_in_points())
        height, width, tmp = rgba.shape
        angle = math.radians(angle)
        # Shift x, y (top left corner) to the nearest CSS pixel edge, to prevent resampling and consequent image blurring
        x = math.floor(x + 0.5)
        y = math.floor(y + 1.5)
        ctx = self.ctx
        if angle != 0:
            ctx.save()
            ctx.translate(x, y)
            ctx.rotate(-angle)
            ctx.translate(-x, -y)
        # Insert math text image into stream, and adjust x, y reference point to be at top left of image
        self._slipstream_png(x, y - height, rgba.tostring(), width, height)
        if angle != 0:
            ctx.restore()

    def flipy(self):
        return True

    def get_canvas_width_height(self):
        return self.width, self.height

    def get_text_width_height_descent(self, s, prop, ismath):
        if ismath:
            image, d = self.mathtext_parser.parse(s, self.dpi, prop)
            w, h = image.get_width(), image.get_height()
        else:
            font = self._get_font(prop)
            font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
            w, h = font.get_width_height()
            w /= 64.0  # convert from subpixels
            h /= 64.0
            d = font.get_descent() / 64.0
        return w, h, d

    def new_gc(self):
        return GraphicsContextH5Canvas()

    def points_to_pixels(self, points):
        # The standard desktop-publishing (Postscript) point is 1/72 of an inch
        return points / 72.0 * self.dpi