From 094abb8c5fc3737034845fadb5b964c3e2424265 Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Thu, 23 Jan 2025 22:59:30 +0100 Subject: [PATCH] [GTK] Fix GC#copyArea() for overlapping source and target areas #1756 The GC provides a #copyArea() method that copies an area inside the surface of the GC to another area in the same surface. When using a surface directly created via the Cairo API (or with the GDK API on Wayland), this operation is broken. When the source and target area are overlapping and the source is above-left of the target area, the source area will appear replicated in the target area, as Cairo does not perform any buffering but does a linewise/chunkwise write from the source area to the target area by default. This change fixes the behavior of GC#copyArea() by explicitly making Cairo first pipe the copied area into an intermediate surface to be painted into the actual target area afterwards. It also removes the existing workaround in images that avoids the instantiation of a surface via the Cairo API, as the reason for that workaround was the issue fixed by this change. It improves the behavior of consumers of that functionality on GTK4 and Wayland. An according regression test is added. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/1756 --- .../gtk/org/eclipse/swt/graphics/GC.java | 6 +++ .../gtk/org/eclipse/swt/graphics/Image.java | 6 +-- .../Test_org_eclipse_swt_graphics_GC.java | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java index 70632dbc2ca..4fa26210447 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java @@ -548,7 +548,13 @@ void copyAreaInPixels(int srcX, int srcY, int width, int height, int destX, int Cairo.cairo_set_source_surface(handle, data.image.surface, deltaX, deltaY); Cairo.cairo_rectangle(handle, destX, destY, width, height); Cairo.cairo_set_operator(handle, Cairo.CAIRO_OPERATOR_SOURCE); + // As source and target area may be overlapping, we need to draw on + // an intermediate surface to avoid that parts of the source area are + // overwritten before reading it to be copied + Cairo.cairo_push_group(handle); Cairo.cairo_fill(handle); + Cairo.cairo_pop_group_to_source(handle); + Cairo.cairo_paint(handle); } else if (drawable != 0) { Cairo.cairo_save(handle); Cairo.cairo_rectangle(handle, destX, destY, width, height); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java index b51af22f7eb..e518cfda153 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java @@ -1260,11 +1260,7 @@ void init(int width, int height) { this.type = SWT.BITMAP; /* Create the pixmap */ - if (GTK.GTK4) { - surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_RGB24, width, height); - } else { - surface = GDK.gdk_window_create_similar_surface(GDK.gdk_get_default_root_window(), Cairo.CAIRO_CONTENT_COLOR, width, height); - } + surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_ARGB32, width, height); if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES); // When we create a blank image we need to set it to 100 in GTK3 as we draw using 100% scale. // Cairo will take care of scaling for us when image needs to be scaled. diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java index 0e0d79f1368..f1b9b6aa808 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java @@ -172,6 +172,52 @@ public void test_copyAreaIIIIII() { assertEquals(":d:", whiteRGB, palette.getRGB(pixel)); } +@Test +public void test_copyAreaIIIIII_overlapingSourceTarget() { + Color red= display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + RGB redRGB = getRealRGB(red); + RGB blueRGB = getRealRGB(blue); + + gc.setBackground(red); + gc.fillRectangle(image.getBounds()); + gc.setBackground(blue); + gc.fillRectangle(0, 100, 200, 100); + + ImageData imageData = image.getImageData(); + PaletteData palette = imageData.palette; + + int pixel = imageData.getPixel(0, 0); + assertEquals(redRGB, palette.getRGB(pixel)); + pixel = imageData.getPixel(0, 105); + assertEquals(blueRGB, palette.getRGB(pixel)); + pixel = imageData.getPixel(0, 155); + assertEquals(blueRGB, palette.getRGB(pixel)); + + gc.copyArea(0, 50, 200, 100, 0, 100); + + imageData = image.getImageData(); + palette = imageData.palette; + + if (DPIUtil.getDeviceZoom() != 100) { + //TODO Fix non integer scaling factors. + if (SwtTestUtil.verbose) { + System.out.println("Excluded test_copyAreaIIIIII(org.eclipse.swt.tests.junit.Test_org_eclipse_swt_graphics_GC)"); + } + return; + } + + pixel = imageData.getPixel(0, 105); + assertEquals(redRGB, palette.getRGB(pixel)); + pixel = imageData.getPixel(0, 145); + assertEquals(redRGB, palette.getRGB(pixel)); + pixel = imageData.getPixel(0, 155); + assertEquals(blueRGB, palette.getRGB(pixel)); + pixel = imageData.getPixel(0, 195); + assertEquals(blueRGB, palette.getRGB(pixel)); +} + + @Test public void test_copyAreaLorg_eclipse_swt_graphics_ImageII() { Color white = display.getSystemColor(SWT.COLOR_WHITE);