|
Visual Content
An Introduction |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
An EM Wave
The Eye
The (Linear) Color Cube
A Translation of (25,25)
A Rotation of \(- \pi / 6\) Radians
A Scaling of (1.5, 1.5)
A Reflection About the Horizontal Axis
JComponent Class:
paint(java.awt.Graphics)
method is called and is passed a rendering engine
JComponent
paint() methodAn Example
import java.awt.*;
import javax.swing.*;
/**
* A concrete extension of a JComponent that illustrates
* the process of obtaining and using a rendering engine
*
* @version 1.0
* @author Prof. David Bernstein, James Madison University
*/
public class BoringComponent extends JComponent
{
/**
* Render this BoringComponent
*
* @param g The rendering engine to use
*/
public void paint(Graphics g)
{
Graphics2D g2;
// Cast the rendering engine appropriately
g2 = (Graphics2D)g;
// Put the rendering code here
}
}
JComponent can be added
to the content pane of a MultimediaApplication
or MultimediaApplet
A Common Design
A Design with a Problem
A Better Design
A Design that Satisfies Almost All of the Requirements
"Enhancing" the View with Specialization
A Good Design
SimpleContent Interfacepackage visual.statik;
import java.awt.*;
/**
* The requirements of simple static visual content
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public interface SimpleContent
{
/**
* Render this static visual content
*
* @param g2 The rendering engine to use
*/
public abstract void render(Graphics g);
}
Visualization ClassSkeleton:
package visual;
import java.awt.event.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import visual.statik.SimpleContent;
/**
* A collection of Content objects to be rendered
* (and a collection of GUI components that will
* render them)
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class Visualization
{
private CopyOnWriteArrayList<SimpleContent> content;
private LinkedList<VisualizationView> views;
/**
* Default Constructor
*/
public Visualization()
{
content = new CopyOnWriteArrayList<SimpleContent>();
views = new LinkedList<VisualizationView>();
views.addFirst(createDefaultView());
}
}
Content Management:
/**
* Add a SimpleContent to the "front" of this Visualization
*
* Note: This method only adds the SimpleContent if it is not
* already in the Visualization. However, the "underlying"
* content (e.g., BufferedImage, Shape) in two different
* SimpleContent objects can be the same.
*
* To change the order of a SimpleContent that is already on the canvas
* use toBack() or toFront().
*
* @param r The SimpleContent to add
*/
public void add(SimpleContent r)
{
if (!content.contains(r))
{
content.add(r);
repaint();
}
}
/**
* Clear this Visualization of all SimpleContent objects
*/
public void clear()
{
content.clear();
}
/**
* Get an Iterator that contains all of the SimpleContent
* objects
*
* @return The SimpleContent objects
*/
public Iterator<SimpleContent> iterator()
{
return content.iterator();
}
/**
* Remove the given SimpleContent from this Canvas
*
* @param r The SimpleContent to remove
*/
public void remove(SimpleContent r)
{
if (content.remove(r)) repaint();
}
/**
* Move the given SimpleContent to the "back"
*
* Note: The SimpleContent must have already been added for this
* method to have an effect.
*
* @param r The SimpleContent to move to the back
*/
public void toBack(SimpleContent r)
{
boolean removed;
removed = content.remove(r);
if (removed)
{
content.add(r);
}
}
/**
* Move the given SimpleContent to the "front".
*
* Note: The SimpleContent must have already been added for this
* method to have an effect.
*
* @param r The SimpleContent to move to the front
*/
public void toFront(SimpleContent r)
{
boolean removed;
removed = content.remove(r);
if (removed)
{
content.add(0, r);
}
}
View Management:
/**
* Add a view to this Visualization
*
* @param view The view to add
*/
public void addView(VisualizationView view)
{
views.addLast(view);
}
/**
* Get the "main" view (i.e., the "main" VisualizationView
* that is used to present the SimpleContent objects) associated
* with this Visualization.
*
* Note: A visualization can actually have multiple
* views associated with it. This is a convenience
* method that can be used when there is only one such
* view.
*
* @return The view
*/
public VisualizationView getView()
{
return views.getFirst();
}
/**
* Get all of the views associated with this Visualization
*
* @return The views
*/
public Iterator<VisualizationView> getViews()
{
return views.iterator();
}
/**
* Remove a view from this Visualization
*
* @param view The view to remove
*/
public void removeView(VisualizationView view)
{
views.remove(view);
}
/**
* Change the "main" view associated with this Visualization
*
* Note: A visualization can actually have multiple
* views associated with it. This is a convenience
* method that can be used when there is only one such
* view.
*
* @param view The new view to use as the "main" view
*/
public void setView(VisualizationView view)
{
views.removeFirst();
views.addFirst(view);
}
Forcing Rendering:
/**
* Repaint the view(s) assocaited with this Visualization
*/
protected void repaint()
{
Iterator<VisualizationView> i;
VisualizationView view;
i = views.iterator();
while (i.hasNext())
{
view = i.next();
view.repaint();
}
}
VisualizationView ClassSkeleton:
package visual;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import visual.statik.*;
/**
* A GUI component that presents a collection of SimpleContent
* objects
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class VisualizationView
extends JComponent
implements MouseListener
{
protected Visualization model;
protected VisualizationRenderer renderer;
/**
* Explicit Value Constructor
*
* @param model The Visualization to use
* @param renderer The VisualizationRenderer to use
*/
public VisualizationView(Visualization model,
VisualizationRenderer renderer)
{
super();
this.model = model;
this.renderer = renderer;
}
/**
* Handle updates
*
* This is overriden so that the background is not
* erased before painting (which is the default)
*/
public void update(Graphics g)
{
paint(g);
}
}
Rendering:
/**
* Set the VisualizationRenderer that this component
* should use when rendering
*
* @param renderer The VisualizationRenderer
*/
public void setRenderer(VisualizationRenderer renderer)
{
this.renderer = renderer;
}
/**
* Operations to perform after rendering.
* This method is called by paint().
*
* @param g The rendering engine
*/
protected void postRendering(Graphics g)
{
renderer.postRendering(g, model, this);
}
/**
* Operations to perform before rendering.
* This method is called by paint().
*
* @param g The rendering engine
*/
protected void preRendering(Graphics g)
{
renderer.preRendering(g, model, this);
}
/**
* Render the content contained in the model.
* This method is called by paint().
*
* @param g The rendering engine
*/
protected void render(Graphics g)
{
renderer.render(g, model, this);
}
PlainVisualizationRenderer Class
/**
* Render the content contained in the model.
* This method is called by paint().
*
* @param g The rendering engine
*/
public void render(Graphics g,
Visualization model,
VisualizationView view)
{
Iterator<SimpleContent> iter;
SimpleContent c;
iter = model.iterator();
while (iter.hasNext())
{
c = iter.next();
if (c != null) c.render(g);
}
}
ScaledVisualizationRenderer ClassSkeleton:
package visual;
import java.awt.*;
import java.util.*;
/**
* A decorator of a VisualizationRenderer that adds
* scaling capabilities. That is, a ScaledVisualizationRenderer
* will scale all of the content to fit its component.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ScaledVisualizationRenderer
implements VisualizationRenderer
{
private double height, scaleX, scaleY, width;
private VisualizationRenderer decorated;
/**
* Explicit Value Constructor
*
* @param decorated The VisualizatioRenderer to decorate
* @param width The full-size width of the content
* @param height The full-size height of the content
*/
public ScaledVisualizationRenderer(VisualizationRenderer decorated,
double width, double height)
{
this.decorated = decorated;
this.width = width;
this.height = height;
}
/**
* Render the content contained in the model.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void render(Graphics g,
Visualization model,
VisualizationView view)
{
decorated.render(g, model, view);
}
}
Before Rendering:
/**
* Operations to perform before rendering.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void preRendering(Graphics g,
Visualization model,
VisualizationView view)
{
Dimension size;
Graphics2D g2;
g2 = (Graphics2D)g;
size = view.getSize();
scaleX = size.getWidth() / width;
scaleY = size.getHeight() / height;
g2.scale(scaleX, scaleY);
decorated.preRendering(g, model, view);
}
After Rendering:
/**
* Operations to perform after rendering.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void postRendering(Graphics g,
Visualization model,
VisualizationView view)
{
Graphics2D g2;
g2 = (Graphics2D)g;
g2.scale(1.0/scaleX, 1.0/scaleY);
decorated.postRendering(g, model, view);
}
PartialVisualizationRenderer ClassSkeleton:
package visual;
import java.awt.*;
import java.util.*;
/**
* A decorator of a VisualizationRenderer that
* only shows part of the content.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class PartialVisualizationRenderer
implements VisualizationRenderer
{
private double x, y;
private VisualizationRenderer decorated;
/**
* Explicit Value Constructor
*
* @param decorated The VisualizatioRenderer to decorate
* @param x The left-most point to render
* @param y The upper-most point to render
*/
public PartialVisualizationRenderer(VisualizationRenderer decorated,
double x, double y)
{
this.decorated = decorated;
this.x = x;
this.y = y;
}
/**
* Render the content contained in the model.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void render(Graphics g,
Visualization model,
VisualizationView view)
{
decorated.render(g, model, view);
}
}
Before Rendering:
/**
* Operations to perform before rendering.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void preRendering(Graphics g,
Visualization model,
VisualizationView view)
{
Graphics2D g2;
g2 = (Graphics2D)g;
g2.translate(-x, -y);
decorated.preRendering(g, model, view);
}
After Rendering:
/**
* Operations to perform after rendering.
*
* @param g The rendering engine
* @param model The Visualization containing the content
* @param view The component presenting the content
*/
public void postRendering(Graphics g,
Visualization model,
VisualizationView view)
{
Graphics2D g2;
g2 = (Graphics2D)g;
g2.translate(x, y);
decorated.postRendering(g, model, view);
}
Transformation class that contains
the necessary attributes and behaviorsSimpleContent
Adding Transformations using a Helper
Adding Transformations using Stand-Alone Interfaces
Adding Transformations using Individual Interfaces
The Best Way to Add Transformations
AbstractTransformableContent Classpackage visual.statik;
import java.awt.geom.*;
/**
* A partial implementation of the TransformableContent
* interface that keeps track of transformation information
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public abstract class AbstractTransformableContent
implements TransformableContent
{
protected boolean relocated, rerotated, rescaled;
protected double angle;
protected double xScale, yScale;
protected double x, y;
protected double xRotation, yRotation;
/**
* Default Constructor
*/
public AbstractTransformableContent()
{
setTransformationRequired(false);
angle = 0.0;
xScale = 1.0;
yScale = 1.0;
x = 0.0;
y = 0.0;
xRotation = 0.0;
yRotation = 0.0;
}
/**
* Get the (concatenated) AffineTransform
*
* @return The rotation, scaling and translation concatenated
*/
protected AffineTransform getAffineTransform()
{
// We could use an object pool rather than local
// variables. Rough tests estimate that this method
// would require 1/3 as much time with an object pool.
AffineTransform at, rotation, scaling, translation;
Rectangle2D bounds;
// Start with the identity transform
at = AffineTransform.getTranslateInstance(0.0, 0.0);
if (rerotated)
{
bounds = getBounds2D(false);
rotation = AffineTransform.getRotateInstance(angle,
xRotation,
yRotation);
at.preConcatenate(rotation);
}
if (rescaled)
{
scaling = AffineTransform.getScaleInstance(xScale,
yScale);
at.preConcatenate(scaling);
}
if (relocated)
{
translation = AffineTransform.getTranslateInstance(x,
y);
at.preConcatenate(translation);
}
return at;
}
/**
* Returns a high precision bounding box of the appropriately
* transformed content (required by TransformedContent)
*
* @return The bounding box
*/
public Rectangle2D getBounds2D()
{
return getBounds2D(true);
}
/**
* 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 abstract Rectangle2D getBounds2D(boolean transformed);
/**
* Set the translation
* (required by TransformedContent)
*
* @param x The x posiiton
* @param y The y position
*/
public void setLocation(double x, double y)
{
this.x = x;
this.y = y;
relocated = true;
}
/**
* Set the angle of rotation around the midpoint
* (required by TransformedContent)
*
* @param angle The rotation angle
* @param x The x-coordinate of the point to rotate around
* @param y The y-coordinate of the point to rotate around
*/
public void setRotation(double angle, double x, double y)
{
this.angle = angle;
xRotation = x;
yRotation = y;
rerotated = true;
}
/**
* Set the scaling
* (required by TransformedContent)
*
* @param xScale The scaling in the horizontal dimension
* @param yScale The scaling in the vertical dimension
*/
public void setScale(double xScale, double yScale)
{
this.xScale = xScale;
this.yScale = yScale;
rescaled = true;
}
/**
* Set the scaling
*
* @param scale The scaling (in both dimensions)
*/
public void setScale(double scale)
{
setScale(scale, scale);
}
/**
* Set whether this AbstractTransformableContent object
* needs to be transformed before being used
*
* @param required true to indicate that a transformation is requried
*/
protected void setTransformationRequired(boolean required)
{
relocated = required;
rerotated = required;
rescaled = required;
}
/**
* Does this AbstractTransformableContent object need
* to be transformed before being used?
*
* @return true to indicate a transformation is required
*/
protected boolean isTransformationRequired()
{
return (relocated || rerotated || rescaled);
}
}