|
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);
}
}
Shape objects and render them
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);
}
}