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