def _cairo_surface_write_to_bmp(img: cairo.ImageSurface) -> bytes: data = bytes(img.get_data()) return ( b"BM" + struct.pack( "<ihhiiiihhiiiiii", 54 + len(data), # size of BMP file 0, # unused 0, # unused 54, # pixel array offset 40, # DIB header img.get_width(), # width img.get_height(), # height 1, # planes 32, # BPP 0, # no compression len(data), # size of the raw bitmap data 2835, # 72DPI H 2835, # 72DPI V 0, # palette 0, # all colors are important ) + data )
def renegerate_overlay_buffer(self): image = ImageSurface(cairo.FORMAT_ARGB32, self.video_width, self.video_height) context = Context(image) text = "Foo bar 123" font = pango.FontDescription('sans normal 22') text_offset = [6, 6] textOverflowed = False if text: pcContext = pangocairo.CairoContext(context) pangoLayout = pcContext.create_layout() font = pango.FontDescription('sans normal 22') pangoLayout.set_font_description(font) context.move_to(text_offset[0]+2, text_offset[1]+2) pangoLayout.set_markup('<span foreground="black" >%s</span>' % text) pcContext.show_layout(pangoLayout) context.move_to(text_offset[0], text_offset[1]) pangoLayout.set_markup('<span foreground="white" >%s</span>' % text) pcContext.show_layout(pangoLayout) textWidth, textHeight = pangoLayout.get_pixel_size() self.overlay_buffer = image.get_data() print "overlay_buffer size: %d" % len(self.overlay_buffer)
def _surface_to_array(surface: cairo.ImageSurface) -> np.ndarray: """convert surface to array""" # NOTE! The format of the array is PREMULTIPLIED ALPHA, not RGBA! res = np.ndarray( (surface.get_height(), surface.get_width(), 4), # do height and width work here??? dtype=np.uint8, buffer=surface.get_data()) res = res[:, :, [2, 1, 0, 3]] # swap RGB order like OpenCV return res
def _tonumpy(surface: cairo.ImageSurface): np_image = np.ndarray( shape=(surface.get_height(), surface.get_width(), 4), dtype=np.uint8, buffer=surface.get_data(), strides=(surface.get_width() * 4, 4, 1) ) np_image = np_image[:, :, :-1] np_image = np_image[:, :, ::-1] np_image = np_image.copy() return np_image
def motionblur(target: cairo.ImageSurface, t: float, draw: Callable, look_ahead: float, n: int): w = target.get_width() h = target.get_width() frames = [] for tt in np.linspace(0, look_ahead, n): frame = np.zeros((w * h * 4,), dtype=np.uint8) surface = cairo.ImageSurface.create_for_data(memoryview(frame), cairo.Format.RGB24, w, h) draw(surface, t + tt) #frames.append(degamma(frame)) frames.append(frame) avg = np.average(np.stack(frames), axis=0).astype(np.uint8) #avg = gamma(np.average(np.stack(frames), axis=0)).astype(np.uint8) data = target.get_data() for i in range(len(data)): data[i] = avg[i]
def draw_triangle(target: cairo.ImageSurface, triangle, attributes, texture: Texture): # drop z coordinate p0, p1, p2 = [p[:2] for p in triangle] # compute area area = edge(p0, p1, p2) if area == 0: return width, height = resolution(target) xmin = int(max(min(p0[0], p1[0], p2[0]), 0)) xmax = int(min(max(p0[0], p1[0], p2[0]), width)) ymin = int(max(min(p0[1], p1[1], p2[1]), 0)) ymax = int(min(max(p0[1], p1[1], p2[1]), height)) x, y = np.meshgrid(range(xmin, xmax), range(ymin, ymax), indexing='xy') p = np.vstack([x.ravel(), y.ravel()]).T # Barycentric coordinates are calculated as the areas of the three sub-triangles divided # by the area of the whole triangle. barycentric = np.vstack( [edge(p1, p2, p), edge(p2, p0, p), edge(p0, p1, p)]).T / area # Find all indices of rows where all columns are positive is_inside = np.all(barycentric >= 0, axis=-1) # Compute indices of all points inside triangle stride = np.array([4, target.get_stride()]) indices = np.dot(p[is_inside], stride) # Interpolate vertex attributes attrs = np.dot(barycentric[is_inside], attributes) # Fill pixels data = target.get_data() for index, (u, v) in zip(indices, attrs): r, g, b = texture(u, v) data[index + 0] = r data[index + 1] = g data[index + 2] = b
def draw(self, target: cairo.ImageSurface): saved = self.positions, self.t n = 4 lookahead = 0.2 dt = lookahead / n targets = [] for _ in range(n): image = cairo.ImageSurface(cairo.Format.RGB24, target.get_width(), target.get_height()) self.draw_frame(image) targets.append(image) self.step(dt) data = target.get_data() for i in range(len(data)): data[i] = int(sum(img.get_data()[i] for img in targets) / n) self.positions, self.t = saved
def _scale_down(self, handle, ratio): xsize, ysize = self.size if ratio >= 1.0: # Convert surface = ImageSurface(FORMAT_ARGB32, xsize, ysize) ctx = Context(surface) else: # Scale xsize, ysize = int(xsize * ratio), int(ysize * ratio) surface = ImageSurface(FORMAT_ARGB32, xsize, ysize) ctx = Context(surface) ctx.scale(ratio, ratio) # Render handle.render_cairo(ctx) # Transform to a PIL image for further manipulation size = (xsize, ysize) im = frombuffer('RGBA', size, surface.get_data(), 'raw', 'BGRA', 0, 1) surface.finish() return im, xsize, ysize
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) pattern.set_matrix(scalematrix) ctxI.set_source_rgba(c,c,c,c)
def generateOverlay(text, font, showFlumotion, showCC, showXiph, width, height): """Generate an transparent image with text + logotypes rendered on top of it suitable for mixing into a video stream @param text: text to put in the top left corner @type text: str @param font: font description used to render the text @type: str @param showFlumotion: if we should show the flumotion logo @type showFlumotion: bool @param showCC: if we should show the Creative Common logo @type showCC: bool @param showXiph: if we should show the xiph logo @type showXiph: bool @param width: width of the image to generate @type width: int @param height: height of the image to generate @type height: int @returns: raw image and if images or if text overflowed @rtype: 3 sized tuple of string and 2 booleans """ from cairo import ImageSurface from cairo import Context image = ImageSurface(cairo.FORMAT_ARGB32, width, height) context = Context(image) subImages = [] if showXiph: subImages.append(os.path.join(configure.imagedir, '36x36', 'xiph.png')) if showCC: subImages.append(os.path.join(configure.imagedir, '36x36', 'cc.png')) if showFlumotion: subImages.append( os.path.join(configure.imagedir, '36x36', 'fluendo.png')) imagesOverflowed = False offsetX = BORDER for subPath in subImages: sub = ImageSurface.create_from_png(subPath) subX = sub.get_width() subY = sub.get_height() offsetY = height - subY - BORDER context.set_source_surface(sub, offsetX, offsetY) context.paint() if (offsetX + subX) > width: imagesOverflowed = True offsetX += subX + BORDER textOverflowed = False if text: pcContext = pangocairo.CairoContext(context) pangoLayout = pcContext.create_layout() if font is not None: font = pango.FontDescription(font) if not font.get_family() or \ not font.get_family().lower() in [family.get_name().lower() for family in pangoLayout.get_context().list_families()]: font.set_family(FONT) if font.get_size() == 0: font.set_size(FONT_SIZE) else: font = pango.FontDescription('%s %s' % (FONT, FONT_PROPS)) pangoLayout.set_font_description(font) context.move_to(TEXT_XOFFSET + 2, TEXT_YOFFSET + 2) pangoLayout.set_markup('<span foreground="black" >%s</span>' % text) pcContext.show_layout(pangoLayout) context.move_to(TEXT_XOFFSET, TEXT_YOFFSET) pangoLayout.set_markup('<span foreground="white" >%s</span>' % text) pcContext.show_layout(pangoLayout) textWidth, textHeight = pangoLayout.get_pixel_size() if textWidth > width: textOverflowed = True if cairo.version < '1.2.6': buf = image.get_data_as_rgba() else: buf = image.get_data() return buf, imagesOverflowed, textOverflowed
# Google's NotoColorEmoji only accept size 109 to get 136x128 bitmaps face.set_char_size( 160*64 ) face.load_char('😀', freetype.FT_LOAD_COLOR) bitmap = face.glyph.bitmap width = face.glyph.bitmap.width rows = face.glyph.bitmap.rows # The line below depends on this assumption. Check. if ( face.glyph.bitmap.pitch != width * 4 ): raise RuntimeError('pitch != width * 4 for color bitmap: Please report this.') bitmap = np.array(bitmap.buffer, dtype=np.uint8).reshape((bitmap.rows,bitmap.width,4)) I = ImageSurface(FORMAT_ARGB32, width, rows) try: ndI = np.ndarray(shape=(rows,width), buffer=I.get_data(), dtype=np.uint32, order='C', strides=[I.get_stride(), 4]) except NotImplementedError: raise SystemExit("For python 3.x, you need pycairo >= 1.11+ (from https://github.com/pygobject/pycairo)") # Although both are 32-bit, cairo is host-order while # freetype is small endian. ndI[:,:] = bitmap[:,:,3] * 2**24 + bitmap[:,:,2] * 2**16 + bitmap[:,:,1] * 2**8 + bitmap[:,:,0] I.mark_dirty() surface = ImageSurface(FORMAT_ARGB32, 2*width, rows) ctx = Context(surface) ctx.set_source_surface(I, 0, 0) ctx.paint()
strides=[pitch, 3]) ndG = ndarray(shape=(rows, width), buffer=copybuffer, dtype=ubyte, order='C', offset=1, strides=[pitch, 3]) ndB = ndarray(shape=(rows, width), buffer=copybuffer, dtype=ubyte, order='C', offset=2, strides=[pitch, 3]) try: ndI = ndarray(shape=(rows, width), buffer=I.get_data(), dtype=uint32, order='C', strides=[I.get_stride(), 4]) except NotImplementedError: raise SystemExit( "For python 3.x, you need pycairo >= 1.11+ (from https://github.com/pygobject/pycairo)" ) # 255 * 2**24 = opaque ndI[:, :] = 255 * 2**24 + ndR[:, :] * 2**16 + ndG[:, :] * 2**8 + ndB[:, :] I.mark_dirty() surface = ImageSurface(FORMAT_ARGB32, 800, 600) ctx = Context(surface) ctx.set_source_surface(I, 0, 0)
def generateOverlay(text, font, showFlumotion, showCC, showXiph, width, height): """Generate an transparent image with text + logotypes rendered on top of it suitable for mixing into a video stream @param text: text to put in the top left corner @type text: str @param font: font description used to render the text @type: str @param showFlumotion: if we should show the flumotion logo @type showFlumotion: bool @param showCC: if we should show the Creative Common logo @type showCC: bool @param showXiph: if we should show the xiph logo @type showXiph: bool @param width: width of the image to generate @type width: int @param height: height of the image to generate @type height: int @returns: raw image and if images or if text overflowed @rtype: 3 sized tuple of string and 2 booleans """ from cairo import ImageSurface from cairo import Context image = ImageSurface(cairo.FORMAT_ARGB32, width, height) context = Context(image) subImages = [] if showXiph: subImages.append(os.path.join(configure.imagedir, '36x36', 'xiph.png')) if showCC: subImages.append(os.path.join(configure.imagedir, '36x36', 'cc.png')) if showFlumotion: subImages.append(os.path.join(configure.imagedir, '36x36', 'fluendo.png')) imagesOverflowed = False offsetX = BORDER for subPath in subImages: sub = ImageSurface.create_from_png(subPath) subX = sub.get_width() subY = sub.get_height() offsetY = height - subY - BORDER context.set_source_surface(sub, offsetX, offsetY) context.paint() if (offsetX + subX) > width: imagesOverflowed = True offsetX += subX + BORDER textOverflowed = False if text: pcContext = pangocairo.CairoContext(context) pangoLayout = pcContext.create_layout() if font is not None: font = pango.FontDescription(font) if not font.get_family() or \ not font.get_family().lower() in [family.get_name().lower() for family in pangoLayout.get_context().list_families()]: font.set_family(FONT) if font.get_size() == 0: font.set_size(FONT_SIZE) else: font = pango.FontDescription('%s %s' % (FONT, FONT_PROPS)) pangoLayout.set_font_description(font) context.move_to(TEXT_XOFFSET+2, TEXT_YOFFSET+2) pangoLayout.set_markup('<span foreground="black" >%s</span>' % text) pcContext.show_layout(pangoLayout) context.move_to(TEXT_XOFFSET, TEXT_YOFFSET) pangoLayout.set_markup('<span foreground="white" >%s</span>' % text) pcContext.show_layout(pangoLayout) textWidth, textHeight = pangoLayout.get_pixel_size() if textWidth > width: textOverflowed = True if cairo.version < '1.2.6': buf = image.get_data_as_rgba() else: buf = image.get_data() return buf, imagesOverflowed, textOverflowed
# Google's NotoColorEmoji only accept size 109 to get 136x128 bitmaps face.set_char_size( 160*64 ) face.load_char('😀', freetype.FT_LOAD_COLOR) bitmap = face.glyph.bitmap width = face.glyph.bitmap.width rows = face.glyph.bitmap.rows # The line below depends on this assumption. Check. if ( face.glyph.bitmap.pitch != width * 4 ): raise RuntimeError('pitch != width * 4 for color bitmap: Please report this.') bitmap = np.array(bitmap.buffer, dtype=np.uint8).reshape((bitmap.rows,bitmap.width,4)) I = ImageSurface(FORMAT_ARGB32, width, rows) try: ndI = np.ndarray(shape=(rows,width), buffer=I.get_data(), dtype=np.uint32, order='C', strides=[I.get_stride(), 4]) except NotImplementedError: raise SystemExit("For python 3.x, you need pycairo >= 1.11+ (from https://github.com/pygobject/pycairo)") # Although both are 32-bit, cairo is host-order while # freetype is small endian. ndI[:,:] = bitmap[:,:,3] * 2**24 + bitmap[:,:,2] * 2**16 + bitmap[:,:,1] * 2**8 + bitmap[:,:,0] # ndI[...] = (bitmap*(1,2**8,2**16,2**24)).sum(axis=-1) I.mark_dirty() surface = ImageSurface(FORMAT_ARGB32, 2*width, rows) ctx = Context(surface)