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