GcSnapshot.java revision d38e776a3cc8cb53945cbebafbe6f6c2e3501fa5
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 17package com.android.layoutlib.bridge.impl; 18 19import com.android.layoutlib.bridge.Bridge; 20 21import android.graphics.Canvas; 22import android.graphics.Paint; 23import android.graphics.Paint_Delegate; 24import android.graphics.PathEffect_Delegate; 25import android.graphics.PorterDuff; 26import android.graphics.PorterDuffXfermode_Delegate; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.graphics.Region; 30import android.graphics.Shader_Delegate; 31import android.graphics.Xfermode_Delegate; 32 33import java.awt.AlphaComposite; 34import java.awt.BasicStroke; 35import java.awt.Color; 36import java.awt.Composite; 37import java.awt.Graphics2D; 38import java.awt.RenderingHints; 39import java.awt.Shape; 40import java.awt.Stroke; 41import java.awt.geom.AffineTransform; 42import java.awt.geom.Area; 43import java.awt.geom.Rectangle2D; 44import java.awt.image.BufferedImage; 45import java.util.ArrayList; 46 47/** 48 * Class representing a graphics context snapshot, as well as a context stack as a linked list. 49 * <p> 50 * This is based on top of {@link Graphics2D} but can operate independently if none are available 51 * yet when setting transforms and clip information. 52 * <p> 53 * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and 54 * {@link #draw(Drawable, Paint_Delegate)} 55 * 56 */ 57public class GcSnapshot { 58 59 private final GcSnapshot mPrevious; 60 private final int mFlags; 61 62 /** list of layers. */ 63 private final ArrayList<Layer> mLayers = new ArrayList<Layer>(); 64 65 /** temp transform in case transformation are set before a Graphics2D exists */ 66 private AffineTransform mTransform = null; 67 /** temp clip in case clipping is set before a Graphics2D exists */ 68 private Area mClip = null; 69 70 // local layer data 71 private final Layer mLocalLayer; 72 private final Paint_Delegate mLocalLayerPaint; 73 private Rect mLocalLayerRegion; 74 75 public interface Drawable { 76 void draw(Graphics2D graphics, Paint_Delegate paint); 77 } 78 79 /** 80 * class containing information about a layer. 81 */ 82 private static class Layer { 83 private final Graphics2D mGraphics; 84 private final BufferedImage mImage; 85 private BufferedImage mOriginalCopy; 86 87 Layer(Graphics2D graphics, BufferedImage image) { 88 mGraphics = graphics; 89 mImage = image; 90 } 91 92 /** The Graphics2D, guaranteed to be non null */ 93 Graphics2D getGraphics() { 94 return mGraphics; 95 } 96 97 /** The BufferedImage, guaranteed to be non null */ 98 BufferedImage getImage() { 99 return mImage; 100 } 101 102 Layer makeCopy() { 103 return new Layer((Graphics2D) mGraphics.create(), mImage); 104 } 105 106 /** sets an optional copy of the original content to be used during restore */ 107 void setOriginalCopy(BufferedImage image) { 108 mOriginalCopy = image; 109 } 110 111 BufferedImage getOriginalCopy() { 112 return mOriginalCopy; 113 } 114 } 115 116 /** 117 * Creates the root snapshot associating it with a given image. 118 * <p> 119 * If <var>image</var> is null, then {@link GcSnapshot#setImage(BufferedImage)} must be 120 * called before the snapshot can be used to draw. Transform and clip operations are permitted 121 * before. 122 * 123 * @param image the image to associate to the snapshot or null. 124 * @return the root snapshot 125 */ 126 public static GcSnapshot createDefaultSnapshot(BufferedImage image) { 127 GcSnapshot snapshot = new GcSnapshot(); 128 if (image != null) { 129 snapshot.setImage(image); 130 } 131 132 return snapshot; 133 } 134 135 /** 136 * Saves the current state according to the given flags and returns the new current snapshot. 137 * <p/> 138 * This is the equivalent of {@link Canvas#save(int)} 139 * 140 * @param flags the save flags. 141 * @return the new snapshot 142 * 143 * @see Canvas#save(int) 144 */ 145 public GcSnapshot save(int flags) { 146 return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags); 147 } 148 149 /** 150 * Saves the current state and creates a new layer, and returns the new current snapshot. 151 * <p/> 152 * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)} 153 * 154 * @param layerBounds the layer bounds 155 * @param paint the Paint information used to blit the layer back into the layers underneath 156 * upon restore 157 * @param flags the save flags. 158 * @return the new snapshot 159 * 160 * @see Canvas#saveLayer(RectF, Paint, int) 161 */ 162 public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) { 163 return new GcSnapshot(this, layerBounds, paint, flags); 164 } 165 166 /** 167 * Creates the root snapshot. 168 * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible. 169 */ 170 private GcSnapshot() { 171 mPrevious = null; 172 mFlags = 0; 173 mLocalLayer = null; 174 mLocalLayerPaint = null; 175 } 176 177 /** 178 * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored 179 * into the main graphics when {@link #restore()} is called. 180 * 181 * @param previous the previous snapshot head. 182 * @param layerBounds the region of the layer. Optional, if null, this is a normal save() 183 * @param paint the Paint information used to blit the layer back into the layers underneath 184 * upon restore 185 * @param flags the flags regarding what should be saved. 186 */ 187 private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) { 188 assert previous != null; 189 mPrevious = previous; 190 mFlags = flags; 191 192 // make a copy of the current layers before adding the new one. 193 // This keeps the same BufferedImage reference but creates new Graphics2D for this 194 // snapshot. 195 // It does not copy whatever original copy the layers have, as they will be done 196 // only if the new layer doesn't clip drawing to itself. 197 for (Layer layer : mPrevious.mLayers) { 198 mLayers.add(layer.makeCopy()); 199 } 200 201 if (layerBounds != null) { 202 // get the current transform 203 AffineTransform matrix = mLayers.get(0).getGraphics().getTransform(); 204 205 // transform the layerBounds and puts it into a int rect 206 RectF rect2 = new RectF(); 207 mapRect(matrix, rect2, layerBounds); 208 mLocalLayerRegion = new Rect(); 209 rect2.round(mLocalLayerRegion); 210 211 // get the base layer (always at index 0) 212 Layer baseLayer = mLayers.get(0); 213 214 // create the image for the layer 215 BufferedImage layerImage = new BufferedImage( 216 baseLayer.getImage().getWidth(), 217 baseLayer.getImage().getHeight(), 218 BufferedImage.TYPE_INT_ARGB); 219 220 // create a graphics for it so that drawing can be done. 221 Graphics2D layerGraphics = layerImage.createGraphics(); 222 223 // because this layer inherits the current context for transform and clip, 224 // set them to one from the base layer. 225 AffineTransform currentMtx = baseLayer.getGraphics().getTransform(); 226 layerGraphics.setTransform(currentMtx); 227 228 Shape currentClip = baseLayer.getGraphics().getClip(); 229 layerGraphics.setClip(currentClip); 230 231 // create a new layer for this new layer and add it to the list at the end. 232 mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage)); 233 234 // if the drawing is not clipped to the local layer only, we save the current content 235 // of all other layers. We are only interested in the part that will actually 236 // be drawn, so we create as small bitmaps as we can. 237 // This is so that we can erase the drawing that goes in the layers below that will 238 // be coming from the layer itself. 239 if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) { 240 int w = mLocalLayerRegion.width(); 241 int h = mLocalLayerRegion.height(); 242 for (int i = 0 ; i < mLayers.size() - 1 ; i++) { 243 Layer layer = mLayers.get(i); 244 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 245 Graphics2D graphics = image.createGraphics(); 246 graphics.drawImage(layer.getImage(), 247 0, 0, w, h, 248 mLocalLayerRegion.left, mLocalLayerRegion.top, 249 mLocalLayerRegion.right, mLocalLayerRegion.bottom, 250 null); 251 graphics.dispose(); 252 layer.setOriginalCopy(image); 253 } 254 } 255 } else { 256 mLocalLayer = null; 257 } 258 259 mLocalLayerPaint = paint; 260 } 261 262 public void dispose() { 263 for (Layer layer : mLayers) { 264 layer.getGraphics().dispose(); 265 } 266 267 if (mPrevious != null) { 268 mPrevious.dispose(); 269 } 270 } 271 272 /** 273 * Restores the top {@link GcSnapshot}, and returns the next one. 274 */ 275 public GcSnapshot restore() { 276 return doRestore(); 277 } 278 279 /** 280 * Restores the {@link GcSnapshot} to <var>saveCount</var>. 281 * @param saveCount the saveCount or -1 to only restore 1. 282 * 283 * @return the new head of the Gc snapshot stack. 284 */ 285 public GcSnapshot restoreTo(int saveCount) { 286 return doRestoreTo(size(), saveCount); 287 } 288 289 public int size() { 290 if (mPrevious != null) { 291 return mPrevious.size() + 1; 292 } 293 294 return 1; 295 } 296 297 /** 298 * Link the snapshot to a BufferedImage. 299 * <p/> 300 * This is only for the case where the snapshot was created with a null image when calling 301 * {@link #createDefaultSnapshot(BufferedImage)}, and is therefore not yet linked to 302 * a previous snapshot. 303 * <p/> 304 * If any transform or clip information was set before, they are put into the Graphics object. 305 * @param image the buffered image to link to. 306 */ 307 public void setImage(BufferedImage image) { 308 assert mLayers.size() == 0; 309 Graphics2D graphics2D = image.createGraphics(); 310 mLayers.add(new Layer(graphics2D, image)); 311 if (mTransform != null) { 312 graphics2D.setTransform(mTransform); 313 mTransform = null; 314 } 315 316 if (mClip != null) { 317 graphics2D.setClip(mClip); 318 mClip = null; 319 } 320 } 321 322 public void translate(float dx, float dy) { 323 if (mLayers.size() > 0) { 324 for (Layer layer : mLayers) { 325 layer.getGraphics().translate(dx, dy); 326 } 327 } else { 328 if (mTransform == null) { 329 mTransform = new AffineTransform(); 330 } 331 mTransform.translate(dx, dy); 332 } 333 } 334 335 public void rotate(double radians) { 336 if (mLayers.size() > 0) { 337 for (Layer layer : mLayers) { 338 layer.getGraphics().rotate(radians); 339 } 340 } else { 341 if (mTransform == null) { 342 mTransform = new AffineTransform(); 343 } 344 mTransform.rotate(radians); 345 } 346 } 347 348 public void scale(float sx, float sy) { 349 if (mLayers.size() > 0) { 350 for (Layer layer : mLayers) { 351 layer.getGraphics().scale(sx, sy); 352 } 353 } else { 354 if (mTransform == null) { 355 mTransform = new AffineTransform(); 356 } 357 mTransform.scale(sx, sy); 358 } 359 } 360 361 public AffineTransform getTransform() { 362 if (mLayers.size() > 0) { 363 // all graphics2D in the list have the same transform 364 return mLayers.get(0).getGraphics().getTransform(); 365 } else { 366 if (mTransform == null) { 367 mTransform = new AffineTransform(); 368 } 369 return mTransform; 370 } 371 } 372 373 public void setTransform(AffineTransform transform) { 374 if (mLayers.size() > 0) { 375 for (Layer layer : mLayers) { 376 layer.getGraphics().setTransform(transform); 377 } 378 } else { 379 if (mTransform == null) { 380 mTransform = new AffineTransform(); 381 } 382 mTransform.setTransform(transform); 383 } 384 } 385 386 public boolean clipRect(float left, float top, float right, float bottom, int regionOp) { 387 if (mLayers.size() > 0) { 388 Shape clip = null; 389 if (regionOp == Region.Op.DIFFERENCE.nativeInt) { 390 Area newClip = new Area(getClip()); 391 newClip.subtract(new Area( 392 new Rectangle2D.Float(left, top, right - left, bottom - top))); 393 clip = newClip; 394 395 } else if (regionOp == Region.Op.INTERSECT.nativeInt) { 396 for (Layer layer : mLayers) { 397 layer.getGraphics().clipRect( 398 (int) left, (int) top, (int) (right - left), (int) (bottom - top)); 399 } 400 401 } else if (regionOp == Region.Op.UNION.nativeInt) { 402 Area newClip = new Area(getClip()); 403 newClip.add(new Area( 404 new Rectangle2D.Float(left, top, right - left, bottom - top))); 405 clip = newClip; 406 407 } else if (regionOp == Region.Op.XOR.nativeInt) { 408 Area newClip = new Area(getClip()); 409 newClip.exclusiveOr(new Area( 410 new Rectangle2D.Float(left, top, right - left, bottom - top))); 411 clip = newClip; 412 413 } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) { 414 Area newClip = new Area( 415 new Rectangle2D.Float(left, top, right - left, bottom - top)); 416 newClip.subtract(new Area(getClip())); 417 clip = newClip; 418 419 } else if (regionOp == Region.Op.REPLACE.nativeInt) { 420 for (Layer layer : mLayers) { 421 layer.getGraphics().setClip( 422 (int) left, (int) top, (int) (right - left), (int) (bottom - top)); 423 } 424 } 425 426 if (clip != null) { 427 for (Layer layer : mLayers) { 428 layer.getGraphics().setClip(clip); 429 } 430 } 431 432 return getClip().getBounds().isEmpty() == false; 433 } else { 434 if (mClip == null) { 435 mClip = new Area(); 436 } 437 438 if (regionOp == Region.Op.DIFFERENCE.nativeInt) { 439 //FIXME 440 } else if (regionOp == Region.Op.DIFFERENCE.nativeInt) { 441 } else if (regionOp == Region.Op.INTERSECT.nativeInt) { 442 } else if (regionOp == Region.Op.UNION.nativeInt) { 443 } else if (regionOp == Region.Op.XOR.nativeInt) { 444 } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) { 445 } else if (regionOp == Region.Op.REPLACE.nativeInt) { 446 } 447 448 return mClip.getBounds().isEmpty() == false; 449 } 450 } 451 452 public Shape getClip() { 453 if (mLayers.size() > 0) { 454 // they all have the same clip 455 return mLayers.get(0).getGraphics().getClip(); 456 } else { 457 if (mClip == null) { 458 mClip = new Area(); 459 } 460 return mClip; 461 } 462 } 463 464 private GcSnapshot doRestoreTo(int size, int saveCount) { 465 if (size <= saveCount) { 466 return this; 467 } 468 469 // restore the current one first. 470 GcSnapshot previous = doRestore(); 471 472 if (size == saveCount + 1) { // this was the only one that needed restore. 473 return previous; 474 } else { 475 return previous.doRestoreTo(size - 1, saveCount); 476 } 477 } 478 479 /** 480 * Executes the Drawable's draw method, with a null paint delegate. 481 * <p/> 482 * Note that the method can be called several times if there are more than one active layer. 483 * @param drawable 484 */ 485 public void draw(Drawable drawable) { 486 draw(drawable, null, false /*compositeOnly*/); 487 } 488 489 /** 490 * Executes the Drawable's draw method. 491 * <p/> 492 * Note that the method can be called several times if there are more than one active layer. 493 * @param drawable 494 * @param paint 495 * @param compositeOnly whether the paint is used for composite only. This is typically 496 * the case for bitmaps. 497 */ 498 public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly) { 499 // if we clip to the layer, then we only draw in the layer 500 if (mLocalLayer != null && (mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) != 0) { 501 drawInLayer(mLocalLayer, drawable, paint, compositeOnly); 502 } else { 503 // draw in all the layers 504 for (Layer layer : mLayers) { 505 drawInLayer(layer, drawable, paint, compositeOnly); 506 } 507 } 508 } 509 510 private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, 511 boolean compositeOnly) { 512 Graphics2D originalGraphics = layer.getGraphics(); 513 // get a Graphics2D object configured with the drawing parameters. 514 Graphics2D configuredGraphics2D = 515 paint != null ? 516 createCustomGraphics(originalGraphics, paint, compositeOnly) : 517 (Graphics2D) originalGraphics.create(); 518 519 try { 520 drawable.draw(configuredGraphics2D, paint); 521 } finally { 522 // dispose Graphics2D object 523 configuredGraphics2D.dispose(); 524 } 525 } 526 527 private GcSnapshot doRestore() { 528 if (mPrevious != null) { 529 boolean forceAllSave = false; 530 if (mLocalLayer != null) { 531 forceAllSave = true; // layers always save both clip and transform 532 533 // prepare to draw the current layer in the previous layers, ie all layers but 534 // the last one, since the last one is the local layer 535 for (int i = 0 ; i < mLayers.size() - 1 ; i++) { 536 Layer layer = mLayers.get(i); 537 538 Graphics2D baseGfx = layer.getImage().createGraphics(); 539 540 // if the layer contains an original copy this means the flags 541 // didn't restrict drawing to the local layer and we need to make sure the 542 // layer bounds in the layer beneath didn't receive any drawing. 543 // so we use the originalCopy to erase the new drawings in there. 544 BufferedImage originalCopy = layer.getOriginalCopy(); 545 if (originalCopy != null) { 546 Graphics2D g = (Graphics2D) baseGfx.create(); 547 g.setComposite(AlphaComposite.Src); 548 549 g.drawImage(originalCopy, 550 mLocalLayerRegion.left, mLocalLayerRegion.top, 551 mLocalLayerRegion.right, mLocalLayerRegion.bottom, 552 0, 0, mLocalLayerRegion.width(), mLocalLayerRegion.height(), 553 null); 554 g.dispose(); 555 } 556 557 // now draw put the content of the local layer onto the layer, using the paint 558 // information 559 Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, 560 true /*alphaOnly*/); 561 562 g.drawImage(mLocalLayer.getImage(), 563 mLocalLayerRegion.left, mLocalLayerRegion.top, 564 mLocalLayerRegion.right, mLocalLayerRegion.bottom, 565 mLocalLayerRegion.left, mLocalLayerRegion.top, 566 mLocalLayerRegion.right, mLocalLayerRegion.bottom, 567 null); 568 g.dispose(); 569 570 baseGfx.dispose(); 571 } 572 } 573 574 // if this snapshot does not save everything, then set the previous snapshot 575 // to this snapshot content 576 577 // didn't save the matrix? set the current matrix on the previous snapshot 578 if (forceAllSave == false && (mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) { 579 AffineTransform mtx = getTransform(); 580 for (Layer layer : mPrevious.mLayers) { 581 layer.getGraphics().setTransform(mtx); 582 } 583 } 584 585 // didn't save the clip? set the current clip on the previous snapshot 586 if (forceAllSave == false && (mFlags & Canvas.CLIP_SAVE_FLAG) == 0) { 587 Shape clip = getClip(); 588 for (Layer layer : mPrevious.mLayers) { 589 layer.getGraphics().setClip(clip); 590 } 591 } 592 } 593 594 for (Layer layer : mLayers) { 595 layer.getGraphics().dispose(); 596 } 597 598 return mPrevious; 599 } 600 601 /** 602 * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. 603 * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. 604 */ 605 private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, 606 boolean compositeOnly) { 607 // make new one graphics 608 Graphics2D g = (Graphics2D) original.create(); 609 610 // configure it 611 612 if (paint.isAntiAliased()) { 613 g.setRenderingHint( 614 RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 615 g.setRenderingHint( 616 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 617 } 618 619 boolean customShader = false; 620 621 // get the shader first, as it'll replace the color if it can be used it. 622 if (compositeOnly == false) { 623 int nativeShader = paint.getShader(); 624 if (nativeShader > 0) { 625 Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader); 626 assert shaderDelegate != null; 627 if (shaderDelegate != null) { 628 if (shaderDelegate.isSupported()) { 629 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); 630 assert shaderPaint != null; 631 if (shaderPaint != null) { 632 g.setPaint(shaderPaint); 633 customShader = true; 634 } 635 } else { 636 Bridge.getLog().fidelityWarning(null, 637 shaderDelegate.getSupportMessage(), 638 null); 639 } 640 } 641 } 642 643 // if no shader, use the paint color 644 if (customShader == false) { 645 g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); 646 } 647 648 boolean customStroke = false; 649 int pathEffect = paint.getPathEffect(); 650 if (pathEffect > 0) { 651 PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect); 652 assert effectDelegate != null; 653 if (effectDelegate != null) { 654 if (effectDelegate.isSupported()) { 655 Stroke stroke = effectDelegate.getStroke(paint); 656 assert stroke != null; 657 if (stroke != null) { 658 g.setStroke(stroke); 659 customStroke = true; 660 } 661 } else { 662 Bridge.getLog().fidelityWarning(null, 663 effectDelegate.getSupportMessage(), 664 null); 665 } 666 } 667 } 668 669 // if no custom stroke as been set, set the default one. 670 if (customStroke == false) { 671 g.setStroke(new BasicStroke( 672 paint.getStrokeWidth(), 673 paint.getJavaCap(), 674 paint.getJavaJoin(), 675 paint.getJavaStrokeMiter())); 676 } 677 } 678 679 // the alpha for the composite. Always opaque if the normal paint color is used since 680 // it contains the alpha 681 int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; 682 683 boolean customXfermode = false; 684 int xfermode = paint.getXfermode(); 685 if (xfermode > 0) { 686 Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode()); 687 assert xfermodeDelegate != null; 688 if (xfermodeDelegate != null) { 689 if (xfermodeDelegate.isSupported()) { 690 Composite composite = xfermodeDelegate.getComposite(alpha); 691 assert composite != null; 692 if (composite != null) { 693 g.setComposite(composite); 694 customXfermode = true; 695 } 696 } else { 697 Bridge.getLog().fidelityWarning(null, 698 xfermodeDelegate.getSupportMessage(), 699 null); 700 } 701 } 702 } 703 704 // if there was no custom xfermode, but we have alpha (due to a shader and a non 705 // opaque alpha channel in the paint color), then we create an AlphaComposite anyway 706 // that will handle the alpha. 707 if (customXfermode == false && alpha != 0xFF) { 708 g.setComposite(PorterDuffXfermode_Delegate.getComposite( 709 PorterDuff.Mode.SRC_OVER, alpha)); 710 } 711 712 return g; 713 } 714 715 716 private void mapRect(AffineTransform matrix, RectF dst, RectF src) { 717 // array with 4 corners 718 float[] corners = new float[] { 719 src.left, src.top, 720 src.right, src.top, 721 src.right, src.bottom, 722 src.left, src.bottom, 723 }; 724 725 // apply the transform to them. 726 matrix.transform(corners, 0, corners, 0, 4); 727 728 // now put the result in the rect. We take the min/max of Xs and min/max of Ys 729 dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); 730 dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); 731 732 dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); 733 dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); 734 } 735 736} 737