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 17 package com.android.layoutlib.bridge.impl; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 22 import android.graphics.Bitmap_Delegate; 23 import android.graphics.Canvas; 24 import android.graphics.Paint; 25 import android.graphics.Paint_Delegate; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.graphics.Region; 29 import android.graphics.Region_Delegate; 30 import android.graphics.Shader_Delegate; 31 import android.graphics.Xfermode_Delegate; 32 33 import java.awt.AlphaComposite; 34 import java.awt.Color; 35 import java.awt.Composite; 36 import java.awt.Graphics2D; 37 import java.awt.RenderingHints; 38 import java.awt.Shape; 39 import java.awt.geom.AffineTransform; 40 import java.awt.geom.Area; 41 import java.awt.geom.Rectangle2D; 42 import java.awt.image.BufferedImage; 43 import 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 */ 59 public 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 // create a new Layer for the bitmap. This will be the base layer. 397 Graphics2D graphics2D = bitmap.getImage().createGraphics(); 398 Layer baseLayer = new Layer(graphics2D, bitmap); 399 400 // Set the current transform and clip which can either come from mTransform/mClip if they 401 // were set when there was no bitmap/layers or from the current base layers if there is 402 // one already. 403 404 graphics2D.setTransform(getTransform()); 405 // reset mTransform in case there was one. 406 mTransform = null; 407 408 baseLayer.setClip(getClip()); 409 // reset mClip in case there was one. 410 mClip = null; 411 412 // replace whatever current layers we have with this. 413 mLayers.clear(); 414 mLayers.add(baseLayer); 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(LayoutLog.TAG_SHADER, 732 shaderDelegate.getSupportMessage(), 733 null /*throwable*/, null /*data*/); 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(LayoutLog.TAG_XFERMODE, 766 xfermodeDelegate.getSupportMessage(), 767 null /*throwable*/, null /*data*/); 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