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