Bug 160904 - FigureCanvas Transparency
Summary: FigureCanvas Transparency
Status: NEW
Alias: None
Product: GEF
Classification: Tools
Component: GEF-Legacy Draw2d (show other bugs)
Version: 3.2.1   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: gef-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on: 395849
Blocks:
  Show dependency tree
 
Reported: 2006-10-13 13:51 EDT by E F CLA
Modified: 2012-12-05 15:57 EST (History)
4 users (show)

See Also:


Attachments
Code snippet reproducing the anomaly (4.36 KB, text/plain)
2006-10-13 13:54 EDT, E F CLA
no flags Details
PNG file showing expected results (9.76 KB, image/png)
2006-10-13 13:57 EDT, E F CLA
no flags Details
PNG file showing unexpected results (9.66 KB, image/png)
2006-10-13 13:58 EDT, E F CLA
no flags Details
Snippet with workaround (4.38 KB, text/plain)
2006-10-19 16:45 EDT, E F CLA
no flags Details
Proposed patch for the problem (4.21 KB, patch)
2012-12-02 12:21 EST, Willem Duminy CLA
willem.duminy: review?
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description E F CLA 2006-10-13 13:51:13 EDT
In my excitement to use the latest, I am in the process of upgrading our 
project software from Eclispe 3.1.2 to 3.2.1.  I encountered a problem that 
I have distilled in the attached example and output.  I posted a message on the GEF newsgroup, but received no answers.  I'm not sure if I'm doing the right thing in opening a bug, but here goes...  

The example has a FigureCanvas in a Shell.  The canvas has a root figure, 
and the root figure has 2 round rectangles.  The example I have provided 
attempts to draw a transparent rectangle on the canvas.  Drawing this 
transparent rectangle is as follows: when the user holds the left mouse 
button down and moves from point A to point B, the rectangle is drawn based 
on these 2 points.

The result using the 3.1.2 libraries is as expected - shown in 
FigureCanvasTransparencyWith_3.1.2_correct.png
The result using the 3.2.1 libraries is clipped - shown in 
FigureCanvasTransparencyWith_3.2.1_incorrect.png, and the transparent 
rectangle is only partially shown over the 2nd child figure added.

Attached is also the source code for the snippet.

The same incorrect behavior is exhibited using 3.2.0.

Thank you!
E.
Comment 1 E F CLA 2006-10-13 13:54:30 EDT
Created attachment 51963 [details]
Code snippet reproducing the anomaly

Running the snippet with a 3.1 target platform works properly.  Running it with a 3.2 target platform exhibits the anomaly.
Comment 2 E F CLA 2006-10-13 13:57:47 EDT
Created attachment 51965 [details]
PNG file showing expected results

Running previous java snippet with the 3.1 platform produces expected results
Comment 3 E F CLA 2006-10-13 13:58:19 EDT
Created attachment 51966 [details]
PNG file showing unexpected results

Running previous java snippet with the 3.2 platform produces unexpected results
Comment 4 E F CLA 2006-10-19 16:45:23 EDT
Created attachment 52347 [details]
Snippet with workaround

From an exchange with Randy Hudson, regarding this issue, on the GEF newsgroup:

"It seems like there is no isolation between two independent paint listeners 
on a Control. So, for some reason, the GC is being left in a different state 
in 3.2 than it was in 3.1.

You can fix the GC's clip by setting it back to the event's clip area.  Also, you could switch to using a figure on a transparent layer above your 
primary figures.  This is how marquees are done."

I can confirm than when I set the GC's clipping area, the transparent drawing worked as expected.  Attached is the working snippet.
Comment 5 Willem Duminy CLA 2012-12-02 05:27:11 EST
I reproduced this problem on 3.8. using the test code supplied.  I think the solution could be related to the clipping region on SWTGraphics.   

I added the following code to org.eclipse.draw2d.test.GraphicsClipping and the test fails:

