/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008). * Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org). * This source code is released under a BSD License, which allows redistribution * in source and binary form, with or without modification, provided copyright * and license information are included, and with no warranty or guarantee of * any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt */ package vmm.core.render; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import vmm.core.Transform; import vmm.core.View; public class ImageRenderer2D implements Renderer2D { protected BufferedImage image; protected Transform transform; protected Graphics2D currentGraphics; protected Color foregroundColor; // from the View protected Color backgroundColor; protected boolean antialiased; protected Color currentColor; private Point2D tempPoint = new Point2D.Double(); public void startRender(View view, Transform transform, int width, int height) { if (image == null || image.getWidth() != width || image.getHeight() != height || image.getType() != BufferedImage.TYPE_INT_RGB) { image = null; image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); } this.transform = transform; foregroundColor = view.getForeground(); backgroundColor = view.getBackground(); currentGraphics = image.createGraphics(); currentGraphics.setBackground(backgroundColor); currentGraphics.clearRect(0, 0, width, height); currentColor = foregroundColor; currentGraphics.setColor(currentColor); antialiased = view.getAntialiased(); if (antialiased) currentGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); transform.setUpDrawInfo(currentGraphics, 0, 0, width, height, view.getPreserveAspect(), view.getApplyGraphics2DTransform()); } public boolean restartRender(View view, Transform transform, int width, int height) { if (image == null || image.getWidth() != width || image.getHeight() != height) return false; this.transform = transform; foregroundColor = view.getForeground(); backgroundColor = view.getBackground(); currentGraphics = image.createGraphics(); currentColor = foregroundColor; currentGraphics.setColor(currentColor); antialiased = view.getAntialiased(); if (antialiased) if (view.getAntialiased()) currentGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); transform.setUpDrawInfo(currentGraphics, 0, 0, width, height, view.getPreserveAspect(), view.getApplyGraphics2DTransform()); return true; } public void endRender() { currentGraphics.dispose(); currentGraphics = null; transform.finishDrawing(); transform = null; } public void dispose() { image = null; } public void draw(Graphics2D g) { g.drawImage(image,0,0,null); } public BufferedImage getImage(boolean alwaysCopy) { if ( image == null || !alwaysCopy ) return image; BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); Graphics g = copy.getGraphics(); g.drawImage(image,0,0,null); g.dispose(); return copy; } /** * This method will set the color in the current graphics context. This method should only * be used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called * at other times, there is no effect. * @param c the color to be used for subsequent drawing; if c is null, then the default foreground * color of the View is restored. */ public void setColor(Color c) { if (currentGraphics != null) currentGraphics.setColor( c == null? foregroundColor : c ); currentColor = currentGraphics.getColor(); } /** * Returns the drawing color of the current graphics context. This method should only be * used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called * at other times, the return value is null. */ public Color getColor() { return (currentGraphics == null)? null : currentGraphics.getColor(); } /** * Returns the Stroke used by current graphics context. This method should only be * used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called * at other times, the return value is null. */ public Stroke getStroke() { if (currentGraphics != null) return currentGraphics.getStroke(); else return null; } /** * This method will set the Stroke for the current graphics context. This method should only * be used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called * at other times, there is no effect. * @param stroke the stroke to be used for subsequent drawing; if stroke is null, then a default stroke * is used with a width of 1 pixel. */ public void setStroke(Stroke stroke) { if (currentGraphics != null) currentGraphics.setStroke(stroke == null? new BasicStroke(transform.getDefaultStrokeSize()) : stroke); } /** * Draws a point by turning on a single pixel. This is done by transforming (x,y) to pixel coordinates, * then using drawPixelDirect to set the color of the single pixel. * @see #drawPixelDirect(Color, int, int) * @param x The x-coordinate of the point, in window (real xy) coodinates. * @param y The y-coordinate of the point, in window (real xy) coodinates. */ public void drawPixel(double x, double y) { tempPoint.setLocation(x,y); transform.windowToViewport(tempPoint); int xInt = (int)(tempPoint.getX() + 0.4999); int yInt = (int)(tempPoint.getY() + 0.4999); int rgb = currentColor.getRGB(); try { image.setRGB(xInt,yInt,rgb); } catch (Exception e) { } } /** * Draws a dot of specified diameter centered at a specified point. * This is done by calling the Graphics2D fill() command for an * appropriate Ellipse2D. The diameter is specified in pixels. * Note that if the preserveAspectRatio is off for this View, then * the dot will can be an oval rather than a circle. */ public void drawDot(Point2D pt, double diameter) { tempPoint.setLocation(pt); transform.windowToDrawingCoords(tempPoint); double h = diameter*transform.getPixelWidth(); double w = diameter*transform.getPixelHeight(); currentGraphics.fill(new Ellipse2D.Double(pt.getX()-h/2,pt.getY()-w/2,h,w)); } /** * Draws a list of pixels in the current drawing context, where the pixels are specified in object coordinates. * This should only be called while drawing is in progress.*/ public void drawPixels(Point2D[] points, int pointIndexStart, int pointIndexEnd) { // points in window coordinates if (points == null) return; if (pointIndexStart >= points.length) pointIndexStart = points.length - 1; if (pointIndexStart < 0) pointIndexStart = 0; if (pointIndexEnd >= points.length) pointIndexEnd = points.length - 1; if (pointIndexEnd < 0) pointIndexEnd = 0; if (pointIndexEnd <= pointIndexStart) return; Color color = currentGraphics.getColor(); int rgb = color.getRGB(); for (int i = pointIndexStart; i <= pointIndexEnd; i++) { if (points[i] != null) { tempPoint.setLocation(points[i]); transform.windowToViewport(tempPoint); try { image.setRGB((int)(tempPoint.getX()+0.499), (int)(tempPoint.getY()+0.499),rgb); } catch (Exception e) { } } } } /** * This can be called during drawing to draw a string at a specified point, given in window (real x,y) coordinates. * The font is NOT transformed, as it would be if you simply used the drawString * method of a drawing context to which a transform has been applied. * The point (x,y) is properly transformed from xy-coordinates to pixel coordinates, whether a * transform has been applied ot the drawing context or not. After conversion, if necessary, the * graphics context returned by {@link Transform#getUntransformedGraphics()} is used to draw the string. * @see #drawString(String, Point2D) */ public void drawString(String s, double x, double y) { Point2D pt = new Point2D.Double(x,y); transform.windowToViewport(pt); transform.getUntransformedGraphics().drawString(s,(float)pt.getX(),(float)pt.getY()); } /** * Draws a line in the current drawing context. This should only be called while drawing is * in progress. The line has endpoints (x1,y1) and (x2,y2), where the coordinates are * in window (real xy) coordinates. This should only be called when a drawing operation is in * progress. */ public void drawLine(double x1, double y1, double x2, double y2) { double tx1, ty1; tempPoint.setLocation(x1,y1); transform.windowToDrawingCoords(tempPoint); tx1 = tempPoint.getX(); ty1 = tempPoint.getY(); tempPoint.setLocation(x2,y2); transform.windowToDrawingCoords(tempPoint); currentGraphics.draw(new Line2D.Double(tx1, ty1, tempPoint.getX(), tempPoint.getY())); } /** * Draws a curve in the current drawing context. The points on the curve are given in window (real xy) coordinates. * This should only be called while drawing is in progress. * @param points The curve is drawn through some or all of the points in this array. If the array is null, nothing is done. * The curve is acutually just made up of lines from one point to the next. An element in the array can be null. * In that case, one or two segments are missing from the curve -- the segments on either side of the missing point. * Consecutive points are also not joined by a segment if the jump from one point to the next is too large. * @param pointIndexStart The number of points in the array that should be used for the curve. A curve is drawn * though points[pointIndexStart], point[pointIndexStart+1], ..., points[pointIndexEnd]. The value of pointIndexStart * is clamped to lie in the range 0 to points.length-1. * @param pointIndexEnd The number of points in the array that should be used for the curve. A curve is drawn * though points[pointIndexStart], point[pointIndexStart+1], ..., points[pointIndexEnd]. The value of pointIndexEnd * is clamped to lie in the range 0 to points.length-1. If pointIndexEnd is less than or equal to pointIndexStart, * nothing is drawn. */ public void drawCurve(Point2D[] points, int pointIndexStart, int pointIndexEnd) { // points in window coordinates if (points == null) return; if (pointIndexStart >= points.length) pointIndexStart = points.length - 1; if (pointIndexStart < 0) pointIndexStart = 0; if (pointIndexEnd >= points.length) pointIndexEnd = points.length - 1; if (pointIndexEnd < 0) pointIndexEnd = 0; if (pointIndexEnd <= pointIndexStart) return; GeneralPath curve = new GeneralPath(); double maxJumpX = Math.abs(transform.getXmax() - transform.getXmin())/4; double maxJumpY = Math.abs(transform.getYmax() - transform.getYmin())/4; if (transform.appliedTransform2D()) maxJumpX = maxJumpY = Math.max(maxJumpX,maxJumpY); boolean moved = false; for (int i = pointIndexStart; i < pointIndexEnd; i++) { if (points[i] != null) { tempPoint.setLocation(points[i]); transform.windowToDrawingCoords(tempPoint); if (i == pointIndexStart) curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY()); else if (i > pointIndexStart && points[i-1] != null && Math.abs(points[i].getX() - points[i-1].getX()) <= maxJumpX && Math.abs(points[i].getY() - points[i-1].getY()) <= maxJumpY) curve.lineTo((float)tempPoint.getX(), (float)tempPoint.getY()); else { curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY()); moved = true; } } } if (points[pointIndexEnd] != null) { if ( (pointIndexStart == 0) && (pointIndexEnd == points.length-1) && (points[0] != null) && (Math.abs(points[0].getX() - points[pointIndexEnd].getX()) <= transform.getPixelWidth()/100 ) && (Math.abs(points[0].getY() - points[pointIndexEnd].getY()) <= transform.getPixelHeight()/100 ) && (!moved) ) { curve.closePath(); // System.out.println("Path was closed"); // Do NOT close if moved = true - leads to errors. } else { // replaced from here tempPoint.setLocation(points[pointIndexEnd]); transform.windowToDrawingCoords(tempPoint); if (points[pointIndexEnd-1] != null && Math.abs(points[pointIndexEnd].getX() - points[pointIndexEnd-1].getX()) <= maxJumpX && Math.abs(points[pointIndexEnd].getY() - points[pointIndexEnd-1].getY()) <= maxJumpY) curve.lineTo((float)tempPoint.getX(), (float)tempPoint.getY()); else curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY()); } } currentGraphics.draw(curve); } public void drawOval(double x, double y, double width, double height) { Point2D pt1 = new Point2D.Double(x,y); Point2D pt2 = new Point2D.Double(x+width,y+height); transform.windowToDrawingCoords(pt1); transform.windowToDrawingCoords(pt2); currentGraphics.draw(new Ellipse2D.Double(pt1.getX(), pt1.getY(), pt2.getX()-pt1.getX(), pt2.getY()-pt1.getY())); } public void fillOval(double x, double y, double width, double height) { Point2D pt1 = new Point2D.Double(x,y); Point2D pt2 = new Point2D.Double(x+width,y+height); transform.windowToDrawingCoords(pt1); transform.windowToDrawingCoords(pt2); currentGraphics.fill(new Ellipse2D.Double(pt1.getX(), pt1.getY(), pt2.getX()-pt1.getX(), pt2.getY()-pt1.getY())); } public void drawRect(double x, double y, double width, double height) { Point2D pt1 = new Point2D.Double(x,y); Point2D pt2 = new Point2D.Double(x+width,y+height); transform.windowToDrawingCoords(pt1); transform.windowToDrawingCoords(pt2); currentGraphics.draw(new Rectangle2D.Double(pt1.getX(), pt1.getY(), pt2.getX()-pt1.getX(), pt2.getY()-pt1.getY())); } public void fillRect(double x, double y, double width, double height) { Point2D pt1 = new Point2D.Double(x,y); Point2D pt2 = new Point2D.Double(x+width,y+height); transform.windowToDrawingCoords(pt1); transform.windowToDrawingCoords(pt2); currentGraphics.fill(new Rectangle2D.Double(pt1.getX(), pt1.getY(), pt2.getX()-pt1.getX(), pt2.getY()-pt1.getY())); } public void drawCrosshair(double x, double y, int armlength, int strokeWidth, Color color, Color background) { Point2D pt = new Point2D.Double(x,y); transform.windowToViewport(pt); int cx = (int)(pt.getX()+0.499); int cy = (int)(pt.getY()+0.499); Graphics2D g1 = transform.getUntransformedGraphics(); Color saveColor = g1.getColor(); int size = 2*armlength + strokeWidth; int offset = (size+1)/2; int offset2 = (strokeWidth+1)/2; if (backgroundColor != null) { g1.setColor(backgroundColor); g1.fillRect(cx-offset-1, cy-offset2-1, size+2, strokeWidth+2); g1.fillRect(cx-offset2-1, cy-offset-1, strokeWidth+2, size+2); } if (color != null) g1.setColor(color); g1.fillRect(cx-offset, cy-offset2, size, strokeWidth); g1.fillRect(cx-offset2, cy-offset, strokeWidth, size); g1.setColor(saveColor); } public void setDrawAntialiased(boolean antialiased) { currentGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiased ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); this.antialiased = antialiased; } public boolean getDrawAntialiased() { return antialiased; } // //--------------------------- Some pixel-oriented drawing methods ----------------------- // // /** // * Sets the pixel with pixel coordinates (x,y) to be a specified color. // * The pixel color is changed in the off-screen image, not on the screen immmediately. // * The current transformation is not applied to the coordinates. // * @param color the color for the pixel; if null, the current drawing color is used. // * @param x the horizontal pixel coordinate. // * @param y the vertical pixel coordinate. // */ // public void drawPixelDirect(Color color, int x, int y) { // if (x < 0 || y < 0 || x >= currentImage.getWidth() || y >= currentImage.getHeight()) // return; // if (color == null) // color = currentGraphics.getColor(); // int rgb; // rgb = (color.getRed() << 16) | (color.getGreen() << 8) | (color.getBlue()); // currentImage.setRGB(x,y,rgb); // } // // /** // * Draws a line in the current color between points that are specified using // * pixel coordinates. // */ // public void drawLineDirect(int x1, int y1, int x2, int y2) { // Graphics2D g = currentImage.createGraphics(); // g.setColor(currentColor); // if (antialiased) // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // g.drawLine(x1,y1,x2,y2); // g.dispose(); // } // // /** // * Draws a filled-in rectangle in the current color, where the rectangle is specified in pixel coordinates. // */ // public void fillRectDirect(int x, int y, int width, int height) { // Graphics g = currentImage.getGraphics(); // g.setColor(currentColor); // g.fillRect(x,y,width,height); // g.dispose(); // } // // public void setImageRGBDirect(int x, int y, int width, int height, int[] rgb) { // try { // currentImage.setRGB(x,y,width,height,rgb,0,width); // } // catch (Exception e) { // } // } // // }