img = np.zeros((256, 256), dtype=np.uint8)
surface = cairo.ImageSurface.create_for_data(
img, cairo.FORMAT_A8, 256, 256
)
ctx = cairo.Context(surface)
At this point, the surface is completely transparent, which is saved as a completely black PNG.
ctx.arc(128, 128, 22, 0, 2 * math.pi)
ctx.set_source_rgba(1, 1, 1, 1)
ctx.fill()
Now you fill a circle with "completely opaque" (the color component is ignored). This ends up as a white circle
ctx.arc(128, 102, 22, 0, 2 * math.pi)
ctx.set_source_rgba(0, 0, 0, 0.5)
ctx.fill()
Here you draw a second circle with "semi transparent". This ends up as black. The part where the other circle was already drawn is "semi transparent" over "completely opaque", which ends up as "completely opaque", so white when saved as a PNG.
The output should look like this:
So, you do not want the second circle to be drawn outside of the first circle. For this, you need ctx.clip()
.
Here is some Lua code...
local cairo = require("lgi").cairo
local surface = cairo.ImageSurface.create(cairo.Format.A8, 256, 256)
local ctx = cairo.Context(surface)
ctx:arc(128, 128, 22, 0, 2 * math.pi)
ctx:set_source_rgba(1, 1, 1, 1)
-- Fill the circle, but do not clear the current path
ctx:fill_preserve()
-- Use the circle as a clip
ctx:clip()
ctx:arc(128, 102, 22, 0, 2 * math.pi)
ctx:set_source_rgba(0, 0, 0, 0.5)
-- Replace the default operator OVER with SOURCE:
-- Instead of drawing "ontop", just copy the source to the target
ctx:set_operator(cairo.Operator.SOURCE)
ctx:fill()
surface:write_to_png("out.png")
...that produces this output (I used the same coordinates that your code uses, no idea how you end up output images with X/Y flipped):
