View Javadoc

1   // Copyright 2006, FreeHEP
2   package org.freehep.graphicsio.test;
3   
4   /*
5    * Copyright (c) 2000 David Flanagan.  All rights reserved.
6    * This code is from the book Java Examples in a Nutshell, 2nd Edition.
7    * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
8    * You may study, use, and modify it for any non-commercial purpose.
9    * You may distribute it non-commercially as long as you retain this notice.
10   * For a commercial use license, or to purchase the book (recommended),
11   * visit http://www.davidflanagan.com/javaexamples2.
12   */
13  
14  import java.awt.BasicStroke;
15  import java.awt.Color;
16  import java.awt.Font;
17  import java.awt.Graphics;
18  import java.awt.RenderingHints;
19  import java.awt.Shape;
20  import java.awt.Stroke;
21  import java.awt.font.GlyphVector;
22  import java.awt.geom.GeneralPath;
23  import java.awt.geom.PathIterator;
24  import java.util.Random;
25  
26  import org.freehep.graphics2d.VectorGraphics;
27  
28  /**
29   * @author Mark Donszelmann
30   * @version $Id: TestCustomStrokes.java 12626 2007-06-08 22:23:13Z duns $
31   */
32  public class TestCustomStrokes extends TestingPanel {
33  
34      private Random random;
35      
36      // These are the various stroke objects we'll demonstrate
37      private Stroke[] strokes = new Stroke[] {
38              new BasicStroke(4.0f),          // The standard, predefined stroke
39              new NullStroke(),               // A Stroke that does nothing
40              new DoubleStroke(8.0f, 2.0f),   // A Stroke that strokes twice
41              new ControlPointsStroke(2.0f),  // Shows the vertices & control
42                                              // points
43              new SloppyStroke(2.0f, 3.0f)    // Perturbs the shape before
44                                              // stroking
45      };
46      
47      public TestCustomStrokes(String[] args) throws Exception {
48          super(args);
49          setName("Custom Strokes");
50          random = new Random(123456L);
51      }
52  
53      public void paintComponent(Graphics g) {
54  
55          VectorGraphics vg = VectorGraphics.create(g);
56          
57          // Get a shape to work with. Here we'll use the letter B
58          Font f = new Font("Serif", Font.BOLD, 150);
59          GlyphVector gv = f.createGlyphVector(vg.getFontRenderContext(), "B");
60          Shape shape = gv.getOutline();
61      
62          // Set drawing attributes and starting position
63          vg.setColor(Color.black);
64          vg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
65                     RenderingHints.VALUE_ANTIALIAS_ON);
66          vg.translate(10, 175);
67      
68          // Draw the shape once with each stroke
69          for(int i = 0; i < strokes.length; i++) {
70              vg.setStroke(strokes[i]);   // set the stroke
71              vg.draw(shape);             // draw the shape
72              vg.translate(100,0);        // move to the right
73          }
74          
75          vg.translate(-strokes.length*100, 0);
76          vg.translate(10, 100);
77          
78          for (int i=0; i < strokes.length; i++) { 
79              vg.setStroke(strokes[i]);
80              vg.drawRect(10, 10, 80, 50);
81              vg.translate(100, 0);
82          }
83      }
84  
85      /**
86       * This Stroke implementation does nothing. Its createStrokedShape() method
87       * returns an unmodified shape. Thus, drawing a shape with this Stroke is
88       * the same as filling that shape!
89       */
90      class NullStroke implements Stroke {
91          public Shape createStrokedShape(Shape s) { return s; }
92      }
93  
94      /**
95       * This Stroke implementation applies a BasicStroke to a shape twice. If you
96       * draw with this Stroke, then instead of outlining the shape, you're
97       * outlining the outline of the shape.
98       */
99      class DoubleStroke implements Stroke {
100         BasicStroke stroke1, stroke2;   // the two strokes to use
101         public DoubleStroke(float width1, float width2) {
102         stroke1 = new BasicStroke(width1);  // Constructor arguments specify
103         stroke2 = new BasicStroke(width2);  // the line widths for the strokes
104         }
105     
106         public Shape createStrokedShape(Shape s) {
107         // Use the first stroke to create an outline of the shape
108         Shape outline = stroke1.createStrokedShape(s);  
109         // Use the second stroke to create an outline of that outline.
110         // It is this outline of the outline that will be filled in
111         return stroke2.createStrokedShape(outline);
112         }
113     }
114 
115     /**
116      * This Stroke implementation strokes the shape using a thin line, and also
117      * displays the end points and Bezier curve control points of all the line
118      * and curve segments that make up the shape. The radius argument to the
119      * constructor specifies the size of the control point markers. Note the use
120      * of PathIterator to break the shape down into its segments, and of
121      * GeneralPath to build up the stroked shape.
122      */
123     class ControlPointsStroke implements Stroke {
124         float radius;  // how big the control point markers should be
125         public ControlPointsStroke(float radius) { this.radius = radius; }
126     
127         public Shape createStrokedShape(Shape shape) {
128         // Start off by stroking the shape with a thin line. Store the
129         // resulting shape in a GeneralPath object so we can add to it.
130         GeneralPath strokedShape = 
131             new GeneralPath(new BasicStroke(1.0f).createStrokedShape(shape));
132         
133         // Use a PathIterator object to iterate through each of the line and
134         // curve segments of the shape. For each one, mark the endpoint and
135         // control points (if any) by adding a rectangle to the GeneralPath
136         float[] coords = new float[6];
137         for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) {
138             int type = i.currentSegment(coords);
139             
140             switch(type) {
141                 case PathIterator.SEG_CUBICTO:
142                     markPoint(strokedShape, coords[4], coords[5]); // falls through
143                 case PathIterator.SEG_QUADTO:
144                     markPoint(strokedShape, coords[2], coords[3]); // falls through
145                 case PathIterator.SEG_MOVETO:
146                 case PathIterator.SEG_LINETO:
147                     markPoint(strokedShape, coords[0], coords[1]); // falls through
148                 case PathIterator.SEG_CLOSE:
149                     break;
150             }
151         }
152 
153         return strokedShape;
154         }
155     
156         /** Add a small square centered at (x,y) to the specified path */
157         void markPoint(GeneralPath path, float x, float y) {
158         path.moveTo(x-radius, y-radius);  // Begin a new sub-path
159         path.lineTo(x+radius, y-radius);  // Add a line segment to it
160         path.lineTo(x+radius, y+radius);  // Add a second line segment
161         path.lineTo(x-radius, y+radius);  // And a third
162         path.closePath();                 // Go back to last moveTo position
163         }
164     }
165 
166     /**
167      * This Stroke implementation randomly perturbs the line and curve segments
168      * that make up a Shape, and then strokes that perturbed shape. It uses
169      * PathIterator to loop through the Shape and GeneralPath to build up the
170      * modified shape. Finally, it uses a BasicStroke to stroke the modified
171      * shape. The result is a "sloppy" looking shape.
172      */
173     class SloppyStroke implements Stroke {
174         BasicStroke stroke;
175         float sloppiness;
176         
177         public SloppyStroke(float width, float sloppiness) {
178             this.stroke = new BasicStroke(width); // Used to stroke modified shape
179             this.sloppiness = sloppiness;         // How sloppy should we be?
180         }
181         
182         public Shape createStrokedShape(Shape shape) {
183             GeneralPath newshape = new GeneralPath();  // Start with an empty shape
184         
185             // Iterate through the specified shape, perturb its coordinates, and
186             // use them to build up the new shape.
187             float[] coords = new float[6];
188             for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) {
189                 int type = i.currentSegment(coords);
190                 
191                 switch(type) {
192                     case PathIterator.SEG_MOVETO:
193                         perturb(coords, 2);
194                         newshape.moveTo(coords[0], coords[1]);
195                         break;
196                     case PathIterator.SEG_LINETO:
197                         perturb(coords, 2);
198                         newshape.lineTo(coords[0], coords[1]);
199                         break;
200                     case PathIterator.SEG_QUADTO:
201                         perturb(coords, 4);
202                         newshape.quadTo(coords[0], coords[1], coords[2], coords[3]);
203                         break;
204                     case PathIterator.SEG_CUBICTO:
205                         perturb(coords, 6);
206                         newshape.curveTo(coords[0], coords[1], coords[2], coords[3],
207                                 coords[4], coords[5]);
208                         break;
209                     case PathIterator.SEG_CLOSE:
210                         newshape.closePath();
211                         break;
212                 }
213             }
214         
215             // Finally, stroke the perturbed shape and return the result
216             return stroke.createStrokedShape(newshape);
217         }
218 
219         // Randomly modify the specified number of coordinates, by an amount
220         // specified by the sloppiness field.
221         void perturb(float[] coords, int numCoords) {
222             for(int i = 0; i < numCoords; i++) {
223                 coords[i] += (float)((random.nextDouble()*2-1.0)*sloppiness);
224             }
225         }
226     } 
227        
228     public static void main(String[] args) throws Exception {
229         new TestCustomStrokes(args).runTest();
230     }
231 }