Skip to content

GC.setClipping(path) doesn't paint on Linux when transformed #3120#3121

Open
ptziegler wants to merge 1 commit intoeclipse-platform:masterfrom
ptziegler:issue3120
Open

GC.setClipping(path) doesn't paint on Linux when transformed #3120#3121
ptziegler wants to merge 1 commit intoeclipse-platform:masterfrom
ptziegler:issue3120

Conversation

@ptziegler
Copy link
Contributor

@ptziegler ptziegler commented Mar 5, 2026

The fix for Bug 531667 (d7ce597) introduced a regression when calling setClipping(Path) after setTransform(Transform). The rectangle used when resetting the clip doesn't consider the previously done transformation, producing an empty clip if this rectangle and the Path don't intersect.

Instead of using the local clipping variable (which contains the initial clip when the GC was created), the resetClipping() method now uses the rectangle returned by getClipping(), which internally applies the transformation matrix.

To reproduce, execute the attached snippet. The rectangle in this example should be green, as it is drawn over the red rectangle.

Closes #3120

@ptziegler
Copy link
Contributor Author

ptziegler commented Mar 5, 2026

I've tested this with all "Bug531667_" snippets and couldn't see any regression. So I think this change is compatible with the fix for bug 531667.

@ptziegler
Copy link
Contributor Author

@Phillipus I've also tested this change with your ClipGraphics example and the problem disappeared there as well.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Test Results (linux)

   58 files   -    30     58 suites   - 30   10m 39s ⏱️ - 4m 38s
4 514 tests  -    40  4 302 ✅  -    32  212 💤  -  8  0 ❌ ±0 
2 160 runs   - 1 106  2 106 ✅  - 1 091   54 💤  - 15  0 ❌ ±0 

Results for commit 1aefce1. ± Comparison against base commit cc2762f.

This pull request removes 40 tests.
org.eclipse.swt.tests.gtk.snippets.Bug336238_ShellSetBoundFailTest ‑ testSetBounds
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_ClearAllSessionCookies
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_OpenWindowListener_openHasValidEventDetails
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_OpenWindowListener_open_ChildPopup
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_OpenWindow_Progress_Listener_ValidateEventOrder
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_StatusTextListener_hoverMouseOverLink
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_VisibilityWindowListener_eventSize
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_VisibilityWindowListener_multiple_shells
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_get_set_Cookies
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser_IE ‑ test_ClearAllSessionCookies
…

♻️ This comment has been updated with latest results.

@Phillipus
Copy link
Contributor

@Phillipus I've also tested this change with your ClipGraphics example and the problem disappeared there as well.

And I've tested it with our RCP app, Archi, and the problem disappeared there as well.

Incredible detective work, thank-you so much!

Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for directly providing a proposal for a fix of the reported issue.

What I do not understand at a first glance is how the changed implementation is supposed to properly deal with multiple calls to setClipping(). In my understanding, calls to that method are supposed to replace the previously set clipping (thus doing a resetClipping() everytime the method is called). After this change, basically the union of the passed paths is calculated.

