/* * JFlow * Created by Tim De Pauw <http://pwnt.be/> * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package be.pwnt.jflow.shape; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.List; import javax.imageio.ImageIO; import be.pwnt.jflow.Configuration; import be.pwnt.jflow.Scene; import be.pwnt.jflow.geometry.Point3D; import be.pwnt.jflow.geometry.RotationMatrix; public class Picture extends Rectangle { private BufferedImage image; private Point3D[] projectedPoints; public Picture(BufferedImage image) { super(new Point3D(0, 0, 0), new Point3D(0, 0, 0), Color.black); this.image = image; projectedPoints = new Point3D[4]; setCoordinates(new Point3D(0, 0, 0), new Point3D(image.getWidth(), image.getHeight(), 0)); } public Picture(URL url) throws IOException { this(ImageIO.read(url)); } public int getWidth() { return image.getWidth(); } public int getHeight() { return image.getHeight(); } public BufferedImage getImage() { return image; } // XXX only works for convex 2D polygons @Override public boolean contains(Point3D point) { if (projectedPoints[0] == null) { return false; } boolean side = checkSide(point, projectedPoints[0], projectedPoints[1]); int i = 1; while (i < projectedPoints.length && side == checkSide(point, projectedPoints[i], projectedPoints[(i + 1) % projectedPoints.length])) { i++; } return i == projectedPoints.length; } private static boolean checkSide(Point3D point, Point3D p1, Point3D p2) { double c = (point.getY() - p1.getY()) * (p2.getX() - p1.getX()) - (point.getX() - p1.getX()) * (p2.getY() - p1.getY()); return c < 0; } private void project(Scene scene, Dimension surfaceSize) { Point3D loc = getLocation(); RotationMatrix rot = getRotationMatrix(); List<Point3D> points = getPoints(); Point3D[] proj = new Point3D[4]; int i = 0; for (Point3D p : points) { Point3D pt = new Point3D(p); pt.rotate(rot); pt.translate(loc); proj[i] = scene.project(pt, surfaceSize); proj[i].setZ(pt.getZ()); i++; } // XXX assumes too much about order Point3D bottomR = proj[0], bottomL = proj[1], topL = proj[2], topR = proj[3]; //comments this to prevent null point exception. // if (bottomR.getX() < 0 || bottomL.getX() >= surfaceSize.getWidth()) { // projectedPoints[0] = null; // return; // } projectedPoints[0] = topL; projectedPoints[1] = topR; projectedPoints[2] = bottomR; projectedPoints[3] = bottomL; if (topL.getX() != bottomL.getX() || topR.getX() != bottomR.getX()) { throw new RuntimeException(); } } private void setHighQuality(boolean on, Graphics2D g) { if (on) { g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } else { g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } } // FIXME only works if same x (no horizontal distortion) @Override public void paint(Graphics graphics, Scene scene, Dimension surfaceSize, boolean active, Configuration config) { Graphics2D g = (Graphics2D) graphics; Stroke defaultStroke = g.getStroke(); Stroke activeStroke = (config.activeShapeBorderWidth > 0 && config.activeShapeBorderColor != null ? new BasicStroke( config.activeShapeBorderWidth) : null); project(scene, surfaceSize); Point3D topL = projectedPoints[0]; Point3D topR = projectedPoints[1]; Point3D bottomR = projectedPoints[2]; Point3D bottomL = projectedPoints[3]; double x0 = topL.getX(); double x1 = topR.getX(); double y0 = Math.min(topL.getY(), topR.getY()); double heightLeft = bottomL.getY() - topL.getY(); double heightRight = bottomR.getY() - topR.getY(); int w = (int) Math.round(x1 - x0); if (w <= 0) { projectedPoints[0] = null; return; } double dt = topR.getY() - topL.getY(); if (dt < config.dragEaseOutFactor) dt = 0; boolean mirror = (dt < 0); if (mirror) { dt = -dt; } int l = (int) Math.round(x0); int r = (int) Math.round(x0 + w); int[] xPoints = new int[] { l, r, r, l }; int[] yPoints = new int[] { (int) Math.round(topL.getY()), (int) Math.round(topR.getY()), (int) Math.round(bottomR.getY()), (int) Math.round(bottomL.getY()) }; // reflection if (config.reflectionOpacity > 0) { setHighQuality(false, g); for (int x = 0; x < w; x++) { double d = 1.0 * x / w; int xo = (int) Math.round(d * image.getWidth()); int xt = (int) Math.round(x0 + x); double colY = dt * (mirror ? 1 - d : d); double colH = heightLeft + (heightRight - heightLeft) * d; int ryt0 = (int) Math.round(y0 + colY + colH); int ryt1 = (int) Math.round(y0 + colY + colH + colH); g.drawImage(image, xt, ryt1, xt + 1, ryt0, xo, 0, xo + 1, image.getHeight(), null); } int[] ryPoints = new int[] { (int) Math.round(topL.getY() + heightLeft), (int) Math.round(topR.getY() + heightRight), (int) Math.round(bottomR.getY() + heightRight), (int) Math.round(bottomL.getY() + heightLeft) }; if (active) { if (config.activeShapeOverlayColor != null) { g.setColor(config.activeShapeOverlayColor); g.fillPolygon(xPoints, ryPoints, 4); } // FIXME outer border doesn't receive overlay, so disable this // if (activeStroke != null) { // g.setColor(config.activeShapeBorderColor); // g.setStroke(activeStroke); // g.drawPolygon(xPoints, ryPoints, 4); // g.setStroke(defaultStroke); // } } g.setColor(getOverlayColor(1 - config.reflectionOpacity, config)); g.fillPolygon(xPoints, ryPoints, 4); } setHighQuality(config.highQuality, g); // image for (int x = 0; x < w; x++) { double d = 1.0 * x / w; int xo = (int) Math.round(d * image.getWidth()); int xt = (int) Math.round(x0 + x); double colY = dt * (mirror ? 1 - d : d); double colH = heightLeft + (heightRight - heightLeft) * d; double z = constrain( -topL.getZ() - (topR.getZ() - topL.getZ()) * d, 0, Double.MAX_VALUE); double ys = colY; double ym = colY + colH; int yt = (int) Math.round(y0 + ys); int yb = (int) Math.round(y0 + ym); g.drawImage(image, xt, yt, xt + 1, yb, xo, 0, xo + 1, image.getHeight(), null); } // gradient for image, drawLine has performance issue in MacOS. if (config.shadingFactor > 0 && dt != 0) { Color transparentColor = getOverlayColor(0, config); Color nonTransparentColor = getOverlayColor(0.9, config); g.setPaint(new GradientPaint((float)x0, (float)y0, mirror ? nonTransparentColor: transparentColor, (float)(x0 + w), (float)y0, mirror ? transparentColor: nonTransparentColor)); g.fillPolygon(new int[]{ (int)topL.getX(), (int)bottomL.getX(), (int)bottomR.getX(), (int)topR.getX() }, new int[]{ (int)topL.getY(), (int)bottomL.getY(), (int)bottomR.getY(), (int)topR.getY()}, 4); } // shade based on z if (config.shadingFactor > 0) { double aL = constrain(topL.getZ() * config.shadingFactor, 0, 1); double aR = constrain(topR.getZ() * config.shadingFactor, 0, 1); Color cL = getOverlayColor(aL, config); Color cR = getOverlayColor(aR, config); Paint oldPaint = g.getPaint(); g.setPaint(new GradientPaint((float) Math.round(x0), 0f, cL, (float) Math.round(x0 + w - 1), 0f, cR)); g.fillPolygon(xPoints, yPoints, 4); g.setPaint(oldPaint); } // border if (active) { if (config.activeShapeOverlayColor != null) { g.setColor(config.activeShapeOverlayColor); g.fillPolygon(xPoints, yPoints, 4); } if (activeStroke != null) { g.setColor(config.activeShapeBorderColor); g.setStroke(activeStroke); g.drawPolygon(xPoints, yPoints, 4); g.setStroke(defaultStroke); } } } private static Color getOverlayColor(double opacity, Configuration config) { Color base = config.backgroundColor; return new Color(base.getRed(), base.getGreen(), base.getBlue(), (int) Math.round(base.getAlpha() * opacity)); } private static double constrain(double a, double min, double max) { return a < min ? min : (a > max ? max : a); } }