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 */ 81 private final Layer mLocalLayer; 82 private final Paint_Delegate mLocalLayerPaint; 83 private final Rect mLayerBounds; 84 85 public interface Drawable { draw(Graphics2D graphics, Paint_Delegate paint)86 void draw(Graphics2D graphics, Paint_Delegate paint); 87 } 88 89 /** 90 * Class containing information about a layer. 91 * 92 * This contains graphics, bitmap and layer information. 93 */ 94 private static class Layer { 95 private final Graphics2D mGraphics; 96 private final Bitmap_Delegate mBitmap; 97 private final BufferedImage mImage; 98 /** the flags that were used to configure the layer. This is never changed, and passed 99 * as is when {@link #makeCopy()} is called */ 100 private final int mFlags; 101 /** the original content of the layer when the next object was created. This is not 102 * passed in {@link #makeCopy()} and instead is recreated when a new layer is added 103 * (depending on its flags) */ 104 private BufferedImage mOriginalCopy; 105 106 /** 107 * Creates a layer with a graphics and a bitmap. This is only used to create 108 * the base layer. 109 * 110 * @param graphics the graphics 111 * @param bitmap the bitmap 112 */ Layer(Graphics2D graphics, Bitmap_Delegate bitmap)113 Layer(Graphics2D graphics, Bitmap_Delegate bitmap) { 114 mGraphics = graphics; 115 mBitmap = bitmap; 116 mImage = mBitmap.getImage(); 117 mFlags = 0; 118 } 119 120 /** 121 * Creates a layer with a graphics and an image. If the image belongs to a 122 * {@link Bitmap_Delegate} (case of the base layer), then 123 * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used. 124 * 125 * @param graphics the graphics the new graphics for this layer 126 * @param image the image the image from which the graphics came 127 * @param flags the flags that were used to save this layer 128 */ Layer(Graphics2D graphics, BufferedImage image, int flags)129 Layer(Graphics2D graphics, BufferedImage image, int flags) { 130 mGraphics = graphics; 131 mBitmap = null; 132 mImage = image; 133 mFlags = flags; 134 } 135 136 /** The Graphics2D, guaranteed to be non null */ getGraphics()137 Graphics2D getGraphics() { 138 return mGraphics; 139 } 140 141 /** The BufferedImage, guaranteed to be non null */ getImage()142 BufferedImage getImage() { 143 return mImage; 144 } 145 146 /** Returns the layer save flags. This is only valid for additional layers. 147 * For the base layer this will always return 0; 148 * For a given layer, all further copies of this {@link Layer} object in new snapshots 149 * will always return the same value. 150 */ getFlags()151 int getFlags() { 152 return mFlags; 153 } 154 makeCopy()155 Layer makeCopy() { 156 if (mBitmap != null) { 157 return new Layer((Graphics2D) mGraphics.create(), mBitmap); 158 } 159 160 return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags); 161 } 162 163 /** sets an optional copy of the original content to be used during restore */ setOriginalCopy(BufferedImage image)164 void setOriginalCopy(BufferedImage image) { 165 mOriginalCopy = image; 166 } 167 getOriginalCopy()168 BufferedImage getOriginalCopy() { 169 return mOriginalCopy; 170 } 171 change()172 void change() { 173 if (mBitmap != null) { 174 mBitmap.change(); 175 } 176 } 177 178 /** 179 * Sets the clip for the graphics2D object associated with the layer. 180 * This should be used over the normal Graphics2D setClip method. 181 * 182 * @param clipShape the shape to use a the clip shape. 183 */ setClip(Shape clipShape)184 void setClip(Shape clipShape) { 185 // because setClip is only guaranteed to work with rectangle shape, 186 // first reset the clip to max and then intersect the current (empty) 187 // clip with the shap. 188 mGraphics.setClip(null); 189 mGraphics.clip(clipShape); 190 } 191 192 /** 193 * Clips the layer with the given shape. This performs an intersect between the current 194 * clip shape and the given shape. 195 * @param shape the new clip shape. 196 */ clip(Shape shape)197 public void clip(Shape shape) { 198 mGraphics.clip(shape); 199 } 200 } 201 202 /** 203 * Creates the root snapshot associating it with a given bitmap. 204 * <p> 205 * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be 206 * called before the snapshot can be used to draw. Transform and clip operations are permitted 207 * before. 208 * 209 * @param bitmap the image to associate to the snapshot or null. 210 * @return the root snapshot 211 */ createDefaultSnapshot(Bitmap_Delegate bitmap)212 public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { 213 GcSnapshot snapshot = new GcSnapshot(); 214 if (bitmap != null) { 215 snapshot.setBitmap(bitmap); 216 } 217 218 return snapshot; 219 } 220 221 /** 222 * Saves the current state according to the given flags and returns the new current snapshot. 223 * <p/> 224 * This is the equivalent of {@link Canvas#save(int)} 225 * 226 * @param flags the save flags. 227 * @return the new snapshot 228 * 229 * @see Canvas#save(int) 230 */ save(int flags)231 public GcSnapshot save(int flags) { 232 return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags); 233 } 234 235 /** 236 * Saves the current state and creates a new layer, and returns the new current snapshot. 237 * <p/> 238 * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)} 239 * 240 * @param layerBounds the layer bounds 241 * @param paint the Paint information used to blit the layer back into the layers underneath 242 * upon restore 243 * @param flags the save flags. 244 * @return the new snapshot 245 * 246 * @see Canvas#saveLayer(RectF, Paint, int) 247 */ saveLayer(RectF layerBounds, Paint_Delegate paint, int flags)248 public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) { 249 return new GcSnapshot(this, layerBounds, paint, flags); 250 } 251 252 /** 253 * Creates the root snapshot. 254 */ GcSnapshot()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 */ GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags)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 dispose()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 */ restore()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 */ restoreTo(int saveCount)375 public GcSnapshot restoreTo(int saveCount) { 376 return doRestoreTo(size(), saveCount); 377 } 378 size()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 */ setBitmap(Bitmap_Delegate bitmap)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 translate(float dx, float dy)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 rotate(double radians)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 scale(float sx, float sy)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 getTransform()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 setTransform(AffineTransform transform)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 clip(Shape shape, int regionOp)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 clipRect(float left, float top, float right, float bottom, int regionOp)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 */ getClip()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 doRestoreTo(int size, int saveCount)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 */ draw(Drawable drawable)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 */ draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, boolean forceSrcMode)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 drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, boolean compositeOnly, int forceMode)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 Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics 620 .getClipBounds() : null; 621 if (clipBounds != null) { 622 if (clipBounds.width == 0 || clipBounds.height == 0) { 623 // Clip is 0 so no need to paint anything. 624 return; 625 } 626 } 627 628 // b/63692596: 629 // Don't use the size of clip bound because it may be smaller than original size. 630 // Which makes Vector Drawable pixelized. 631 int width = layer.getImage().getWidth(); 632 int height = layer.getImage().getHeight(); 633 634 // Create a temporary image to which the color filter will be applied. 635 BufferedImage image = new BufferedImage(width, height, 636 BufferedImage.TYPE_INT_ARGB); 637 Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics(); 638 // Configure the Graphics2D object with drawing parameters and shader. 639 Graphics2D imageGraphics = createCustomGraphics( 640 imageBaseGraphics, paint, compositeOnly, 641 AlphaComposite.SRC_OVER); 642 643 // get a Graphics2D object configured with the drawing parameters, but no shader. 644 Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint, 645 true /*compositeOnly*/, forceMode); 646 configuredGraphics.setTransform(new AffineTransform()); 647 try { 648 // The main draw operation. 649 // We translate the operation to take into account that the rendering does not 650 // know about the clipping area. 651 imageGraphics.setTransform(originalGraphics.getTransform()); 652 drawable.draw(imageGraphics, paint); 653 654 // Apply the color filter. 655 // Restore the original coordinates system and apply the filter only to the 656 // clipped area. 657 imageGraphics.setTransform(new AffineTransform()); 658 filter.applyFilter(imageGraphics, width, height); 659 660 // Draw the tinted image on the main layer using as start point the clipping 661 // upper left coordinates. 662 configuredGraphics.drawImage(image, 0, 0, null); 663 layer.change(); 664 } finally { 665 // dispose Graphics2D objects 666 imageGraphics.dispose(); 667 imageBaseGraphics.dispose(); 668 configuredGraphics.dispose(); 669 } 670 } 671 } 672 drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint, Layer layer)673 private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint, 674 Layer layer) { 675 try { 676 drawable.draw(g, paint); 677 layer.change(); 678 } finally { 679 g.dispose(); 680 } 681 } 682 doRestore()683 private GcSnapshot doRestore() { 684 if (mPrevious != null) { 685 if (mLocalLayer != null) { 686 // prepare to blit the layers in which we have draw, in the layer beneath 687 // them, starting with the top one (which is the current local layer). 688 int i = mLayers.size() - 1; 689 int flags; 690 do { 691 Layer dstLayer = mLayers.get(i - 1); 692 693 restoreLayer(dstLayer); 694 695 flags = dstLayer.getFlags(); 696 i--; 697 } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); 698 } 699 700 // if this snapshot does not save everything, then set the previous snapshot 701 // to this snapshot content 702 703 // didn't save the matrix? set the current matrix on the previous snapshot 704 if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) { 705 AffineTransform mtx = getTransform(); 706 for (Layer layer : mPrevious.mLayers) { 707 layer.getGraphics().setTransform(mtx); 708 } 709 } 710 711 // didn't save the clip? set the current clip on the previous snapshot 712 if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) { 713 Shape clip = getClip(); 714 for (Layer layer : mPrevious.mLayers) { 715 layer.setClip(clip); 716 } 717 } 718 } 719 720 for (Layer layer : mLayers) { 721 layer.getGraphics().dispose(); 722 } 723 724 return mPrevious; 725 } 726 restoreLayer(Layer dstLayer)727 private void restoreLayer(Layer dstLayer) { 728 729 Graphics2D baseGfx = dstLayer.getImage().createGraphics(); 730 731 // if the layer contains an original copy this means the flags 732 // didn't restrict drawing to the local layer and we need to make sure the 733 // layer bounds in the layer beneath didn't receive any drawing. 734 // so we use the originalCopy to erase the new drawings in there. 735 BufferedImage originalCopy = dstLayer.getOriginalCopy(); 736 if (originalCopy != null) { 737 Graphics2D g = (Graphics2D) baseGfx.create(); 738 g.setComposite(AlphaComposite.Src); 739 740 g.drawImage(originalCopy, 741 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 742 0, 0, mLayerBounds.width(), mLayerBounds.height(), 743 null); 744 g.dispose(); 745 } 746 747 // now draw put the content of the local layer onto the layer, 748 // using the paint information 749 Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, 750 true /*alphaOnly*/, 0 /*forceMode*/); 751 752 g.drawImage(mLocalLayer.getImage(), 753 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 754 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, 755 null); 756 g.dispose(); 757 758 baseGfx.dispose(); 759 } 760 761 /** 762 * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. 763 * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. 764 */ createCustomGraphics(Graphics2D original, Paint_Delegate paint, boolean compositeOnly, int forceMode)765 private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, 766 boolean compositeOnly, int forceMode) { 767 // make new one graphics 768 Graphics2D g = (Graphics2D) original.create(); 769 770 if (paint == null) { 771 return g; 772 } 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 if (!compositeOnly) { 785 setShader(g, paint); 786 // set the stroke 787 g.setStroke(paint.getJavaStroke()); 788 } 789 // set the composite. 790 setComposite(g, paint, compositeOnly, forceMode); 791 792 return g; 793 } 794 setShader(Graphics2D g, Paint_Delegate paint)795 private void setShader(Graphics2D g, Paint_Delegate paint) { 796 Shader_Delegate shaderDelegate = paint.getShader(); 797 if (shaderDelegate != null) { 798 if (shaderDelegate.isSupported()) { 799 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); 800 assert shaderPaint != null; 801 g.setPaint(shaderPaint); 802 return; 803 } else { 804 Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, 805 shaderDelegate.getSupportMessage(), null, null, null); 806 } 807 } 808 809 // if no shader, use the paint color 810 g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); 811 } 812 setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha, int forceMode)813 private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha, 814 int forceMode) { 815 // the alpha for the composite. Always opaque if the normal paint color is used since 816 // it contains the alpha 817 int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF; 818 Shader_Delegate shader = paint.getShader(); 819 if (shader != null) { 820 alpha = (int)(alpha * shader.getAlpha()); 821 } 822 if (forceMode != 0) { 823 g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f)); 824 return; 825 } 826 Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode()); 827 Composite composite = PorterDuffUtility.getComposite(mode, alpha); 828 g.setComposite(composite); 829 } 830 mapRect(AffineTransform matrix, RectF dst, RectF src)831 private void mapRect(AffineTransform matrix, RectF dst, RectF src) { 832 // array with 4 corners 833 float[] corners = new float[] { 834 src.left, src.top, 835 src.right, src.top, 836 src.right, src.bottom, 837 src.left, src.bottom, 838 }; 839 840 // apply the transform to them. 841 matrix.transform(corners, 0, corners, 0, 4); 842 843 // now put the result in the rect. We take the min/max of Xs and min/max of Ys 844 dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); 845 dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); 846 847 dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); 848 dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); 849 } 850 851 /** 852 * Returns the clip of the oldest snapshot of the stack, appropriately translated to be 853 * expressed in the coordinate system of the latest snapshot. 854 */ getOriginalClip()855 public Rectangle getOriginalClip() { 856 GcSnapshot originalSnapshot = this; 857 while (originalSnapshot.mPrevious != null) { 858 originalSnapshot = originalSnapshot.mPrevious; 859 } 860 if (originalSnapshot.mLayers.isEmpty()) { 861 return null; 862 } 863 Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics(); 864 Rectangle bounds = graphics2D.getClipBounds(); 865 if (bounds == null) { 866 return null; 867 } 868 try { 869 AffineTransform originalTransform = 870 ((Graphics2D) graphics2D.create()).getTransform().createInverse(); 871 AffineTransform latestTransform = getTransform().createInverse(); 872 bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX(); 873 bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY(); 874 } catch (NoninvertibleTransformException e) { 875 Bridge.getLog().warning(null, "Non invertible transformation", null); 876 } 877 return bounds; 878 } 879 880 } 881