def create_surface(self): bw, bh = self.size old_backing = self._backing #should we honour self.depth here? self._backing = None if bw==0 or bh==0: #this can happen during cleanup return None backing = ImageSurface(FORMAT_ARGB32, bw, bh) self._backing = backing cr = Context(backing) if self._alpha_enabled: cr.set_operator(OPERATOR_CLEAR) cr.set_source_rgba(1, 1, 1, 0) else: cr.set_operator(OPERATOR_SOURCE) cr.set_source_rgba(1, 1, 1, 1) cr.rectangle(0, 0, bw, bh) cr.fill() if COPY_OLD_BACKING and old_backing is not None: oldw, oldh = old_backing.get_width(), old_backing.get_height() sx, sy, dx, dy, w, h = self.gravity_copy_coords(oldw, oldh, bw, bh) cr.translate(dx-sx, dy-sy) cr.rectangle(sx, sy, w, h) cr.clip() cr.set_operator(OPERATOR_SOURCE) cr.set_source_surface(old_backing, 0, 0) cr.paint() backing.flush() return cr
def svg_to_png(filename, icondata, w=128, h=128): if not SVG_TO_PNG: return None Rsvg = load_Rsvg() log("svg_to_png%s Rsvg=%s", (filename, "%i bytes" % len(icondata), w, h), Rsvg) if not Rsvg: return None try: from cairo import ImageSurface, Context, FORMAT_ARGB32 #pylint: disable=no-name-in-module, import-outside-toplevel #'\sinkscape:[a-zA-Z]*=["a-zA-Z0-9]*' img = ImageSurface(FORMAT_ARGB32, w, h) ctx = Context(img) handle = Rsvg.Handle.new_from_data(icondata) dim = handle.get_dimensions() ctx.scale(w / dim.width, h / dim.height) handle.render_cairo(ctx) del handle img.flush() buf = BytesIO() img.write_to_png(buf) icondata = buf.getvalue() buf.close() img.finish() return icondata except Exception: log("svg_to_png%s", (icondata, w, h), exc_info=True) if re.findall(INKSCAPE_RE, icondata): #try again after stripping the bogus inkscape attributes #as some rsvg versions can't handle that (ie: Debian Bullseye) icondata = re.sub(INKSCAPE_RE, b"", icondata) return svg_to_png(filename, icondata, w, h) if icondata.find(INKSCAPE_BROKEN_SODIPODI_DTD) > 0: icondata = icondata.replace(INKSCAPE_BROKEN_SODIPODI_DTD, INKSCAPE_SODIPODI_DTD) return svg_to_png(filename, icondata, w, h) log.error("Error: failed to convert svg icon") if filename: log.error(" '%s':", filename) log.error(" %i bytes, %s", len(icondata), ellipsizer(icondata)) return None
def _to_png(self, font, font_position=None, dst=None, limit=800, size=1500, tab_width=1500, padding_characters=""): """Use HB, FreeType and Cairo to produce a png for a table. Parameters ---------- font: DFont font_position: str Label indicating which font has been used. dst: str Path to output image. If no path is given, return in-memory """ # TODO (M Foley) better packaging for pycairo, freetype-py # and uharfbuzz. # Users should be able to pip install these bindings without needing # to install the correct libs. # A special mention to the individuals who maintain these packages. Using # these dependencies has sped up the process of creating diff images # significantly. It's an incredible age we live in. y_tab = int(1500 / 25) x_tab = int(tab_width / 64) width, height = 1024, 200 cells_per_row = int((width - x_tab) / x_tab) # Compute height of image x, y, baseline = x_tab, 0, 0 for idx, row in enumerate(self._data[:limit]): x += x_tab if idx % cells_per_row == 0: y += y_tab x = x_tab height += y height += 100 # draw image Z = ImageSurface(FORMAT_ARGB32, width, height) ctx = Context(Z) ctx.rectangle(0, 0, width, height) ctx.set_source_rgb(1, 1, 1) ctx.fill() # label image ctx.set_font_size(30) ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.move_to(x_tab, 50) ctx.show_text("{}: {}".format(self.table_name, len(self._data))) ctx.move_to(x_tab, 100) if font_position: ctx.show_text("Font Set: {}".format(font_position)) if len(self._data) > limit: ctx.set_font_size(20) ctx.move_to(x_tab, 150) ctx.show_text( "Warning: {} different items. Only showing most serious {}". format(len(self._data), limit)) hb.ot_font_set_funcs(font.hbfont) # Draw glyphs x, y, baseline = x_tab, 200, 0 x_pos = x_tab y_pos = 200 for idx, row in enumerate(self._data[:limit]): string = "{}{}{}".format(padding_characters, row['string'], padding_characters) buf = self._shape_string(font, string, row['features']) char_info = buf.glyph_infos char_pos = buf.glyph_positions for info, pos in zip(char_info, char_pos): gid = info.codepoint font.ftfont.load_glyph(gid, flags=6) bitmap = font.ftslot.bitmap if bitmap.width > 0: ctx.set_source_rgb(0, 0, 0) glyph_surface = _make_image_surface( font.ftfont.glyph.bitmap, copy=False) ctx.set_source_surface( glyph_surface, x_pos + font.ftslot.bitmap_left + (pos.x_offset / 64.), y_pos - font.ftslot.bitmap_top - (pos.y_offset / 64.)) glyph_surface.flush() ctx.paint() x_pos += (pos.x_advance) / 64. y_pos += (pos.y_advance) / 64. x_pos += x_tab - (x_pos % x_tab) if idx % cells_per_row == 0: # add label if font_position: ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.set_font_size(10) ctx.move_to(width - 20, y_pos) ctx.rotate(1.5708) ctx.show_text(font_position) ctx.set_source_rgb(0, 0, 0) ctx.rotate(-1.5708) # Start a new row y_pos += y_tab x_pos = x_tab Z.flush() if dst: Z.write_to_png(dst) else: img = StringIO() Z.write_to_png(img) return Image.open(img)
def make_label(text, filename, size=12, angle=0): ''' Parameters: ----------- text : string Text to be displayed filename : string Path to a font size : int Font size in 1/64th points angle : float Text angle in degrees ''' face = Face(filename) face.set_char_size( size*64 ) # FT_Angle is a 16.16 fixed-point value expressed in degrees. angle = FT_Angle(angle * 65536) matrix = FT_Matrix( FT_Cos( angle ), - FT_Sin( angle ), FT_Sin( angle ) , FT_Cos( angle ) ) flags = FT_LOAD_RENDER pen = FT_Vector(0,0) FT_Set_Transform( face._FT_Face, byref(matrix), byref(pen) ) previous = 0 xmin, xmax = 0, 0 ymin, ymax = 0, 0 for c in text: face.load_char(c, flags) kerning = face.get_kerning(previous, c) previous = c bitmap = face.glyph.bitmap pitch = face.glyph.bitmap.pitch width = face.glyph.bitmap.width rows = face.glyph.bitmap.rows top = face.glyph.bitmap_top left = face.glyph.bitmap_left pen.x += kerning.x x0 = (pen.x >> 6) + left x1 = x0 + width y0 = (pen.y >> 6) - (rows - top) y1 = y0 + rows xmin, xmax = min(xmin, x0), max(xmax, x1) ymin, ymax = min(ymin, y0), max(ymax, y1) pen.x += face.glyph.advance.x pen.y += face.glyph.advance.y L = ImageSurface(FORMAT_A8, xmax-xmin, ymax-ymin) previous = 0 pen.x, pen.y = 0, 0 ctx = Context(L) for c in text: face.load_char(c, flags) kerning = face.get_kerning(previous, c) previous = c bitmap = face.glyph.bitmap pitch = face.glyph.bitmap.pitch width = face.glyph.bitmap.width rows = face.glyph.bitmap.rows top = face.glyph.bitmap_top left = face.glyph.bitmap_left pen.x += kerning.x x = (pen.x >> 6) - xmin + left y = - (pen.y >> 6) + ymax - top if (width > 0): glyph_surface = make_image_surface(face.glyph.bitmap) ctx.set_source_surface(glyph_surface, x, y) ctx.paint() pen.x += face.glyph.advance.x pen.y += face.glyph.advance.y L.flush() return L
angle = random.randint(-25,25) try: L = make_label('Hello', './Vera.ttf', size, angle=angle) except NotImplementedError: raise SystemExit("For python 3.x, you need pycairo >= 1.11+ (from https://github.com/pygobject/pycairo)") h = L.get_height() w = L.get_width() if h < H and w < W: x0 = W//2 + (random.uniform()-.1)*50 y0 = H//2 + (random.uniform()-.1)*50 for dx,dy in spiral(): c = .25+.75*random.random() x = int(x0+dx) y = int(y0+dy) checked = False I.flush() if not (x <= w//2 or y <= h//2 or x >= (W-w//2) or y >= (H-h//2)): ndI = ndarray(shape=(h,w), buffer=I.get_data(), dtype=ubyte, order='C', offset=(x-w//2) + I.get_stride() * (y-h//2), strides=[I.get_stride(), 1]) ndL = ndarray(shape=(h,w), buffer=L.get_data(), dtype=ubyte, order='C', strides=[L.get_stride(), 1]) if ((ndI * ndL).sum() == 0): checked = True new_region = RectangleInt(x-w//2, y-h//2, w, h) if (checked or ( drawn_regions.contains_rectangle(new_region) == REGION_OVERLAP_OUT )): ctxI.set_source_surface(L, 0, 0) pattern = ctxI.get_source() scalematrix = Matrix() scalematrix.scale(1.0,1.0) scalematrix.translate(w//2 - x, h//2 - y)
Z = ImageSurface(FORMAT_A8, int(round(height+0.5)), int(round(width+0.5))) ctx = Context(Z) if (wantRotate): ctx.set_matrix(Matrix(xx=0.0,xy=-1.0,yx=1.0,yy=0.0,x0=height)) # Second pass for actual rendering x, y = -sc(glyph_extents[0].x_bearing + positions[0].x_offset), height + sc(min_iy) if (emulate_default): x = 16 y = sc(font_extents.asscender) + 16 if (wantTTB): x = sc(max_ix) y = sc(max_iy) for info,pos,extent in zip(infos, positions, glyph_extents): face.load_glyph(info.codepoint) x += sc(extent.x_bearing + pos.x_offset) y -= sc(extent.y_bearing + pos.y_offset) # cairo does not like zero-width bitmap from the space character! if (face.glyph.bitmap.width > 0): glyph_surface = make_image_surface(face.glyph.bitmap) ctx.set_source_surface(glyph_surface, x, y) ctx.paint() x += sc(pos.x_advance - extent.x_bearing - pos.x_offset) y -= sc(pos.y_advance - extent.y_bearing - pos.y_offset) Z.flush() Z.write_to_png("hb-view.png") Image.open("hb-view.png").show() ### Derived from freetype-py:examples/hello-world-cairo.py ends. #####################################################################
[tags.pop() for x in range(len(segment) - 1)] VERTS.extend(verts) CODES.extend(codes) start = end+1 ctx.new_path() ctx.set_source_rgba(1,1,0, 0.5) i = 0 while (i < len(CODES)): if (CODES[i] == MOVETO): ctx.move_to(VERTS[i][0],VERTS[i][1]) i += 1 elif (CODES[i] == LINETO): ctx.line_to(VERTS[i][0],VERTS[i][1]) i += 1 elif (CODES[i] == CURVE3): ctx.curve_to(VERTS[i][0],VERTS[i][1], VERTS[i+1][0],VERTS[i+1][1], # undocumented VERTS[i+1][0],VERTS[i+1][1]) i += 2 elif (CODES[i] == CURVE4): ctx.curve_to(VERTS[i][0],VERTS[i][1], VERTS[i+1][0],VERTS[i+1][1], VERTS[i+2][0],VERTS[i+2][1]) i += 3 ctx.fill() surface.flush() surface.write_to_png("glyph-vector-cairo.png") Image.open("glyph-vector-cairo.png").show()
def paint_to_image(self, bw, bh, background, foreground, antialias=ANTIALIAS_NONE): img = ImageSurface(FORMAT_RGB24, bw, bh) icr = Context(img) self.paint_pattern(icr, 0, 0, antialias, None, background, foreground) img.flush() return img
baseline = max(baseline, max(0,-(slot.bitmap_top-bitmap.rows))) kerning = face.get_kerning(previous, c) width += (slot.advance.x >> 6) + (kerning.x >> 6) previous = c Z = ImageSurface(FORMAT_A8, width, height) ctx = Context(Z) # Second pass for actual rendering x, y = 0, 0 previous = 0 for c in text: face.load_char(c) bitmap = slot.bitmap top = slot.bitmap_top left = slot.bitmap_left w,h = bitmap.width, bitmap.rows y = height-baseline-top kerning = face.get_kerning(previous, c) x += (kerning.x >> 6) # cairo does not like zero-width bitmap from the space character! if (bitmap.width > 0): glyph_surface = make_image_surface(face.glyph.bitmap) ctx.set_source_surface(glyph_surface, x, y) ctx.paint() x += (slot.advance.x >> 6) previous = c Z.flush() Z.write_to_png("hello-world-cairo.png") Image.open("hello-world-cairo.png").show()
class CairoBackingBase(WindowBackingBase): HAS_ALPHA = envbool("XPRA_ALPHA", True) def __init__(self, wid, window_alpha, _pixel_depth=0): super().__init__(wid, window_alpha and self.HAS_ALPHA) self.idle_add = GLib.idle_add self.size = 0, 0 self.render_size = 0, 0 def init(self, ww: int, wh: int, bw: int, bh: int): mod = self.size != (bw, bh) or self.render_size != (ww, wh) self.size = bw, bh self.render_size = ww, wh if mod: self.create_surface() def get_info(self): info = super().get_info() info.update({ "type": "Cairo", "rgb-formats": self.RGB_MODES, }) return info def create_surface(self): bw, bh = self.size old_backing = self._backing #should we honour self.depth here? self._backing = None if bw == 0 or bh == 0: #this can happen during cleanup return None self._backing = ImageSurface(FORMAT_ARGB32, bw, bh) cr = Context(self._backing) cr.set_operator(OPERATOR_CLEAR) cr.set_source_rgba(1, 1, 1, 1) cr.rectangle(0, 0, bw, bh) cr.fill() if old_backing is not None: oldw, oldh = old_backing.get_width(), old_backing.get_height() sx, sy, dx, dy, w, h = self.gravity_copy_coords(oldw, oldh, bw, bh) cr.translate(dx - sx, dy - sy) cr.rectangle(sx, sy, w, h) cr.fill() cr.set_operator(OPERATOR_SOURCE) cr.set_source_surface(old_backing, 0, 0) cr.paint() self._backing.flush() return cr def close(self): if self._backing: self._backing.finish() WindowBackingBase.close(self) def cairo_paint_pixbuf(self, pixbuf, x: int, y: int, options): """ must be called from UI thread """ log("source pixbuf: %s", pixbuf) w, h = pixbuf.get_width(), pixbuf.get_height() self.cairo_paint_from_source(Gdk.cairo_set_source_pixbuf, pixbuf, x, y, w, h, w, h, options) def cairo_paint_surface(self, img_surface, x: int, y: int, width: int, height: int, options): iw, ih = img_surface.get_width(), img_surface.get_height() log("source image surface: %s", ( img_surface.get_format(), iw, ih, img_surface.get_stride(), img_surface.get_content(), )) def set_source_surface(gc, surface, sx, sy): gc.set_source_surface(surface, sx, sy) self.cairo_paint_from_source(set_source_surface, img_surface, x, y, iw, ih, width, height, options) def cairo_paint_from_source(self, set_source_fn, source, x: int, y: int, iw: int, ih: int, width: int, height: int, options): """ must be called from UI thread """ log("cairo_paint_surface%s backing=%s, paint box line width=%i", (set_source_fn, source, x, y, iw, ih, width, height, options), self._backing, self.paint_box_line_width) gc = Context(self._backing) if self.paint_box_line_width: gc.save() gc.rectangle(x, y, width, height) gc.clip() gc.set_operator(OPERATOR_CLEAR) gc.rectangle(x, y, width, height) gc.fill() gc.set_operator(OPERATOR_SOURCE) gc.translate(x, y) if iw != width or ih != height: gc.scale(width / iw, height / ih) gc.rectangle(0, 0, width, height) set_source_fn(gc, source, 0, 0) gc.paint() if self.paint_box_line_width: gc.restore() encoding = options.get("encoding") self.cairo_paint_box(gc, encoding, x, y, width, height) def cairo_paint_box(self, gc, encoding, x, y, w, h): color = get_paint_box_color(encoding) gc.set_line_width(self.paint_box_line_width) gc.set_source_rgba(*color) gc.rectangle(x, y, w, h) gc.stroke() def _do_paint_rgb16(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): return self._do_paint_rgb(FORMAT_RGB16_565, False, img_data, x, y, width, height, render_width, render_height, rowstride, options) def _do_paint_rgb24(self, img_data, x: int, y: int, width: int, height: int, render_width: int, render_height: int, rowstride: int, options): return self._do_paint_rgb(FORMAT_RGB24, False, img_data, x, y, width, height, render_width, render_height, rowstride, options) def _do_paint_rgb30(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): return self._do_paint_rgb(FORMAT_RGB30, True, img_data, x, y, width, height, render_width, render_height, rowstride, options) def _do_paint_rgb32(self, img_data, x: int, y: int, width: int, height: int, render_width: int, render_height: int, rowstride: int, options): if self._alpha_enabled: cformat = FORMAT_ARGB32 else: cformat = FORMAT_RGB24 return self._do_paint_rgb(cformat, True, img_data, x, y, width, height, render_width, render_height, rowstride, options) def _do_paint_rgb(self, *args): raise NotImplementedError() def get_encoding_properties(self): props = WindowBackingBase.get_encoding_properties(self) if SCROLL_ENCODING: props["encoding.scrolling"] = True return props def paint_scroll(self, img_data, options, callbacks): self.idle_add(self.do_paint_scroll, img_data, callbacks) def do_paint_scroll(self, scrolls, callbacks): old_backing = self._backing gc = self.create_surface() if not gc: fire_paint_callbacks(callbacks, False, message="no context") return gc.set_operator(OPERATOR_SOURCE) for sx, sy, sw, sh, xdelta, ydelta in scrolls: gc.set_source_surface(old_backing, xdelta, ydelta) x = sx + xdelta y = sy + ydelta gc.rectangle(x, y, sw, sh) gc.fill() if self.paint_box_line_width > 0: self.cairo_paint_box(gc, "scroll", x, y, sw, sh) del gc self._backing.flush() fire_paint_callbacks(callbacks) def nasty_rgb_via_png_paint(self, cairo_format, has_alpha: bool, img_data, x: int, y: int, width: int, height: int, rowstride: int, rgb_format): log.warn("nasty_rgb_via_png_paint%s", (cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, rgb_format)) #PIL fallback from PIL import Image if has_alpha: oformat = "RGBA" else: oformat = "RGB" #use frombytes rather than frombuffer to be compatible with python3 new-style buffers #this is slower, but since this codepath is already dreadfully slow, we don't care bdata = memoryview_to_bytes(img_data) src_format = rgb_format.replace("X", "A") try: img = Image.frombytes(oformat, (width, height), bdata, "raw", src_format, rowstride, 1) except ValueError as e: log("PIL Image frombytes:", exc_info=True) raise Exception("failed to parse raw %s data as %s to %s: %s" % (rgb_format, src_format, oformat, e)) from None #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: from io import BytesIO png = BytesIO() img.save(png, format="PNG") reader = BytesIO(png.getvalue()) png.close() img = ImageSurface.create_from_png(reader) self.cairo_paint_surface(img, x, y, width, height, {}) return True def cairo_draw(self, context): log( "cairo_draw: size=%s, render-size=%s, offsets=%s, pointer_overlay=%s", self.size, self.render_size, self.offsets, self.pointer_overlay) if self._backing is None: return #try: # log("clip rectangles=%s", context.copy_clip_rectangle_list()) #except: # log.error("clip:", exc_info=True) ww, wh = self.render_size w, h = self.size if ww == 0 or w == 0 or wh == 0 or h == 0: return if w != ww or h != wh: context.scale(ww / w, wh / h) x, y = self.offsets[:2] if x != 0 or y != 0: context.translate(x, y) context.set_operator(OPERATOR_SOURCE) context.set_source_surface(self._backing, 0, 0) context.paint() if self.pointer_overlay and self.cursor_data: px, py, _size, start_time = self.pointer_overlay[2:] spx = round(w * px / ww) spy = round(h * py / wh) cairo_paint_pointer_overlay(context, self.cursor_data, x + spx, y + spy, start_time)