Java2D & Piccolo

skatechildrenSoftware and s/w Development

Nov 3, 2013 (3 years and 7 months ago)

60 views

Java2D & Piccolo

The Origins of the Java®
Graphics 2D API



The original Java® GUI toolkit, the AWT, was a
quick and dirty solution. It used native peer
components to draw all widgets.


Swing draws all components except the top level
containers using Java® methods rather than
relying on platform specific widgets.


To do this, Swing required improved graphics.


The Java® 2D API was born as enabling
technology for Swing.


There is now a Java® 3D API also.


See tutorial at
http://java.sun.com/docs/books/tutorial/2d/index.ht
ml




Java Graphics Architecture

JFrame : Heavyweight

JPanel : Lightweight

Event Handling


One of the key concepts in GUI programming is
Event Handling.


Something (Mouse click / Key press, Menu
Selection, Timer expiring, Network message
received) happens. These would be all
Event
Sources
.


Some GUI components might care about one of
these things happening and need to react to it.
These components would register themselves
with the Event Source, so the source would tell
them when something happens. These are the
Event Listeners
.

Event Basics


All Events are objects of Event
Classes.


All Event Classes derive from
EventObject.


When an Event occurs, Java sends a
message to all registered Event
Listeners from the Event source (this
actually was a change, the old AWT
event model would send it out to
everyone).

Event Handling in Java


Action that results in the event

Listener type

User clicks a button, presses Return while typing
in a text field, or chooses a menu item

ActionListener

User closes a frame (main window)

WindowListener

User presses a mouse button while the cursor is
over a component

MouseListener

User moves the mouse over a component

MouseMotionListener

Component becomes visible

ComponentListener

Component gets the keyboard focus

FocusListener

Table of list selection changes

ListSelectionListener

Registering Event Listeners



To register a listener object with a source
object, you use lines of code that follow the
model



eventSourceListener.addEventListener(eventListenerObject);



So, from here we have 2 ways to create the
listener object.


Create a class that implements that interface
(ActionListener, WindowListener, etc.)


Use an anonymous Inner Class and attach it


Registering Event Listeners

import javax.swing.*;

import java.awt.*;

import java.awt.event.*;


class MyListener
implements ActionListener

{


public void actionPerformed(ActionEvent e)


{



//code to run


}

}


ActionListener listener = new MyListener();

JButton button = new JButton(“Ok”);

button.addActionListener(listener);

Event Listeners


When the user clicks the button, the JButton
object generates an ActionEvent object.


It this calls the listener object’s
actionPerformed() method and passes that
method the event object it just generated.


Note that a single event source can have
multiple listeners listening for its events. In
this case, the source calls the
actionPerformed() method of each of its
listeners when the event is generated.

Or… (the way everyone
does it.. with a Inner Class)

JButton button=new JButton(“OK”);

button.addActionListener( new ActionListener()
//Anon Inner class

{


public void actionPerformed(ActionEvent e)


{



//handling code here


}

});

Things to realize


If we’re doing Event handling, we probably
need to import javaw.swing.* (for the Swing
components), java.awt.* and
java.awt.event.* (for the Event handling
stuff). Another example of how Swing and
AWT comingle.


the ActionListener interface defines only 1
method, actionPerformed. Others, like
WindowListener and the MouseListeners,
define more than 1. You have to implement
them all to use it. More on this shortly
.


So, How Do I Draw in
Java®


You might think that if you want to do
your own drawing in Java®, you do it in
the main thread just as you create a GUI
out of components in the main thread.


In fact, you have to create components
on which you draw, like all your other
GUI components in the main thread.


But the drawing has to be done in the
event thread.


And to understand why, we need to start
by considering when drawing happens.




When Does a GUI Get
Drawn?


On initial display (but not
necessarily when the program gets
started)



When the display gets damaged.
For example when it gets hidden
and then exposed.



When the content changes, and
Swing or the programmer calls for
a refresh (repaint()).


