Skip to content

[GTK] GC#copyArea() produces wrong results when source and target area are overlapping #1756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
HeikoKlare opened this issue Jan 24, 2025 · 0 comments · Fixed by #1757
Closed
Labels
bug Something isn't working gtk4 GTK4 issues Linux/GTK Happens on Linux Wayland

Comments

@HeikoKlare
Copy link
Contributor

The GC provides an operation copyArea() that is supposed to copy an area inside the GC's surface, i.e., from a source area of the surface into a target area of the same surface. When the source and target area are overlapping, the result is not as expected.

Expected Behavior Example

Take the following original surface data:
Image

Let's assume we want to copy the red-marked area down to the yellow-marked area:
Image

Then we would expect the following result:
Image

Actual Behavior Example

The expected result is only achieved under very specific conditions:

  • You are not using GTK4 or Wayland
  • You do not create the GC on an Image instance or the Image you pass was created via the Image constructor just accepting width/height or a size

The reason for the latter is that for that exact case (creating Image via width/height or size) a very specific workaround has been implemented via
https://bugs.eclipse.org/bugs/show_bug.cgi?id=571166:

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);
}

In all other cases (using GTK4, using Wayland, using an Image created in a different way) you will see this result:
Image

Since this is a derivation from the existing behavior of GTK and the behavior on Windows and MacOS, consumers may experience inconsistent cross-platform behavior, such as in eclipse-platform/eclipse.platform.ui#2740

Cause

The cause for the mentioned issue is in different behavior of copy operations inside the same surface depending on the used API and environment on which you create a surface. The usage of GDK.gdk_window_create_similar_surface() on X11 seems to perform some sufficient buffering of the data to be copied inside the same surface. In all other cases, the operation already overwrites its own source region while still reading from in.

In the example show above, the area is linewise/chunkwise copied from top-left downwards. When the copy operation reaches the overlapping red/yellow region, is reads already overwritten data to copy and copies that over again. This leads to duplications of the data in the non-overlapping area.

This can be easily experienced in the LineNumberRuler, which we have already seen several years ago in https://bugs.eclipse.org/bugs/show_bug.cgi?id=571166:
Image

Potential Fix

Completely buffering the data to be copied to avoid concurrent overwrites would solve the problem. I will submit a PR for this. There might be better ways directly via the Cairo API.

@HeikoKlare HeikoKlare added bug Something isn't working gtk4 GTK4 issues Linux/GTK Happens on Linux Wayland labels Jan 24, 2025
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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.

This change fixes the behavior of GC#copyArea() by explicitly buffering
the area to be copied. 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.

Fixes eclipse-platform#1756
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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.

This change fixes the behavior of GC#copyArea() by explicitly buffering
the area to be copied. 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. An
according regression test is added.

Fixes eclipse-platform#1756
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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.

This change fixes the behavior of GC#copyArea() by explicitly buffering
the area to be copied. 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 eclipse-platform#1756
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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 eclipse-platform#1756
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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 eclipse-platform#1756
HeikoKlare added a commit to HeikoKlare/eclipse.platform.swt that referenced this issue Jan 24, 2025
…se-platform#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 eclipse-platform#1756
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working gtk4 GTK4 issues Linux/GTK Happens on Linux Wayland
Projects
None yet
1 participant