1 /* CairoGraphics2D.java -- 2 Copyright (C) 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.awt.peer.gtk; 40 41 import gnu.classpath.Configuration; 42 43 import gnu.java.awt.ClasspathToolkit; 44 45 import java.awt.AWTPermission; 46 import java.awt.AlphaComposite; 47 import java.awt.BasicStroke; 48 import java.awt.Color; 49 import java.awt.Composite; 50 import java.awt.CompositeContext; 51 import java.awt.Font; 52 import java.awt.FontMetrics; 53 import java.awt.GradientPaint; 54 import java.awt.Graphics; 55 import java.awt.Graphics2D; 56 import java.awt.GraphicsConfiguration; 57 import java.awt.Image; 58 import java.awt.Paint; 59 import java.awt.PaintContext; 60 import java.awt.Point; 61 import java.awt.Polygon; 62 import java.awt.Rectangle; 63 import java.awt.RenderingHints; 64 import java.awt.Shape; 65 import java.awt.Stroke; 66 import java.awt.TexturePaint; 67 import java.awt.Toolkit; 68 import java.awt.font.FontRenderContext; 69 import java.awt.font.GlyphVector; 70 import java.awt.font.TextLayout; 71 import java.awt.geom.AffineTransform; 72 import java.awt.geom.Arc2D; 73 import java.awt.geom.Area; 74 import java.awt.geom.Ellipse2D; 75 import java.awt.geom.GeneralPath; 76 import java.awt.geom.Line2D; 77 import java.awt.geom.NoninvertibleTransformException; 78 import java.awt.geom.PathIterator; 79 import java.awt.geom.Point2D; 80 import java.awt.geom.Rectangle2D; 81 import java.awt.geom.RoundRectangle2D; 82 import java.awt.image.AffineTransformOp; 83 import java.awt.image.BufferedImage; 84 import java.awt.image.BufferedImageOp; 85 import java.awt.image.ColorModel; 86 import java.awt.image.DataBuffer; 87 import java.awt.image.DataBufferInt; 88 import java.awt.image.DirectColorModel; 89 import java.awt.image.ImageObserver; 90 import java.awt.image.ImageProducer; 91 import java.awt.image.ImagingOpException; 92 import java.awt.image.MultiPixelPackedSampleModel; 93 import java.awt.image.Raster; 94 import java.awt.image.RenderedImage; 95 import java.awt.image.SampleModel; 96 import java.awt.image.WritableRaster; 97 import java.awt.image.renderable.RenderContext; 98 import java.awt.image.renderable.RenderableImage; 99 import java.text.AttributedCharacterIterator; 100 import java.util.HashMap; 101 import java.util.Map; 102 103 /** 104 * This is an abstract implementation of Graphics2D on Cairo. 105 * 106 * It should be subclassed for different Cairo contexts. 107 * 108 * Note for subclassers: Apart from the constructor (see comments below), 109 * The following abstract methods must be implemented: 110 * 111 * Graphics create() 112 * GraphicsConfiguration getDeviceConfiguration() 113 * copyArea(int x, int y, int width, int height, int dx, int dy) 114 * 115 * Also, dispose() must be overloaded to free any native datastructures 116 * used by subclass and in addition call super.dispose() to free the 117 * native cairographics2d structure and cairo_t. 118 * 119 * @author Sven de Marothy 120 */ 121 public abstract class CairoGraphics2D extends Graphics2D 122 { 123 static 124 { 125 if (true) // GCJ LOCAL 126 { 127 System.loadLibrary("gtkpeer"); 128 } 129 } 130 131 /** 132 * Important: This is a pointer to the native cairographics2d structure 133 * 134 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE. 135 */ 136 long nativePointer; 137 138 // Drawing state variables 139 /** 140 * The current paint 141 */ 142 Paint paint; 143 boolean customPaint; 144 145 /** 146 * The current stroke 147 */ 148 Stroke stroke; 149 150 /* 151 * Current foreground and background color. 152 */ 153 Color fg, bg; 154 155 /** 156 * Current clip shape. 157 */ 158 Shape clip; 159 160 /** 161 * Current transform. 162 */ 163 AffineTransform transform; 164 165 /** 166 * Current font. 167 */ 168 Font font; 169 170 /** 171 * The current compositing context, if any. 172 */ 173 Composite comp; 174 CompositeContext compCtx; 175 176 /** 177 * Rendering hint map. 178 */ 179 private RenderingHints hints; 180 181 /** 182 * Status of the anti-alias flag in cairo. 183 */ 184 private boolean antialias = false; 185 private boolean ignoreAA = false; 186 187 /** 188 * Some operations (drawing rather than filling) require that their 189 * coords be shifted to land on 0.5-pixel boundaries, in order to land on 190 * "middle of pixel" coordinates and light up complete pixels. 191 */ 192 protected boolean shiftDrawCalls = false; 193 194 /** 195 * Keep track if the first clip to be set, which is restored on setClip(null); 196 */ 197 private boolean firstClip = true; 198 private Shape originalClip; 199 200 /** 201 * Stroke used for 3DRects 202 */ 203 private static BasicStroke draw3DRectStroke = new BasicStroke(); 204 205 static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF); 206 static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 207 0xFF000000); 208 209 /** 210 * Native constants for interpolation methods. 211 * Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h 212 */ 213 public static final int INTERPOLATION_NEAREST = 0, 214 INTERPOLATION_BILINEAR = 1, 215 INTERPOLATION_BICUBIC = 5, 216 ALPHA_INTERPOLATION_SPEED = 2, 217 ALPHA_INTERPOLATION_QUALITY = 3, 218 ALPHA_INTERPOLATION_DEFAULT = 4; 219 // TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD? 220 221 /** 222 * Constructor does nothing. 223 */ CairoGraphics2D()224 public CairoGraphics2D() 225 { 226 } 227 228 /** 229 * Sets up the default values and allocates the native cairographics2d structure 230 * @param cairo_t_pointer a native pointer to a cairo_t of the context. 231 */ setup(long cairo_t_pointer)232 public void setup(long cairo_t_pointer) 233 { 234 nativePointer = init(cairo_t_pointer); 235 setRenderingHints(new RenderingHints(getDefaultHints())); 236 setFont(new Font("SansSerif", Font.PLAIN, 12)); 237 setColor(Color.black); 238 setBackground(Color.white); 239 setPaint(Color.black); 240 setStroke(new BasicStroke()); 241 setTransform(new AffineTransform()); 242 cairoSetAntialias(nativePointer, antialias); 243 } 244 245 /** 246 * Same as above, but copies the state of another CairoGraphics2D. 247 */ copy(CairoGraphics2D g, long cairo_t_pointer)248 public void copy(CairoGraphics2D g, long cairo_t_pointer) 249 { 250 nativePointer = init(cairo_t_pointer); 251 paint = g.paint; 252 stroke = g.stroke; 253 setRenderingHints(g.hints); 254 255 Color foreground; 256 257 if (g.fg.getAlpha() != -1) 258 foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(), 259 g.fg.getAlpha()); 260 else 261 foreground = new Color(g.fg.getRGB()); 262 263 if (g.bg != null) 264 { 265 if (g.bg.getAlpha() != -1) 266 bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(), 267 g.bg.getAlpha()); 268 else 269 bg = new Color(g.bg.getRGB()); 270 } 271 272 firstClip = g.firstClip; 273 originalClip = g.originalClip; 274 clip = g.getClip(); 275 276 if (g.transform == null) 277 transform = null; 278 else 279 transform = new AffineTransform(g.transform); 280 281 setFont(g.font); 282 setColor(foreground); 283 setBackground(bg); 284 setPaint(paint); 285 setStroke(stroke); 286 setTransformImpl(transform); 287 setClip(clip); 288 setComposite(comp); 289 290 antialias = !g.antialias; 291 setAntialias(g.antialias); 292 } 293 294 /** 295 * Generic destructor - call the native dispose() method. 296 */ finalize()297 public void finalize() 298 { 299 dispose(); 300 } 301 302 /** 303 * Disposes the native cairographics2d structure, including the 304 * cairo_t and any gradient stuff, if allocated. 305 * Subclasses should of course overload and call this if 306 * they have additional native structures. 307 */ dispose()308 public void dispose() 309 { 310 disposeNative(nativePointer); 311 nativePointer = 0; 312 if (compCtx != null) 313 compCtx.dispose(); 314 } 315 316 /** 317 * Allocate the cairographics2d structure and set the cairo_t pointer in it. 318 * @param pointer - a cairo_t pointer, casted to a long. 319 */ init(long pointer)320 protected native long init(long pointer); 321 322 /** 323 * These are declared abstract as there may be context-specific issues. 324 */ create()325 public abstract Graphics create(); 326 getDeviceConfiguration()327 public abstract GraphicsConfiguration getDeviceConfiguration(); 328 copyAreaImpl(int x, int y, int width, int height, int dx, int dy)329 protected abstract void copyAreaImpl(int x, int y, int width, int height, 330 int dx, int dy); 331 332 333 /** 334 * Find the bounds of this graphics context, in device space. 335 * 336 * @return the bounds in device-space 337 */ getRealBounds()338 protected abstract Rectangle2D getRealBounds(); 339 340 ////// Native Methods //////////////////////////////////////////////////// 341 342 /** 343 * Dispose of allocate native resouces. 344 */ disposeNative(long pointer)345 public native void disposeNative(long pointer); 346 347 /** 348 * Draw pixels as an RGBA int matrix 349 * @param w - width 350 * @param h - height 351 * @param stride - stride of the array width 352 * @param i2u - affine transform array 353 */ drawPixels(long pointer, int[] pixels, int w, int h, int stride, double[] i2u, double alpha, int interpolation)354 protected native void drawPixels(long pointer, int[] pixels, int w, int h, 355 int stride, double[] i2u, double alpha, 356 int interpolation); 357 setGradient(long pointer, double x1, double y1, double x2, double y2, int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, boolean cyclic)358 protected native void setGradient(long pointer, double x1, double y1, 359 double x2, double y2, 360 int r1, int g1, int b1, int a1, int r2, 361 int g2, int b2, int a2, boolean cyclic); 362 setPaintPixels(long pointer, int[] pixels, int w, int h, int stride, boolean repeat, int x, int y)363 protected native void setPaintPixels(long pointer, int[] pixels, int w, 364 int h, int stride, boolean repeat, 365 int x, int y); 366 367 /** 368 * Set the current transform matrix 369 */ cairoSetMatrix(long pointer, double[] m)370 protected native void cairoSetMatrix(long pointer, double[] m); 371 372 /** 373 * Scaling method 374 */ cairoScale(long pointer, double x, double y)375 protected native void cairoScale(long pointer, double x, double y); 376 377 /** 378 * Set the compositing operator 379 */ cairoSetOperator(long pointer, int cairoOperator)380 protected native void cairoSetOperator(long pointer, int cairoOperator); 381 382 /** 383 * Sets the current color in RGBA as a 0.0-1.0 double 384 */ cairoSetRGBAColor(long pointer, double red, double green, double blue, double alpha)385 protected native void cairoSetRGBAColor(long pointer, double red, double green, 386 double blue, double alpha); 387 388 /** 389 * Sets the current winding rule in Cairo 390 */ cairoSetFillRule(long pointer, int cairoFillRule)391 protected native void cairoSetFillRule(long pointer, int cairoFillRule); 392 393 /** 394 * Set the line style, cap, join and miter limit. 395 * Cap and join parameters are in the BasicStroke enumerations. 396 */ cairoSetLine(long pointer, double width, int cap, int join, double miterLimit)397 protected native void cairoSetLine(long pointer, double width, int cap, 398 int join, double miterLimit); 399 400 /** 401 * Set the dash style 402 */ cairoSetDash(long pointer, double[] dashes, int ndash, double offset)403 protected native void cairoSetDash(long pointer, double[] dashes, int ndash, 404 double offset); 405 406 /* 407 * Draws a Glyph Vector 408 */ cairoDrawGlyphVector(long pointer, GdkFontPeer font, float x, float y, int n, int[] codes, float[] positions, long[] fontset)409 protected native void cairoDrawGlyphVector(long pointer, GdkFontPeer font, 410 float x, float y, int n, 411 int[] codes, float[] positions, long[] fontset); 412 413 /** 414 * Set the font in cairo. 415 */ cairoSetFont(long pointer, GdkFontPeer font)416 protected native void cairoSetFont(long pointer, GdkFontPeer font); 417 418 /** 419 * Appends a rectangle to the current path 420 */ cairoRectangle(long pointer, double x, double y, double width, double height)421 protected native void cairoRectangle(long pointer, double x, double y, 422 double width, double height); 423 424 /** 425 * Appends an arc to the current path 426 */ cairoArc(long pointer, double x, double y, double radius, double angle1, double angle2)427 protected native void cairoArc(long pointer, double x, double y, 428 double radius, double angle1, double angle2); 429 430 /** 431 * Save / restore a cairo path 432 */ cairoSave(long pointer)433 protected native void cairoSave(long pointer); cairoRestore(long pointer)434 protected native void cairoRestore(long pointer); 435 436 /** 437 * New current path 438 */ cairoNewPath(long pointer)439 protected native void cairoNewPath(long pointer); 440 441 /** 442 * Close current path 443 */ cairoClosePath(long pointer)444 protected native void cairoClosePath(long pointer); 445 446 /** moveTo */ cairoMoveTo(long pointer, double x, double y)447 protected native void cairoMoveTo(long pointer, double x, double y); 448 449 /** lineTo */ cairoLineTo(long pointer, double x, double y)450 protected native void cairoLineTo(long pointer, double x, double y); 451 452 /** Cubic curve-to */ cairoCurveTo(long pointer, double x1, double y1, double x2, double y2, double x3, double y3)453 protected native void cairoCurveTo(long pointer, double x1, double y1, 454 double x2, double y2, 455 double x3, double y3); 456 457 /** 458 * Stroke current path 459 */ cairoStroke(long pointer)460 protected native void cairoStroke(long pointer); 461 462 /** 463 * Fill current path 464 */ cairoFill(long pointer, double alpha)465 protected native void cairoFill(long pointer, double alpha); 466 467 /** 468 * Clip current path 469 */ cairoClip(long pointer)470 protected native void cairoClip(long pointer); 471 472 /** 473 * Clear clip 474 */ cairoResetClip(long pointer)475 protected native void cairoResetClip(long pointer); 476 477 /** 478 * Set antialias. 479 */ cairoSetAntialias(long pointer, boolean aa)480 protected native void cairoSetAntialias(long pointer, boolean aa); 481 482 483 ///////////////////////// TRANSFORMS /////////////////////////////////// 484 /** 485 * Set the current transform 486 */ setTransform(AffineTransform tx)487 public void setTransform(AffineTransform tx) 488 { 489 // Transform clip into target space using the old transform. 490 updateClip(transform); 491 492 // Update the native transform. 493 setTransformImpl(tx); 494 495 // Transform the clip back into user space using the inverse new transform. 496 try 497 { 498 updateClip(transform.createInverse()); 499 } 500 catch (NoninvertibleTransformException ex) 501 { 502 // TODO: How can we deal properly with this? 503 ex.printStackTrace(); 504 } 505 506 if (clip != null) 507 setClip(clip); 508 } 509 setTransformImpl(AffineTransform tx)510 private void setTransformImpl(AffineTransform tx) 511 { 512 transform = tx; 513 if (transform != null) 514 { 515 double[] m = new double[6]; 516 transform.getMatrix(m); 517 cairoSetMatrix(nativePointer, m); 518 } 519 } 520 transform(AffineTransform tx)521 public void transform(AffineTransform tx) 522 { 523 if (transform == null) 524 transform = new AffineTransform(tx); 525 else 526 transform.concatenate(tx); 527 528 if (clip != null) 529 { 530 try 531 { 532 AffineTransform clipTransform = tx.createInverse(); 533 updateClip(clipTransform); 534 } 535 catch (NoninvertibleTransformException ex) 536 { 537 // TODO: How can we deal properly with this? 538 ex.printStackTrace(); 539 } 540 } 541 542 setTransformImpl(transform); 543 } 544 rotate(double theta)545 public void rotate(double theta) 546 { 547 transform(AffineTransform.getRotateInstance(theta)); 548 } 549 rotate(double theta, double x, double y)550 public void rotate(double theta, double x, double y) 551 { 552 transform(AffineTransform.getRotateInstance(theta, x, y)); 553 } 554 scale(double sx, double sy)555 public void scale(double sx, double sy) 556 { 557 transform(AffineTransform.getScaleInstance(sx, sy)); 558 } 559 560 /** 561 * Translate the system of the co-ordinates. As translation is a frequent 562 * operation, it is done in an optimised way, unlike scaling and rotating. 563 */ translate(double tx, double ty)564 public void translate(double tx, double ty) 565 { 566 if (transform != null) 567 transform.translate(tx, ty); 568 else 569 transform = AffineTransform.getTranslateInstance(tx, ty); 570 571 if (clip != null) 572 { 573 // FIXME: this should actuall try to transform the shape 574 // rather than degrade to bounds. 575 if (clip instanceof Rectangle2D) 576 { 577 Rectangle2D r = (Rectangle2D) clip; 578 r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(), 579 r.getHeight()); 580 } 581 else 582 { 583 AffineTransform clipTransform = 584 AffineTransform.getTranslateInstance(-tx, -ty); 585 updateClip(clipTransform); 586 } 587 } 588 589 setTransformImpl(transform); 590 } 591 translate(int x, int y)592 public void translate(int x, int y) 593 { 594 translate((double) x, (double) y); 595 } 596 shear(double shearX, double shearY)597 public void shear(double shearX, double shearY) 598 { 599 transform(AffineTransform.getShearInstance(shearX, shearY)); 600 } 601 602 ///////////////////////// DRAWING STATE /////////////////////////////////// 603 clip(Shape s)604 public void clip(Shape s) 605 { 606 // Do not touch clip when s == null. 607 if (s == null) 608 { 609 // The spec says this should clear the clip. The reference 610 // implementation throws a NullPointerException instead. I think, 611 // in this case we should conform to the specs, as it shouldn't 612 // affect compatibility. 613 setClip(null); 614 return; 615 } 616 617 // If the current clip is still null, initialize it. 618 if (clip == null) 619 { 620 clip = getRealBounds(); 621 } 622 623 // This is so common, let's optimize this. 624 if (clip instanceof Rectangle2D && s instanceof Rectangle2D) 625 { 626 Rectangle2D clipRect = (Rectangle2D) clip; 627 Rectangle2D r = (Rectangle2D) s; 628 Rectangle2D.intersect(clipRect, r, clipRect); 629 setClip(clipRect); 630 } 631 else 632 { 633 Area current; 634 if (clip instanceof Area) 635 current = (Area) clip; 636 else 637 current = new Area(clip); 638 639 Area intersect; 640 if (s instanceof Area) 641 intersect = (Area) s; 642 else 643 intersect = new Area(s); 644 645 current.intersect(intersect); 646 clip = current; 647 // Call setClip so that the native side gets notified. 648 setClip(clip); 649 } 650 } 651 getPaint()652 public Paint getPaint() 653 { 654 return paint; 655 } 656 getTransform()657 public AffineTransform getTransform() 658 { 659 return (AffineTransform) transform.clone(); 660 } 661 setPaint(Paint p)662 public void setPaint(Paint p) 663 { 664 if (p == null) 665 return; 666 667 paint = p; 668 if (paint instanceof Color) 669 { 670 setColor((Color) paint); 671 customPaint = false; 672 } 673 674 else if (paint instanceof TexturePaint) 675 { 676 TexturePaint tp = (TexturePaint) paint; 677 BufferedImage img = tp.getImage(); 678 679 // map the image to the anchor rectangle 680 int width = (int) tp.getAnchorRect().getWidth(); 681 int height = (int) tp.getAnchorRect().getHeight(); 682 683 double scaleX = width / (double) img.getWidth(); 684 double scaleY = height / (double) img.getHeight(); 685 686 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0); 687 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints()); 688 BufferedImage texture = op.filter(img, null); 689 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width); 690 setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0); 691 customPaint = false; 692 } 693 694 else if (paint instanceof GradientPaint) 695 { 696 GradientPaint gp = (GradientPaint) paint; 697 Point2D p1 = gp.getPoint1(); 698 Point2D p2 = gp.getPoint2(); 699 Color c1 = gp.getColor1(); 700 Color c2 = gp.getColor2(); 701 setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(), 702 c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(), 703 c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(), 704 gp.isCyclic()); 705 customPaint = false; 706 } 707 else 708 { 709 customPaint = true; 710 } 711 } 712 713 /** 714 * Sets a custom paint 715 * 716 * @param bounds the bounding box, in user space 717 */ setCustomPaint(Rectangle bounds)718 protected void setCustomPaint(Rectangle bounds) 719 { 720 if (paint instanceof Color || paint instanceof TexturePaint 721 || paint instanceof GradientPaint) 722 return; 723 724 int userX = bounds.x; 725 int userY = bounds.y; 726 int userWidth = bounds.width; 727 int userHeight = bounds.height; 728 729 // Find bounds in device space 730 Rectangle2D bounds2D = getTransformedBounds(bounds, transform); 731 int deviceX = (int)bounds2D.getX(); 732 int deviceY = (int)bounds2D.getY(); 733 int deviceWidth = (int)Math.ceil(bounds2D.getWidth()); 734 int deviceHeight = (int)Math.ceil(bounds2D.getHeight()); 735 736 // Get raster of the paint background 737 PaintContext pc = paint.createContext(CairoSurface.cairoColorModel, 738 new Rectangle(deviceX, deviceY, 739 deviceWidth, 740 deviceHeight), 741 bounds, 742 transform, hints); 743 744 Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth, 745 deviceHeight); 746 747 // Clear the transform matrix in Cairo, since the raster returned by the 748 // PaintContext is already in device-space 749 AffineTransform oldTx = new AffineTransform(transform); 750 setTransformImpl(new AffineTransform()); 751 752 // Set pixels in cairo, aligning the top-left of the background image 753 // to the top-left corner in device space 754 if (pc.getColorModel().equals(CairoSurface.cairoColorModel) 755 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT) 756 { 757 // Use a fast copy if the paint context can uses a Cairo-compatible 758 // color model 759 setPaintPixels(nativePointer, 760 (int[])raster.getDataElements(0, 0, deviceWidth, 761 deviceHeight, null), 762 deviceWidth, deviceHeight, deviceWidth, false, 763 deviceX, deviceY); 764 } 765 766 else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque) 767 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT) 768 { 769 // We can also optimize if the context uses a similar color model 770 // but without an alpha channel; we just add the alpha 771 int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth, 772 deviceHeight, null); 773 774 for (int i = 0; i < pixels.length; i++) 775 pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff); 776 777 setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight, 778 deviceWidth, false, deviceX, deviceY); 779 } 780 781 else 782 { 783 // Fall back on wrapping the raster in a BufferedImage, and 784 // use BufferedImage.getRGB() to do color-model conversion 785 WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(), 786 new Point(raster.getMinX(), 787 raster.getMinY())); 788 wr.setRect(raster); 789 790 BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr, 791 pc.getColorModel().isAlphaPremultiplied(), 792 null); 793 794 setPaintPixels(nativePointer, 795 img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0, 796 deviceWidth), 797 deviceWidth, deviceHeight, deviceWidth, false, 798 deviceX, deviceY); 799 } 800 801 // Restore transform 802 setTransformImpl(oldTx); 803 } 804 getStroke()805 public Stroke getStroke() 806 { 807 return stroke; 808 } 809 setStroke(Stroke st)810 public void setStroke(Stroke st) 811 { 812 stroke = st; 813 if (stroke instanceof BasicStroke) 814 { 815 BasicStroke bs = (BasicStroke) stroke; 816 cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(), 817 bs.getLineJoin(), bs.getMiterLimit()); 818 819 float[] dashes = bs.getDashArray(); 820 if (dashes != null) 821 { 822 double[] double_dashes = new double[dashes.length]; 823 for (int i = 0; i < dashes.length; i++) 824 double_dashes[i] = dashes[i]; 825 826 cairoSetDash(nativePointer, double_dashes, double_dashes.length, 827 (double) bs.getDashPhase()); 828 } 829 else 830 cairoSetDash(nativePointer, new double[0], 0, 0.0); 831 } 832 } 833 834 /** 835 * Utility method to find the bounds of a shape, including the stroke width. 836 * 837 * @param s the shape 838 * @return the bounds of the shape, including stroke width 839 */ findStrokedBounds(Shape s)840 protected Rectangle findStrokedBounds(Shape s) 841 { 842 Rectangle r = s.getBounds(); 843 844 if (stroke instanceof BasicStroke) 845 { 846 int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth()); 847 r.x -= strokeWidth / 2; 848 r.y -= strokeWidth / 2; 849 r.height += strokeWidth; 850 r.width += strokeWidth; 851 } 852 else 853 { 854 Shape s2 = stroke.createStrokedShape(s); 855 r = s2.getBounds(); 856 } 857 858 return r; 859 } 860 setPaintMode()861 public void setPaintMode() 862 { 863 setComposite(AlphaComposite.SrcOver); 864 } 865 setXORMode(Color c)866 public void setXORMode(Color c) 867 { 868 // FIXME: implement 869 } 870 setColor(Color c)871 public void setColor(Color c) 872 { 873 if (c == null) 874 c = Color.BLACK; 875 876 fg = c; 877 paint = c; 878 updateColor(); 879 } 880 881 /** 882 * Set the current fg value as the cairo color. 883 */ updateColor()884 void updateColor() 885 { 886 if (fg == null) 887 fg = Color.BLACK; 888 889 cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0, 890 fg.getGreen() / 255.0,fg.getBlue() / 255.0, 891 fg.getAlpha() / 255.0); 892 } 893 getColor()894 public Color getColor() 895 { 896 return fg; 897 } 898 clipRect(int x, int y, int width, int height)899 public void clipRect(int x, int y, int width, int height) 900 { 901 if (clip == null) 902 setClip(new Rectangle(x, y, width, height)); 903 else if (clip instanceof Rectangle) 904 { 905 computeIntersection(x, y, width, height, (Rectangle) clip); 906 setClip(clip); 907 } 908 else 909 clip(new Rectangle(x, y, width, height)); 910 } 911 getClip()912 public Shape getClip() 913 { 914 if (clip == null) 915 return null; 916 else if (clip instanceof Rectangle2D) 917 return clip.getBounds2D(); //getClipInDevSpace(); 918 else 919 { 920 GeneralPath p = new GeneralPath(); 921 PathIterator pi = clip.getPathIterator(null); 922 p.append(pi, false); 923 return p; 924 } 925 } 926 getClipBounds()927 public Rectangle getClipBounds() 928 { 929 if (clip == null) 930 return null; 931 else 932 return clip.getBounds(); 933 } 934 getClipInDevSpace()935 protected Rectangle2D getClipInDevSpace() 936 { 937 Rectangle2D uclip = clip.getBounds2D(); 938 if (transform == null) 939 return uclip; 940 else 941 return getTransformedBounds(clip.getBounds2D(), transform); 942 } 943 setClip(int x, int y, int width, int height)944 public void setClip(int x, int y, int width, int height) 945 { 946 if( width < 0 || height < 0 ) 947 return; 948 949 setClip(new Rectangle2D.Double(x, y, width, height)); 950 } 951 setClip(Shape s)952 public void setClip(Shape s) 953 { 954 // The first time the clip is set, save it as the original clip 955 // to reset to on s == null. We can rely on this being non-null 956 // because the constructor in subclasses is expected to set the 957 // initial clip properly. 958 if( firstClip ) 959 { 960 originalClip = s; 961 firstClip = false; 962 } 963 964 clip = s; 965 cairoResetClip(nativePointer); 966 967 if (clip != null) 968 { 969 cairoNewPath(nativePointer); 970 if (clip instanceof Rectangle2D) 971 { 972 Rectangle2D r = (Rectangle2D) clip; 973 cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(), 974 r.getHeight()); 975 } 976 else 977 walkPath(clip.getPathIterator(null), false); 978 979 cairoClip(nativePointer); 980 } 981 } 982 setBackground(Color c)983 public void setBackground(Color c) 984 { 985 if (c == null) 986 c = Color.WHITE; 987 bg = c; 988 } 989 getBackground()990 public Color getBackground() 991 { 992 return bg; 993 } 994 995 /** 996 * Return the current composite. 997 */ getComposite()998 public Composite getComposite() 999 { 1000 if (comp == null) 1001 return AlphaComposite.SrcOver; 1002 else 1003 return comp; 1004 } 1005 1006 /** 1007 * Sets the current composite context. 1008 */ setComposite(Composite comp)1009 public void setComposite(Composite comp) 1010 { 1011 if (this.comp == comp) 1012 return; 1013 1014 this.comp = comp; 1015 if (compCtx != null) 1016 compCtx.dispose(); 1017 compCtx = null; 1018 1019 if (comp instanceof AlphaComposite) 1020 { 1021 AlphaComposite a = (AlphaComposite) comp; 1022 cairoSetOperator(nativePointer, a.getRule()); 1023 } 1024 1025 else 1026 { 1027 cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER); 1028 1029 if (comp != null) 1030 { 1031 // FIXME: this check is only required "if this Graphics2D 1032 // context is drawing to a Component on the display screen". 1033 SecurityManager sm = System.getSecurityManager(); 1034 if (sm != null) 1035 sm.checkPermission(new AWTPermission("readDisplayPixels")); 1036 1037 compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * Returns the Colour Model describing the native, raw image data for this 1044 * specific peer. 1045 * 1046 * @return ColorModel the ColorModel of native data in this peer 1047 */ getNativeCM()1048 protected abstract ColorModel getNativeCM(); 1049 1050 /** 1051 * Returns the Color Model describing the buffer that this peer uses 1052 * for custom composites. 1053 * 1054 * @return ColorModel the ColorModel of the composite buffer in this peer. 1055 */ getBufferCM()1056 protected ColorModel getBufferCM() 1057 { 1058 // This may be overridden by some subclasses 1059 return getNativeCM(); 1060 } 1061 1062 ///////////////////////// DRAWING PRIMITIVES /////////////////////////////////// 1063 draw(Shape s)1064 public void draw(Shape s) 1065 { 1066 if ((stroke != null && ! (stroke instanceof BasicStroke)) 1067 || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0)) 1068 { 1069 // Cairo doesn't support stroking with alpha, so we create the stroked 1070 // shape and fill with alpha instead 1071 fill(stroke.createStrokedShape(s)); 1072 return; 1073 } 1074 1075 if (customPaint) 1076 { 1077 Rectangle r = findStrokedBounds(s); 1078 setCustomPaint(r); 1079 } 1080 1081 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING) 1082 .equals(RenderingHints.VALUE_ANTIALIAS_OFF)); 1083 createPath(s, true); 1084 cairoStroke(nativePointer); 1085 } 1086 fill(Shape s)1087 public void fill(Shape s) 1088 { 1089 createPath(s, false); 1090 1091 if (customPaint) 1092 setCustomPaint(s.getBounds()); 1093 1094 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING) 1095 .equals(RenderingHints.VALUE_ANTIALIAS_OFF)); 1096 double alpha = 1.0; 1097 if (comp instanceof AlphaComposite) 1098 alpha = ((AlphaComposite) comp).getAlpha(); 1099 cairoFill(nativePointer, alpha); 1100 } 1101 createPath(Shape s, boolean isDraw)1102 private void createPath(Shape s, boolean isDraw) 1103 { 1104 cairoNewPath(nativePointer); 1105 1106 // Optimize rectangles, since there is a direct Cairo function 1107 if (s instanceof Rectangle2D) 1108 { 1109 Rectangle2D r = (Rectangle2D) s; 1110 1111 // Pixels need to be shifted in draw operations to ensure that they 1112 // light up entire pixels, but we also need to make sure the rectangle 1113 // does not get distorted by this shifting operation 1114 double x = shiftX(r.getX(),shiftDrawCalls && isDraw); 1115 double y = shiftY(r.getY(), shiftDrawCalls && isDraw); 1116 double w = Math.round(r.getWidth()); 1117 double h = Math.round(r.getHeight()); 1118 cairoRectangle(nativePointer, x, y, w, h); 1119 } 1120 1121 // Lines are easy too 1122 else if (s instanceof Line2D) 1123 { 1124 Line2D l = (Line2D) s; 1125 cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw), 1126 shiftY(l.getY1(), shiftDrawCalls && isDraw)); 1127 cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw), 1128 shiftY(l.getY2(), shiftDrawCalls && isDraw)); 1129 } 1130 1131 // We can optimize ellipses too; however we don't bother optimizing arcs: 1132 // the iterator is fast enough (an ellipse requires 5 steps using the 1133 // iterator, while most arcs are only 2-3) 1134 else if (s instanceof Ellipse2D) 1135 { 1136 Ellipse2D e = (Ellipse2D) s; 1137 1138 double radius = Math.min(e.getHeight(), e.getWidth()) / 2; 1139 1140 // Cairo only draws circular shapes, but we can use a stretch to make 1141 // them into ellipses 1142 double xscale = 1, yscale = 1; 1143 if (e.getHeight() != e.getWidth()) 1144 { 1145 cairoSave(nativePointer); 1146 1147 if (e.getHeight() < e.getWidth()) 1148 xscale = e.getWidth() / (radius * 2); 1149 else 1150 yscale = e.getHeight() / (radius * 2); 1151 1152 if (xscale != 1 || yscale != 1) 1153 cairoScale(nativePointer, xscale, yscale); 1154 } 1155 1156 cairoArc(nativePointer, 1157 shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw), 1158 shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw), 1159 radius, 0, Math.PI * 2); 1160 1161 if (xscale != 1 || yscale != 1) 1162 cairoRestore(nativePointer); 1163 } 1164 1165 // All other shapes are broken down and drawn in steps using the 1166 // PathIterator 1167 else 1168 walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw); 1169 } 1170 1171 /** 1172 * Note that the rest of the drawing methods go via fill() or draw() for the drawing, 1173 * although subclasses may with to overload these methods where context-specific 1174 * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int) 1175 */ 1176 clearRect(int x, int y, int width, int height)1177 public void clearRect(int x, int y, int width, int height) 1178 { 1179 if (bg != null) 1180 cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0, 1181 bg.getGreen() / 255.0, bg.getBlue() / 255.0, 1182 bg.getAlpha() / 255.0); 1183 1184 Composite oldcomp = comp; 1185 setComposite(AlphaComposite.Src); 1186 fillRect(x, y, width, height); 1187 1188 setComposite(oldcomp); 1189 updateColor(); 1190 } 1191 draw3DRect(int x, int y, int width, int height, boolean raised)1192 public void draw3DRect(int x, int y, int width, int height, boolean raised) 1193 { 1194 Stroke tmp = stroke; 1195 setStroke(draw3DRectStroke); 1196 super.draw3DRect(x, y, width, height, raised); 1197 setStroke(tmp); 1198 } 1199 drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)1200 public void drawArc(int x, int y, int width, int height, int startAngle, 1201 int arcAngle) 1202 { 1203 draw(new Arc2D.Double((double) x, (double) y, (double) width, 1204 (double) height, (double) startAngle, 1205 (double) arcAngle, Arc2D.OPEN)); 1206 } 1207 drawLine(int x1, int y1, int x2, int y2)1208 public void drawLine(int x1, int y1, int x2, int y2) 1209 { 1210 // The coordinates being pairwise identical means one wants 1211 // to draw a single pixel. This is emulated by drawing 1212 // a one pixel sized rectangle. 1213 if (x1 == x2 && y1 == y2) 1214 fill(new Rectangle(x1, y1, 1, 1)); 1215 else 1216 draw(new Line2D.Double(x1, y1, x2, y2)); 1217 } 1218 drawRect(int x, int y, int width, int height)1219 public void drawRect(int x, int y, int width, int height) 1220 { 1221 draw(new Rectangle(x, y, width, height)); 1222 } 1223 fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)1224 public void fillArc(int x, int y, int width, int height, int startAngle, 1225 int arcAngle) 1226 { 1227 fill(new Arc2D.Double((double) x, (double) y, (double) width, 1228 (double) height, (double) startAngle, 1229 (double) arcAngle, Arc2D.PIE)); 1230 } 1231 fillRect(int x, int y, int width, int height)1232 public void fillRect(int x, int y, int width, int height) 1233 { 1234 fill (new Rectangle(x, y, width, height)); 1235 } 1236 fillPolygon(int[] xPoints, int[] yPoints, int nPoints)1237 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) 1238 { 1239 fill(new Polygon(xPoints, yPoints, nPoints)); 1240 } 1241 drawPolygon(int[] xPoints, int[] yPoints, int nPoints)1242 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) 1243 { 1244 draw(new Polygon(xPoints, yPoints, nPoints)); 1245 } 1246 drawPolyline(int[] xPoints, int[] yPoints, int nPoints)1247 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) 1248 { 1249 for (int i = 1; i < nPoints; i++) 1250 draw(new Line2D.Double(xPoints[i - 1], yPoints[i - 1], 1251 xPoints[i], yPoints[i])); 1252 } 1253 drawOval(int x, int y, int width, int height)1254 public void drawOval(int x, int y, int width, int height) 1255 { 1256 drawArc(x, y, width, height, 0, 360); 1257 } 1258 drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)1259 public void drawRoundRect(int x, int y, int width, int height, int arcWidth, 1260 int arcHeight) 1261 { 1262 draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); 1263 } 1264 fillOval(int x, int y, int width, int height)1265 public void fillOval(int x, int y, int width, int height) 1266 { 1267 fillArc(x, y, width, height, 0, 360); 1268 } 1269 fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)1270 public void fillRoundRect(int x, int y, int width, int height, int arcWidth, 1271 int arcHeight) 1272 { 1273 fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); 1274 } 1275 1276 /** 1277 * CopyArea - performs clipping to the native surface as a convenience 1278 * (requires getRealBounds). Then calls copyAreaImpl. 1279 */ copyArea(int ox, int oy, int owidth, int oheight, int odx, int ody)1280 public void copyArea(int ox, int oy, int owidth, int oheight, 1281 int odx, int ody) 1282 { 1283 // FIXME: does this handle a rotation transform properly? 1284 // (the width/height might not be correct) 1285 Point2D pos = transform.transform(new Point2D.Double(ox, oy), 1286 (Point2D) null); 1287 Point2D dim = transform.transform(new Point2D.Double(ox + owidth, 1288 oy + oheight), 1289 (Point2D) null); 1290 Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody), 1291 (Point2D) null); 1292 int x = (int)pos.getX(); 1293 int y = (int)pos.getY(); 1294 int width = (int)(dim.getX() - pos.getX()); 1295 int height = (int)(dim.getY() - pos.getY()); 1296 int dx = (int)(p2.getX() - pos.getX()); 1297 int dy = (int)(p2.getY() - pos.getY()); 1298 1299 Rectangle2D r = getRealBounds(); 1300 1301 if( width <= 0 || height <= 0 ) 1302 return; 1303 // Return if outside the surface 1304 if( x + dx > r.getWidth() || y + dy > r.getHeight() ) 1305 return; 1306 1307 if( x + dx + width < r.getX() || y + dy + height < r.getY() ) 1308 return; 1309 1310 // Clip edges if necessary 1311 if( x + dx < r.getX() ) // left 1312 { 1313 width = x + dx + width; 1314 x = (int)r.getX() - dx; 1315 } 1316 1317 if( y + dy < r.getY() ) // top 1318 { 1319 height = y + dy + height; 1320 y = (int)r.getY() - dy; 1321 } 1322 1323 if( x + dx + width >= r.getWidth() ) // right 1324 width = (int)r.getWidth() - dx - x; 1325 1326 if( y + dy + height >= r.getHeight() ) // bottom 1327 height = (int)r.getHeight() - dy - y; 1328 1329 copyAreaImpl(x, y, width, height, dx, dy); 1330 } 1331 1332 ///////////////////////// RENDERING HINTS /////////////////////////////////// 1333 setRenderingHint(RenderingHints.Key hintKey, Object hintValue)1334 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) 1335 { 1336 hints.put(hintKey, hintValue); 1337 1338 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) 1339 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); 1340 } 1341 getRenderingHint(RenderingHints.Key hintKey)1342 public Object getRenderingHint(RenderingHints.Key hintKey) 1343 { 1344 return hints.get(hintKey); 1345 } 1346 setRenderingHints(Map<?,?> hints)1347 public void setRenderingHints(Map<?,?> hints) 1348 { 1349 this.hints = new RenderingHints(getDefaultHints()); 1350 this.hints.putAll(hints); 1351 1352 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) 1353 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); 1354 1355 if (compCtx != null) 1356 { 1357 compCtx.dispose(); 1358 compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints); 1359 } 1360 } 1361 addRenderingHints(Map hints)1362 public void addRenderingHints(Map hints) 1363 { 1364 this.hints.putAll(hints); 1365 } 1366 getRenderingHints()1367 public RenderingHints getRenderingHints() 1368 { 1369 return hints; 1370 } 1371 getInterpolation()1372 private int getInterpolation() 1373 { 1374 if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) 1375 return INTERPOLATION_NEAREST; 1376 1377 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) 1378 return INTERPOLATION_BILINEAR; 1379 1380 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC)) 1381 return INTERPOLATION_BICUBIC; 1382 1383 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)) 1384 return ALPHA_INTERPOLATION_SPEED; 1385 1386 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)) 1387 return ALPHA_INTERPOLATION_QUALITY; 1388 1389 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT)) 1390 return ALPHA_INTERPOLATION_DEFAULT; 1391 1392 // Do bilinear interpolation as default 1393 return INTERPOLATION_BILINEAR; 1394 } 1395 1396 /** 1397 * Set antialias if needed. If the ignoreAA flag is set, this method will 1398 * return without doing anything. 1399 * 1400 * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF 1401 */ setAntialias(boolean needAA)1402 private void setAntialias(boolean needAA) 1403 { 1404 if (ignoreAA) 1405 return; 1406 1407 if (needAA != antialias) 1408 { 1409 antialias = !antialias; 1410 cairoSetAntialias(nativePointer, antialias); 1411 } 1412 } 1413 1414 ///////////////////////// IMAGE. METHODS /////////////////////////////////// 1415 drawImage(Image img, AffineTransform xform, Color bgcolor, ImageObserver obs)1416 protected boolean drawImage(Image img, AffineTransform xform, 1417 Color bgcolor, ImageObserver obs) 1418 { 1419 if (img == null) 1420 return false; 1421 1422 if (xform == null) 1423 xform = new AffineTransform(); 1424 1425 // In this case, xform is an AffineTransform that transforms bounding 1426 // box of the specified image from image space to user space. However 1427 // when we pass this transform to cairo, cairo will use this transform 1428 // to map "user coordinates" to "pixel" coordinates, which is the 1429 // other way around. Therefore to get the "user -> pixel" transform 1430 // that cairo wants from "image -> user" transform that we currently 1431 // have, we will need to invert the transformation matrix. 1432 AffineTransform invertedXform; 1433 1434 try 1435 { 1436 invertedXform = xform.createInverse(); 1437 } 1438 catch (NoninvertibleTransformException e) 1439 { 1440 throw new ImagingOpException("Unable to invert transform " 1441 + xform.toString()); 1442 } 1443 1444 // Unrecognized image - convert to a BufferedImage 1445 // Note - this can get us in trouble when the gdk lock is re-acquired. 1446 // for example by VolatileImage. See ComponentGraphics for how we work 1447 // around this. 1448 img = AsyncImage.realImage(img, obs); 1449 if( !(img instanceof BufferedImage) ) 1450 { 1451 ImageProducer source = img.getSource(); 1452 if (source == null) 1453 return false; 1454 img = Toolkit.getDefaultToolkit().createImage(source); 1455 } 1456 1457 BufferedImage b = (BufferedImage) img; 1458 Raster raster; 1459 double[] i2u = new double[6]; 1460 int width = b.getWidth(); 1461 int height = b.getHeight(); 1462 1463 // If this BufferedImage has a BufferedImageGraphics object, 1464 // use the cached CairoSurface that BIG is drawing onto 1465 1466 if( BufferedImageGraphics.bufferedImages.get( b ) != null ) 1467 raster = BufferedImageGraphics.bufferedImages.get( b ); 1468 else 1469 raster = b.getRaster(); 1470 1471 invertedXform.getMatrix(i2u); 1472 1473 double alpha = 1.0; 1474 if (comp instanceof AlphaComposite) 1475 alpha = ((AlphaComposite) comp).getAlpha(); 1476 1477 if(raster instanceof CairoSurface 1478 && ((CairoSurface)raster).sharedBuffer == true) 1479 { 1480 drawCairoSurface((CairoSurface)raster, xform, alpha, getInterpolation()); 1481 updateColor(); 1482 return true; 1483 } 1484 1485 if( bgcolor != null ) 1486 { 1487 Color oldColor = bg; 1488 setBackground(bgcolor); 1489 1490 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height); 1491 bounds = getTransformedBounds(bounds, xform); 1492 1493 clearRect((int)bounds.getX(), (int)bounds.getY(), 1494 (int)bounds.getWidth(), (int)bounds.getHeight()); 1495 1496 setBackground(oldColor); 1497 } 1498 1499 int[] pixels = b.getRGB(0, 0, width, height, null, 0, width); 1500 // FIXME: The above method returns data in the standard ARGB colorspace, 1501 // meaning data should NOT be alpha pre-multiplied; however Cairo expects 1502 // data to be premultiplied. 1503 1504 cairoSave(nativePointer); 1505 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height); 1506 bounds = getTransformedBounds(bounds, xform); 1507 cairoRectangle(nativePointer, bounds.getX(), bounds.getY(), 1508 bounds.getWidth(), bounds.getHeight()); 1509 cairoClip(nativePointer); 1510 1511 drawPixels(nativePointer, pixels, width, height, width, i2u, alpha, 1512 getInterpolation()); 1513 1514 cairoRestore(nativePointer); 1515 1516 // Cairo seems to lose the current color which must be restored. 1517 updateColor(); 1518 return true; 1519 } 1520 drawRenderedImage(RenderedImage image, AffineTransform xform)1521 public void drawRenderedImage(RenderedImage image, AffineTransform xform) 1522 { 1523 drawRaster(image.getColorModel(), image.getData(), xform, null); 1524 } 1525 drawRenderableImage(RenderableImage image, AffineTransform xform)1526 public void drawRenderableImage(RenderableImage image, AffineTransform xform) 1527 { 1528 drawRenderedImage(image.createRendering(new RenderContext(xform)), xform); 1529 } 1530 drawImage(Image img, AffineTransform xform, ImageObserver obs)1531 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) 1532 { 1533 return drawImage(img, xform, null, obs); 1534 } 1535 drawImage(BufferedImage image, BufferedImageOp op, int x, int y)1536 public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) 1537 { 1538 Image filtered = image; 1539 if (op != null) 1540 filtered = op.filter(image, null); 1541 drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null); 1542 } 1543 drawImage(Image img, int x, int y, ImageObserver observer)1544 public boolean drawImage(Image img, int x, int y, ImageObserver observer) 1545 { 1546 return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, 1547 observer); 1548 } 1549 drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)1550 public boolean drawImage(Image img, int x, int y, Color bgcolor, 1551 ImageObserver observer) 1552 { 1553 return drawImage(img, x, y, img.getWidth(observer), 1554 img.getHeight(observer), bgcolor, observer); 1555 } 1556 drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)1557 public boolean drawImage(Image img, int x, int y, int width, int height, 1558 Color bgcolor, ImageObserver observer) 1559 { 1560 double scaleX = width / (double) img.getWidth(observer); 1561 double scaleY = height / (double) img.getHeight(observer); 1562 if( scaleX == 0 || scaleY == 0 ) 1563 return true; 1564 1565 return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y), 1566 bgcolor, observer); 1567 } 1568 drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)1569 public boolean drawImage(Image img, int x, int y, int width, int height, 1570 ImageObserver observer) 1571 { 1572 return drawImage(img, x, y, width, height, null, observer); 1573 } 1574 drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)1575 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 1576 int sx1, int sy1, int sx2, int sy2, Color bgcolor, 1577 ImageObserver observer) 1578 { 1579 if (img == null) 1580 return false; 1581 1582 int sourceWidth = sx2 - sx1; 1583 int sourceHeight = sy2 - sy1; 1584 1585 int destWidth = dx2 - dx1; 1586 int destHeight = dy2 - dy1; 1587 1588 if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 || 1589 sourceHeight == 0) 1590 return true; 1591 1592 double scaleX = destWidth / (double) sourceWidth; 1593 double scaleY = destHeight / (double) sourceHeight; 1594 1595 // FIXME: Avoid using an AT if possible here - it's at least twice as slow. 1596 1597 Shape oldClip = getClip(); 1598 int cx, cy, cw, ch; 1599 if( dx1 < dx2 ) 1600 { cx = dx1; cw = dx2 - dx1; } 1601 else 1602 { cx = dx2; cw = dx1 - dx2; } 1603 if( dy1 < dy2 ) 1604 { cy = dy1; ch = dy2 - dy1; } 1605 else 1606 { cy = dy2; ch = dy1 - dy2; } 1607 1608 clipRect( cx, cy, cw, ch ); 1609 1610 AffineTransform tx = new AffineTransform(); 1611 tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY ); 1612 tx.scale( scaleX, scaleY ); 1613 1614 boolean retval = drawImage(img, tx, bgcolor, observer); 1615 setClip( oldClip ); 1616 return retval; 1617 } 1618 drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)1619 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 1620 int sx1, int sy1, int sx2, int sy2, 1621 ImageObserver observer) 1622 { 1623 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer); 1624 } 1625 1626 /** 1627 * Optimized method for drawing a CairoSurface onto this graphics context. 1628 * 1629 * @param surface The surface to draw. 1630 * @param tx The transformation matrix (cannot be null). 1631 * @param alpha The alpha value to paint with ( 0 <= alpha <= 1). 1632 * @param interpolation The interpolation type. 1633 */ drawCairoSurface(CairoSurface surface, AffineTransform tx, double alpha, int interpolation)1634 protected void drawCairoSurface(CairoSurface surface, AffineTransform tx, 1635 double alpha, int interpolation) 1636 { 1637 // Find offset required if this surface is a sub-raster, and append offset 1638 // to transformation. 1639 if (surface.getSampleModelTranslateX() != 0 1640 || surface.getSampleModelTranslateY() != 0) 1641 { 1642 Point2D origin = new Point2D.Double(0, 0); 1643 Point2D offset = new Point2D.Double(surface.getSampleModelTranslateX(), 1644 surface.getSampleModelTranslateY()); 1645 1646 tx.transform(origin, origin); 1647 tx.transform(offset, offset); 1648 1649 tx.translate(offset.getX() - origin.getX(), 1650 offset.getY() - origin.getY()); 1651 } 1652 1653 // Find dimensions of this surface relative to the root parent surface 1654 Rectangle bounds = new Rectangle(-surface.getSampleModelTranslateX(), 1655 -surface.getSampleModelTranslateY(), 1656 surface.width, surface.height); 1657 1658 // Clip to the translated image 1659 // We use direct cairo methods to avoid the overhead of maintaining a 1660 // java copy of the clip, since we will be reverting it immediately 1661 // after drawing 1662 Shape newBounds = tx.createTransformedShape(bounds); 1663 cairoSave(nativePointer); 1664 walkPath(newBounds.getPathIterator(null), false); 1665 cairoClip(nativePointer); 1666 1667 // Draw the surface 1668 try 1669 { 1670 double[] i2u = new double[6]; 1671 tx.createInverse().getMatrix(i2u); 1672 surface.nativeDrawSurface(surface.surfacePointer, nativePointer, i2u, 1673 alpha, interpolation); 1674 } 1675 catch (NoninvertibleTransformException ex) 1676 { 1677 // This should never happen(?), so we don't need to do anything here. 1678 ; 1679 } 1680 1681 // Restore clip 1682 cairoRestore(nativePointer); 1683 } 1684 1685 1686 ///////////////////////// TEXT METHODS //////////////////////////////////// 1687 drawString(String str, float x, float y)1688 public void drawString(String str, float x, float y) 1689 { 1690 if (str == null || str.length() == 0) 1691 return; 1692 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer(); 1693 TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str); 1694 if (tl == null) 1695 { 1696 tl = new TextLayout( str, getFont(), getFontRenderContext() ); 1697 fontPeer.textLayoutCache.put(str, tl); 1698 } 1699 1700 // Set antialias to text_antialiasing, and set the ignoreAA flag so that 1701 // the setting doesn't get overridden in a draw() or fill() call. 1702 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING) 1703 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)); 1704 ignoreAA = true; 1705 1706 tl.draw(this, x, y); 1707 ignoreAA = false; 1708 } 1709 drawString(String str, int x, int y)1710 public void drawString(String str, int x, int y) 1711 { 1712 drawString (str, (float) x, (float) y); 1713 } 1714 drawString(AttributedCharacterIterator ci, int x, int y)1715 public void drawString(AttributedCharacterIterator ci, int x, int y) 1716 { 1717 drawString (ci, (float) x, (float) y); 1718 } 1719 drawGlyphVector(GlyphVector gv, float x, float y)1720 public void drawGlyphVector(GlyphVector gv, float x, float y) 1721 { 1722 double alpha = 1.0; 1723 1724 if( gv.getNumGlyphs() <= 0 ) 1725 return; 1726 1727 if (customPaint) 1728 setCustomPaint(gv.getOutline().getBounds()); 1729 1730 if (comp instanceof AlphaComposite) 1731 alpha = ((AlphaComposite) comp).getAlpha(); 1732 1733 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING) 1734 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)); 1735 ignoreAA = true; 1736 1737 if (gv instanceof FreetypeGlyphVector && alpha == 1.0 1738 && !((FreetypeGlyphVector)gv).hasTransforms()) 1739 { 1740 int n = gv.getNumGlyphs (); 1741 int[] codes = gv.getGlyphCodes (0, n, null); 1742 long[] fontset = ((FreetypeGlyphVector)gv).getGlyphFonts (0, n, null); 1743 float[] positions = gv.getGlyphPositions (0, n, null); 1744 1745 setFont (gv.getFont ()); 1746 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer(); 1747 synchronized (fontPeer) 1748 { 1749 cairoDrawGlyphVector(nativePointer, fontPeer, 1750 x, y, n, codes, positions, fontset); 1751 } 1752 } 1753 else 1754 { 1755 translate(x, y); 1756 fill(gv.getOutline()); 1757 translate(-x, -y); 1758 } 1759 1760 ignoreAA = false; 1761 } 1762 drawString(AttributedCharacterIterator ci, float x, float y)1763 public void drawString(AttributedCharacterIterator ci, float x, float y) 1764 { 1765 GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci); 1766 drawGlyphVector(gv, x, y); 1767 } 1768 1769 /** 1770 * Should perhaps be contexct dependent, but this is left for now as an 1771 * overloadable default implementation. 1772 */ getFontRenderContext()1773 public FontRenderContext getFontRenderContext() 1774 { 1775 return new FontRenderContext(transform, true, true); 1776 } 1777 1778 // Until such time as pango is happy to talk directly to cairo, we 1779 // actually need to redirect some calls from the GtkFontPeer and 1780 // GtkFontMetrics into the drawing kit and ask cairo ourselves. 1781 getFontMetrics()1782 public FontMetrics getFontMetrics() 1783 { 1784 return getFontMetrics(getFont()); 1785 } 1786 getFontMetrics(Font f)1787 public FontMetrics getFontMetrics(Font f) 1788 { 1789 return ((GdkFontPeer) f.getPeer()).getFontMetrics(f); 1790 } 1791 setFont(Font f)1792 public void setFont(Font f) 1793 { 1794 // Sun's JDK does not throw NPEs, instead it leaves the current setting 1795 // unchanged. So do we. 1796 if (f == null) 1797 return; 1798 1799 if (f.getPeer() instanceof GdkFontPeer) 1800 font = f; 1801 else 1802 font = 1803 ((ClasspathToolkit)(Toolkit.getDefaultToolkit())) 1804 .getFont(f.getName(), f.getAttributes()); 1805 1806 GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer(); 1807 synchronized (fontpeer) 1808 { 1809 cairoSetFont(nativePointer, fontpeer); 1810 } 1811 } 1812 getFont()1813 public Font getFont() 1814 { 1815 if (font == null) 1816 return new Font("SansSerif", Font.PLAIN, 12); 1817 return font; 1818 } 1819 1820 /////////////////////// MISC. PUBLIC METHODS ///////////////////////////////// 1821 hit(Rectangle rect, Shape s, boolean onStroke)1822 public boolean hit(Rectangle rect, Shape s, boolean onStroke) 1823 { 1824 if( onStroke ) 1825 { 1826 Shape stroked = stroke.createStrokedShape( s ); 1827 return stroked.intersects( (double)rect.x, (double)rect.y, 1828 (double)rect.width, (double)rect.height ); 1829 } 1830 return s.intersects( (double)rect.x, (double)rect.y, 1831 (double)rect.width, (double)rect.height ); 1832 } 1833 toString()1834 public String toString() 1835 { 1836 return (getClass().getName() 1837 + "[font=" + getFont().toString() 1838 + ",color=" + fg.toString() 1839 + "]"); 1840 } 1841 1842 ///////////////////////// PRIVATE METHODS /////////////////////////////////// 1843 1844 /** 1845 * All the drawImage() methods eventually get delegated here if the image 1846 * is not a Cairo surface. 1847 * 1848 * @param bgcolor - if non-null draws the background color before 1849 * drawing the image. 1850 */ drawRaster(ColorModel cm, Raster r, AffineTransform imageToUser, Color bgcolor)1851 private boolean drawRaster(ColorModel cm, Raster r, 1852 AffineTransform imageToUser, Color bgcolor) 1853 { 1854 if (r == null) 1855 return false; 1856 1857 SampleModel sm = r.getSampleModel(); 1858 DataBuffer db = r.getDataBuffer(); 1859 1860 if (db == null || sm == null) 1861 return false; 1862 1863 if (cm == null) 1864 cm = ColorModel.getRGBdefault(); 1865 1866 double[] i2u = new double[6]; 1867 if (imageToUser != null) 1868 imageToUser.getMatrix(i2u); 1869 else 1870 { 1871 i2u[0] = 1; 1872 i2u[1] = 0; 1873 i2u[2] = 0; 1874 i2u[3] = 1; 1875 i2u[4] = 0; 1876 i2u[5] = 0; 1877 } 1878 1879 int[] pixels = findSimpleIntegerArray(cm, r); 1880 1881 if (pixels == null) 1882 { 1883 // FIXME: I don't think this code will work correctly with a non-RGB 1884 // MultiPixelPackedSampleModel. Although this entire method should 1885 // probably be rewritten to better utilize Cairo's different supported 1886 // data formats. 1887 if (sm instanceof MultiPixelPackedSampleModel) 1888 { 1889 pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels); 1890 for (int i = 0; i < pixels.length; i++) 1891 pixels[i] = cm.getRGB(pixels[i]); 1892 } 1893 else 1894 { 1895 pixels = new int[r.getWidth() * r.getHeight()]; 1896 for (int i = 0; i < pixels.length; i++) 1897 pixels[i] = cm.getRGB(db.getElem(i)); 1898 } 1899 } 1900 1901 // Change all transparent pixels in the image to the specified bgcolor, 1902 // or (if there's no alpha) fill in an alpha channel so that it paints 1903 // correctly. 1904 if (cm.hasAlpha()) 1905 { 1906 if (bgcolor != null && cm.hasAlpha()) 1907 for (int i = 0; i < pixels.length; i++) 1908 { 1909 if (cm.getAlpha(pixels[i]) == 0) 1910 pixels[i] = bgcolor.getRGB(); 1911 } 1912 } 1913 else 1914 for (int i = 0; i < pixels.length; i++) 1915 pixels[i] |= 0xFF000000; 1916 1917 double alpha = 1.0; 1918 if (comp instanceof AlphaComposite) 1919 alpha = ((AlphaComposite) comp).getAlpha(); 1920 1921 drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(), 1922 r.getWidth(), i2u, alpha, getInterpolation()); 1923 1924 // Cairo seems to lose the current color which must be restored. 1925 updateColor(); 1926 1927 return true; 1928 } 1929 1930 /** 1931 * Shifts an x-coordinate by 0.5 in device space. 1932 */ shiftX(double coord, boolean doShift)1933 private double shiftX(double coord, boolean doShift) 1934 { 1935 if (doShift) 1936 { 1937 double shift = 0.5; 1938 if (!transform.isIdentity()) 1939 shift /= transform.getScaleX(); 1940 return (coord + shift); 1941 } 1942 else 1943 return coord; 1944 } 1945 1946 /** 1947 * Shifts a y-coordinate by 0.5 in device space. 1948 */ shiftY(double coord, boolean doShift)1949 private double shiftY(double coord, boolean doShift) 1950 { 1951 if (doShift) 1952 { 1953 double shift = 0.5; 1954 if (!transform.isIdentity()) 1955 shift /= transform.getScaleY(); 1956 return (coord + shift); 1957 } 1958 else 1959 return coord; 1960 } 1961 1962 /** 1963 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule. 1964 */ walkPath(PathIterator p, boolean doShift)1965 private void walkPath(PathIterator p, boolean doShift) 1966 { 1967 double x = 0; 1968 double y = 0; 1969 double[] coords = new double[6]; 1970 1971 cairoSetFillRule(nativePointer, p.getWindingRule()); 1972 for (; ! p.isDone(); p.next()) 1973 { 1974 int seg = p.currentSegment(coords); 1975 switch (seg) 1976 { 1977 case PathIterator.SEG_MOVETO: 1978 x = shiftX(coords[0], doShift); 1979 y = shiftY(coords[1], doShift); 1980 cairoMoveTo(nativePointer, x, y); 1981 break; 1982 case PathIterator.SEG_LINETO: 1983 x = shiftX(coords[0], doShift); 1984 y = shiftY(coords[1], doShift); 1985 cairoLineTo(nativePointer, x, y); 1986 break; 1987 case PathIterator.SEG_QUADTO: 1988 // splitting a quadratic bezier into a cubic: 1989 // see: http://pfaedit.sourceforge.net/bezier.html 1990 double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x); 1991 double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y); 1992 1993 double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x); 1994 double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y); 1995 1996 x = shiftX(coords[2], doShift); 1997 y = shiftY(coords[3], doShift); 1998 cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y); 1999 break; 2000 case PathIterator.SEG_CUBICTO: 2001 x = shiftX(coords[4], doShift); 2002 y = shiftY(coords[5], doShift); 2003 cairoCurveTo(nativePointer, shiftX(coords[0], doShift), 2004 shiftY(coords[1], doShift), 2005 shiftX(coords[2], doShift), 2006 shiftY(coords[3], doShift), x, y); 2007 break; 2008 case PathIterator.SEG_CLOSE: 2009 cairoClosePath(nativePointer); 2010 break; 2011 } 2012 } 2013 } 2014 2015 /** 2016 * Used by setRenderingHints() 2017 */ getDefaultHints()2018 private Map<RenderingHints.Key, Object> getDefaultHints() 2019 { 2020 HashMap<RenderingHints.Key, Object> defaultHints = 2021 new HashMap<RenderingHints.Key, Object>(); 2022 2023 defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, 2024 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); 2025 2026 defaultHints.put(RenderingHints.KEY_STROKE_CONTROL, 2027 RenderingHints.VALUE_STROKE_DEFAULT); 2028 2029 defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS, 2030 RenderingHints.VALUE_FRACTIONALMETRICS_OFF); 2031 2032 defaultHints.put(RenderingHints.KEY_ANTIALIASING, 2033 RenderingHints.VALUE_ANTIALIAS_OFF); 2034 2035 defaultHints.put(RenderingHints.KEY_RENDERING, 2036 RenderingHints.VALUE_RENDER_DEFAULT); 2037 2038 return defaultHints; 2039 } 2040 2041 /** 2042 * Used by drawRaster and GdkPixbufDecoder 2043 */ findSimpleIntegerArray(ColorModel cm, Raster raster)2044 public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster) 2045 { 2046 if (cm == null || raster == null) 2047 return null; 2048 2049 if (! cm.getColorSpace().isCS_sRGB()) 2050 return null; 2051 2052 if (! (cm instanceof DirectColorModel)) 2053 return null; 2054 2055 DirectColorModel dcm = (DirectColorModel) cm; 2056 2057 if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00 2058 || dcm.getBlueMask() != 0x000000FF) 2059 return null; 2060 2061 if (! (raster instanceof WritableRaster)) 2062 return null; 2063 2064 if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT) 2065 return null; 2066 2067 if (! (raster.getDataBuffer() instanceof DataBufferInt)) 2068 return null; 2069 2070 DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); 2071 2072 if (db.getNumBanks() != 1) 2073 return null; 2074 2075 // Finally, we have determined that this is a single bank, [A]RGB-int 2076 // buffer in sRGB space. It's worth checking all this, because it means 2077 // that cairo can paint directly into the data buffer, which is very 2078 // fast compared to all the normal copying and converting. 2079 2080 return db.getData(); 2081 } 2082 2083 /** 2084 * Helper method to transform the clip. This is called by the various 2085 * transformation-manipulation methods to update the clip (which is in 2086 * userspace) accordingly. 2087 * 2088 * The transform usually is the inverse transform that was applied to the 2089 * graphics object. 2090 * 2091 * @param t the transform to apply to the clip 2092 */ updateClip(AffineTransform t)2093 private void updateClip(AffineTransform t) 2094 { 2095 if (clip == null) 2096 return; 2097 2098 // If the clip is a rectangle, and the transformation preserves the shape 2099 // (translate/stretch only), then keep the clip as a rectangle 2100 double[] matrix = new double[4]; 2101 t.getMatrix(matrix); 2102 if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0) 2103 { 2104 Rectangle2D rect = (Rectangle2D)clip; 2105 double[] origin = new double[] {rect.getX(), rect.getY()}; 2106 double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()}; 2107 t.transform(origin, 0, origin, 0, 1); 2108 t.deltaTransform(dimensions, 0, dimensions, 0, 1); 2109 rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]); 2110 } 2111 else 2112 { 2113 if (! (clip instanceof GeneralPath)) 2114 clip = new GeneralPath(clip); 2115 2116 GeneralPath p = (GeneralPath) clip; 2117 p.transform(t); 2118 } 2119 } 2120 computeIntersection(int x, int y, int w, int h, Rectangle rect)2121 private static Rectangle computeIntersection(int x, int y, int w, int h, 2122 Rectangle rect) 2123 { 2124 int x2 = rect.x; 2125 int y2 = rect.y; 2126 int w2 = rect.width; 2127 int h2 = rect.height; 2128 2129 int dx = (x > x2) ? x : x2; 2130 int dy = (y > y2) ? y : y2; 2131 int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx); 2132 int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy); 2133 2134 if (dw >= 0 && dh >= 0) 2135 rect.setBounds(dx, dy, dw, dh); 2136 else 2137 rect.setBounds(0, 0, 0, 0); 2138 2139 return rect; 2140 } 2141 getTransformedBounds(Rectangle2D bounds, AffineTransform tx)2142 static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx) 2143 { 2144 double x1 = bounds.getX(); 2145 double x2 = bounds.getX() + bounds.getWidth(); 2146 double x3 = x1; 2147 double x4 = x2; 2148 double y1 = bounds.getY(); 2149 double y2 = y1; 2150 double y3 = bounds.getY() + bounds.getHeight(); 2151 double y4 = y3; 2152 2153 double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4}; 2154 tx.transform(points, 0, points, 0, 4); 2155 2156 double minX = points[0]; 2157 double maxX = minX; 2158 double minY = points[1]; 2159 double maxY = minY; 2160 for (int i = 0; i < 8; i++) 2161 { 2162 if (points[i] < minX) 2163 minX = points[i]; 2164 if (points[i] > maxX) 2165 maxX = points[i]; 2166 i++; 2167 2168 if (points[i] < minY) 2169 minY = points[i]; 2170 if (points[i] > maxY) 2171 maxY = points[i]; 2172 } 2173 2174 return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY)); 2175 } 2176 } 2177