GcSnapshot.java revision 918aaa5717fce6081557c82ce1c439b6922737d5
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.layoutlib.bridge.impl; 18 19import com.android.ide.common.rendering.api.LayoutLog; 20import com.android.layoutlib.bridge.Bridge; 21 22import android.graphics.Bitmap_Delegate; 23import android.graphics.Canvas; 24import android.graphics.Paint; 25import android.graphics.Paint_Delegate; 26import android.graphics.Rect; 27import android.graphics.RectF; 28import android.graphics.Region; 29import android.graphics.Region_Delegate; 30import android.graphics.Shader_Delegate; 31import android.graphics.Xfermode_Delegate; 32 33import java.awt.AlphaComposite; 34import java.awt.Color; 35import java.awt.Composite; 36import java.awt.Graphics2D; 37import java.awt.RenderingHints; 38import java.awt.Shape; 39import java.awt.geom.AffineTransform; 40import java.awt.geom.Area; 41import java.awt.geom.Rectangle2D; 42import java.awt.image.BufferedImage; 43import java.util.ArrayList; 44 45/** 46 * Class representing a graphics context snapshot, as well as a context stack as a linked list. 47 * <p> 48 * This is based on top of {@link Graphics2D} but can operate independently if none are available 49 * yet when setting transforms and clip information. 50 * <p> 51 * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and 52 * {@link #draw(Drawable, Paint_Delegate)} 53 * 54 * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through 55 * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer} 56 * for each layer. Doing a save() will duplicate this list so that each graphics2D object 57 * ({@link Layer#getGraphics()}) is configured only for the new snapshot. 58 */ 59public class GcSnapshot { 60 61 private final GcSnapshot mPrevious; 62 private final int mFlags; 63 64 /** list of layers. The first item in the list is always the */ 65 private final ArrayList<Layer> mLayers = new ArrayList<Layer>(); 66 67 /** temp transform in case transformation are set before a Graphics2D exists */ 68 private AffineTransform mTransform = null; 69 /** temp clip in case clipping is set before a Graphics2D exists */ 70 private Area mClip = null; 71 72 // local layer data 73 /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}. 74 * If this is null, this does not mean there's no layer, just that the snapshot is not the 75 * one that created the layer. 76 * @see #getLayerSnapshot() 77 */ 78 private final Layer mLocalLayer; 79 private final Paint_Delegate mLocalLayerPaint; 80 private final Rect mLayerBounds; 81 82 public interface Drawable { 83 void draw(Graphics2D graphics, Paint_Delegate paint); 84 } 85 86 /** 87 * Class containing information about a layer. 88 * 89 * This contains graphics, bitmap and layer information. 90 */ 91 private static class Layer { 92 private final Graphics2D mGraphics; 93 private final Bitmap_Delegate mBitmap; 94 private final BufferedImage mImage; 95 /** the flags that were used to configure the layer. This is never changed, and passed 96 * as is when {@link #makeCopy()} is called */ 97 private final int mFlags; 98 /** the original content of the layer when the next object was created. This is not 99 * passed in {@link #makeCopy()} and instead is recreated when a new layer is added 100 * (depending on its flags) */ 101 private BufferedImage mOriginalCopy; 102 103 /** 104 * Creates a layer with a graphics and a bitmap. This is only used to create 105 * the base layer. 106 * 107 * @param graphics the graphics 108 * @param bitmap the bitmap 109 */ 110 Layer(Graphics2D graphics, Bitmap_Delegate bitmap) { 111 mGraphics = graphics; 112 mBitmap = bitmap; 113 mImage = mBitmap.getImage(); 114 mFlags = 0; 115 } 116 117 /** 118 * Creates a layer with a graphics and an image. If the image belongs to a 119 * {@link Bitmap_Delegate} (case of the base layer), then 120 * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used. 121 * 122 * @param graphics the graphics the new graphics for this layer 123 * @param image the image the image from which the graphics came 124 * @param flags the flags that were used to save this layer 125 */ 126 Layer(Graphics2D graphics, BufferedImage image, int flags) { 127 mGraphics = graphics; 128 mBitmap = null; 129 mImage = image; 130 mFlags = flags; 131 } 132 133 /** The Graphics2D, guaranteed to be non null */ 134 Graphics2D getGraphics() { 135 return mGraphics; 136 } 137 138 /** The BufferedImage, guaranteed to be non null */ 139 BufferedImage getImage() { 140 return mImage; 141 } 142 143 /** Returns the layer save flags. This is only valid for additional layers. 144 * For the base layer this will always return 0; 145 * For a given layer, all further copies of this {@link Layer} object in new snapshots 146 * will always return the same value. 147 */ 148 int getFlags() { 149 return mFlags; 150 } 151 152 Layer makeCopy() { 153 if (mBitmap != null) { 154 return new Layer((Graphics2D) mGraphics.create(), mBitmap); 155 } 156 157 return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags); 158 } 159 160 /** sets an optional copy of the original content to be used during restore */ 161 void setOriginalCopy(BufferedImage image) { 162 mOriginalCopy = image; 163 } 164 165 BufferedImage getOriginalCopy() { 166 return mOriginalCopy; 167 } 168 169 void change() { 170 if (mBitmap != null) { 171 mBitmap.change(); 172 } 173 } 174 175 /** 176 * Sets the clip for the graphics2D object associated with the layer. 177 * This should be used over the normal Graphics2D setClip method. 178 * 179 * @param clipShape the shape to use a the clip shape. 180 */ 181 void setClip(Shape clipShape) { 182 // because setClip is only guaranteed to work with rectangle shape, 183 // first reset the clip to max and then intersect the current (empty) 184 // clip with the shap. 185 mGraphics.setClip(null); 186 mGraphics.clip(clipShape); 187 } 188 189 /** 190 * Clips the layer with the given shape. This performs an intersect between the current 191 * clip shape and the given shape. 192 * @param shape the new clip shape. 193 */ 194 public void clip(Shape shape) { 195 mGraphics.clip(shape); 196 } 197 } 198 199 /** 200 * Creates the root snapshot associating it with a given bitmap. 201 * <p> 202 * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be 203 * called before the snapshot can be used to draw. Transform and clip operations are permitted 204 * before. 205 * 206 * @param image the image to associate to the snapshot or null. 207 * @return the root snapshot 208 */ 209 public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { 210 GcSnapshot snapshot = new GcSnapshot(); 211 if (bitmap != null) { 212 snapshot.setBitmap(bitmap); 213 } 214 215 return snapshot; 216 } 217 218 /** 219 * Saves the current state according to the given flags and returns the new current snapshot. 220 * <p/> 221 * This is the equivalent of {@link Canvas#save(int)} 222 * 223 * @param flags the save flags. 224 * @return the new snapshot 225 * 226 * @see Canvas#save(int) 227 */ 228 public GcSnapshot save(int flags) { 229 return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags); 230 } 231 232 /** 233 * Saves the current state and creates a new layer, and returns the new current snapshot. 234 * <p/> 235 * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)} 236 * 237 * @param layerBounds the layer bounds 238 * @param paint the Paint information used to blit the layer back into the layers underneath 239 * upon restore 240 * @param flags the save flags. 241 * @return the new snapshot 242 * 243 * @see Canvas#saveLayer(RectF, Paint, int) 244 */ 245 public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) { 246 return new GcSnapshot(this, layerBounds, paint, flags); 247 } 248 249 /** 250 * Creates the root snapshot. 251 * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible. 252 */ 253 private GcSnapshot() { 254 mPrevious = null; 255 mFlags = 0; 256 mLocalLayer = null; 257 mLocalLayerPaint = null; 258 mLayerBounds = null; 259 } 260 261 /** 262 * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored 263 * into the main graphics when {@link #restore()} is called. 264 * 265 * @param previous the previous snapshot head. 266 * @param layerBounds the region of the layer. Optional, if null, this is a normal save() 267 * @param paint the Paint information used to blit the layer back into the layers underneath 268 * upon restore 269 * @param flags the flags regarding what should be saved. 270 */ 271 private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) { 272 assert previous != null; 273 mPrevious = previous; 274 mFlags = flags; 275 276 // make a copy of the current layers before adding the new one. 277 // This keeps the same BufferedImage reference but creates new Graphics2D for this 278 // snapshot. 279 // It does not copy whatever original copy the layers have, as they will be done 280 // only if the new layer doesn't clip drawing to itself. 281 for (Layer layer : mPrevious.mLayers) { 282 mLayers.add(layer.makeCopy()); 283 } 284 285 if (layerBounds != null) { 286 // get the current transform 287 AffineTransform matrix = mLayers.get(0).getGraphics().getTransform(); 288 289 // transform the layerBounds with the current transform and stores it into a int rect 290 RectF rect2 = new RectF(); 291 mapRect(matrix, rect2, layerBounds); 292 mLayerBounds = new Rect(); 293 rect2.round(mLayerBounds); 294 295 // get the base layer (always at index 0) 296 Layer baseLayer = mLayers.get(0); 297 298 // create the image for the layer 299 BufferedImage layerImage = new BufferedImage( 300 baseLayer.getImage().getWidth(), 301 baseLayer.getImage().getHeight(), 302 (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ? 303 BufferedImage.TYPE_INT_ARGB : 304 BufferedImage.TYPE_INT_RGB); 305 306 // create a graphics for it so that drawing can be done. 307 Graphics2D layerGraphics = layerImage.createGraphics(); 308 309 // because this layer inherits the current context for transform and clip, 310 // set them to one from the base layer. 311 AffineTransform currentMtx = baseLayer.getGraphics().getTransform(); 312 layerGraphics.setTransform(currentMtx); 313 314 // create a new layer for this new layer and add it to the list at the end. 315 mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags)); 316 317 // set the clip on it. 318 Shape currentClip = baseLayer.getGraphics().getClip(); 319 mLocalLayer.setClip(currentClip); 320 321 // if the drawing is not clipped to the local layer only, we save the current content 322 // of all other layers. We are only interested in the part that will actually 323 // be drawn, so we create as small bitmaps as we can. 324 // This is so that we can erase the drawing that goes in the layers below that will 325 // be coming from the layer itself. 326 if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) { 327 int w = mLayerBounds.width(); 328 int h = mLayerBounds.height(); 329 for (int i = 0 ; i < mLayers.size() - 1 ; i++) { 330 Layer layer = mLayers.get(i); 331 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 332 Graphics2D graphics = image.createGraphics(); 333 graphics.drawImage(layer.getImage(), 334 0, 0, w, h, 335 mLayerBounds.left, mLayerBounds.top, 336 mLayerBounds.right, mLayerBounds.bottom, 337 null); 338 graphics.dispose(); 339 layer.setOriginalCopy(image); 340 } 341 } 342 } else { 343 mLocalLayer = null; 344 mLayerBounds = null; 345 } 346 347 mLocalLayerPaint = paint; 348 } 349 350 public void dispose() { 351 for (Layer layer : mLayers) { 352 layer.getGraphics().dispose(); 353 } 354 355 if (mPrevious != null) { 356 mPrevious.dispose(); 357 } 358 } 359 360 /** 361 * Restores the top {@link GcSnapshot}, and returns the next one. 362 */ 363 public GcSnapshot restore() { 364 return doRestore(); 365 } 366 367 /** 368 * Restores the {@link GcSnapshot} to <var>saveCount</var>. 369 * @param saveCount the saveCount or -1 to only restore 1. 370 * 371 * @return the new head of the Gc snapshot stack. 372 */ 373 public GcSnapshot restoreTo(int saveCount) { 374 return doRestoreTo(size(), saveCount); 375 } 376 377 public int size() { 378 if (mPrevious != null) { 379 return mPrevious.size() + 1; 380 } 381 382 return 1; 383 } 384 385 /** 386 * Link the snapshot to a Bitmap_Delegate. 387 * <p/> 388 * This is only for the case where the snapshot was created with a null image when calling 389 * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to 390 * a previous snapshot. 391 * <p/> 392 * If any transform or clip information was set before, they are put into the Graphics object. 393 * @param bitmap the bitmap to link to. 394 */ 395 public void setBitmap(Bitmap_Delegate bitmap) { 396 assert mLayers.size() == 0; 397 398 // create a new Layer for the bitmap. This will be the base layer. 399 Graphics2D graphics2D = bitmap.getImage().createGraphics(); 400 Layer baseLayer = new Layer(graphics2D, bitmap); 401 402 // add it to the list. 403 mLayers.add(baseLayer); 404 405 // if transform and clip where modified before, get the information and give it to the 406 // layer. 407 408 if (mTransform != null) { 409 graphics2D.setTransform(mTransform); 410 mTransform = null; 411 } 412 413 if (mClip != null) { 414 baseLayer.setClip(mClip); 415 mClip = null; 416 } 417 } 418 419 public void translate(float dx, float dy) { 420 if (mLayers.size() > 0) { 421 for (Layer layer : mLayers) { 422 layer.getGraphics().translate(dx, dy); 423 } 424 } else { 425 if (mTransform == null) { 426 mTransform = new AffineTransform(); 427 } 428 mTransform.translate(dx, dy); 429 } 430 } 431 432 public void rotate(double radians) { 433 if (mLayers.size() > 0) { 434 for (Layer layer : mLayers) { 435 layer.getGraphics().rotate(radians); 436 } 437 } else { 438 if (mTransform == null) { 439 mTransform = new AffineTransform(); 440 } 441 mTransform.rotate(radians); 442 } 443 } 444 445 public void scale(float sx, float sy) { 446 if (mLayers.size() > 0) { 447 for (Layer layer : mLayers) { 448 layer.getGraphics().scale(sx, sy); 449 } 450 } else { 451 if (mTransform == null) { 452 mTransform = new AffineTransform(); 453 } 454 mTransform.scale(sx, sy); 455 } 456 } 457 458 public AffineTransform getTransform() { 459 if (mLayers.size() > 0) { 460 // all graphics2D in the list have the same transform 461 return mLayers.get(0).getGraphics().getTransform(); 462 } else { 463 if (mTransform == null) { 464 mTransform = new AffineTransform(); 465 } 466 return mTransform; 467 } 468 } 469 470 public void setTransform(AffineTransform transform) { 471 if (mLayers.size() > 0) { 472 for (Layer layer : mLayers) { 473 layer.getGraphics().setTransform(transform); 474 } 475 } else { 476 if (mTransform == null) { 477 mTransform = new AffineTransform(); 478 } 479 mTransform.setTransform(transform); 480 } 481 } 482 483 public boolean clip(Shape shape, int regionOp) { 484 // Simple case of intersect with existing layers. 485 // Because Graphics2D#setClip works a bit peculiarly, we optimize 486 // the case of clipping by intersection, as it's supported natively. 487 if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) { 488 for (Layer layer : mLayers) { 489 layer.clip(shape); 490 } 491 492 Shape currentClip = getClip(); 493 return currentClip != null && currentClip.getBounds().isEmpty() == false; 494 } 495 496 Area area = null; 497 498 if (regionOp == Region.Op.REPLACE.nativeInt) { 499 area = new Area(shape); 500 } else { 501 area = Region_Delegate.combineShapes(getClip(), shape, regionOp); 502 } 503 504 assert area != null; 505 506 if (mLayers.size() > 0) { 507 if (area != null) { 508 for (Layer layer : mLayers) { 509 layer.setClip(area); 510 } 511 } 512 513 Shape currentClip = getClip(); 514 return currentClip != null && currentClip.getBounds().isEmpty() == false; 515 } else { 516 if (area != null) { 517 mClip = area; 518 } else { 519 mClip = new Area(); 520 } 521 522 return mClip.getBounds().isEmpty() == false; 523 } 524 } 525 526 public boolean clipRect(float left, float top, float right, float bottom, int regionOp) { 527 return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp); 528 } 529 530 /** 531 * Returns the current clip, or null if none have been setup. 532 */ 533 public Shape getClip() { 534 if (mLayers.size() > 0) { 535 // they all have the same clip 536 return mLayers.get(0).getGraphics().getClip(); 537 } else { 538 return mClip; 539 } 540 } 541 542 private GcSnapshot doRestoreTo(int size, int saveCount) { 543 if (size <= saveCount) { 544 return this; 545 } 546 547 // restore the current one first. 548 GcSnapshot previous = doRestore(); 549 550 if (size == saveCount + 1) { // this was the only one that needed restore. 551 return previous; 552 } else { 553 return previous.doRestoreTo(size - 1, saveCount); 554 } 555 } 556 557 /** 558 * Executes the Drawable's draw method, with a null paint delegate. 559 * <p/> 560 * Note that the method can be called several times if there are more than one active layer. 561 * @param drawable 562 */ 563 public void draw(Drawable drawable) { 564 draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/); 565 } 566 567 /** 568 * Executes the Drawable's draw method. 569 * <p/> 570 * Note that the method can be called several times if there are more than one active layer. 571 * @param drawable 572 * @param paint 573 * @param compositeOnly whether the paint is used for composite only. This is typically 574 * the case for bitmaps. 575 * @param forceSrcMode if true, this overrides the composite to be SRC 576 */ 577 public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, 578 boolean forceSrcMode) { 579 // the current snapshot may not have a mLocalLayer (ie it was created on save() instead 580 // of saveLayer(), but that doesn't mean there's no layer. 581 // mLayers however saves all the information we need (flags). 582 if (mLayers.size() == 1) { 583 // no layer, only base layer. easy case. 584 drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode); 585 } else { 586 // draw in all the layers until the layer save flags tells us to stop (ie drawing 587 // in that layer is limited to the layer itself. 588 int flags; 589 int i = mLayers.size() - 1; 590 591 do { 592 Layer layer = mLayers.get(i); 593 594 drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode); 595 596 // then go to previous layer, only if there are any left, and its flags 597 // doesn't restrict drawing to the layer itself. 598 i--; 599 flags = layer.getFlags(); 600 } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); 601 } 602 } 603 604 private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, 605 boolean compositeOnly, boolean forceSrcMode) { 606 Graphics2D originalGraphics = layer.getGraphics(); 607 // get a Graphics2D object configured with the drawing parameters. 608 Graphics2D configuredGraphics2D = 609 paint != null ? 610 createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) : 611 (Graphics2D) originalGraphics.create(); 612 613 try { 614 drawable.draw(configuredGraphics2D, paint); 615 layer.change(); 616 } finally { 617 // dispose Graphics2D object 618 configuredGraphics2D.dispose(); 619 } 620 } 621 622 private GcSnapshot doRestore() { 623 if (mPrevious != null) { 624 if (mLocalLayer != null) { 625 // prepare to blit the layers in which we have draw, in the layer beneath 626 // them, starting with the top one (which is the current local layer). 627 int i = mLayers.size() - 1; 628 int flags; 629 do { 630 Layer dstLayer = mLayers.get(i - 1); 631 632 restoreLayer(dstLayer); 633 634 flags = dstLayer.getFlags(); 635 i--; 636 } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); 637 } 638 639 // if this snapshot does not save everything, then set the previous snapshot 640 // to this snapshot content 641 642 // didn't save the matrix? set the current matrix on the previous snapshot 643 if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) { 644 AffineTransform mtx = getTransform(); 645 for (Layer layer : mPrevious.mLayers) { 646 layer.getGraphics().setTransform(mtx); 647 } 648 } 649 650 // didn't save the clip? set the current clip on the previous snapshot 651 if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) { 652 Shape clip = getClip(); 653 for (Layer layer : mPrevious.mLayers) { 654 layer.setClip(clip); 655 } 656 } 657 } 658 659 for (Layer layer : mLayers) { 660 layer.getGraphics().dispose(); 661 } 662 663 return mPrevious; 664 } 665 666 private void restoreLayer(Layer dstLayer) { 667 668 Graphics2D baseGfx = dstLayer.getImage().createGraphics(); 669 670 // if the layer contains an original copy this means the flags 671 // didn't restrict drawing to the local layer and we need to make sure the 672 // layer bounds in the layer beneath didn't receive any drawing. 673 // so we use the originalCopy to erase the new drawings in there. 674 BufferedImage originalCopy = dstLayer.getOriginalCopy(); 675 if (originalCopy != null) { 676 Graphics2D g = (Graphics2D) baseGfx.create(); 677 g.setComposite(AlphaComposite.Src); 678 679 g.drawImage(originalCopy, 680 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 681 0, 0, mLayerBounds.width(), mLayerBounds.height(), 682 null); 683 g.dispose(); 684 } 685 686 // now draw put the content of the local layer onto the layer, 687 // using the paint information 688 Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, 689 true /*alphaOnly*/, false /*forceSrcMode*/); 690 691 g.drawImage(mLocalLayer.getImage(), 692 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 693 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 694 null); 695 g.dispose(); 696 697 baseGfx.dispose(); 698 } 699 700 /** 701 * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. 702 * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. 703 */ 704 private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, 705 boolean compositeOnly, boolean forceSrcMode) { 706 // make new one graphics 707 Graphics2D g = (Graphics2D) original.create(); 708 709 // configure it 710 711 if (paint.isAntiAliased()) { 712 g.setRenderingHint( 713 RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 714 g.setRenderingHint( 715 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 716 } 717 718 boolean customShader = false; 719 720 // get the shader first, as it'll replace the color if it can be used it. 721 if (compositeOnly == false) { 722 Shader_Delegate shaderDelegate = paint.getShader(); 723 if (shaderDelegate != null) { 724 if (shaderDelegate.isSupported()) { 725 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); 726 assert shaderPaint != null; 727 if (shaderPaint != null) { 728 g.setPaint(shaderPaint); 729 customShader = true; 730 } 731 } else { 732 Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, 733 shaderDelegate.getSupportMessage(), 734 null); 735 } 736 } 737 738 // if no shader, use the paint color 739 if (customShader == false) { 740 g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); 741 } 742 743 // set the stroke 744 g.setStroke(paint.getJavaStroke()); 745 } 746 747 // the alpha for the composite. Always opaque if the normal paint color is used since 748 // it contains the alpha 749 int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; 750 751 if (forceSrcMode) { 752 g.setComposite(AlphaComposite.getInstance( 753 AlphaComposite.SRC, (float) alpha / 255.f)); 754 } else { 755 boolean customXfermode = false; 756 Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); 757 if (xfermodeDelegate != null) { 758 if (xfermodeDelegate.isSupported()) { 759 Composite composite = xfermodeDelegate.getComposite(alpha); 760 assert composite != null; 761 if (composite != null) { 762 g.setComposite(composite); 763 customXfermode = true; 764 } 765 } else { 766 Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, 767 xfermodeDelegate.getSupportMessage(), 768 null); 769 } 770 } 771 772 // if there was no custom xfermode, but we have alpha (due to a shader and a non 773 // opaque alpha channel in the paint color), then we create an AlphaComposite anyway 774 // that will handle the alpha. 775 if (customXfermode == false && alpha != 0xFF) { 776 g.setComposite(AlphaComposite.getInstance( 777 AlphaComposite.SRC_OVER, (float) alpha / 255.f)); 778 } 779 } 780 781 return g; 782 } 783 784 private void mapRect(AffineTransform matrix, RectF dst, RectF src) { 785 // array with 4 corners 786 float[] corners = new float[] { 787 src.left, src.top, 788 src.right, src.top, 789 src.right, src.bottom, 790 src.left, src.bottom, 791 }; 792 793 // apply the transform to them. 794 matrix.transform(corners, 0, corners, 0, 4); 795 796 // now put the result in the rect. We take the min/max of Xs and min/max of Ys 797 dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); 798 dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); 799 800 dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); 801 dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); 802 } 803 804} 805