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 }