Source code: gnu/java/awt/peer/gtk/GdkGraphics2D.java
1 /* GdkGraphics2D.java --
2 Copyright (C) 2003, 2004, 2005 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 import gnu.java.awt.ClasspathToolkit;
43
44 import java.awt.AlphaComposite;
45 import java.awt.BasicStroke;
46 import java.awt.Color;
47 import java.awt.Composite;
48 import java.awt.Dimension;
49 import java.awt.Font;
50 import java.awt.FontMetrics;
51 import java.awt.GradientPaint;
52 import java.awt.Graphics;
53 import java.awt.Graphics2D;
54 import java.awt.GraphicsConfiguration;
55 import java.awt.Image;
56 import java.awt.Paint;
57 import java.awt.Rectangle;
58 import java.awt.RenderingHints;
59 import java.awt.Shape;
60 import java.awt.Stroke;
61 import java.awt.TexturePaint;
62 import java.awt.Toolkit;
63 import java.awt.font.FontRenderContext;
64 import java.awt.font.GlyphVector;
65 import java.awt.geom.AffineTransform;
66 import java.awt.geom.Arc2D;
67 import java.awt.geom.GeneralPath;
68 import java.awt.geom.NoninvertibleTransformException;
69 import java.awt.geom.PathIterator;
70 import java.awt.geom.Point2D;
71 import java.awt.geom.Rectangle2D;
72 import java.awt.image.AffineTransformOp;
73 import java.awt.image.BufferedImage;
74 import java.awt.image.BufferedImageOp;
75 import java.awt.image.ColorModel;
76 import java.awt.image.CropImageFilter;
77 import java.awt.image.DataBuffer;
78 import java.awt.image.DataBufferInt;
79 import java.awt.image.DirectColorModel;
80 import java.awt.image.FilteredImageSource;
81 import java.awt.image.ImageObserver;
82 import java.awt.image.ImagingOpException;
83 import java.awt.image.MultiPixelPackedSampleModel;
84 import java.awt.image.Raster;
85 import java.awt.image.RenderedImage;
86 import java.awt.image.SampleModel;
87 import java.awt.image.WritableRaster;
88 import java.awt.image.renderable.RenderContext;
89 import java.awt.image.renderable.RenderableImage;
90 import java.text.AttributedCharacterIterator;
91 import java.util.HashMap;
92 import java.util.Map;
93 import java.util.Stack;
94
95 public class GdkGraphics2D extends Graphics2D
96 {
97 //////////////////////////////////////
98 ////// State Management Methods //////
99 //////////////////////////////////////
100
101 static
102 {
103 if (! Configuration.GTK_CAIRO_ENABLED)
104 throw new Error("Grahics2D not implemented. "
105 + "Cairo was not found or disabled at configure time");
106
107 if (Configuration.INIT_LOAD_LIBRARY)
108 System.loadLibrary("gtkpeer");
109
110 initStaticState();
111 }
112
113 static native void initStaticState();
114
115 private final int native_state = GtkGenericPeer.getUniqueInteger();
116
117 // These are package-private to avoid accessor methods.
118 Paint paint;
119 Stroke stroke;
120 Color fg;
121 Color bg;
122 Shape clip;
123 AffineTransform transform;
124 private GtkComponentPeer component;
125 // This is package-private to avoid an accessor method.
126 Font font;
127 private RenderingHints hints;
128 private BufferedImage bimage;
129 private boolean pixelConversionRequired;
130 private int[] pixelBuffer;
131 // This is package-private to avoid an accessor method.
132 Composite comp;
133 private Stack stateStack;
134
135 private native void initStateUnlocked(GtkComponentPeer component);
136 private native void initState(GtkComponentPeer component);
137 private native void initState(int width, int height);
138 private native void initState(int[] pixes, int width, int height);
139 private native void copyState(GdkGraphics2D g);
140 public native void dispose();
141 private native void cairoSurfaceSetFilter(int filter);
142 private native void cairoSurfaceSetFilterUnlocked(int filter);
143 native void connectSignals(GtkComponentPeer component);
144
145 public void finalize()
146 {
147 dispose();
148 }
149
150 public Graphics create()
151 {
152 return new GdkGraphics2D(this);
153 }
154
155 public Graphics create(int x, int y, int width, int height)
156 {
157 return new GdkGraphics2D(width, height);
158 }
159
160 GdkGraphics2D(GdkGraphics2D g)
161 {
162 paint = g.paint;
163 stroke = g.stroke;
164 setRenderingHints(g.hints);
165
166 if (g.fg.getAlpha() != -1)
167 fg = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
168 g.fg.getAlpha());
169 else
170 fg = new Color(g.fg.getRGB());
171
172 if (g.bg.getAlpha() != -1)
173 bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
174 g.bg.getAlpha());
175 else
176 bg = new Color(g.bg.getRGB());
177
178 if (g.clip == null)
179 clip = null;
180 else
181 clip = new Rectangle(g.getClipBounds());
182
183 if (g.transform == null)
184 transform = new AffineTransform();
185 else
186 transform = new AffineTransform(g.transform);
187
188 font = g.font;
189 component = g.component;
190 copyState(g);
191
192 setColor(fg);
193 setBackground(bg);
194 setPaint(paint);
195 setStroke(stroke);
196 setTransform(transform);
197 setClip(clip);
198 stateStack = new Stack();
199 }
200
201 GdkGraphics2D(int width, int height)
202 {
203 initState(width, height);
204
205 setColor(Color.black);
206 setBackground(new Color(0, 0, 0, 0));
207 setPaint(getColor());
208 setFont(new Font("SansSerif", Font.PLAIN, 12));
209 setTransform(new AffineTransform());
210 setStroke(new BasicStroke());
211 setRenderingHints(getDefaultHints());
212
213 stateStack = new Stack();
214 }
215
216 GdkGraphics2D(GtkComponentPeer component)
217 {
218 this.component = component;
219
220 if (component.isRealized())
221 initComponentGraphics2D();
222 else
223 connectSignals(component);
224 }
225
226 void initComponentGraphics2D()
227 {
228 initState(component);
229
230 setColor(component.awtComponent.getForeground());
231 setBackground(component.awtComponent.getBackground());
232 setPaint(getColor());
233 setTransform(new AffineTransform());
234 setStroke(new BasicStroke());
235 setRenderingHints(getDefaultHints());
236 setFont(new Font("SansSerif", Font.PLAIN, 12));
237
238 stateStack = new Stack();
239 }
240
241 void initComponentGraphics2DUnlocked()
242 {
243 initStateUnlocked(component);
244
245 setColorUnlocked(component.awtComponent.getForeground());
246 setBackgroundUnlocked(component.awtComponent.getBackground());
247 setPaintUnlocked(getColorUnlocked());
248 setTransformUnlocked(new AffineTransform());
249 setStrokeUnlocked(new BasicStroke());
250 setRenderingHintsUnlocked(getDefaultHints());
251 setFontUnlocked(new Font("SansSerif", Font.PLAIN, 12));
252
253 stateStack = new Stack();
254 }
255
256 GdkGraphics2D(BufferedImage bimage)
257 {
258 this.bimage = bimage;
259 this.pixelBuffer = findSimpleIntegerArray(bimage.getColorModel(),
260 bimage.getRaster());
261 if (this.pixelBuffer == null)
262 {
263 this.pixelBuffer = new int[bimage.getRaster().getWidth() * bimage.getRaster()
264 .getHeight()];
265 this.pixelConversionRequired = true;
266 }
267 else
268 {
269 this.pixelConversionRequired = false;
270 }
271
272 initState(this.pixelBuffer, bimage.getWidth(), bimage.getHeight());
273
274 setColor(Color.black);
275 setBackground(new Color(0, 0, 0, 0));
276 setPaint(getColor());
277 setFont(new Font("SansSerif", Font.PLAIN, 12));
278 setTransform(new AffineTransform());
279 setStroke(new BasicStroke());
280 setRenderingHints(getDefaultHints());
281
282 stateStack = new Stack();
283
284 // draw current buffered image to the pixmap associated
285 // with it, if the image is not equal to our paint buffer.
286 if (pixelConversionRequired)
287 drawImage(bimage, new AffineTransform(1, 0, 0, 1, 0, 0), bg, null);
288 }
289
290 ////////////////////////////////////
291 ////// Native Drawing Methods //////
292 ////////////////////////////////////
293
294 // GDK drawing methods
295 private native void gdkDrawDrawable(GdkGraphics2D other, int x, int y);
296
297 // drawing utility methods
298 private native void drawPixels(int[] pixels, int w, int h, int stride,
299 double[] i2u);
300 private native void setTexturePixelsUnlocked(int[] pixels, int w, int h, int stride);
301 private native void setTexturePixels(int[] pixels, int w, int h, int stride);
302 private native void setGradient(double x1, double y1, double x2, double y2,
303 int r1, int g1, int b1, int a1, int r2,
304 int g2, int b2, int a2, boolean cyclic);
305 private native void setGradientUnlocked(double x1, double y1, double x2, double y2,
306 int r1, int g1, int b1, int a1, int r2,
307 int g2, int b2, int a2, boolean cyclic);
308
309 // simple passthroughs to cairo
310 private native void cairoSave();
311 private native void cairoRestore();
312 private native void cairoSetMatrix(double[] m);
313 private native void cairoSetMatrixUnlocked(double[] m);
314 private native void cairoSetOperator(int cairoOperator);
315 private native void cairoSetRGBAColor(double red, double green,
316 double blue, double alpha);
317 private native void cairoSetRGBAColorUnlocked(double red, double green,
318 double blue, double alpha);
319 private native void cairoSetFillRule(int cairoFillRule);
320 private native void cairoSetLineWidth(double width);
321 private native void cairoSetLineWidthUnlocked(double width);
322 private native void cairoSetLineCap(int cairoLineCap);
323 private native void cairoSetLineCapUnlocked(int cairoLineCap);
324 private native void cairoSetLineJoin(int cairoLineJoin);
325 private native void cairoSetLineJoinUnlocked(int cairoLineJoin);
326 private native void cairoSetDash(double[] dashes, int ndash, double offset);
327 private native void cairoSetDashUnlocked(double[] dashes, int ndash, double offset);
328
329 private native void cairoSetMiterLimit(double limit);
330 private native void cairoSetMiterLimitUnlocked(double limit);
331 private native void cairoNewPath();
332 private native void cairoMoveTo(double x, double y);
333 private native void cairoLineTo(double x, double y);
334 private native void cairoCurveTo(double x1, double y1, double x2, double y2,
335 double x3, double y3);
336 private native void cairoRelMoveTo(double dx, double dy);
337 private native void cairoRelLineTo(double dx, double dy);
338 private native void cairoRelCurveTo(double dx1, double dy1, double dx2,
339 double dy2, double dx3, double dy3);
340 private native void cairoRectangle(double x, double y, double width,
341 double height);
342 private native void cairoClosePath();
343 private native void cairoStroke();
344 private native void cairoFill();
345 private native void cairoClip();
346
347 /////////////////////////////////////////////
348 ////// General Drawing Support Methods //////
349 /////////////////////////////////////////////
350
351 private class DrawState
352 {
353 private Paint paint;
354 private Stroke stroke;
355 private Color fg;
356 private Color bg;
357 private Shape clip;
358 private AffineTransform transform;
359 private Font font;
360 private Composite comp;
361
362 DrawState(GdkGraphics2D g)
363 {
364 this.paint = g.paint;
365 this.stroke = g.stroke;
366 this.fg = g.fg;
367 this.bg = g.bg;
368 this.clip = g.clip;
369 if (g.transform != null)
370 this.transform = (AffineTransform) g.transform.clone();
371 this.font = g.font;
372 this.comp = g.comp;
373 }
374
375 public void restore(GdkGraphics2D g)
376 {
377 g.paint = this.paint;
378 g.stroke = this.stroke;
379 g.fg = this.fg;
380 g.bg = this.bg;
381 g.clip = this.clip;
382 g.transform = this.transform;
383 g.font = this.font;
384 g.comp = this.comp;
385 }
386 }
387
388 private void stateSave()
389 {
390 stateStack.push(new DrawState(this));
391 cairoSave();
392 }
393
394 private void stateRestore()
395 {
396 ((DrawState) (stateStack.pop())).restore(this);
397 cairoRestore();
398 }
399
400 // Some operations (drawing rather than filling) require that their
401 // coords be shifted to land on 0.5-pixel boundaries, in order to land on
402 // "middle of pixel" coordinates and light up complete pixels.
403 private boolean shiftDrawCalls = false;
404
405 private double shifted(double coord, boolean doShift)
406 {
407 if (doShift)
408 return Math.floor(coord) + 0.5;
409 else
410 return coord;
411 }
412
413 private void walkPath(PathIterator p, boolean doShift)
414 {
415 double x = 0;
416 double y = 0;
417 double[] coords = new double[6];
418
419 cairoSetFillRule(p.getWindingRule());
420 for (; ! p.isDone(); p.next())
421 {
422 int seg = p.currentSegment(coords);
423 switch (seg)
424 {
425 case PathIterator.SEG_MOVETO:
426 x = shifted(coords[0], doShift);
427 y = shifted(coords[1], doShift);
428 cairoMoveTo(x, y);
429 break;
430 case PathIterator.SEG_LINETO:
431 x = shifted(coords[0], doShift);
432 y = shifted(coords[1], doShift);
433 cairoLineTo(x, y);
434 break;
435 case PathIterator.SEG_QUADTO:
436 // splitting a quadratic bezier into a cubic:
437 // see: http://pfaedit.sourceforge.net/bezier.html
438 double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
439 double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
440
441 double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
442 double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
443
444 x = shifted(coords[2], doShift);
445 y = shifted(coords[3], doShift);
446 cairoCurveTo(x1, y1, x2, y2, x, y);
447 break;
448 case PathIterator.SEG_CUBICTO:
449 x = shifted(coords[4], doShift);
450 y = shifted(coords[5], doShift);
451 cairoCurveTo(shifted(coords[0], doShift),
452 shifted(coords[1], doShift),
453 shifted(coords[2], doShift),
454 shifted(coords[3], doShift), x, y);
455 break;
456 case PathIterator.SEG_CLOSE:
457 cairoClosePath();
458 break;
459 }
460 }
461 }
462
463 private Map getDefaultHints()
464 {
465 HashMap defaultHints = new HashMap();
466
467 defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
468 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
469
470 defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
471 RenderingHints.VALUE_STROKE_DEFAULT);
472
473 defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
474 RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
475
476 defaultHints.put(RenderingHints.KEY_ANTIALIASING,
477 RenderingHints.VALUE_ANTIALIAS_OFF);
478
479 defaultHints.put(RenderingHints.KEY_RENDERING,
480 RenderingHints.VALUE_RENDER_DEFAULT);
481
482 return defaultHints;
483 }
484
485 public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
486 {
487 if (cm == null || raster == null)
488 return null;
489
490 if (! cm.getColorSpace().isCS_sRGB())
491 return null;
492
493 if (! (cm instanceof DirectColorModel))
494 return null;
495
496 DirectColorModel dcm = (DirectColorModel) cm;
497
498 if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
499 || dcm.getBlueMask() != 0x000000FF)
500 return null;
501
502 if (! (raster instanceof WritableRaster))
503 return null;
504
505 if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
506 return null;
507
508 if (! (raster.getDataBuffer() instanceof DataBufferInt))
509 return null;
510
511 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
512
513 if (db.getNumBanks() != 1)
514 return null;
515
516 // Finally, we have determined that this is a single bank, [A]RGB-int
517 // buffer in sRGB space. It's worth checking all this, because it means
518 // that cairo can paint directly into the data buffer, which is very
519 // fast compared to all the normal copying and converting.
520
521 return db.getData();
522 }
523
524 private void updateBufferedImage()
525 {
526 if (bimage != null && pixelConversionRequired)
527 {
528 int height = bimage.getHeight();
529 int width = bimage.getWidth();
530 int index = 0;
531 for (int y = 0; y < height; ++y)
532 for (int x = 0; x < width; ++x)
533 bimage.setRGB(x, y, pixelBuffer[index++]);
534 }
535 }
536
537 private boolean drawImage(Image img, AffineTransform xform,
538 Color bgcolor, ImageObserver obs)
539 {
540 if (img == null)
541 return false;
542
543 // FIXME: I'll fix this, /Sven
544 // if (img instanceof GtkOffScreenImage
545 // && img.getGraphics() instanceof GdkGraphics2D
546 // && (xform == null || xform.getType() == AffineTransform.TYPE_IDENTITY
547 // || xform.getType() == AffineTransform.TYPE_TRANSLATION))
548 // {
549 // // we are being asked to flush a double buffer from Gdk
550 // GdkGraphics2D g2 = (GdkGraphics2D) img.getGraphics();
551 // gdkDrawDrawable(g2, (int) xform.getTranslateX(),
552 // (int) xform.getTranslateY());
553
554 // updateBufferedImage();
555
556 // return true;
557 // }
558 // else
559 {
560 // In this case, xform is an AffineTransform that transforms bounding
561 // box of the specified image from image space to user space. However
562 // when we pass this transform to cairo, cairo will use this transform
563 // to map "user coordinates" to "pixel" coordinates, which is the
564 // other way around. Therefore to get the "user -> pixel" transform
565 // that cairo wants from "image -> user" transform that we currently
566 // have, we will need to invert the transformation matrix.
567 AffineTransform invertedXform = new AffineTransform();
568
569 try
570 {
571 invertedXform = xform.createInverse();
572 if (img instanceof BufferedImage)
573 {
574 // draw an image which has actually been loaded
575 // into memory fully
576 BufferedImage b = (BufferedImage) img;
577 return drawRaster(b.getColorModel(), b.getTile(0, 0),
578 invertedXform, bgcolor);
579 }
580 else
581 return this.drawImage(GdkPixbufDecoder.createBufferedImage(img
582 .getSource()),
583 xform, bgcolor, obs);
584 }
585 catch (NoninvertibleTransformException e)
586 {
587 throw new ImagingOpException("Unable to invert transform "
588 + xform.toString());
589 }
590 }
591 }
592
593 //////////////////////////////////////////////////
594 ////// Implementation of Graphics2D Methods //////
595 //////////////////////////////////////////////////
596
597 public void draw(Shape s)
598 {
599 if (stroke != null && ! (stroke instanceof BasicStroke))
600 {
601 fill(stroke.createStrokedShape(s));
602 return;
603 }
604
605 cairoNewPath();
606
607 if (s instanceof Rectangle2D)
608 {
609 Rectangle2D r = (Rectangle2D) s;
610 cairoRectangle(shifted(r.getX(), shiftDrawCalls),
611 shifted(r.getY(), shiftDrawCalls), r.getWidth(),
612 r.getHeight());
613 }
614 else
615 walkPath(s.getPathIterator(null), shiftDrawCalls);
616 cairoStroke();
617
618 updateBufferedImage();
619 }
620
621 public void fill(Shape s)
622 {
623 cairoNewPath();
624 if (s instanceof Rectangle2D)
625 {
626 Rectangle2D r = (Rectangle2D) s;
627 cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
628 }
629 else
630 walkPath(s.getPathIterator(null), false);
631
632 cairoFill();
633
634 updateBufferedImage();
635 }
636
637 public void clip(Shape s)
638 {
639 // update it
640 if (clip == null || s == null)
641 clip = s;
642 else if (s instanceof Rectangle2D && clip instanceof Rectangle2D)
643 {
644 Rectangle2D r = (Rectangle2D) s;
645 Rectangle2D curr = (Rectangle2D) clip;
646 clip = curr.createIntersection(r);
647 }
648 else
649 throw new UnsupportedOperationException();
650
651 // draw it
652 if (clip != null)
653 {
654 cairoNewPath();
655 if (clip instanceof Rectangle2D)
656 {
657 Rectangle2D r = (Rectangle2D) clip;
658 cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
659 }
660 else
661 walkPath(clip.getPathIterator(null), false);
662
663 // cairoClosePath ();
664 cairoClip();
665 }
666 }
667
668 public Paint getPaint()
669 {
670 return paint;
671 }
672
673 public AffineTransform getTransform()
674 {
675 return (AffineTransform) transform.clone();
676 }
677
678 public void setPaint(Paint p)
679 {
680 if (paint == null)
681 return;
682
683 paint = p;
684 if (paint instanceof Color)
685 {
686 setColor((Color) paint);
687 }
688 else if (paint instanceof TexturePaint)
689 {
690 TexturePaint tp = (TexturePaint) paint;
691 BufferedImage img = tp.getImage();
692
693 // map the image to the anchor rectangle
694 int width = (int) tp.getAnchorRect().getWidth();
695 int height = (int) tp.getAnchorRect().getHeight();
696
697 double scaleX = width / (double) img.getWidth();
698 double scaleY = width / (double) img.getHeight();
699
700 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
701 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
702 BufferedImage texture = op.filter(img, null);
703 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
704 setTexturePixels(pixels, width, height, width);
705 }
706 else if (paint instanceof GradientPaint)
707 {
708 GradientPaint gp = (GradientPaint) paint;
709 Point2D p1 = gp.getPoint1();
710 Point2D p2 = gp.getPoint2();
711 Color c1 = gp.getColor1();
712 Color c2 = gp.getColor2();
713 setGradient(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
714 c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
715 c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
716 }
717 else
718 throw new java.lang.UnsupportedOperationException();
719 }
720
721 public void setPaintUnlocked(Paint p)
722 {
723 if (paint == null)
724 return;
725
726 paint = p;
727 if (paint instanceof Color)
728 {
729 setColorUnlocked((Color) paint);
730 }
731 else if (paint instanceof TexturePaint)
732 {
733 TexturePaint tp = (TexturePaint) paint;
734 BufferedImage img = tp.getImage();
735
736 // map the image to the anchor rectangle
737 int width = (int) tp.getAnchorRect().getWidth();
738 int height = (int) tp.getAnchorRect().getHeight();
739
740 double scaleX = width / (double) img.getWidth();
741 double scaleY = width / (double) img.getHeight();
742
743 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
744 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
745 BufferedImage texture = op.filter(img, null);
746 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
747 setTexturePixelsUnlocked(pixels, width, height, width);
748 }
749 else if (paint instanceof GradientPaint)
750 {
751 GradientPaint gp = (GradientPaint) paint;
752 Point2D p1 = gp.getPoint1();
753 Point2D p2 = gp.getPoint2();
754 Color c1 = gp.getColor1();
755 Color c2 = gp.getColor2();
756 setGradientUnlocked(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
757 c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
758 c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
759 }
760 else
761 throw new java.lang.UnsupportedOperationException();
762 }
763
764 public void setTransform(AffineTransform tx)
765 {
766 transform = tx;
767 if (transform != null)
768 {
769 double[] m = new double[6];
770 transform.getMatrix(m);
771 cairoSetMatrix(m);
772 }
773 }
774
775 public void setTransformUnlocked(AffineTransform tx)
776 {
777 transform = tx;
778 if (transform != null)
779 {
780 double[] m = new double[6];
781 transform.getMatrix(m);
782 cairoSetMatrixUnlocked(m);
783 }
784 }
785
786 public void transform(AffineTransform tx)
787 {
788 if (transform == null)
789 transform = new AffineTransform(tx);
790 else
791 transform.concatenate(tx);
792 setTransform(transform);
793 if (clip != null)
794 {
795 // FIXME: this should actuall try to transform the shape
796 // rather than degrade to bounds.
797 Rectangle2D r = clip.getBounds2D();
798 double[] coords = new double[]
799 {
800 r.getX(), r.getY(), r.getX() + r.getWidth(),
801 r.getY() + r.getHeight()
802 };
803 try
804 {
805 tx.createInverse().transform(coords, 0, coords, 0, 2);
806 r.setRect(coords[0], coords[1], coords[2] - coords[0],
807 coords[3] - coords[1]);
808 clip = r;
809 }
810 catch (java.awt.geom.NoninvertibleTransformException e)
811 {
812 }
813 }
814 }
815
816 public void rotate(double theta)
817 {
818 transform(AffineTransform.getRotateInstance(theta));
819 }
820
821 public void rotate(double theta, double x, double y)
822 {
823 transform(AffineTransform.getRotateInstance(theta, x, y));
824 }
825
826 public void scale(double sx, double sy)
827 {
828 transform(AffineTransform.getScaleInstance(sx, sy));
829 }
830
831 public void translate(double tx, double ty)
832 {
833 transform(AffineTransform.getTranslateInstance(tx, ty));
834 }
835
836 public void translate(int x, int y)
837 {
838 translate((double) x, (double) y);
839 }
840
841 public void shear(double shearX, double shearY)
842 {
843 transform(AffineTransform.getShearInstance(shearX, shearY));
844 }
845
846 public Stroke getStroke()
847 {
848 return stroke;
849 }
850
851 public void setStroke(Stroke st)
852 {
853 stroke = st;
854 if (stroke instanceof BasicStroke)
855 {
856 BasicStroke bs = (BasicStroke) stroke;
857 cairoSetLineCap(bs.getEndCap());
858 cairoSetLineWidth(bs.getLineWidth());
859 cairoSetLineJoin(bs.getLineJoin());
860 cairoSetMiterLimit(bs.getMiterLimit());
861 float[] dashes = bs.getDashArray();
862 if (dashes != null)
863 {
864 double[] double_dashes = new double[dashes.length];
865 for (int i = 0; i < dashes.length; i++)
866 double_dashes[i] = dashes[i];
867 cairoSetDash(double_dashes, double_dashes.length,
868 (double) bs.getDashPhase());
869 }
870 else
871 cairoSetDash(new double[0], 0, 0.0);
872 }
873 }
874
875 public void setStrokeUnlocked(Stroke st)
876 {
877 stroke = st;
878 if (stroke instanceof BasicStroke)
879 {
880 BasicStroke bs = (BasicStroke) stroke;
881 cairoSetLineCapUnlocked(bs.getEndCap());
882 cairoSetLineWidthUnlocked(bs.getLineWidth());
883 cairoSetLineJoinUnlocked(bs.getLineJoin());
884 cairoSetMiterLimitUnlocked(bs.getMiterLimit());
885 float[] dashes = bs.getDashArray();
886 if (dashes != null)
887 {
888 double[] double_dashes = new double[dashes.length];
889 for (int i = 0; i < dashes.length; i++)
890 double_dashes[i] = dashes[i];
891 cairoSetDashUnlocked(double_dashes, double_dashes.length,
892 (double) bs.getDashPhase());
893 }
894 else
895 cairoSetDashUnlocked(new double[0], 0, 0.0);
896 }
897 }
898
899 ////////////////////////////////////////////////
900 ////// Implementation of Graphics Methods //////
901 ////////////////////////////////////////////////
902
903 public void setPaintMode()
904 {
905 setComposite(java.awt.AlphaComposite.SrcOver);
906 }
907
908 public void setXORMode(Color c)
909 {
910 setComposite(new gnu.java.awt.BitwiseXORComposite(c));
911 }
912
913 public void setColor(Color c)
914 {
915 if (c == null)
916 c = Color.BLACK;
917
918 fg = c;
919 paint = c;
920 cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0,
921 fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
922 }
923
924 public void setColorUnlocked(Color c)
925 {
926 if (c == null)
927 c = Color.BLACK;
928
929 fg = c;
930 paint = c;
931 cairoSetRGBAColorUnlocked(fg.getRed() / 255.0, fg.getGreen() / 255.0,
932 fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
933 }
934
935 public Color getColor()
936 {
937 return fg;
938 }
939
940 public Color getColorUnlocked()
941 {
942 return getColor();
943 }
944
945 public void clipRect(int x, int y, int width, int height)
946 {
947 clip(new Rectangle(x, y, width, height));
948 }
949
950 public Shape getClip()
951 {
952 return clip.getBounds2D(); //getClipInDevSpace();
953 }
954
955 public Rectangle getClipBounds()
956 {
957 if (clip == null)
958 return null;
959 else
960 return clip.getBounds();
961 }
962
963 protected Rectangle2D getClipInDevSpace()
964 {
965 Rectangle2D uclip = clip.getBounds2D();
966 if (transform == null)
967 return uclip;
968 else
969 {
970 Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
971 uclip.getY()),
972 (Point2D) null);
973 Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
974 .getWidth(),
975 uclip
976 .getHeight()),
977 (Point2D) null);
978 return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
979 extent.getY());
980 }
981 }
982
983 public void setClip(int x, int y, int width, int height)
984 {
985 setClip(new Rectangle2D.Double((double) x, (double) y, (double) width,
986 (double) height));
987 }
988
989 public void setClip(Shape s)
990 {
991 clip = s;
992 if (clip == null)
993 {
994 // Reset clipping.
995 Dimension d = component.awtComponent.getSize();
996 setClip(0, 0, d.width, d.height);
997 }
998 else
999 {
1000 cairoNewPath();
1001 if (s instanceof Rectangle2D)
1002 {
1003 Rectangle2D r = (Rectangle2D) s;
1004 cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
1005 }
1006 else
1007 walkPath(s.getPathIterator(null), false);
1008
1009 // cairoClosePath ();
1010 cairoClip();
1011 }
1012 }
1013
1014 private static BasicStroke draw3DRectStroke = new BasicStroke();
1015
1016 public void draw3DRect(int x, int y, int width, int height, boolean raised)
1017 {
1018 Stroke tmp = stroke;
1019 setStroke(draw3DRectStroke);
1020 super.draw3DRect(x, y, width, height, raised);
1021 setStroke(tmp);
1022 updateBufferedImage();
1023 }
1024
1025 public void fill3DRect(int x, int y, int width, int height, boolean raised)
1026 {
1027 Stroke tmp = stroke;
1028 setStroke(draw3DRectStroke);
1029 super.fill3DRect(x, y, width, height, raised);
1030 setStroke(tmp);
1031 updateBufferedImage();
1032 }
1033
1034 public void drawRect(int x, int y, int width, int height)
1035 {
1036 draw(new Rectangle(x, y, width, height));
1037 }
1038
1039 public void fillRect(int x, int y, int width, int height)
1040 {
1041 cairoNewPath();
1042 cairoRectangle(x, y, width, height);
1043 cairoFill();
1044 }
1045
1046 public void clearRect(int x, int y, int width, int height)
1047 {
1048 cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0,
1049 bg.getBlue() / 255.0, 1.0);
1050 cairoNewPath();
1051 cairoRectangle(x, y, width, height);
1052 cairoFill();
1053 setColor(fg);
1054
1055 updateBufferedImage();
1056 }
1057
1058 public void setBackground(Color c)
1059 {
1060 bg = c;
1061 }
1062
1063 public void setBackgroundUnlocked(Color c)
1064 {
1065 setBackground(c);
1066 }
1067
1068 public Color getBackground()
1069 {
1070 return bg;
1071 }
1072
1073 private void doPolygon(int[] xPoints, int[] yPoints, int nPoints,
1074 boolean close, boolean fill)
1075 {
1076 if (nPoints < 1)
1077 return;
1078 GeneralPath gp = new GeneralPath(PathIterator.WIND_EVEN_ODD);
1079 gp.moveTo((float) xPoints[0], (float) yPoints[0]);
1080 for (int i = 1; i < nPoints; i++)
1081 gp.lineTo((float) xPoints[i], (float) yPoints[i]);
1082
1083 if (close)
1084 gp.closePath();
1085
1086 Shape sh = gp;
1087 if (fill == false && stroke != null && ! (stroke instanceof BasicStroke))
1088 {
1089 sh = stroke.createStrokedShape(gp);
1090 fill = true;
1091 }
1092
1093 if (fill)
1094 fill(sh);
1095 else
1096 draw(sh);
1097 }
1098
1099 public void drawLine(int x1, int y1, int x2, int y2)
1100 {
1101 int[] xp = new int[2];
1102 int[] yp = new int[2];
1103
1104 xp[0] = x1;
1105 xp[1] = x2;
1106 yp[0] = y1;
1107 yp[1] = y2;
1108
1109 doPolygon(xp, yp, 2, false, false);
1110 }
1111
1112 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1113 {
1114 doPolygon(xPoints, yPoints, nPoints, true, true);
1115 }
1116
1117 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1118 {
1119 doPolygon(xPoints, yPoints, nPoints, true, false);
1120 }
1121
1122 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1123 {
1124 doPolygon(xPoints, yPoints, nPoints, false, false);
1125 }
1126
1127 private boolean drawRaster(ColorModel cm, Raster r,
1128 AffineTransform imageToUser, Color bgcolor)
1129 {
1130 if (r == null)
1131 return false;
1132
1133 SampleModel sm = r.getSampleModel();
1134 DataBuffer db = r.getDataBuffer();
1135
1136 if (db == null || sm == null)
1137 return false;
1138
1139 if (cm == null)
1140 cm = ColorModel.getRGBdefault();
1141
1142 double[] i2u = new double[6];
1143 if (imageToUser != null)
1144 imageToUser.getMatrix(i2u);
1145 else
1146 {
1147 i2u[0] = 1;
1148 i2u[1] = 0;
1149 i2u[2] = 0;
1150 i2u[3] = 1;
1151 i2u[4] = 0;
1152 i2u[5] = 0;
1153 }
1154
1155 int[] pixels = findSimpleIntegerArray(cm, r);
1156
1157 if (pixels == null)
1158 {
1159 // FIXME: I don't think this code will work correctly with a non-RGB
1160 // MultiPixelPackedSampleModel. Although this entire method should
1161 // probably be rewritten to better utilize Cairo's different supported
1162 // data formats.
1163 if (sm instanceof MultiPixelPackedSampleModel)
1164 {
1165 pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1166 for (int i = 0; i < pixels.length; i++)
1167 pixels[i] = cm.getRGB(pixels[i]);
1168 }
1169 else
1170 {
1171 pixels = new int[r.getWidth() * r.getHeight()];
1172 for (int i = 0; i < pixels.length; i++)
1173 pixels[i] = cm.getRGB(db.getElem(i));
1174 }
1175 }
1176
1177 // Change all transparent pixels in the image to the specified bgcolor,
1178 // or (if there's no alpha) fill in an alpha channel so that it paints
1179 // correctly.
1180 if (cm.hasAlpha())
1181 {
1182 if (bgcolor != null && cm.hasAlpha())
1183 for (int i = 0; i < pixels.length; i++)
1184 {
1185 if (cm.getAlpha(pixels[i]) == 0)
1186 pixels[i] = bgcolor.getRGB();
1187 }
1188 }
1189 else
1190 for (int i = 0; i < pixels.length; i++)
1191 pixels[i] |= 0xFF000000;
1192
1193 drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
1194
1195 updateBufferedImage();
1196
1197 return true;
1198 }
1199
1200 public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1201 {
1202 drawRaster(image.getColorModel(), image.getData(), xform, bg);
1203 }
1204
1205 public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1206 {
1207 drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1208 }
1209
1210 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1211 {
1212 return drawImage(img, xform, bg, obs);
1213 }
1214
1215 public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1216 {
1217 Image filtered = op.filter(image, null);
1218 drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg, null);
1219 }
1220
1221 public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1222 {
1223 return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg,
1224 observer);
1225 }
1226
1227 ///////////////////////////////////////////////
1228 ////// Unimplemented Stubs and Overloads //////
1229 ///////////////////////////////////////////////
1230
1231 public boolean hit(Rectangle rect, Shape text, boolean onStroke)
1232 {
1233 throw new java.lang.UnsupportedOperationException();
1234 }
1235
1236 public GraphicsConfiguration getDeviceConfiguration()
1237 {
1238 throw new java.lang.UnsupportedOperationException();
1239 }
1240
1241 public void setComposite(Composite comp)
1242 {
1243 this.comp = comp;
1244
1245 if (comp instanceof AlphaComposite)
1246 {
1247 AlphaComposite a = (AlphaComposite) comp;
1248 cairoSetOperator(a.getRule());
1249 Color c = getColor();
1250 setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(),
1251 (int) (a.getAlpha() * ((float) c.getAlpha()))));
1252 }
1253 else
1254 throw new java.lang.UnsupportedOperationException();
1255 }
1256
1257 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1258 {
1259 hints.put(hintKey, hintValue);
1260
1261 if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1262 || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1263 {
1264 if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1265 cairoSurfaceSetFilter(0);
1266
1267 else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1268 cairoSurfaceSetFilter(1);
1269
1270 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1271 cairoSurfaceSetFilter(2);
1272
1273 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1274 cairoSurfaceSetFilter(3);
1275
1276 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1277 cairoSurfaceSetFilter(4);
1278 }
1279
1280 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1281 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1282 }
1283
1284 public Object getRenderingHint(RenderingHints.Key hintKey)
1285 {
1286 return hints.get(hintKey);
1287 }
1288
1289 public void setRenderingHints(Map hints)
1290 {
1291 this.hints = new RenderingHints(getDefaultHints());
1292 this.hints.add(new