pygobject / pycairo

Python bindings for cairo
https://pycairo.readthedocs.io
Other
623 stars 85 forks source link

RecordingSurface is Corrupted in X11 with OpenSUSE Tumbleweed #228

Closed danyeaw closed 3 years ago

danyeaw commented 3 years ago

When using a RecordingSurface I am getting bad image corruption. It changes / gets worse as the window is resized. Here is an example using the pycairo gtk example.

X11

image

Wayland

image

This was originally found when trying to use the RenderingSurface with https://github.com/gaphor/gaphas/pull/224 to speed up the rendering pipeline.

I have only been able to reproduce this issue with OpenSUSE Tumbleweed (rolling release, should be latest version of most libraries).

Here is the code, only lines changed are in the draw() function for creation of the overlay, punch, and circles as a RecordingSurface.

#!/usr/bin/env python
"""Based on gtk+/test/testcairo.c
"""

from __future__ import division
import math

import cairo
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

def oval_path(ctx, xc, yc, xr, yr):
    ctx.save()

    ctx.translate(xc, yc)
    ctx.scale(1.0, yr / xr)
    ctx.move_to(xr, 0.0)
    ctx.arc(0, 0, xr, 0, 2 * math.pi)
    ctx.close_path()

    ctx.restore()

def fill_checks(ctx, x, y, width, height):
    CHECK_SIZE = 32

    ctx.rectangle(x, y, width, height)
    ctx.set_source_rgb(0.4, 0.4, 0.4)
    ctx.fill()

    # Only works for CHECK_SIZE a power of 2
    for j in range(x & -CHECK_SIZE, height, CHECK_SIZE):
        for i in range(y & -CHECK_SIZE, width, CHECK_SIZE):
            if((i / CHECK_SIZE + j / CHECK_SIZE) % 2 == 0):
                ctx.rectangle(i, j, CHECK_SIZE, CHECK_SIZE)

    ctx.set_source_rgb(0.7, 0.7, 0.7)
    ctx.fill()

def draw_3circles(ctx, xc, yc, radius, alpha):
    subradius = radius * (2 / 3. - 0.1)

    ctx.set_source_rgba(1, 0, 0, alpha)
    oval_path(ctx,
              xc + radius / 3. * math.cos(math.pi * 0.5),
              yc - radius / 3. * math.sin(math.pi * 0.5),
              subradius, subradius)
    ctx.fill()

    ctx.set_source_rgba(0, 1, 0, alpha)
    oval_path(ctx,
              xc + radius / 3. * math.cos(math.pi * (0.5 + 2 / .3)),
              yc - radius / 3. * math.sin(math.pi * (0.5 + 2 / .3)),
              subradius, subradius)
    ctx.fill()

    ctx.set_source_rgba(0, 0, 1, alpha)
    oval_path(ctx,
              xc + radius / 3. * math.cos(math.pi * (0.5 + 4 / .3)),
              yc - radius / 3. * math.sin(math.pi * (0.5 + 4 / .3)),
              subradius, subradius)
    ctx.fill()

def draw(ctx, width, height):
    radius = 0.5 * min(width, height) - 10
    xc = width / 2.
    yc = height / 2.

    rect = cairo.Rectangle(0, 0, width, height)
    overlay = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, rect)
    punch = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, rect) 
    circles = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, rect)

    fill_checks(ctx, 0, 0, width, height)

    # Draw a black circle on the overlay
    overlay_cr = cairo.Context(overlay)
    overlay_cr.set_source_rgb(0, 0, 0)
    oval_path(overlay_cr, xc, yc, radius, radius)
    overlay_cr.fill()

    # Draw 3 circles to the punch surface, then cut
    # that out of the main circle in the overlay
    punch_cr = cairo.Context(punch)
    draw_3circles(punch_cr, xc, yc, radius, 1.0)

    overlay_cr.set_operator(cairo.OPERATOR_DEST_OUT)
    overlay_cr.set_source_surface(punch, 0, 0)
    overlay_cr.paint()

    # Now draw the 3 circles in a subgroup again
    # at half intensity, and use OperatorAdd to join up
    # without seams.
    circles_cr = cairo.Context(circles)

    circles_cr.set_operator(cairo.OPERATOR_OVER)
    draw_3circles(circles_cr, xc, yc, radius, 0.5)

    overlay_cr.set_operator(cairo.OPERATOR_ADD)
    overlay_cr.set_source_surface(circles, 0, 0)
    overlay_cr.paint()

    ctx.set_source_surface(overlay, 0, 0)
    ctx.paint()

def draw_event(drawingarea, ctx):
    alloc = drawingarea.get_allocation()
    draw(ctx, alloc.width, alloc.height)
    return False

def main():
    win = Gtk.Window()
    win.connect('destroy', Gtk.main_quit)
    win.set_title('Knockout Groups')
    win.set_default_size(400, 400)

    drawingarea = Gtk.DrawingArea()
    win.add(drawingarea)
    drawingarea.connect('draw', draw_event)

    win.show_all()
    Gtk.main()

if __name__ == '__main__':
    main()
naveen521kk commented 3 years ago

I don't think this should be a bug in pycairo, maybe contact the Cairo mailing list or make an issue upstream?

danyeaw commented 3 years ago

@naveen521kk Thanks, I think you are right. Since I can only reproduce this in OpenSUSE, I submitted the bug there: https://bugzilla.opensuse.org/show_bug.cgi?id=1183562