Described Static Visual Content
An Introduction with Examples in Java |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
package visual.statik.described; import java.awt.*; import java.awt.geom.*; import java.util.Random; import javax.swing.*; /** * A JComponent that renders a Rectangle with randomly * generated attributes each time it's paint method is called * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class RandomRectangleCanvas extends JComponent { private Random generator; /** * Default Constructor */ public RandomRectangleCanvas() { super(); generator = new Random(System.currentTimeMillis()); } /** * Paint this Component * * @param g The rendering engine to use */ public void paint(Graphics g) { Graphics2D g2; int height, maxHeight, maxWidth, width, x, y; Rectangle rectangle; g2 = (Graphics2D)g; maxHeight = getHeight(); maxWidth = getWidth(); x = generator.nextInt(maxWidth - 1); y = generator.nextInt(maxHeight - 1); width = generator.nextInt(maxWidth - x - 1); height = generator.nextInt(maxHeight - y - 1); rectangle = new Rectangle(x, y, width, height); g2.draw(rectangle); } }
\(x(u) = p_x + u q_x - u p_x = (1 - u) p_x + u q_x\)
\(y(u) = p_y + u q_y - u p_y = (1 - u) p_y + u q_y\)
// Construct a quadratic curve quadraticCurve = new QuadCurve2D.Double( 120.0, 120.0, // End 1 300.0, 180.0, // Control 130.0, 190.0); // End 2
// Construct a cubic curve cubicCurve = new CubicCurve2D.Double( 320.0, 320.0, // End 1 300.0, 180.0, // Control 1 330.0, 370.0, // End 2 360.0, 390.0); // Control 2
// Construct a rectangle rectangle = new Rectangle2D.Double( 10.0, 20.0, // Upper Left 30.0, // Width 60.0); // Height
// Construct an arc arc = new Arc2D.Double(10.0, 200.0, // Upper Left 150.0, // Width 100.0, // Height 0.0, // Starting Angle 135.0, // Angular Extent Arc2D.PIE); // Type
f and i as two glyphs and as a ligature
The RSB is positive for A and negative for f
Kerning - The Use of Different Bearings for Differnt Glyphs
Line Metrics
package visual.statik.described; import java.awt.*; import java.awt.font.*; import javax.swing.*; import app.*; /** * A JComponent that displays a GlyphVector * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class GlyphCanvas extends JComponent { protected Font font; protected String text; /** * Default Constructor */ public GlyphCanvas() { super(); // Construct a (logical) font font = new Font("Serif", Font.ITALIC, 100); } /** * Paint this component * * @param g The Graphics context to use */ public void paint(Graphics g) { Graphics2D g2; g2 = (Graphics2D)g; // Set the font g2.setFont(font); // Use antialiasing for text and shapes g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Use high-quality rendering g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Use floating point for font metrics g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); // Render the glyphs if (text != null) paintGlyphs(g2, text); } /** * Render a String */ protected void paintGlyphs(Graphics2D g2, String text) { FontRenderContext frc; GlyphVector glyphs; Shape shape; frc = g2.getFontRenderContext(); glyphs = font.createGlyphVector(frc, text); shape = glyphs.getOutline(0.0f, 100.0f); g2.setColor(Color.BLACK); g2.draw(shape); } /** * Set the text associated with this component */ public void setText(String text) { this.text = text; } }
package visual.statik.described; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import javax.swing.*; /** * A VisualComponent that displays a GlyphVector and LineMetrics * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class GlyphMetricsCanvas extends GlyphCanvas { /** * Default Constructor */ public GlyphMetricsCanvas() { super(); // Construct a (physical) font font = new Font("Default", Font.PLAIN, 100); } /** * Render the glyphs and the metrics * * Note: There are better approaches. This method * is used for illustrative purposes only. */ protected void paintGlyphs(Graphics2D g2, String text) { FontRenderContext frc; GlyphVector glyphs; frc = g2.getFontRenderContext(); glyphs = font.createGlyphVector(frc, text); LineMetrics lm; lm = font.getLineMetrics(text, frc); float ascent, descent, height, leading; // Get the various metrics ascent = lm.getAscent(); descent = lm.getDescent(); height = lm.getHeight(); leading = lm.getLeading(); Dimension d; float baseline, pWidth, y; Rectangle2D bounds; Shape shape; d = getSize(); g2.setColor(Color.BLACK); baseline = (float)(d.height)/2.0f; pWidth = (float)(d.width); // Draw the metrics y = baseline - ascent; g2.setColor(Color.BLUE); g2.draw(new Line2D.Float(0.0f, y, pWidth, y)); y = baseline; g2.setColor(Color.WHITE); g2.draw(new Line2D.Float(0.0f, y, pWidth, y)); g2.setColor(Color.RED); y = baseline + descent; g2.draw(new Line2D.Float(0.0f, y, pWidth, y)); if (leading > 0) { y = baseline + descent + leading; g2.setColor(Color.GREEN); g2.draw(new Line2D.Float(0.0f, y, pWidth, y)); } // Get the outline at (0,baseline) shape = glyphs.getOutline(0, baseline); g2.setColor(Color.BLACK); g2.draw(shape); g2.fill(shape); } }
package visual.statik.described; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import javax.swing.*; /** * A panel that displays a GlyphVector centered * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class CenteredGlyphCanvas extends GlyphCanvas { /** * Default Constructor */ public CenteredGlyphCanvas() { super(); } /** * Render the glyphs centered * * Note: There are better approaches. This method * is used for illustrative purposes only. */ protected void paintGlyphs(Graphics2D g2, String text) { FontRenderContext frc; GlyphVector glyphs; Rectangle2D bounds; Shape shape; frc = g2.getFontRenderContext(); glyphs = font.createGlyphVector(frc, text); g2.setColor(Color.black); // Get the outline at (0,0) shape = glyphs.getOutline(); // Get the bounding box bounds = glyphs.getVisualBounds(); Dimension d; float x, y; d = getSize(); // Center the text x = (float)(d.width/2-bounds.getWidth()/2); y = (float)(d.height/2+bounds.getHeight()/2); // Get the outline when centered shape = glyphs.getOutline(x,y); g2.draw(shape); } }
package visual.statik.described; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import java.text.*; import javax.swing.*; /** * A panel that displays an AttributedString * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class AttributedStringCanvas extends GlyphCanvas { /** * Default Constructor */ public AttributedStringCanvas() { super(); } /** * Paint this component * * @param g The Graphics context to use */ public void paint(Graphics g) { Graphics2D g2; super.paint(g); g2 = (Graphics2D)g; // Use antialiasing for text and shapes g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Use high-quality rendering g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Use floating point for font metrics g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); // Render the glyphs if (text != null) paintGlyphs(g2, text); } /** * Render the text using attributes */ protected void paintGlyphs(Graphics2D g2, String text) { AttributedString as; as = new AttributedString(text); for (int i=0; i < text.length(); i++) { as.addAttribute(TextAttribute.FONT, new Font("Serif", Font.PLAIN, (i+10)), i, i+1); } g2.setColor(Color.BLACK); g2.drawString(as.getIterator(), 0, 100); } }
bodyShape = new Path2D.Float(); bodyShape.moveTo( 20, 50); bodyShape.lineTo( 20, 70); bodyShape.lineTo( 20, 90); bodyShape.lineTo( 10, 90); bodyShape.lineTo( 10,100); bodyShape.lineTo( 80,100); bodyShape.lineTo( 80, 90); bodyShape.lineTo( 40, 90); bodyShape.lineTo( 40, 70); bodyShape.lineTo( 40, 50); bodyShape.closePath();
package visual.statik.described; import java.awt.*; import java.awt.geom.*; import javax.swing.*; /** * A JPanel that performs constructive area geometryt operations * on an Ellipse2D and a Rectangle2D * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class ConstructiveAreaGeometryCanvas extends JComponent { private Ellipse2D ellipse; private Rectangle2D rectangle; private String op; private static final Color GOLD =new Color(194,161, 77); private static final Color PURPLE=new Color( 69, 0,132); /** * Default Constructor */ public ConstructiveAreaGeometryCanvas() { ellipse = new Ellipse2D.Double(20, 20, 100, 100); rectangle = new Rectangle2D.Double(60, 40, 200, 200); op = "Outline"; } /** * Paint this Component * * @param g The rendering engine to use */ public void paint(Graphics g) { Area e, r; Graphics2D g2; g2 = (Graphics2D)g; if (op.equalsIgnoreCase("Outline")) { g2.setPaint(Color.BLACK); g2.draw(ellipse); g2.draw(rectangle); } else { e = new Area(ellipse); r = new Area(rectangle); if (op.equalsIgnoreCase("Union")) e.add(r); else if (op.equalsIgnoreCase("Intersection")) e.intersect(r); else if (op.equalsIgnoreCase("Difference")) e.subtract(r); else e.exclusiveOr(r); g2.setPaint(GOLD); g2.fill(e); g2.setPaint(PURPLE); g2.draw(e); } } /** * Set the CAG operation (Outline, Union, Intersection, * Difference, or Symmetric_Difference) * * @op The operation */ public void setOperation(String op) { this.op = op; } }
Translation
Two Scalings
Reflection
Rotation
package visual.statik.described; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import javax.swing.*; /** * A panel that displays a GlyphVector * in a spiral * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class SpiralGlyphCanvas extends GlyphCanvas { /** * Constructor */ public SpiralGlyphCanvas() { super(); } /** * Render the glyphs */ protected void paintGlyphs(Graphics2D g2, String text) { AffineTransform at, trans; AlphaComposite alpha; Dimension d; float angle, x, y; FontRenderContext frc; GlyphVector glyphs; int i; Shape shape, transformedShape; d = getSize(); frc = g2.getFontRenderContext(); glyphs = font.createGlyphVector(frc, text); shape = glyphs.getOutline(0.0f, 100.0f); g2.setColor(Color.BLACK); for (i=0; i < 6; i++) { angle = (float)(Math.PI/6.0 * i); x = (float)(d.width/2.0); y = (float)(d.height/2.0); at = AffineTransform.getRotateInstance(angle,x,y); trans = AffineTransform.getTranslateInstance(x,y); at.concatenate(trans); transformedShape = at.createTransformedShape(shape); g2.fill(transformedShape); } } }
Stroke Joins
Stroke Caps
package visual.statik.described; import java.awt.*; import java.awt.geom.*; import javax.swing.*; /** * A VisualComponent that illustrates the rendering of * described visual content * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class RenderingExampleCanvas extends JComponent { /** * Default Constructor */ public RenderingExampleCanvas() { super(); } /** * Paint this Component * * @param g The rendering engine to use */ public void paint(Graphics g) { AlphaComposite composite; GradientPaint gradient; Graphics2D g2; Rectangle2D rectangle; Stroke stroke; g2 = (Graphics2D)g; // Use antialiasing for text and shapes g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Use high-quality rendering g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // The first rectangle rectangle = new Rectangle2D.Double( 10.0, 20.0, 100.0, 150.0); // Fill in JMU Gold g2.setPaint(new Color(0xC2, 0xA1, 0x4D)); g2.fill(rectangle); // Stroke in JMU purple stroke = new BasicStroke(5.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2.setStroke(stroke); g2.setColor(new Color(0x45, 0x00, 0x84)); g2.draw(rectangle); // The second rectangle rectangle = new Rectangle2D.Double( 200.0, 200.0, 100.0, 150.0); // Fill using a gradient gradient=new GradientPaint(200.0f, 275.0f, Color.CYAN, 300.0f, 275.0f, Color.WHITE); g2.setPaint(gradient); g2.fill(rectangle); // Stroke in black stroke = new BasicStroke(10.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); g2.setStroke(stroke); g2.setColor(Color.BLACK); g2.draw(rectangle); // The third rectangle rectangle = new Rectangle2D.Double( 50.0, 50.0, 200.0, 250.0); // Use alpha blending to achieve a transparency effect composite = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f); g2.setComposite(composite); // Fill in gray g2.setPaint(Color.YELLOW); g2.fill(rectangle); // Don't stroke } }
Two Designs
A Better Design
TransformableContent
package visual.statik.described; import java.awt.*; import java.awt.geom.*; /** * The requirements of transformable static * described visual content * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public interface TransformableContent extends visual.statik.TransformableContent { /** * Set the Color to use for stroking * * @param color The Color to use (or null to prevent stroking) */ public abstract void setColor(Color color); /** * Set the Paint to use for filling * * @param paint The Paint to use (or null to prevent filling) */ public abstract void setPaint(Paint paint); /** * Set the Stroke to use * * @param stroke The Stroke to use (or null to use the default) */ public abstract void setStroke(Stroke stroke); }
Content
package visual.statik.described; import java.awt.*; import java.awt.geom.*; /** * Described visual content that knows how to render and transform * itself * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Content extends visual.statik.AbstractTransformableContent implements TransformableContent { private Color color; private Paint paint; private Rectangle2D.Double originalBounds; private Rectangle2D.Double transformedBounds; private Shape originalShape; private Shape transformedShape; private Stroke stroke; private final Stroke DEFAULT_STROKE = new BasicStroke(); private final AffineTransform IDENTITY = new AffineTransform(); /** * Default Constructor */ public Content() { this(null, null, null, null); } /** * Explicit Value Constructor * * @param shape The Shape * @param color The Color to stroke with (or null for no stroke) * @param paint The Paint to fill with (or null for no fill) * @param stroke The Stroke (or null for the default) */ public Content(Shape shape, Color color, Paint paint, Stroke stroke) { super(); setColor(color); setPaint(paint); setStroke(stroke); setShape(shape); } /** * Fill the given Rectangle2D with the bounds * from the given Shape * * @param r The Rectangle2D to fill (out) * @param s The Shape to use (in) */ private void getBoundsFor(Rectangle2D.Double r, Shape s) { Rectangle2D temp; if (s != null) { temp = s.getBounds2D(); if (temp != null) { r.x = temp.getX(); r.y = temp.getY(); r.width = temp.getWidth(); r.height = temp.getHeight(); } } } }
/** * Get the stroke Color * * @return The stroke Color */ public Color getColor() { return color; } /** * Get the fill Paint * * @return The Paint */ public Paint getPaint() { return paint; } /** * Get the Stroke * * @return The Stroke */ public Stroke getStroke() { return stroke; }
/** * Set the Color to use for stroking * * @param color The Color to use (or null to prevent stroking) */ public void setColor(Color color) { this.color = color; } /** * Set the Paint to use for filling * * @param paint The Paint to use (or null to prevent filling) */ public void setPaint(Paint paint) { this.paint = paint; } /** * Set the Shape * * @param shape The Shape (or null to render nothing) */ public void setShape(Shape shape) { originalShape = shape; transformedBounds = new Rectangle2D.Double(); originalBounds = new Rectangle2D.Double(); if (originalShape != null) { if (isTransformationRequired()) { createTransformedContent(); } else { transformedShape = shape; } getBoundsFor(originalBounds, originalShape); getBoundsFor(transformedBounds, transformedShape); } } /** * Set the Stroke to use * * @param stroke The Stroke to use (or null to use the default) */ public void setStroke(Stroke stroke) { if (stroke == null) this.stroke = DEFAULT_STROKE; else this.stroke = stroke; }
/** * Create the transformed version of the shape */ private void createTransformedContent() { createTransformedContent(getAffineTransform()); } /** * Create a transformed version of the shape * * @param at The AffineTransform to use */ private void createTransformedContent(AffineTransform at) { transformedShape = at.createTransformedShape( originalShape); setTransformationRequired(false); getBoundsFor(transformedBounds, transformedShape); }
/** * Returns a high precision bounding box of the Content * either before or after it is transformed * * @param transformed true to get the BB of the transformed content * @return The bounding box */ public Rectangle2D getBounds2D(boolean transformed) { if (transformed) return transformedBounds; else return originalBounds; }
/** * Render this described visual content * * @param g2 The rendering engine to use */ public void render(Graphics g) { Color oldColor; Graphics2D g2; Paint oldPaint; Stroke oldStroke; g2 = (Graphics2D)g; // Transform the Shape (if necessary) if (isTransformationRequired()) { createTransformedContent(); } if (transformedShape != null) { // Save the state oldColor = g2.getColor(); oldPaint = g2.getPaint(); oldStroke = g2.getStroke(); // Fill the Shape (if appropriate) if (paint != null) { g2.setPaint(paint); g2.fill(transformedShape); } // Stroke the Shape if appropriate if (color != null) { if (stroke != null) g2.setStroke(stroke); g2.setColor(color); g2.draw(transformedShape); } // Restore the state g2.setColor(oldColor); g2.setPaint(oldPaint); g2.setStroke(oldStroke); } }