import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.BasicStroke; import java.awt.Font; import java.awt.FontMetrics; import java.awt.geom.Rectangle2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Arc2D; import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.awt.geom.Line2D; import java.awt.geom.AffineTransform; import java.lang.Math; import java.util.ArrayList; import java.awt.Color; class GraphicsWrapper { AffineTransform originalTransform = null; private int windowWidthInPixels = 10; // must be initialized to something positive private int windowHeightInPixels = 10; // must be initialized to something positive // The client may either call frame() or resize() first, // and we must initialize ourself differently depending on the case. private boolean hasFrameOrResizeBeenCalledBefore = false; public int getWidth() { return windowWidthInPixels; } public int getHeight() { return windowHeightInPixels; } private Graphics g = null; private Graphics2D g2 = null; private GeneralPath generalPath = new GeneralPath(); private Line2D line2D = new Line2D.Float(); private Path2D path2D = new Path2D.Float(); private Rectangle2D.Float rectangle2D = new Rectangle2D.Float(); private Ellipse2D.Float ellipse2D = new Ellipse2D.Float(); private Arc2D.Float arc2D = new Arc2D.Float(); public void set( Graphics g ) { this.g = g; this.g2 = (Graphics2D)g; this.originalTransform = g2.getTransform(); } private int fontHeight = 14; private Font font = new Font( "Sans-serif", Font.BOLD, fontHeight ); private FontMetrics fontMetrics = null; public void setFontHeight( int h ) { fontHeight = h; font = new Font( "Sans-serif", Font.BOLD, fontHeight ); fontMetrics = null; } public int getFontHeight() { return fontHeight; } private float offsetXInPixels = 0; private float offsetYInPixels = 0; private float scaleFactorInWorldSpaceUnitsPerPixel = 1.0f; // greater if user is more zoomed out public float convertPixelsToWorldSpaceUnitsX( float XInPixels ) { return ( XInPixels - offsetXInPixels )*scaleFactorInWorldSpaceUnitsPerPixel; } public float convertPixelsToWorldSpaceUnitsY( float YInPixels ) { return ( YInPixels - offsetYInPixels )*scaleFactorInWorldSpaceUnitsPerPixel; } public Point2D convertPixelsToWorldSpaceUnits( Point2D p ) { return new Point2D(convertPixelsToWorldSpaceUnitsX(p.x()),convertPixelsToWorldSpaceUnitsY(p.y())); } public int convertWorldSpaceUnitsToPixelsX( float x ) { return Math.round( x / scaleFactorInWorldSpaceUnitsPerPixel + offsetXInPixels ); } public int convertWorldSpaceUnitsToPixelsY( float y ) { return Math.round( y / scaleFactorInWorldSpaceUnitsPerPixel + offsetYInPixels ); } public Point2D convertWorldSpaceUnitsToPixels( Point2D p ) { return new Point2D(convertWorldSpaceUnitsToPixelsX(p.x()),convertWorldSpaceUnitsToPixelsY(p.y())); } public float getScaleFactorInWorldSpaceUnitsPerPixel() { return scaleFactorInWorldSpaceUnitsPerPixel; } public void pan( float dx, float dy ) { offsetXInPixels += dx; offsetYInPixels += dy; } public void zoomIn( float zoomFactor, // greater than 1 to zoom in, between 0 and 1 to zoom out float centerXInPixels, float centerYInPixels ) { scaleFactorInWorldSpaceUnitsPerPixel /= zoomFactor; offsetXInPixels = centerXInPixels - (centerXInPixels - offsetXInPixels) * zoomFactor; offsetYInPixels = centerYInPixels - (centerYInPixels - offsetYInPixels) * zoomFactor; } public void zoomIn( float zoomFactor // greater than 1 to zoom in, between 0 and 1 to zoom out ) { zoomIn( zoomFactor, windowWidthInPixels * 0.5f, windowHeightInPixels * 0.5f ); } // This can be used to implement bimanual (2-handed) camera control, // or 2-finger camera control, as in a "pinch" gesture public void panAndZoomBasedOnDisplacementOfTwoPoints( // these are assumed to be in pixel coordinates Point2D A_old, Point2D B_old, Point2D A_new, Point2D B_new ) { // Compute midpoints of each pair of points Point2D M1 = Point2D.average( A_old, B_old ); Point2D M2 = Point2D.average( A_new, B_new ); // This is the translation that the world should appear to undergo. Vector2D translation = Point2D.diff( M2, M1 ); // Compute a vector associated with each pair of points. Vector2D v1 = Point2D.diff( A_old, B_old ); Vector2D v2 = Point2D.diff( A_new, B_new ); float v1_length = v1.length(); float v2_length = v2.length(); float scaleFactor = 1; if ( v1_length > 0 && v2_length > 0 ) scaleFactor = v2_length / v1_length; pan( translation.x(), translation.y() ); zoomIn( scaleFactor, M2.x(), M2.y() ); } public void frame( AlignedRectangle2D rect, boolean expand // true if caller wants a margin of whitespace added around the rect ) { hasFrameOrResizeBeenCalledBefore = true; assert windowWidthInPixels > 0 && windowHeightInPixels > 0; if ( rect.isEmpty() || rect.getDiagonal().x() == 0 || rect.getDiagonal().y() == 0 ) { return; } if ( expand ) { float diagonal = rect.getDiagonal().length() / 20; Vector2D v = new Vector2D( diagonal, diagonal ); rect = new AlignedRectangle2D( Point2D.diff(rect.getMin(),v), Point2D.sum(rect.getMax(),v) ); } if ( rect.getDiagonal().x() / rect.getDiagonal().y() >= windowWidthInPixels / (float)windowHeightInPixels ) { offsetXInPixels = - rect.getMin().x() * windowWidthInPixels / rect.getDiagonal().x(); scaleFactorInWorldSpaceUnitsPerPixel = rect.getDiagonal().x() / windowWidthInPixels; offsetYInPixels = windowHeightInPixels/2 - rect.getCenter().y() / scaleFactorInWorldSpaceUnitsPerPixel; } else { offsetYInPixels = - rect.getMin().y() * windowHeightInPixels / rect.getDiagonal().y(); scaleFactorInWorldSpaceUnitsPerPixel = rect.getDiagonal().y() / windowHeightInPixels; offsetXInPixels = windowWidthInPixels/2 - rect.getCenter().x() / scaleFactorInWorldSpaceUnitsPerPixel; } } public void resize( int w, int h ) { if ( ! hasFrameOrResizeBeenCalledBefore ) { windowWidthInPixels = w; windowHeightInPixels = h; hasFrameOrResizeBeenCalledBefore = true; return; } Point2D oldCenter = convertPixelsToWorldSpaceUnits( new Point2D( windowWidthInPixels * 0.5f, windowHeightInPixels * 0.5f ) ); float radius = Math.min( windowWidthInPixels, windowHeightInPixels ) * 0.5f * scaleFactorInWorldSpaceUnitsPerPixel; windowWidthInPixels = w; windowHeightInPixels = h; if ( radius > 0 ) { frame( new AlignedRectangle2D( new Point2D( oldCenter.x() - radius, oldCenter.y() - radius ), new Point2D( oldCenter.x() + radius, oldCenter.y() + radius ) ), false ); } } public void setCoordinateSystemToPixels() { AffineTransform transform = new AffineTransform(); g2.setTransform(originalTransform); g2.transform(transform); } public void setCoordinateSystemToWorldSpaceUnits() { AffineTransform transform = new AffineTransform(); transform.translate( offsetXInPixels, offsetYInPixels ); float s = 1.0f/scaleFactorInWorldSpaceUnitsPerPixel; transform.scale( s, s ); g2.setTransform(originalTransform); g2.transform(transform); } public void clear( float r, float g, float b ) { setColor(r,g,b); setCoordinateSystemToPixels(); this.g.fillRect( 0, 0, windowWidthInPixels, windowHeightInPixels ); } public void setupForDrawing() { } public void enableAlphaBlending() { } public void disableAlphaBlending() { } public void setColor( float r, float g, float b ) { g2.setColor( new Color( r, g, b ) ); } public void setColor( float r, float g, float b, float alpha ) { g2.setColor( new Color( r, g, b, alpha ) ); } public void setColor( Color c ) { setColor( c.getRed()/255.0f, c.getGreen()/255.0f, c.getBlue()/255.0f, c.getAlpha()/255.0f ); } public void setColor( Color c, float alpha ) { setColor( c.getRed()/255.0f, c.getGreen()/255.0f, c.getBlue()/255.0f, alpha ); } public void setLineWidth( float width ) { g2.setStroke( new BasicStroke( width ) ); } public void drawLine( float x1, float y1, float x2, float y2 ) { //generalPath.reset(); //generalPath.moveTo( x1, y1 ); //generalPath.lineTo( x2, y2 ); //g2.draw( generalPath ); line2D.setLine( x1, y1, x2, y2 ); g2.draw( line2D ); } public void drawPolyline( ArrayList< Point2D > points, boolean isClosed, boolean isFilled ) { if ( points.size() <= 1 ) return; path2D.reset(); Point2D p = points.get(0); path2D.moveTo( p.x(), p.y() ); for ( int i = 1; i < points.size(); ++i ) { p = points.get(i); path2D.lineTo( p.x(), p.y() ); } if ( isClosed ) path2D.closePath(); if ( isFilled ) g2.fill( path2D ); else g2.draw( path2D ); } public void drawPolyline( ArrayList< Point2D > points ) { drawPolyline( points, false, false ); } public void drawPolygon( ArrayList< Point2D > points ) { drawPolyline( points, true, false ); } public void fillPolygon( ArrayList< Point2D > points ) { drawPolyline( points, true, true ); } public void drawRect( float x, float y, float w, float h, boolean isFilled ) { if ( isFilled ) fillRect( x, y, w, h ); else drawRect( x, y, w, h ); } public void drawRect( float x, float y, float w, float h ) { if ( w < 0 ) { w = -w; x -= w; } if ( h < 0 ) { h = -h; y -= h; } rectangle2D.setRect( x, y, w, h ); g2.draw( rectangle2D ); } public void fillRect( float x, float y, float w, float h ) { if ( w < 0 ) { w = -w; x -= w; } if ( h < 0 ) { h = -h; y -= h; } rectangle2D.setRect( x, y, w, h ); g2.fill( rectangle2D ); } public void drawCircle( float x, float y, float radius, boolean isFilled ) { ellipse2D.setFrame( x, y, 2*radius, 2*radius ); if ( isFilled ) g2.fill( ellipse2D ); else g2.draw( ellipse2D ); } public void drawCircle( float x, float y, float radius ) { drawCircle( x, y, radius, false ); } public void fillCircle( float x, float y, float radius ) { drawCircle( x, y, radius, true ); } public void drawCenteredCircle( float x, float y, float radius, boolean isFilled ) { x -= radius; y -= radius; drawCircle( x, y, radius, isFilled ); } public void drawArc( float center_x, // increases right float center_y, // increases down float radius, float startAngle, // in radians; zero for right, increasing counterclockwise float angleExtent, // in radians; positive for counterclockwise boolean isFilled ) { if ( isFilled ) { arc2D.setArcByCenter( center_x, center_y, radius, startAngle/Math.PI*180.0f, angleExtent/Math.PI*180.0f, Arc2D.PIE ); g2.fill( arc2D ); } else { arc2D.setArcByCenter( center_x, center_y, radius, startAngle/Math.PI*180.0f, angleExtent/Math.PI*180.0f, Arc2D.OPEN ); g2.draw( arc2D ); } } public void drawArc( float center_x, float center_y, float radius, float startAngle, // in radians float angleExtent // in radians ) { drawArc( center_x, center_y, radius, startAngle, angleExtent, false ); } public void fillArc( float center_x, float center_y, float radius, float startAngle, // in radians float angleExtent // in radians ) { drawArc( center_x, center_y, radius, startAngle, angleExtent, true ); } // returns the width of a string public float stringWidth( String s ) { if ( s == null || s.length() == 0 ) return 0; if ( fontMetrics == null ) { assert g2 != null; if ( g2 == null ) return 0; fontMetrics = g2.getFontMetrics( font ); } return fontMetrics.stringWidth( s ); } public void drawString( float x, float y, // lower left corner of the string String s // the string ) { if ( s == null || s.length() == 0 ) return; g2.setFont( font ); g2.drawString( s, x, y ); } }