I adapted the snippet to not set a transform to the GC and instead set a different (much smaller) clipping before the actual one (adding the following before gc.setClipping(p):

Path p2 = new Path(display);
p2.addRectangle(100, 400, 10, 10);
gc.setClipping(p2);

The result used to be this:
Image

And with this change the result is this:
Image

Maybe the correct solution would be to still call resetClipping() but in that method temporarily revert the transformation applied to Cairo for setting the original clipping bounds or otherwise adapt the original clipping bounds applied in resetClipping() to be transformed by an inverse of the GC's transform?

@ptziegler
Copy link
Contributor Author

Maybe the correct solution would be to still call resetClipping() but in that method temporarily revert the transformation applied to Cairo for setting the original clipping bounds or otherwise adapt the original clipping bounds applied in resetClipping() to be transformed by an inverse of the GC's transform?

Previously the clipping was reset with setClipping(0);, which was then changed to resetClipping(). I'm fairly certain this also fixed the underlying problem, so perhaps the fix is rather to add this call back to the else branch.

@Phillipus
Copy link
Contributor

Previously the clipping was reset with setClipping(0);, which was then changed to resetClipping(). I'm fairly certain this also fixed the underlying problem, so perhaps the fix is rather to add this call back to the else branch.

I tried that and that seems to work for all cases.

@Phillipus
Copy link
Contributor

Phillipus commented Mar 7, 2026

Previously the clipping was reset with setClipping(0);, which was then changed to resetClipping(). I'm fairly certain this also fixed the underlying problem, so perhaps the fix is rather to add this call back to the else branch.

I tried that and that seems to work for all cases.

Actually, in real world usage it doesn't. I tested it on our RCP app and as you scroll up and down some of the figures' parts are being drawn outside the GEF editor:

Screenshot from 2026-03-07 15-11-21 .

And another case:

Screenshot from 2026-03-07 15-24-06

@ptziegler
Copy link
Contributor Author

Actually, in real world usage it doesn't. I tested it on our RCP app and as you scroll up and down some of the figures' parts are being drawn outside the GEF editor:

From what I understand, this is because the clipping is shared between all widgets. So callling setClipping(0) removes the clipping for the whole application.

/*
* Bug 531667: widgets paint over other widgets
*
* The Cairo handle is shared by all widgets, and GC.setClipping(0) allows painting outside the current GC area.
* So if we reset any custom clipping we still want to restrict GC operations with the initial GC clipping.
*/

In the end, I've applied @HeikoKlare suggestion of still calling resetClipping(), but using the clipping rectangle on which the transformation matrix has been applied.

@Phillipus
Copy link
Contributor

Phillipus commented Mar 8, 2026

In the end, I've applied @HeikoKlare suggestion of still calling resetClipping(), but using the clipping rectangle on which the transformation matrix has been applied.

Many thanks for continuing to look at this. I tested the latest commit and it works in Draw2d if scaling is 100% but not when scaling is any other value.

Here's the snippet from the original report using a ScalableFreeformLayeredPane with scaling of 2. The bottom rectangle line is not drawn. Values of 0.5, 1.5, are the same. Only a value of 1 works

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class LinuxClipGraphics {
    
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());
        shell.setSize(600, 300);
        shell.setText("Clip Graphics");
        
        ScalableFreeformLayeredPane layeredPane = new ScalableFreeformLayeredPane();
        layeredPane.setOpaque(true);
        layeredPane.setBackgroundColor(new Color(255, 255, 255));
        layeredPane.setPreferredSize(580, 800);
        layeredPane.setScale(2);

        FigureCanvas canvas = new FigureCanvas(shell);
        canvas.setContents(layeredPane);
        
        Figure figure1 = new TestFigure();
        figure1.setBounds(new Rectangle(10, 120, 120, 120));
        layeredPane.add(figure1);
        
        Figure figure2 = new TestFigure();
        figure2.setBounds(new Rectangle(10, 300, 120, 120));
        layeredPane.add(figure2);
        
        shell.open();
        
        while(!shell.isDisposed()) {
            if(!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }
    
    private static class TestFigure extends Figure {
        Color background = new Color(181, 255, 255);
        
        @Override
        protected void paintFigure(Graphics graphics) {
            Rectangle rect = getBounds().getCopy();
            graphics.setBackgroundColor(background);
            graphics.fillRectangle(rect);
            
            graphics.setLineWidth(6);
            
            Path path = new Path(null);
            path.addRectangle(rect.x, rect.y, rect.width, rect.height);
            graphics.setClip(path);
            graphics.drawPath(path);
            path.dispose();
        }
    }
}

@ptziegler ptziegler force-pushed the issue3120 branch 2 times, most recently from 4893673 to 8934a73 Compare March 9, 2026 18:41
…platform#3120

The fix for Bug 531667 (d7ce597)
introduced a regression when calling `setClipping(Path)` after
`setTransform(Transform)`. The rectangle used when resetting the clip
doesn't consider the previously done transformation, producing an empty
clip if this rectangle and the `Path` don't intersect.

Instead of using the local `clipping` variable (which contains the
initial clip when the GC was created), the `resetClipping()` method now
uses the rectangle returned by `getClipping()`, which internally applies
the transformation matrix.

To reproduce, execute the attached snippet. The rectangle in this
example should be green, as it is drawn over the red rectangle.

Closes eclipse-platform#3120
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Test Results

162 files   -     8  162 suites   - 8   10s ⏱️ - 28m 0s
109 tests  - 4 567  108 ✅  - 4 547  0 💤  -  21  0 ❌ ±0  1 🔥 +1 
449 runs   - 6 125  448 ✅  - 5 971  0 💤  - 155  0 ❌ ±0  1 🔥 +1 

For more details on these errors, see this check.

Results for commit a0b6fb0. ± Comparison against base commit cc2762f.

This pull request removes 4567 tests.
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDisposeDrawnImageBeforeRequestingTargetForOtherZoom
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDrawImageAtDifferentZooms(boolean)[1] true
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDrawImageAtDifferentZooms(boolean)[2] false
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageDataForDifferentFractionalZoomsShouldBeDifferent
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageShouldHaveDimesionAsPerZoomLevel
org.eclipse.swt.graphics.ImageWin32Tests ‑ testRetrieveImageDataAtDifferentZooms(boolean)[1] true
org.eclipse.swt.graphics.ImageWin32Tests ‑ testRetrieveImageDataAtDifferentZooms(boolean)[2] false
org.eclipse.swt.graphics.ImageWin32Tests ‑ test_getImageData_fromCopiedImage
org.eclipse.swt.graphics.ImageWin32Tests ‑ test_getImageData_fromImageForImageDataFromImage
org.eclipse.swt.tests.gtk.Test_GtkConverter ‑ test_HeuristicASCII_dollarSign
…

@ptziegler
Copy link
Contributor Author

I think there are some infra problems going on? All tests are crashing the moment the Surefire thread is started. Nonetheless, I've added/reactivated some tests to verify some assumptions.

The short version is that clipping on Linux is weird. When using a Path, more often than not, the whole client area is used as clip.

@akurtakov
Copy link
Member

I have noticed the test failure too but so far I have no clue what caused it.

@akurtakov
Copy link
Member

So the problem is #3129

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GC.setClipping(path) doesn't paint on Linux when transformed

4 participants