2

Based on this answer, I have two cairo.ImageSurface objects generated at runtime. Both are RGBA of equal dimensions. I'd like to combine / stack them before saving them to disk:

new_surface = surface1 + surface2 # pseudo-code

(How) does this work?

s-m-e
  • 3,433
  • 2
  • 34
  • 71

3 Answers3

1

Cairo lets you to convert ImageSurface to Numpy array:

import numpy
import cairo

width, height = 255, 255
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
buf = surface.get_data()
data = numpy.ndarray(shape=(width, height),
                     dtype=numpy.uint32,
                     buffer=buf)

After you convert two ImageSurface you can sum them using "+" or numpy.add() (choose your sum func for your case)

1

(How) does this work?

You create a new cairo image surface of the larger size and draw your original images to this surface.

Untested pseudo-code (using the C API instead of pycairo):

cairo_surface_t *merged = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height * 2);
cairo_t *cr = cairo_create(merged);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);

cairo_set_source_surface(cr, surface1, 0, 0);
cairo_paint(cr);

cairo_set_source_surface(cr, surface2, 0, height);
cairo_rectangle(cr, 0, height, width, height);
cairo_fill(cr);

cairo_destroy(cr);
Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
  • Thanks! This actually raises some questions ;) I want to stack them as in surface1, surface2 and merged should all have the same dimensions (e.g. 10x10). The surfaces contain alpha components. I followed your sequence with pycairo - the code runs and adds surface1 to merged, but it does not add surface2. I do not quite understand the logic of your code example. Can you elaborate on the sequence of API calls? – s-m-e Apr 29 '20 at 18:16
  • Oh actually it does. surface2 substitutes surface1. So I end up with surface2 only. – s-m-e Apr 29 '20 at 18:19
  • So, you want the surfaces "ontop" of each other not as in "one is at (0,0) the other is at (0, height)", but as in "both are at (0,0) and one surface is behind the other"? In that case, you should switch back to the default operator before drawing the second surface: `cairo_set_operator(cr, CAIRO_OPERATOR_OVER);`. `SOURCE` is a copy while `OVER` is "do something see-through-y based on the alpha channel". – Uli Schlachter Apr 30 '20 at 15:08
  • Well, and of course also draw the second surface at `(0,0)`, but I think that part is already clear to you. – Uli Schlachter Apr 30 '20 at 15:09
-1

Going through PIL (pillow) actually gives pretty decent and relatively performant results. The following works for me:

import cairo
from PIL import Image

def surface_to_pil(surface):
    return Image.frombuffer(
        mode = 'RGBA',
        size = (surface.get_width(), surface.get_height()),
        data = surface.get_data(),
    )

def pil_to_surface(image):
    return cairo.ImageSurface.create_for_data(
        bytearray(image.tobytes('raw', 'BGRa')),
        cairo.FORMAT_ARGB32,
        image.width,
        image.height,
        )

def add_surfaces(a, b):
    assert a.get_width() == b.get_width() and a.get_height() == b.get_height()
    result_pil = Image.new(
        mode = 'RGBA',
        size = (a.get_width(), a.get_height()),
        color = (0.0, 0.0, 0.0, 0.0),
        )
    for surface in (a, b):
        surface_pil = surface_to_pil(surface)
        result_pil.paste(im = surface_pil, mask = surface_pil)
    return pil_to_surface(result_pil)

new_surface = add_surfaces(surface1, surface2)
s-m-e
  • 3,433
  • 2
  • 34
  • 71