1 /*
   2  * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package test.com.sun.javafx.sg.prism;
  27 
  28 import com.sun.javafx.geom.BaseBounds;
  29 import com.sun.javafx.geom.RectBounds;
  30 import com.sun.javafx.geom.Rectangle;
  31 import com.sun.javafx.geom.transform.BaseTransform;
  32 import com.sun.javafx.sg.prism.NGNode;
  33 import com.sun.javafx.sg.prism.NGNodeShim;
  34 import com.sun.javafx.sg.prism.NGPath;
  35 import com.sun.javafx.sg.prism.NGRectangle;
  36 import com.sun.prism.Graphics;
  37 import com.sun.prism.paint.Color;
  38 import com.sun.scenario.effect.Blend;
  39 import com.sun.scenario.effect.Effect;
  40 import com.sun.scenario.effect.FilterContext;
  41 import com.sun.scenario.effect.ImageData;
  42 import org.junit.Before;
  43 import org.junit.Test;
  44 import static org.junit.Assert.assertEquals;
  45 import static org.junit.Assert.assertFalse;
  46 import static org.junit.Assert.assertNotNull;
  47 import static org.junit.Assert.assertNotSame;
  48 import static org.junit.Assert.assertNull;
  49 import static org.junit.Assert.assertSame;
  50 import static org.junit.Assert.assertTrue;
  51 
  52 /**
  53  */
  54 public class NGNodeTest extends NGTestBase {
  55     NGNodeMock n;
  56 
  57     @Before
  58     public void setup() {
  59         n = new NGNodeMock();
  60     }
  61 
  62     /**************************************************************************
  63      *                                                                        *
  64      * Various tests for hasOpaqueRegion                                      *
  65      *                                                                        *
  66      *************************************************************************/
  67 
  68     @Test
  69     public void hasOpaqueRegionIsFalseIfOpacityIsLessThanOne() {
  70         n.setOpacity(1);
  71         assertTrue(n.hasOpaqueRegion());
  72 
  73         n.setOpacity(.5f);
  74         assertFalse(n.hasOpaqueRegion());
  75 
  76         n.setOpacity(0);
  77         assertFalse(n.hasOpaqueRegion());
  78     }
  79 
  80     @Test
  81     public void hasOpaqueRegionIsFalseIfEffectIsNotNullAndEffect_reducesOpaquePixels_returnsFalse() {
  82         n.setEffect(new TransparentEffect());
  83         assertFalse(n.hasOpaqueRegion());
  84         n.setEffect(null);
  85         assertTrue(n.hasOpaqueRegion());
  86     }
  87 
  88     @Test
  89     public void hasOpaqueRegionIsTrueIfEffectIsNotNullAndEffect_reducesOpaquePixels_returnsTrue() {
  90         n.setEffect(new OpaqueEffect());
  91         assertTrue(n.hasOpaqueRegion());
  92     }
  93 
  94     @Test
  95     public void hasOpaqueRegionIsTrueIfClipIsNullOrHappy() {
  96         n.setClipNode(null);
  97         assertTrue(n.hasOpaqueRegion());
  98 
  99         NGRectangle r = new NGRectangle();
 100         r.updateRectangle(0, 0, 10, 10, 0, 0);
 101         r.setFillPaint(Color.BLACK);
 102         n.setClipNode(r);
 103 
 104         assertTrue(n.hasOpaqueRegion());
 105     }
 106 
 107     @Test
 108     public void hasOpaqueRegionIsFalseIfClipDoesNotSupportOpaqueRegions() {
 109         n.setClipNode(new NGPath());
 110         assertFalse(n.hasOpaqueRegion());
 111     }
 112 
 113     @Test
 114     public void hasOpaqueRegionIsFalseIfClipDoesNotHaveAnOpaqueRegion() {
 115         NGRectangle r = new NGRectangle();
 116         n.setClipNode(r);
 117         assertFalse(n.hasOpaqueRegion());
 118     }
 119 
 120     @Test
 121     public void hasOpaqueRegionIsFalseIfBlendModeIsNotNullOrSRC_OVER() {
 122         for (Blend.Mode mode : Blend.Mode.values()) {
 123             // Set blend mode
 124             n.setNodeBlendMode(mode);
 125             if (mode == Blend.Mode.SRC_OVER) {
 126                 assertTrue(n.hasOpaqueRegion());
 127             } else {
 128                 assertFalse(n.hasOpaqueRegion());
 129             }
 130         }
 131         n.setNodeBlendMode(null);
 132         assertTrue(n.hasOpaqueRegion());
 133     }
 134 
 135     /**************************************************************************
 136      *                                                                        *
 137      * Various tests for opaque region caching and invalidation               *
 138      *                                                                        *
 139      *************************************************************************/
 140 
 141     /**
 142      * The same opaqueRegion instance should be reused every time, unless
 143      * we transition to a state where opaqueRegion is not true, in which case
 144      * opaqueRegion will be null.
 145      */
 146     @Test
 147     public void opaqueRegionCached() {
 148         RectBounds or = n.getOpaqueRegion();
 149         assertNotNull(or);
 150 
 151         // Simulating changing state that would result in a different opaque region
 152         // but should be the same instance
 153         n.changeOpaqueRegion(10, 10, 100, 100);
 154         assertSame(or, n.getOpaqueRegion());
 155 
 156         // Changing to something that will cause the hasOpaqueRegions to return false
 157         n.setEffect(new TransparentEffect());
 158         assertNull(n.getOpaqueRegion());
 159 
 160         // Change back to something that will work and get a different instance
 161         n.setEffect(null);
 162         assertNotSame(or, n.getOpaqueRegion());
 163     }
 164 
 165     /**
 166      * Test that as long as the opaque region is valid, that we don't
 167      * try to compute a new one
 168      */
 169     @Test
 170     public void opaqueRegionCached2() {
 171         // Cause it to be cached
 172         n.getOpaqueRegion();
 173         n.opaqueRegionRecomputed = false;
 174 
 175         // Just calling it again should do nothing
 176         n.getOpaqueRegion();
 177         assertFalse(n.opaqueRegionRecomputed);
 178     }
 179 
 180     /**
 181      * If the transform is changed, we need to recompute the opaque region. But
 182      * if the transform is not changed, then we shouldn't. Since NGNode doesn't keep
 183      * a reference to the last transform object it was passed and doesn't perform
 184      * any kind of check, we're going to invalidate the opaque region every time
 185      * it is called. If we change that behavior later, this test will need to be
 186      * updated accordingly.
 187      */
 188     @Test
 189     public void opaqueRegionRecomputedWhenTransformChanges() {
 190         n.getOpaqueRegion();
 191 
 192         n.opaqueRegionRecomputed = false;
 193         n.setTransformMatrix(BaseTransform.getTranslateInstance(1, 1));
 194         n.getOpaqueRegion();
 195         assertTrue(n.opaqueRegionRecomputed);
 196 
 197         n.opaqueRegionRecomputed = false;
 198         n.setTransformMatrix(BaseTransform.getTranslateInstance(1, 1));
 199         n.getOpaqueRegion();
 200         assertFalse(n.opaqueRegionRecomputed);
 201 
 202         n.opaqueRegionRecomputed = false;
 203         n.setTransformMatrix(BaseTransform.IDENTITY_TRANSFORM);
 204         n.getOpaqueRegion();
 205         assertTrue(n.opaqueRegionRecomputed);
 206 
 207         n.opaqueRegionRecomputed = false;
 208         n.setTransformMatrix(BaseTransform.IDENTITY_TRANSFORM);
 209         n.getOpaqueRegion();
 210         assertFalse(n.opaqueRegionRecomputed);
 211     }
 212 
 213     /**
 214      * If we change references to the clip node, then we must recompute the
 215      * opaque region, regardless of whether we set the reference to a
 216      * different equivalent guy (since we don't check for equals and without
 217      * doing deep equals it would fail anyway)
 218      */
 219     @Test
 220     public void opaqueRegionRecomputedWhenClipNodeReferenceChanges() {
 221         n.getOpaqueRegion();
 222 
 223         // I'm setting a clip, and even though the clip isn't opaque,
 224         // I will have to ask the hasOpaqueRegions question again
 225         n.opaqueRegionRecomputed = false;
 226         NGRectangle clip = new NGRectangle();
 227         n.setClipNode(clip);
 228         n.getOpaqueRegion();
 229         assertTrue(n.opaqueRegionRecomputed);
 230 
 231         // Set the same clip instance again, which should have no effect
 232         n.opaqueRegionRecomputed = false;
 233         n.setClipNode(clip);
 234         n.getOpaqueRegion();
 235         assertFalse(n.opaqueRegionRecomputed);
 236 
 237         // Try out a nice clip, this time with a nice opaque region.
 238         n.opaqueRegionRecomputed = false;
 239         clip = new NGRectangle();
 240         clip.updateRectangle(0, 0, 10, 10, 0, 0);
 241         clip.setFillPaint(Color.BLACK);
 242         n.setClipNode(clip);
 243         n.getOpaqueRegion();
 244         assertTrue(n.opaqueRegionRecomputed);
 245 
 246         // Try out an equivalent, but different instance, clip.
 247         n.opaqueRegionRecomputed = false;
 248         clip = new NGRectangle();
 249         clip.updateRectangle(0, 0, 10, 10, 0, 0);
 250         clip.setFillPaint(Color.BLACK);
 251         n.setClipNode(clip);
 252         n.getOpaqueRegion();
 253         assertTrue(n.opaqueRegionRecomputed);
 254 
 255         // Set it to null
 256         n.opaqueRegionRecomputed = false;
 257         n.setClipNode(null);
 258         n.getOpaqueRegion();
 259         assertTrue(n.opaqueRegionRecomputed);
 260 
 261         // Set it to null again, which should have no effect
 262         n.opaqueRegionRecomputed = false;
 263         n.setClipNode(null);
 264         n.getOpaqueRegion();
 265         assertFalse(n.opaqueRegionRecomputed);
 266     }
 267 
 268     /**************************************************************************
 269      *                                                                        *
 270      * Various tests for opaque region caching and invalidation               *
 271      *                                                                        *
 272      *************************************************************************/
 273 
 274     /**
 275      * If one of the inputs to the "hasOpaqueRegions" changes in such a way
 276      * that we have to recompute the opaque region for the clip, then we
 277      * also need to recompute the opaque region of the node being clipped!
 278      */
 279     @Test
 280     public void opaqueRegionRecomputedWhenClipNodeHasOpaqueRegionChanges() {
 281         n.getOpaqueRegion();
 282 
 283         // Start off with a clip which supports an opaque region
 284         n.opaqueRegionRecomputed = false;
 285         NGRectangle clip = new NGRectangle();
 286         clip.updateRectangle(0, 0, 10, 10, 0, 0);
 287         clip.setFillPaint(Color.BLACK);
 288         n.setClipNode(clip);
 289         n.getOpaqueRegion();
 290 
 291         // Switch the clip so that it doesn't any longer
 292         n.opaqueRegionRecomputed = false;
 293         clip.setFillPaint(null);
 294         n.getOpaqueRegion();
 295         assertTrue(n.opaqueRegionRecomputed);
 296 
 297         // Switch back to the clip so that it does
 298         n.opaqueRegionRecomputed = false;
 299         clip.setFillPaint(Color.WHITE);
 300         n.getOpaqueRegion();
 301         assertTrue(n.opaqueRegionRecomputed);
 302     }
 303 
 304     /**
 305      * If the opacity switches from 1 to a value < 1, then we must recompute
 306      * the opaque region (the answer will always be false! I guess if we were
 307      * really clever we could just set opaque region to null in this case and
 308      * set "invalid" to false!)
 309      */
 310     @Test
 311     public void opaqueRegionRecomputedWhenOpacityGoesFromOneToLessThanOne() {
 312         n.getOpaqueRegion();
 313 
 314         // Change to less than one
 315         n.opaqueRegionRecomputed = false;
 316         n.setOpacity(.9f);
 317         n.getOpaqueRegion();
 318         assertTrue(n.opaqueRegionRecomputed);
 319 
 320         // Change to one again
 321         n.opaqueRegionRecomputed = false;
 322         n.setOpacity(1f);
 323         n.getOpaqueRegion();
 324         assertTrue(n.opaqueRegionRecomputed);
 325 
 326         // Change to one again!
 327         n.opaqueRegionRecomputed = false;
 328         n.setOpacity(1f);
 329         n.getOpaqueRegion();
 330         assertFalse(n.opaqueRegionRecomputed);
 331 
 332         // Change to zero. Note that changing to 0 doesn't mean that we have
 333         // to invalidate the opaque region, because a 0 opacity node will never
 334         // be asked for its opaque region, but we do it anyway :-(.
 335         n.opaqueRegionRecomputed = false;
 336         n.setOpacity(0f);
 337         n.getOpaqueRegion();
 338         assertTrue(n.opaqueRegionRecomputed);
 339     }
 340 
 341     /**
 342      * Tests for handling what happens as we toggle between Zero and other values.
 343      */
 344     @Test
 345     public void opaqueRegionRecomputedWhenOpacityGoesFromZeroToMoreThanZero() {
 346         n.setOpacity(0f);
 347         n.getOpaqueRegion();
 348 
 349         // Change to > 0 and < 1
 350         n.opaqueRegionRecomputed = false;
 351         n.setOpacity(.9f);
 352         n.getOpaqueRegion();
 353         assertTrue(n.opaqueRegionRecomputed);
 354 
 355         // Change to zero again
 356         n.opaqueRegionRecomputed = false;
 357         n.setOpacity(0f);
 358         n.getOpaqueRegion();
 359         assertTrue(n.opaqueRegionRecomputed);
 360 
 361         // Change to zero again!
 362         n.opaqueRegionRecomputed = false;
 363         n.setOpacity(0f);
 364         n.getOpaqueRegion();
 365         assertFalse(n.opaqueRegionRecomputed);
 366 
 367         // Change to one
 368         n.opaqueRegionRecomputed = false;
 369         n.setOpacity(1f);
 370         n.getOpaqueRegion();
 371         assertTrue(n.opaqueRegionRecomputed);
 372     }
 373 
 374     /**
 375      * If the opacity changes between 0 and 1, there is no point invalidating the
 376      * opaqueRegion since it will always remain disabled in this range.
 377      */
 378     @Test
 379     public void opaqueRegionNotRecomputedWhenOpacityNeverGoesToOneOrZero() {
 380         n.setOpacity(.1f);
 381         n.getOpaqueRegion();
 382 
 383         for (float f=.1f; f<.9f; f+=.1) {
 384             // Change to > 0 and < 1
 385             n.opaqueRegionRecomputed = false;
 386             n.setOpacity(f);
 387             n.getOpaqueRegion();
 388             assertFalse(n.opaqueRegionRecomputed);
 389         }
 390     }
 391 
 392     // Note that when depth buffer is enabled we should NEVER be asking for the opaque
 393     // region, so we don't do anything special in terms of invalidating the opaque region
 394 
 395     @Test
 396     public void opaqueRegionRecomputedWhenBlendModeChanges() {
 397         n.getOpaqueRegion();
 398 
 399         for (Blend.Mode mode : Blend.Mode.values()) {
 400             // Set blend mode
 401             n.opaqueRegionRecomputed = false;
 402             n.setNodeBlendMode(mode);
 403             n.getOpaqueRegion();
 404             if (mode == Blend.Mode.SRC_OVER) continue;
 405             assertTrue(n.opaqueRegionRecomputed);
 406 
 407             // Set back to null
 408             n.opaqueRegionRecomputed = false;
 409             n.setNodeBlendMode(null);
 410             n.getOpaqueRegion();
 411             assertTrue(n.opaqueRegionRecomputed);
 412 
 413             // Set to SRC_OVER (should recompute even though it may be a NOP)
 414             // For leaf nodes it is a NOP, but for groups there is a difference
 415             n.opaqueRegionRecomputed = false;
 416             n.setNodeBlendMode(Blend.Mode.SRC_OVER);
 417             n.getOpaqueRegion();
 418             assertTrue(n.opaqueRegionRecomputed);
 419 
 420             // Set to blend mode (should do it)
 421             n.opaqueRegionRecomputed = false;
 422             n.setNodeBlendMode(mode);
 423             n.getOpaqueRegion();
 424             assertTrue(n.opaqueRegionRecomputed);
 425 
 426             // Set back to SRC_OVER (should do it)
 427             n.opaqueRegionRecomputed = false;
 428             n.setNodeBlendMode(Blend.Mode.SRC_OVER);
 429             n.getOpaqueRegion();
 430             assertTrue(n.opaqueRegionRecomputed);
 431 
 432             // Set back to null
 433             n.opaqueRegionRecomputed = false;
 434             n.setNodeBlendMode(null);
 435             n.getOpaqueRegion();
 436             assertTrue(n.opaqueRegionRecomputed);
 437         }
 438     }
 439 
 440     /**
 441      * If we change references to the effect, we're might have to
 442      * figure things out again. Right now, we only pay attention to changes
 443      * from null to not-null (and vice versa). To enable opaque regions for
 444      * effects, we need to be notified by the effect whenever the effect
 445      * changes in such a way as to impact whether it can participate in
 446      * opaque regions.
 447      */
 448     @Test
 449     public void opaqueRegionNotRecomputedWhenEffectReferenceChanges() {
 450         n.getOpaqueRegion();
 451 
 452         // Set some effect (was null)
 453         n.opaqueRegionRecomputed = false;
 454         n.setEffect(new TransparentEffect());
 455         n.getOpaqueRegion();
 456         assertTrue(n.opaqueRegionRecomputed);
 457 
 458         // Change to some effect which messes with alpha.
 459         n.opaqueRegionRecomputed = false;
 460         n.setEffect(new TransparentEffect());
 461         n.getOpaqueRegion();
 462         assertTrue(n.opaqueRegionRecomputed);
 463 
 464         // Change to null.
 465         n.opaqueRegionRecomputed = false;
 466         n.setEffect(null);
 467         n.getOpaqueRegion();
 468         assertTrue(n.opaqueRegionRecomputed);
 469 
 470         // Change to some effect that messes with alpha
 471         n.opaqueRegionRecomputed = false;
 472         Effect effect = new TransparentEffect();
 473         n.setEffect(effect);
 474         n.getOpaqueRegion();
 475         assertTrue(n.opaqueRegionRecomputed);
 476 
 477         // Set the same effect again! Right now we simply recompute
 478         // every time if an effect may mess with alpha
 479         n.opaqueRegionRecomputed = false;
 480         n.setEffect(effect);
 481         n.getOpaqueRegion();
 482         assertTrue(n.opaqueRegionRecomputed);
 483 
 484         // Set the effect to be one that will never mess with alpha
 485         n.opaqueRegionRecomputed = false;
 486         effect = new OpaqueEffect();
 487         n.setEffect(effect);
 488         n.getOpaqueRegion();
 489         assertTrue(n.opaqueRegionRecomputed);
 490 
 491         // Set it to the same instance. Right now we simply recompute
 492         // every time if an effect may mess with alpha
 493         n.opaqueRegionRecomputed = false;
 494         n.setEffect(effect);
 495         n.getOpaqueRegion();
 496         assertTrue(n.opaqueRegionRecomputed);
 497 
 498         // Set it to another instance that also doesn't mess with alpha.
 499         // Right now we simply recompute every time if an effect may mess with alpha
 500         n.opaqueRegionRecomputed = false;
 501         n.setEffect(new OpaqueEffect());
 502         n.getOpaqueRegion();
 503         assertTrue(n.opaqueRegionRecomputed);
 504     }
 505 
 506     /**************************************************************************
 507      *                                                                        *
 508      * getOpaqueRegion tests                                                  *
 509      *                                                                        *
 510      *************************************************************************/
 511 
 512     @Test
 513     public void testGetOpaqueRegionReturnsNullIf_supportsOpaqueRegion_returnsFalse() {
 514         NGPath path = new NGPath();
 515         path.setFillPaint(Color.BLACK);
 516         assertNull(path.getOpaqueRegion());
 517     }
 518 
 519     @Test public void testGetOpaqueRegionReturnsNullIf_hasOpaqueRegion_returnsFalse() {
 520         n.setEffect(new TransparentEffect());
 521         assertNull(n.getOpaqueRegion());
 522     }
 523 
 524     @Test public void testGetOpaqueRegionWithNoClip() {
 525         assertEquals(new RectBounds(0, 0, 10, 10), n.getOpaqueRegion());
 526     }
 527 
 528     @Test public void testGetOpaqueRegionWithSimpleRectangleClip() {
 529         NGRectangle clip = new NGRectangle();
 530         clip.setFillPaint(Color.BLACK);
 531         clip.updateRectangle(3, 3, 4, 4, 0, 0);
 532         n.setClipNode(clip);
 533         assertEquals(new RectBounds(3, 3, 7, 7), n.getOpaqueRegion());
 534     }
 535 
 536     @Test public void testGetOpaqueRegionWithSimpleRectangleClipWithNoFill() {
 537         NGRectangle clip = new NGRectangle();
 538         clip.updateRectangle(3, 3, 4, 4, 0, 0);
 539         n.setClipNode(clip);
 540         assertNull(n.getOpaqueRegion());
 541     }
 542 
 543     @Test public void testGetOpaqueRegionWithTranslatedRectangleClip() {
 544         NGRectangle clip = new NGRectangle();
 545         clip.setFillPaint(Color.BLACK);
 546         clip.updateRectangle(0, 0, 4, 4, 0, 0);
 547         clip.setTransformMatrix(BaseTransform.getTranslateInstance(2, 2));
 548         n.setClipNode(clip);
 549         assertEquals(new RectBounds(2, 2, 6, 6), n.getOpaqueRegion());
 550     }
 551 
 552     @Test public void testGetOpaqueRegionWithScaledRectangleClip() {
 553         NGRectangle clip = new NGRectangle();
 554         clip.setFillPaint(Color.BLACK);
 555         clip.updateRectangle(0, 0, 4, 4, 0, 0);
 556         clip.setTransformMatrix(BaseTransform.getScaleInstance(.5, .5));
 557         n.setClipNode(clip);
 558         assertEquals(new RectBounds(0, 0, 2, 2), n.getOpaqueRegion());
 559     }
 560 
 561     @Test public void testGetOpaqueRegionWithTranslatedAndScaledRectangleClip() {
 562         NGRectangle clip = new NGRectangle();
 563         clip.setFillPaint(Color.BLACK);
 564         clip.updateRectangle(0, 0, 4, 4, 0, 0);
 565         clip.setTransformMatrix(
 566                 BaseTransform.getTranslateInstance(2, 2).deriveWithConcatenation(
 567                         BaseTransform.getScaleInstance(.5, .5)));
 568         n.setClipNode(clip);
 569         assertEquals(new RectBounds(2, 2, 4, 4), n.getOpaqueRegion());
 570     }
 571 
 572     @Test public void testGetOpaqueRegionWithRotatedRectangleClip() {
 573         NGRectangle clip = new NGRectangle();
 574         clip.setFillPaint(Color.BLACK);
 575         clip.updateRectangle(0, 0, 4, 4, 0, 0);
 576         clip.setTransformMatrix(BaseTransform.getRotateInstance(45, 5, 5));
 577         n.setClipNode(clip);
 578         assertNull(n.getOpaqueRegion());
 579     }
 580 
 581     class NGNodeMock extends NGNodeShim {
 582         boolean opaqueRegionRecomputed = false;
 583         RectBounds computedOpaqueRegion = new RectBounds(0, 0, 10, 10);
 584 
 585         void changeOpaqueRegion(float x, float y, float x2, float y2) {
 586             computedOpaqueRegion = new RectBounds(x, y, x2, y2);
 587             geometryChanged();
 588         }
 589 
 590         @Override
 591         public boolean hasOpaqueRegion() {
 592             opaqueRegionRecomputed = true;
 593             return super.hasOpaqueRegion()
 594                     && computedOpaqueRegion != null;
 595         }
 596 
 597         @Override
 598         protected RectBounds computeOpaqueRegion(RectBounds opaqueRegion) {
 599             opaqueRegionRecomputed = true;
 600             assert computedOpaqueRegion != null;
 601             return (RectBounds) opaqueRegion.deriveWithNewBounds(computedOpaqueRegion);
 602         }
 603 
 604         @Override
 605         protected void renderContent(Graphics g) { }
 606 
 607         @Override
 608         protected boolean hasOverlappingContents() {
 609             return false;
 610         }
 611 
 612         @Override
 613         protected boolean supportsOpaqueRegions() {
 614             return true;
 615         }
 616     }
 617 
 618     static abstract class MockEffect extends Effect {
 619 
 620         @Override
 621         public ImageData filter(FilterContext fctx, BaseTransform transform, Rectangle outputClip, Object renderHelper, Effect defaultInput) {
 622             return null;
 623         }
 624 
 625         @Override
 626         public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
 627             return null;
 628         }
 629 
 630         @Override
 631         public AccelType getAccelType(FilterContext fctx) {
 632             return AccelType.OPENGL;
 633         }
 634     }
 635 
 636     class TransparentEffect extends MockEffect {
 637         @Override
 638         public boolean reducesOpaquePixels() {
 639             return true;
 640         }
 641     }
 642 
 643     class OpaqueEffect extends MockEffect {
 644         @Override
 645         public boolean reducesOpaquePixels() {
 646             return false;
 647         }
 648 
 649     }
 650 }