📄 display.java
字号:
/**
* Saves a copy of this display as an image to the specified output stream.
* @param output the output stream to write to.
* @param format the image format (e.g., "JPG", "PNG"). The number and kind
* of available formats varies by platform. See
* {@link javax.imageio.ImageIO} and related classes for more.
* @param scale how much to scale the image by. For example, a value of 2.0
* will result in an image with twice the pixel width and height of this
* Display.
* @return true if image was successfully saved, false if an error occurred.
*/
public boolean saveImage(OutputStream output, String format, double scale)
{
try {
// get an image to draw into
Dimension d = new Dimension((int)(scale*getWidth()),
(int)(scale*getHeight()));
BufferedImage img = getNewOffscreenBuffer(d.width, d.height);
Graphics2D g = (Graphics2D)img.getGraphics();
// set up the display, render, then revert to normal settings
Point2D p = new Point2D.Double(0,0);
zoom(p, scale); // also takes care of damage report
boolean q = isHighQuality();
setHighQuality(true);
paintDisplay(g, d);
setHighQuality(q);
zoom(p, 1/scale); // also takes care of damage report
// save the image and return
ImageIO.write(img, format, output);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* @see java.awt.Component#update(java.awt.Graphics)
*/
public void update(Graphics g) {
paint(g);
}
/**
* Paints the offscreen buffer to the provided graphics context.
* @param g the Graphics context to paint to
*/
protected void paintBufferToScreen(Graphics g) {
synchronized ( this ) {
g.drawImage(m_offscreen, 0, 0, null);
}
}
/**
* Immediately repaints the contents of the offscreen buffer
* to the screen. This bypasses the usual rendering loop.
*/
public void repaintImmediate() {
Graphics g = this.getGraphics();
if (g != null && m_offscreen != null) {
paintBufferToScreen(g);
}
}
/**
* Sets the transform of the provided Graphics context to be the
* transform of this Display and sets the desired rendering hints.
* @param g the Graphics context to prepare.
*/
protected void prepareGraphics(Graphics2D g) {
if ( m_transform != null )
g.transform(m_transform);
setRenderingHints(g);
}
/**
* Sets the rendering hints that should be used while drawing
* the visualization to the screen. Subclasses can override
* this method to set hints as desired. Such subclasses should
* consider honoring the high quality flag in one form or another.
* @param g the Graphics context on which to set the rendering hints
*/
protected void setRenderingHints(Graphics2D g) {
if ( m_highQuality ) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
} else {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
/**
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
public void paintComponent(Graphics g) {
if (m_offscreen == null) {
m_offscreen = getNewOffscreenBuffer(getWidth(), getHeight());
damageReport();
}
Graphics2D g2D = (Graphics2D)g;
Graphics2D buf_g2D = (Graphics2D) m_offscreen.getGraphics();
// Why not fire a pre-paint event here?
// Pre-paint events are fired by the clearRegion method
// paint the visualization
paintDisplay(buf_g2D, getSize());
paintBufferToScreen(g2D);
// fire post-paint events to any painters
firePostPaint(g2D);
buf_g2D.dispose();
// compute frame rate
nframes++;
if ( mark < 0 ) {
mark = System.currentTimeMillis();
nframes = 0;
} else if ( nframes == sampleInterval ){
long t = System.currentTimeMillis();
frameRate = (1000.0*nframes)/(t-mark);
mark = t;
nframes = 0;
}
}
/**
* Renders the display within the given graphics context and size bounds.
* @param g2D the <code>Graphics2D</code> context to use for rendering
* @param d the rendering width and height of the Display
*/
public void paintDisplay(Graphics2D g2D, Dimension d) {
// if double-locking *ALWAYS* lock on the visualization first
synchronized ( m_vis ) {
synchronized ( this ) {
if ( m_clip.isEmpty() )
return; // no damage, no render
// map the screen bounds to absolute coords
m_screen.setClip(0, 0, d.width+1, d.height+1);
m_screen.transform(m_itransform);
// compute the approximate size of an "absolute pixel"
// values too large are OK (though cause unnecessary rendering)
// values too small will cause incorrect rendering
double pixel = 1.0 + 1.0/getScale();
if ( m_damageRedraw ) {
if ( m_clip.isInvalid() ) {
// if clip is invalid, we clip to the entire screen
m_clip.setClip(m_screen);
} else {
// otherwise intersect damaged region with display bounds
m_clip.intersection(m_screen);
}
// expand the clip by the extra pixel margin
m_clip.expand(pixel);
// set the transform, rendering keys, etc
prepareGraphics(g2D);
// now set the actual rendering clip
m_rclip.setFrameFromDiagonal(
m_clip.getMinX(), m_clip.getMinY(),
m_clip.getMaxX(), m_clip.getMaxY());
g2D.setClip(m_rclip);
// finally, we want to clear the region we'll redraw. we clear
// a slightly larger area than the clip. if we don't do this,
// we sometimes get rendering artifacts, possibly due to
// scaling mismatches in the Java2D implementation
m_rclip.setFrameFromDiagonal(
m_clip.getMinX()-pixel, m_clip.getMinY()-pixel,
m_clip.getMaxX()+pixel, m_clip.getMaxY()+pixel);
} else {
// set the background region to clear
m_rclip.setFrame(m_screen.getMinX(), m_screen.getMinY(),
m_screen.getWidth(), m_screen.getHeight());
// set the item clip to the current screen
m_clip.setClip(m_screen);
// set the transform, rendering keys, etc
prepareGraphics(g2D);
}
// now clear the region
clearRegion(g2D, m_rclip);
// -- render ----------------------------
// the actual rendering loop
// copy current item bounds into m_rclip, reset item bounds
getItemBounds(m_rclip);
m_bounds.reset();
// fill the rendering and picking queues
m_queue.clear(); // clear the queue
Iterator items = m_vis.items(m_predicate);
for ( m_visibleCount=0; items.hasNext(); ++m_visibleCount ) {
VisualItem item = (VisualItem)items.next();
Rectangle2D bounds = item.getBounds();
m_bounds.union(bounds); // add to item bounds
if ( m_clip.intersects(bounds, pixel) )
m_queue.addToRenderQueue(item);
if ( item.isInteractive() )
m_queue.addToPickingQueue(item);
}
// sort the rendering queue
m_queue.sortRenderQueue();
// render each visual item
for ( int i=0; i<m_queue.rsize; ++i ) {
m_queue.ritems[i].render(g2D);
}
// no more damage so reset the clip
if ( m_damageRedraw )
m_clip.reset();
// fire bounds change, if appropriate
checkItemBoundsChanged(m_rclip);
}} // end synchronized block
}
/**
* Immediately render the given VisualItem to the screen. This method
* bypasses the Display's offscreen buffer.
* @param item the VisualItem to render immediately
*/
public void renderImmediate(VisualItem item) {
Graphics2D g2D = (Graphics2D)this.getGraphics();
prepareGraphics(g2D);
item.render(g2D);
}
/**
* Paints the graph to the provided graphics context, for output to a
* printer. This method does not double buffer the painting, in order to
* provide the maximum print quality.
*
* <b>This method may not be working correctly,
* and will be repaired at a later date.</b>
*
* @param g the printer graphics context.
*/
protected void printComponent(Graphics g) {
boolean wasHighQuality = m_highQuality;
try {
// Set the quality to high for the duration of the printing.
m_highQuality = true;
// Paint directly to the print graphics context.
paintDisplay((Graphics2D) g, getSize());
} finally {
// Reset the quality to the state it was in before printing.
m_highQuality = wasHighQuality;
}
}
/**
* Clears the specified region of the display
* in the display's offscreen buffer.
*/
protected void clearRegion(Graphics2D g, Rectangle2D r) {
g.setColor(getBackground());
g.fill(r);
// fire pre-paint events to any painters
firePrePaint(g);
}
// ------------------------------------------------------------------------
// Transformations
/**
* Set the 2D AffineTransform (e.g., scale, shear, pan, rotate) used by
* this display before rendering visual items. The provided transform
* must be invertible, otherwise an expection will be thrown. For simple
* panning and zooming transforms, you can instead use the provided
* pan() and zoom() methods.
*/
public synchronized void setTransform(AffineTransform transform)
throws NoninvertibleTransformException
{
damageReport();
m_transform = transform;
m_itransform = m_transform.createInverse();
}
/**
* Returns a reference to the AffineTransformation used by this Display.
* Changes made to this reference WILL corrupt the state of
* this display. Use setTransform() to safely update the transform state.
* @return the AffineTransform
*/
public AffineTransform getTransform() {
return m_transform;
}
/**
* Returns a reference to the inverse of the AffineTransformation used by
* this display. Direct changes made to this reference WILL corrupt the
* state of this display.
* @return the inverse AffineTransform
*/
public AffineTransform getInverseTransform() {
return m_itransform;
}
/**
* Gets the absolute co-ordinate corresponding to the given screen
* co-ordinate.
* @param screen the screen co-ordinate to transform
* @param abs a reference to put the result in. If this is the same
* object as the screen co-ordinate, it will be overridden safely. If
* this value is null, a new Point2D instance will be created and
* returned.
* @return the point in absolute co-ordinates
*/
public Point2D getAbsoluteCoordinate(Point2D screen, Point2D abs) {
return m_itransform.transform(screen, abs);
}
/**
* Returns the current scale (zoom) value.
* @return the current scale. This is the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -