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