package gutenberg.ditaa; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stathissideris.ascii2image.core.RenderingOptions; import org.stathissideris.ascii2image.core.Shape3DOrderingComparator; import org.stathissideris.ascii2image.graphics.Diagram; import org.stathissideris.ascii2image.graphics.DiagramShape; import org.stathissideris.ascii2image.graphics.DiagramText; import org.stathissideris.ascii2image.graphics.ShapePoint; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; /** * Almost completely copied from <a href="https://github.com/stathissideris/ditaa/blob/master/src/org/stathissideris/ascii2image/graphics/BitmapRenderer.java">org.stathissideris.ascii2image.graphics.BitmapRenderer</a> * * @author Efstathios Sideris * @author <a href="http://twitter.com/aloyer">@aloyer</a> (minor adjustements) */ public class GraphicsRenderer { private Logger log = LoggerFactory.getLogger(GraphicsRenderer.class); private Color backgroundColor = Color.WHITE; @SuppressWarnings("unchecked") public void render(Diagram diagram, Graphics2D g2, RenderingOptions options) { Object antialiasSetting = RenderingHints.VALUE_ANTIALIAS_OFF; if (options.performAntialias()) antialiasSetting = RenderingHints.VALUE_ANTIALIAS_ON; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting); g2.setColor(backgroundColor); g2.fillRect(0, 0, diagram.getWidth() + 10, diagram.getHeight() + 10); g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND)); ArrayList<DiagramShape> shapes = diagram.getAllDiagramShapes(); log.debug("Rendering " + shapes.size() + " shapes (groups flattened)"); Iterator<DiagramShape> shapesIt; if (options.dropShadows()) { //render shadows shapesIt = shapes.iterator(); while (shapesIt.hasNext()) { DiagramShape shape = shapesIt.next(); if (shape.getPoints().isEmpty()) continue; //GeneralPath path = shape.makeIntoPath(); GeneralPath path = shape.makeIntoRenderPath(diagram); float offset = diagram.getMinimumOfCellDimension() / 3.333f; if (path != null && shape.dropsShadow() && shape.getType() != DiagramShape.TYPE_CUSTOM) { GeneralPath shadow = new GeneralPath(path); AffineTransform translate = new AffineTransform(); translate.setToTranslation(offset, offset); shadow.transform(translate); g2.setColor(new Color(150, 150, 150)); g2.fill(shadow); } } //blur shadows // // if(true) { // int blurRadius = 6; // int blurRadius2 = blurRadius * blurRadius; // float blurRadius2F = blurRadius2; // float weight = 1.0f / blurRadius2F; // float[] elements = new float[blurRadius2]; // for (int k = 0; k < blurRadius2; k++) // elements[k] = weight; // Kernel myKernel = new Kernel(blurRadius, blurRadius, elements); // // //if EDGE_NO_OP is not selected, EDGE_ZERO_FILL is the default which creates a black border // ConvolveOp simpleBlur = // new ConvolveOp(myKernel, ConvolveOp.EDGE_NO_OP, null); // // BufferedImage destination = // new BufferedImage( // image.getWidth(), // image.getHeight(), // image.getType()); // // simpleBlur.filter(image, (BufferedImage) destination); // // //destination = destination.getSubimage(blurRadius/2, blurRadius/2, image.getWidth(), image.getHeight()); // g2 = (Graphics2D) destination.getGraphics(); // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting); // renderedImage = (RenderedImage) destination; // } } //fill and stroke float dashInterval = Math.min(diagram.getCellWidth(), diagram.getCellHeight()) / 2; //Stroke normalStroke = g2.getStroke(); float strokeWeight = diagram.getMinimumOfCellDimension() / 10; Stroke normalStroke = new BasicStroke( strokeWeight, //10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ); Stroke dashStroke = new BasicStroke( strokeWeight, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0, new float[]{dashInterval}, 0 ); //TODO: at this stage we should draw the open shapes first in order to make sure they are at the bottom (this is useful for the {mo} shape) //find storage shapes ArrayList<DiagramShape> storageShapes = new ArrayList<DiagramShape>(); shapesIt = shapes.iterator(); while (shapesIt.hasNext()) { DiagramShape shape = shapesIt.next(); if (shape.getType() == DiagramShape.TYPE_STORAGE) { storageShapes.add(shape); } } //render storage shapes //special case since they are '3d' and should be //rendered bottom to top //TODO: known bug: if a storage object is within a bigger normal box, it will be overwritten in the main drawing loop //(BUT this is not possible since tags are applied to all shapes overlaping shapes) Collections.sort(storageShapes, new Shape3DOrderingComparator()); g2.setStroke(normalStroke); shapesIt = storageShapes.iterator(); while (shapesIt.hasNext()) { DiagramShape shape = shapesIt.next(); GeneralPath path = shape.makeIntoRenderPath(diagram); if (!shape.isStrokeDashed()) { if (shape.getFillColor() != null) g2.setColor(shape.getFillColor()); else g2.setColor(Color.white); g2.fill(path); } if (shape.isStrokeDashed()) g2.setStroke(dashStroke); else g2.setStroke(normalStroke); g2.setColor(shape.getStrokeColor()); g2.draw(path); } //sort so that the largest shapes are rendered first Collections.sort(shapes, new ShapeAreaComparator()); //render the rest of the shapes ArrayList<DiagramShape> pointMarkers = new ArrayList<DiagramShape>(); shapesIt = shapes.iterator(); while (shapesIt.hasNext()) { DiagramShape shape = shapesIt.next(); if (shape.getType() == DiagramShape.TYPE_POINT_MARKER) { pointMarkers.add(shape); continue; } if (shape.getType() == DiagramShape.TYPE_STORAGE) { continue; } if (shape.getType() == DiagramShape.TYPE_CUSTOM) { renderCustomShape(shape, g2); continue; } if (shape.getPoints().isEmpty()) continue; GeneralPath path = shape.makeIntoRenderPath(diagram); //fill if (path != null && shape.isClosed() && !shape.isStrokeDashed()) { if (shape.getFillColor() != null) g2.setColor(shape.getFillColor()); else g2.setColor(Color.white); g2.fill(path); } //draw if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) { g2.setColor(shape.getStrokeColor()); if (shape.isStrokeDashed()) g2.setStroke(dashStroke); else g2.setStroke(normalStroke); g2.draw(path); } } //render point markers g2.setStroke(normalStroke); shapesIt = pointMarkers.iterator(); while (shapesIt.hasNext()) { DiagramShape shape = shapesIt.next(); //if(shape.getType() != DiagramShape.TYPE_POINT_MARKER) continue; GeneralPath path; path = shape.makeIntoRenderPath(diagram); g2.setColor(Color.white); g2.fill(path); g2.setColor(shape.getStrokeColor()); g2.draw(path); } //handle text //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //renderTextLayer(diagram.getTextObjects().iterator()); for (DiagramText text : (Iterable<DiagramText>) diagram.getTextObjects()) { g2.setFont(text.getFont()); if (text.hasOutline()) { g2.setColor(text.getOutlineColor()); g2.drawString(text.getText(), text.getXPos() + 1, text.getYPos()); g2.drawString(text.getText(), text.getXPos() - 1, text.getYPos()); g2.drawString(text.getText(), text.getXPos(), text.getYPos() + 1); g2.drawString(text.getText(), text.getXPos(), text.getYPos() - 1); } g2.setColor(text.getColor()); g2.drawString(text.getText(), text.getXPos(), text.getYPos()); } if (options.renderDebugLines()) { Stroke debugStroke = new BasicStroke( 1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ); g2.setStroke(debugStroke); g2.setColor(new Color(170, 170, 170)); g2.setXORMode(Color.white); for (int x = 0; x < diagram.getWidth(); x += diagram.getCellWidth()) g2.drawLine(x, 0, x, diagram.getHeight()); for (int y = 0; y < diagram.getHeight(); y += diagram.getCellHeight()) g2.drawLine(0, y, diagram.getWidth(), y); } } @SuppressWarnings("UnusedParameters") private void renderCustomShape(DiagramShape shape, Graphics2D g2) { log.warn("Unsupported custom shape rendering... {}", shape); } public class ShapeAreaComparator implements Comparator<DiagramShape> { /** * Puts diagram shapes in order or area starting from largest to smallest */ public int compare(DiagramShape shape1, DiagramShape shape2) { double y1 = calculateArea(shape1); double y2 = calculateArea(shape2); if (y1 > y2) return -1; if (y1 < y2) return 1; return 0; } /** * https://github.com/stathissideris/ditaa/blob/master/src/org/stathissideris/ascii2image/graphics/DiagramShape.java#L959 */ private double calculateArea(DiagramShape shape) { ArrayList points = shape.getPoints(); if (points.size() == 0) return 0; double area = 0; for (int i = 0; i < points.size() - 1; i++) { ShapePoint point1 = (ShapePoint) points.get(i); ShapePoint point2 = (ShapePoint) points.get(i + 1); area += point1.x * point2.y; area -= point2.x * point1.y; } ShapePoint point1 = (ShapePoint) points.get(points.size() - 1); ShapePoint point2 = (ShapePoint) points.get(0); area += point1.x * point2.y; area -= point2.x * point1.y; return Math.abs(area / 2); } } }