Skip to content

Commit 5c2611d

Browse files
committed
[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 #1756
1 parent 8d65ddc commit 5c2611d

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java

+6
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,13 @@ void copyAreaInPixels(int srcX, int srcY, int width, int height, int destX, int
548548
Cairo.cairo_set_source_surface(handle, data.image.surface, deltaX, deltaY);
549549
Cairo.cairo_rectangle(handle, destX, destY, width, height);
550550
Cairo.cairo_set_operator(handle, Cairo.CAIRO_OPERATOR_SOURCE);
551+
// As source and target area may be overlapping, we need to draw on
552+
// an intermediate surface to avoid that parts of the source area are
553+
// overwritten before reading it to be copied
554+
Cairo.cairo_push_group(handle);
551555
Cairo.cairo_fill(handle);
556+
Cairo.cairo_pop_group_to_source(handle);
557+
Cairo.cairo_paint(handle);
552558
} else if (drawable != 0) {
553559
Cairo.cairo_save(handle);
554560
Cairo.cairo_rectangle(handle, destX, destY, width, height);

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -1260,11 +1260,7 @@ void init(int width, int height) {
12601260
this.type = SWT.BITMAP;
12611261

12621262
/* Create the pixmap */
1263-
if (GTK.GTK4) {
1264-
surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_RGB24, width, height);
1265-
} else {
1266-
surface = GDK.gdk_window_create_similar_surface(GDK.gdk_get_default_root_window(), Cairo.CAIRO_CONTENT_COLOR, width, height);
1267-
}
1263+
surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_ARGB32, width, height);
12681264
if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES);
12691265
// When we create a blank image we need to set it to 100 in GTK3 as we draw using 100% scale.
12701266
// Cairo will take care of scaling for us when image needs to be scaled.

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java

+46
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,52 @@ public void test_copyAreaIIIIII() {
172172
assertEquals(":d:", whiteRGB, palette.getRGB(pixel));
173173
}
174174

175+
@Test
176+
public void test_copyAreaIIIIII_overlapingSourceTarget() {
177+
Color red= display.getSystemColor(SWT.COLOR_RED);
178+
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
179+
RGB redRGB = getRealRGB(red);
180+
RGB blueRGB = getRealRGB(blue);
181+
182+
gc.setBackground(red);
183+
gc.fillRectangle(image.getBounds());
184+
gc.setBackground(blue);
185+
gc.fillRectangle(0, 100, 200, 100);
186+
187+
ImageData imageData = image.getImageData();
188+
PaletteData palette = imageData.palette;
189+
190+
int pixel = imageData.getPixel(0, 0);
191+
assertEquals(redRGB, palette.getRGB(pixel));
192+
pixel = imageData.getPixel(0, 105);
193+
assertEquals(blueRGB, palette.getRGB(pixel));
194+
pixel = imageData.getPixel(0, 155);
195+
assertEquals(blueRGB, palette.getRGB(pixel));
196+
197+
gc.copyArea(0, 50, 200, 100, 0, 100);
198+
199+
imageData = image.getImageData();
200+
palette = imageData.palette;
201+
202+
if (DPIUtil.getDeviceZoom() != 100) {
203+
//TODO Fix non integer scaling factors.
204+
if (SwtTestUtil.verbose) {
205+
System.out.println("Excluded test_copyAreaIIIIII(org.eclipse.swt.tests.junit.Test_org_eclipse_swt_graphics_GC)");
206+
}
207+
return;
208+
}
209+
210+
pixel = imageData.getPixel(0, 105);
211+
assertEquals(redRGB, palette.getRGB(pixel));
212+
pixel = imageData.getPixel(0, 145);
213+
assertEquals(redRGB, palette.getRGB(pixel));
214+
pixel = imageData.getPixel(0, 155);
215+
assertEquals(blueRGB, palette.getRGB(pixel));
216+
pixel = imageData.getPixel(0, 195);
217+
assertEquals(blueRGB, palette.getRGB(pixel));
218+
}
219+
220+
175221
@Test
176222
public void test_copyAreaLorg_eclipse_swt_graphics_ImageII() {
177223
Color white = display.getSystemColor(SWT.COLOR_WHITE);

0 commit comments

Comments
 (0)