How does a GUI Get
Drawn?



Swing schedules the drawing. It may
combine multiple redraw requests that
occur in rapid succession.



Swing calls the following three methods
in order (on the event thread):


paintComponent() paintBorder()


paintChildren()

The last recursively


paints a container's children.


How to Do Custom
Drawing?


Standard Swing components like JLabel and
JComboBox use paintComponent() to draw
themselves.


If you want to do custom drawing, extend a
container class, usually JPanel, and override
paintComponent().


Use calls from the 2D API in paintComponent() to
draw what you want on the JPanel background.


Override getPreferredSize() or call
setPreferredSize() to size JPanel to your drawing.


The Graphics Class



paintComponent() is called with argumentGraphics
g, that serves as a
drawing toolkit
initialized to the
component's defaults.


In more recent versions of the JDK, the argument
is really a Graphics2D object, which extends
Graphics for the 2D API . So cast it. Graphics was
the original AWT class.


Using the 2D API usually starts off like this:


public void paintComponent( Graphics g ) {


super.paintComponent( g );

Graphics2D g2 = (Graphics2D) g;



Where Does the Graphics
Argument Come From?


The Graphics argument to the paintComponent()
method is a snapshot of your component's default
graphics values like font and drawing color at the
moment the paint methods are called.


It is only a copy of these values. Each time the
paint methods are called, you get a new version of
Graphics.

No changes you make to a Graphics
instance in one call to paintComponent() are
remembered the next time you enter the method.
And no changes, like setFont() are propagated
back to the component itself.


Basic 2D API Operations


You can use the Graphics2D argument to


1. draw outline figures using the method
public void draw( Shape s )

2. draw filled figures using the method
public void fill( Shape s )

3. draw an image using one of the
methods: public void drawImage( . . . )

4. draw a text string using the methods:
public void drawString( . . . )




Graphics 2D Rendering
Context


Much of the power of the 2D API comes
from the user’s ability to set attributes of
the Graphics2D object known
collectively as the rendering context:


public void setStroke(Stroke s)


public void setPaint(Paint p)


public void setFont(Font f)


public void setComposite(Composite c)


public void setTransform(Transform t)


public void setRenderingHints(Map m)


Custom Drawing Template


import java.awt.*; // for Graphics2D, Paint, Shape, …

import java.awt.geom.*; // for concrete Shape classes

import javax.swing.*; // for JPanel, etc


public class MyPanel extends JPanel { . . .



public void paintComponent( Graphics g ) {
super.paintComponent( g ); Graphics2D g2 =
(Graphics2D) g; . . .


g2.set
Paint/Stroke/Font/etc
(...);


Shape s = new
Shape2D.Float/Double
( ... );
g2.
draw/fill
( s );


2D Shapes


Shape is an interface defined in java.awt,
but the classes that implement Shape
are all defined in java.awt.geom.


Shapes come in two versions, one with
high precision coordinates and one with
low, e.g.: Ellipse2D.Double // high
precision Ellipse2D.Float // low precision


Each shape has a different constructor
arguments, doubles or floats depending
on whether they are high precision or
low.


Creating an Ellipse

Graphics2D Coordinate
System


The Graphics2D object uses a variety of
world
coordinates
(as opposed to
device coordinates
)
that Java® calls
user space
.


Graphics2D shapes and operations are defined in
floating point (float or double) but the Y coordinate
increases downwards.


Some Graphics2D calls only take floats.


The floating point coordinates are designed to
make your graphics independent of the output
device.


The 2D API is designed for printing as well as
output to screens at different resolutions.


The Graphics2D Default
Transformation



The 2D rendering pipeline applies a
geometric
transformation
to map user
space to device space.


The default transformation maps 1.0 user
space units to ~1/72 of an inch, which
happens to be the average pixel size on
a screen or a printer’s point size on a
printer.


So unless you do something special, 2D
drawing defaults to pixel coordinates.




Ellipse.java

import java.awt.*;

import java.awt.geom.*;

import javax.swing.*;


public class Ellipse extends JPanel {


private Shape ellipse;


public static void main( String[] args ) {



JFrame frame = new JFrame( "Ellipse" );

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


frame.getContentPane().add( new Ellipse(), "Center" );


frame.setSize( 650, 300 ); frame.setVisible( true );


}


Ellipse.java, 2


public Ellipse() {


ellipse = new Ellipse2D.Double( 100.0, 50.0, 300.0, 150.0 );


setBackground( Color.white ); setOpaque( true );


}




public void paintComponent( Graphics g ) {

super.paintComponent( g );



Graphics2D g2 = (Graphics2D) g;



g2.setPaint(Color.red);



g2.fill( ellipse );


}

}




Piccolo Framework


Piccolo is a direct
-
manipulation
graphics framework that supports
constructing zoomable interfaces.

Four main classes that define
the framework's core:



PNode (anything that is visible and gets events)


PCamera (a node that looks at other layer nodes, and
applies a view transform)


PLayer (a node that can be looked at by a camera)


PRoot (the root of the Piccolo display tree)


PCanvas (the host component that lets PNodes exist in a
Java Swing application. Each PCanvas is associated with
a PCamera. But all cameras are not necessarily directly
associated with a PCanvas, internal cameras for example
are not.)


Piccolo at Runtime

Piccolo at Runtime


At runtime these classes form a tree
-
like
structure with the PRoot situated at the top.


Each PCamera is normally linked with at
least one PLayer that it looks at through it's
view transform.


If a camera is associated with a canvas
then that camera's view is displayed on the
canvas, and input events from the canvas
enter the Piccolo scene graph at that
camera's point in the hierarchy.

PNode


Nodes are the central design concept
in Piccolo. Any object that wants to
paint itself on the screen should
inherit from the node class. In addition
to painting on the screen all nodes
may have other "child" nodes added
to them. Visual structures are build up
by grouping and sub grouping
collections of nodes.

PNode (continued)


Each node also has its own
affine transform

that is applied before the node is drawn to
the screen. This transform can be modified
to scale and translate the node. The
transform exits directly above the node, but
below the node's parent.


Thus, translating it
will translate the node (and all its
descendents) but will not translate the
node's parent.


PCamera



Cameras are nodes that have an additional
view transform and a collection of layers in
addition to the collection of children that
they inherit from PNode.


The view transform is applied before
drawing or picking the layers, but not when
drawing or picking the camera's children.
Cameras may (an internal camera might
not) also reference a PCanvas, and forward
repaint events to that canvas. The canvas
would then later ask the camera to draw the
damaged region on its surface.

PLayer



Layer nodes are nodes that can be
viewed by a one or more cameras.
They maintain a list of the cameras
that are viewing them, and notify
these cameras when they are
repainted.

PRoot



The PRoot serves as the topmost
node in the Piccolo runtime structure;
all other nodes are its direct children
or descendents of its children. The
PCanvas communicates with the root
node to manage screen updates and
to dispatch events to its children.

PCanvas



The PCanvas is a JComponent in Piccolo.
Thus, it is used to view a Piccolo scene
graph in Java Swing. The PCanvas views
the scene graph through a PCamera. It
forwards input events to that camera, and
uses that camera to draw itself. Translating
and scaling that camera's view transform is
how panning and zooming are
accomplished.

“Full”


Piccolo uses the term "full" to mean a node
and its descendants with the node's
transform applied. This helps distinguish
between methods that work on a single
node and those that work on a node
together with all of its descendants with the
node's transform applied. When traversing
the node scene graph to paint or calculate
bounds parent nodes generally call the "full"
methods of their direct child nodes.

First Application


All Piccolo interfaces need to be
placed in a PCanvas so that they may
be viewed and interacted with by the
user. PCanvas extends the
javax.swing.JComponent class in
Piccolo.Java



See Example