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