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