public void testClipRestChanging() {
  Rectangle fullRect = new Rectangle(0, 0, 800, 600);
  Rectangle bigRect = new Rectangle(0, 0, 200, 200);
  Rectangle smallRect = new Rectangle(0, 0, 100, 100);

  // clip to big rect works fine
  graphics.clipRect(bigRect);
  Assert.assertEquals(200,    graphics.getClip(fullRect.getCopy()).width);

  // clip to small rect works fine
  graphics.clipRect(smallRect);
  Assert.assertEquals(100, graphics.getClip(fullRect.getCopy()).width);

  // clip back to big rect does not work as I expected.
  graphics.clipRect(bigRect);
  Assert.assertEquals(200, graphics.getClip(fullRect.getCopy()).width);
}

Is this a valid test?  Should it run successfully?
Comment 6 Alexander Nyßen CLA 2012-12-02 08:56:50 EST
(In reply to comment #5)
> I reproduced this problem on 3.8. using the test code supplied.  I think the
> solution could be related to the clipping region on SWTGraphics.   
> 
> I added the following code to org.eclipse.draw2d.test.GraphicsClipping and
> the test fails:
> 
> public void testClipRestChanging() {
>   Rectangle fullRect = new Rectangle(0, 0, 800, 600);
>   Rectangle bigRect = new Rectangle(0, 0, 200, 200);
>   Rectangle smallRect = new Rectangle(0, 0, 100, 100);
> 
>   // clip to big rect works fine
>   graphics.clipRect(bigRect);
>   Assert.assertEquals(200,    graphics.getClip(fullRect.getCopy()).width);
> 
>   // clip to small rect works fine
>   graphics.clipRect(smallRect);
>   Assert.assertEquals(100, graphics.getClip(fullRect.getCopy()).width);
> 
>   // clip back to big rect does not work as I expected.
>   graphics.clipRect(bigRect);
>   Assert.assertEquals(200, graphics.getClip(fullRect.getCopy()).width);
> }
> 
> Is this a valid test?  Should it run successfully?

No, it isn't. There are actually two clipping methods, setClip and clipRect. clipRect intersects the current clipping of the graphics with the given rectangle and uses the resulting intersection rectangle. setClip takes the argument as new clipping region. If you use setClip instead of clipRect for resetting the clipping to the large rectangle, everything is fine.
Comment 7 Willem Duminy CLA 2012-12-02 12:21:49 EST
Created attachment 224200 [details]
Proposed patch for the problem

Thanks for your help Alexander.  

I think I found the problem.  SWTGraphics has a push and pop state feature.  When you push, a copy of the gc state is made, and when you pop, the gc state should restore.  However, the gc state is actually restored only when you call a 'draw' method on SWTGraphics.  The problem is that the state was not restored if the client code uses GC directly (like in the snippet).

So I fixed the problem by making sure the cg is updated when popping.  

I also added a unit test that initially failed and is now fixed.
Comment 8 Alexander Nyßen CLA 2012-12-02 16:14:31 EST
(In reply to comment #7)
> Created attachment 224200 [details]
> Proposed patch for the problem
> 
> Thanks for your help Alexander.  
> 
> I think I found the problem.  SWTGraphics has a push and pop state feature. 
> When you push, a copy of the gc state is made, and when you pop, the gc
> state should restore.  However, the gc state is actually restored only when
> you call a 'draw' method on SWTGraphics.  The problem is that the state was
> not restored if the client code uses GC directly (like in the snippet).
> 
> So I fixed the problem by making sure the cg is updated when popping.  
> 
> I also added a unit test that initially failed and is now fixed.

Yes, indeed that seems to be the problem. However, as checkGC affects the graphicsHints as well, I want to get sure this does not involve any side effects before applying it. Anyway, thanks for figuring that out!
Comment 9 Randy Hudson CLA 2012-12-03 09:42:57 EST
> When you push, a copy of the gc state is made, and when you pop, the gc
> state should restore.

No, only the SWTGraphics state is restored.  This is intentional.  When popState() is called repeatedly, it is wasteful to update the real GC with different clip regions that never get used.

> However, the gc state is actually restored only when
> you call a 'draw' method on SWTGraphics.

By design; this is an optimization.

> The problem is that the state was
> not restored if the client code uses GC directly (like in the snippet).

It is invalid to use a mixture of the SWT graphics and GC that it wrappers.  There are many methods on SWT graphics that have no immediate effect on the GC.

> So I fixed the problem by making sure the cg is updated when popping.

This would probably introduce a performance regression.

> I also added a unit test that initially failed and is now fixed.

The last part of the unit test is invalid:

+	graphics.popState();
+	assertEquals(800, graphics.getClip(fullRect.getCopy()).width);
+	assertEquals(800, gc.getClipping().width);

There's no guarantee that popState() will modify the wrappered GC in any way.
Comment 10 Willem Duminy CLA 2012-12-03 13:18:02 EST
(In reply to comment #9)

Thank you Randy. 

Considering your assertion: "It is invalid to use a mixture of the SWT graphics and GC that it wrappers."; it is clear that my test case is not valid. However, could this assertion also imply that the attached 'snippet that reproduces the anomaly' makes invalid use of draw2d?

Assuming that the snippet is valid use and that changing GC on every pop incurs a performance penalty; here are two alternative fixes I tested:
1. Call checkGC() at the last pop (when stackPointer == 0)
2. Call checkGC() when the SWTGraphics instance is disposed.

Would any of these be an acceptable solution?

Is it better to call checkPaint() or something else than checkGC()?
Comment 11 Randy Hudson CLA 2012-12-03 13:45:54 EST
Can't this problem be reproduced with just SWT?  Add two Paint listeners to a Canvas, the first one modifies the GC's clip (or foreground color, line width, or anything else), while the second listener assumes the GC is unmodified and starts painting.

Your PaintListener receives the region (x,y,width,height) in the PaintEvent, so it could reset the clip using that.
Comment 12 Willem Duminy CLA 2012-12-04 10:50:38 EST
(In reply to comment #11)
> Can't this problem be reproduced with just SWT?  Add two Paint listeners to
> a Canvas, the first one modifies the GC's clip (or foreground color, line
> width, or anything else), while the second listener assumes the GC is
> unmodified and starts painting.


If I wrote both listeners, I can fix my code.  And I would definitely consider this particular situation to be a bug in my code.  In short, I think it is reasonable to expect that the painting logic within draw2d leaves the GC unchanged.  

If this is not a reasonable expectation, then any careful programmer must assume the worst.  He now has to assume that the library may make arbitrary changes to any of the CG properties.  To make his program 'future proof' he now has to reset all GC fields (clipping, foreground color, background color and so on) before every draw response.  In other words, the client takes a performance knock because the library guarantees nothing in this regard. 

It is also a very real fact that the definition of what is 'reasonable expectation' is the responsibility of the informed designer of a library.  And I (being a novice) have merely an opinion. Thus, I have to accept your call on the matter.  

It seems like the choice is between four options:
a. We explore the question of whether this is a bug or not a bit further.
b. We try and find alternative (and more correct) patches.
c. We extend the public interface of the library that provides an alternative way a client would implement the functionality.
d. We do nothing. Close the bug as 'won't fix'.
Comment 13 Alexander Nyßen CLA 2012-12-04 11:54:26 EST
While this optimization within FigureCanvas might have been built-in by design, I agree that it is not transparent to clients that the underlying GC is left in a changed state after the SWTGraphics state has been fully restored.

I think we should raise a bug against SWT and request that paint listeners get decoupled (which seems to have been the case up to 3.1, otherwise this regression would not have been reported). Having obtained the opinion of the SWT team, we can then decide what to do with SWTGraphics
Comment 14 Willem Duminy CLA 2012-12-05 12:53:28 EST
(In reply to comment #13)
> I think we should raise a bug against SWT and request that paint listeners get
> decoupled (which seems to have been the case up to 3.1, otherwise this
> regression would not have been reported). Having obtained the opinion of the SWT
> team, we can then decide what to do with SWTGraphics

Ok, I understand Randy's point of view a bit better now.  Draw2d expects SWT to take care of this issue.   I'll raise a bug there.
Comment 15 Willem Duminy CLA 2012-12-05 13:36:36 EST
Related bug url: 
https://bugs.eclipse.org/bugs/show_bug.cgi?id=395849