/* * JAME 6.2.1 * http://jame.sourceforge.net * * Copyright 2001, 2016 Andrea Medeghini * * This file is part of JAME. * * JAME is an application for creating fractals and other graphics artifacts. * * JAME is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JAME is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JAME. If not, see <http://www.gnu.org/licenses/>. * */ package net.sf.jame.contextfree.renderer.support; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.Collections; import java.util.Random; import java.util.Stack; import java.util.logging.Logger; public class CFRenderer { private static final Logger logger = Logger.getLogger(CFRenderer.class.getName()); public static final int MAX_SHAPES = 5000000; public static final float MIN_SIZE = 0.0001f; public static final float SHAPE_BORDER = 2.0f; public static final float FIXED_BORDER = 8.0f; private final Random random; private final ArrayList<CFFinishedShape> finishedSet = new ArrayList<CFFinishedShape>(); private final ArrayList<CFShape> unfinishedSet = new ArrayList<CFShape>(); private final CFContext context; private final CFBounds bounds; private float totalArea; private float scaleArea; private float minArea; private float scale; private float currScale; private float fixedBorder; private int width; private int height; private volatile boolean requestStop; private AffineTransform currTrans; private boolean fullOutput; public CFRenderer(CFContext context, int variation, int width, int height, float border, float minSize) { this.context = context; bounds = new CFBounds(); minSize = (minSize < 0.01f) ? 0.01f : minSize; this.minArea = minSize * minSize; this.scaleArea = 1; this.width = width; this.height = height; this.fixedBorder = ((border < 0) ? 0 : border); random = new Random(variation); if (context.isSized() || context.isTiled()) { fixedBorder = 0.0f; bounds.setMinX(-context.getTileX() / 2.0); bounds.setMinY(-context.getTileY() / 2.0); bounds.setMaxX(+context.getTileX() / 2.0); bounds.setMaxY(+context.getTileY() / 2.0); rescale(width, height, true); scaleArea = currScale * currScale; } else { rescale(width, height, true); scaleArea = currScale * currScale; } } public int getUnfinishedCount() { return unfinishedSet.size(); } public int getFinishedCount() { return finishedSet.size(); } public CFShape nextUnfinishedShape() { return unfinishedSet.remove(0); } public void render(Graphics2D g2d, boolean partial) { if (!partial || fullOutput) { CFColor c = context.getBackground(); Color color = Color.getHSBColor(c.getHue() / 360, c.getSaturation(), c.getBrightness()); Composite composite = AlphaComposite.Src.derive(c.getAlpha()); AffineTransform tmpTransform = g2d.getTransform(); Composite tmpComposite = g2d.getComposite(); Color tmpColor = g2d.getColor(); g2d.setComposite(composite); g2d.setColor(color); g2d.fillRect(0, 0, width, height); g2d.translate(width / 2, height / 2); g2d.scale(1, -1); g2d.translate(-width / 2, -height / 2); g2d.translate(fixedBorder, fixedBorder); g2d.transform(currTrans); CFShapeRenderer render = null; if (context.isTiled() || context.isSized()) { render = new TiledShapeRenderer(g2d, context); } else { render = new SimpleShapeRenderer(g2d, context); } for (CFFinishedShape shape : finishedSet) { shape.render(render); if (Thread.currentThread().isInterrupted()) { break; } } g2d.setTransform(tmpTransform); g2d.setComposite(tmpComposite); g2d.setColor(tmpColor); fullOutput = false; } } private void rescale(int width, int height, boolean last) { AffineTransform transform = new AffineTransform(); float scale = (float) bounds.computeScale(width, height, fixedBorder, transform, context.isTiled() || context.isSized()); if (last || currScale == 0.0 || (currScale * 0.90) > scale) { currScale = scale; currTrans = transform; fullOutput = true; } } public void addUnfinishedShape(CFShape shape) { unfinishedSet.add(shape); } public void addFinishedShape(CFFinishedShape shape) { finishedSet.add(shape); } public void sortShapes() { Collections.sort(finishedSet, new CFFinishedShapeComparator()); } public void executeShape(CFShape shape) { Stack<CFShape> shapeStack = new Stack<CFShape>(); Stack<Integer> counterStack = new Stack<Integer>(); Stack<Integer> replacementStack = new Stack<Integer>(); CFRule rule = context.findRule(shape.getInitialShapeType(), random.nextDouble()); if (rule == null) { unfinishedSet.clear(); return; } for (int i = 0; i < rule.getReplacementCount(); i++) { if (requestStop) { shapeStack.clear(); counterStack.clear(); replacementStack.clear(); return; } if (rule.getReplacement(i).getShapeType() == context.getLoopStartShapeType()) { shapeStack.push(shape.clone()); counterStack.push(0); replacementStack.push(i); continue; } if (rule.getReplacement(i).getShapeType() == context.getLoopEndShapeType()) { if (replacementStack.peek() == i - 2) { CFShape s = shape.clone(); shape = shapeStack.peek(); for (int c = 1; c < rule.getReplacement(i).getLoopCount(); c++) { s.concatenate(rule.getReplacement(i)); CFShape t = s.clone(); t.concatenate(rule.getReplacement(i - 1)); processShape(t); if (requestStop) { break; } } shapeStack.pop(); counterStack.pop(); replacementStack.pop(); continue; } else { int j = counterStack.pop(); counterStack.push(j + 1); if (counterStack.peek() >= rule.getReplacement(i).getLoopCount()) { shape = shapeStack.pop(); counterStack.pop(); replacementStack.pop(); } else { shape.concatenate(rule.getReplacement(i)); i = replacementStack.peek(); } continue; } } CFShape t = shape.clone(); t.concatenate(rule.getReplacement(i)); processShape(t); } } public void processShape(CFShape shape) { double area = shape.area(); if (Double.isInfinite(area)) { requestStop = true; return; } ShapeType shapeType = context.shapeType(shape.getInitialShapeType()); if (shapeType.getType() == ShapeType.TYPE_RULE && shapeType.hasRules()) { if (!bounds.isValid() || area * scaleArea >= minArea) { unfinishedSet.add(shape); // } else { // logger.debug("Small area, stop recursion for shape " + context.decodeShapeName(shape.getInitialShapeType())); } } else if (shapeType.getType() == ShapeType.TYPE_PATH) { CFShape tmpShape = shape.clone(); CFRule rule = context.findRule(tmpShape.getInitialShapeType(), 0); CFPath path = rule.getPath(); if (path == null) { return; } if (rule.getAttributeCount() == 0) { rule.addAttribute(new CFPathAttribute(CFPathCommand.FILL)); } else if (rule.getAttribute(rule.getAttributeCount() - 1).getCommand() != CFPathCommand.FILL && rule.getAttribute(rule.getAttributeCount() - 1).getCommand() != CFPathCommand.STROKE) { rule.addAttribute(new CFPathAttribute(CFPathCommand.FILL)); } Stack<CFShape> shapeStack = new Stack<CFShape>(); Stack<Integer> counterStack = new Stack<Integer>(); Stack<CFPathAttribute> attributeStack = new Stack<CFPathAttribute>(); for (int i = 0; i < rule.getAttributeCount(); i++) { CFPathAttribute attribute = rule.getAttribute(i); if (attribute.getCommand() == CFPathCommand.LOOP_START) { shapeStack.push(tmpShape.clone()); counterStack.push(0); attributeStack.push(attribute); continue; } if (attribute.getCommand() == CFPathCommand.LOOP_END) { int j = counterStack.pop(); counterStack.push(j + 1); if (counterStack.peek() >= attribute.getCount()) { tmpShape = shapeStack.pop(); counterStack.pop(); attributeStack.pop(); } else { tmpShape.getModification().concatenate(attribute.getModification()); attribute = attributeStack.peek(); } continue; } if (attribute.getCommand() == CFPathCommand.FILL || attribute.getCommand() == CFPathCommand.STROKE) { CFShape finalShape = tmpShape.clone(); finalShape.getModification().concatenate(attribute.getModification()); totalArea += finalShape.area() * attribute.area(); CFPathAttribute finalAttribute = attribute.clone(); finalAttribute.setModification(finalShape.getModification()); if (Double.isInfinite(finalAttribute.area())) { requestStop = true; return; } finishedSet.add(new CFFinishedShape(path, finalAttribute, totalArea)); if (!context.isTiled() && !context.isSized()) { if (scale == 0) { scale = (float) ((width + height) / Math.sqrt(finalAttribute.area())); } bounds.update(path, finalAttribute, scale); scale = (float) bounds.computeScale(width, height, fixedBorder, currTrans, false); scaleArea = scale * scale; } } } } else { logger.warning("Shape with no rule: " + context.decodeShapeName(shape.getInitialShapeType())); } } public void dump() { for (CFFinishedShape shape : finishedSet) { logger.info(shape.toString()); } } public boolean isRequestStop() { return requestStop; } public void setRequestStop(boolean requestStop) { this.requestStop = requestStop; } }