1 /* 2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab 3 * Copyright (C) 2012 - Scilab Enterprises - Calixte Denizet 4 * 5 * Copyright (C) 2012 - 2016 - Scilab Enterprises 6 * 7 * This file is hereby licensed under the terms of the GNU GPL v2.0, 8 * pursuant to article 5.3.4 of the CeCILL v.2.1. 9 * This file was originally licensed under the terms of the CeCILL v2.1, 10 * and continues to be available under such terms. 11 * For more information, see the COPYING file which you should have received 12 * along with this program. 13 */ 14 15 package org.scilab.forge.scirenderer.implementation.g2d.motor; 16 17 import java.awt.BasicStroke; 18 import java.awt.Color; 19 import java.awt.GradientPaint; 20 import java.awt.Graphics2D; 21 import java.awt.LinearGradientPaint; 22 import java.awt.Paint; 23 import java.awt.RenderingHints; 24 import java.awt.Shape; 25 import java.awt.Stroke; 26 import java.awt.geom.AffineTransform; 27 import java.awt.geom.Area; 28 import java.awt.geom.NoninvertibleTransformException; 29 import java.awt.geom.Path2D; 30 import java.awt.geom.Rectangle2D; 31 import java.awt.image.BufferedImage; 32 import java.awt.image.DataBufferInt; 33 34 import java.awt.geom.Point2D; 35 import java.awt.MultipleGradientPaint; 36 37 /** 38 * @author Calixte DENIZET 39 */ 40 public final class DrawTools { 41 42 private static final Stroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); 43 private static final Stroke EMPTYSTROKE = new BasicStroke(0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); 44 private static final Color TRANSLUCENT_BLACK = new Color(0, 0, 0, 0); 45 46 /** 47 * Fill a triangle in using a Gouraud shading 48 * Only two gradient are used rather than three. 49 * @param g2d the Graphics2D where to draw 50 * @param t the Triangle to fill 51 */ fillGouraud(final Graphics2D g2d, final Triangle t)52 public static final void fillGouraud(final Graphics2D g2d, final Triangle t) { 53 Path2D contour = t.getProjectedContour(); 54 double[] v0 = new double[] {t.vertices[0].getX(), t.vertices[0].getY()}; 55 double[] v1 = new double[] {t.vertices[1].getX(), t.vertices[1].getY()}; 56 double[] v2 = new double[] {t.vertices[2].getX(), t.vertices[2].getY()}; 57 //double[] pv0 = get2DProjection(v0[0], v0[1], v1[0], v1[1], v2[0], v2[1]); 58 double[] pv1 = get2DProjection(v1[0], v1[1], v0[0], v0[1], v2[0], v2[1]); 59 double[] pv2 = get2DProjection(v2[0], v2[1], v0[0], v0[1], v1[0], v1[1]); 60 61 Paint oldPaint = g2d.getPaint(); 62 Area area = new Area(contour); 63 area.add(new Area(stroke.createStrokedShape(contour))); 64 65 66 g2d.setColor(t.getColor(0)); 67 g2d.fill(area); 68 69 70 float[] col = t.getColor(1).getComponents(null); 71 GradientPaint gp = new GradientPaint((float) v1[0], (float) v1[1], t.getColor(1), (float) pv1[0], (float) pv1[1], new Color(col[0], col[1], col[2], 0.0f)); 72 g2d.setPaint(gp); 73 g2d.fill(area); 74 75 76 col = t.getColor(2).getComponents(null); 77 gp = new GradientPaint((float) v2[0], (float) v2[1], t.getColor(2), (float) pv2[0], (float) pv2[1], new Color(col[0], col[1], col[2], 0.0f)); 78 g2d.setPaint(gp); 79 g2d.fill(area); 80 81 g2d.setPaint(oldPaint); 82 } 83 84 /** 85 * Draw a texture (ie a BufferedImage) in a triangle 86 * @param g2d the Graphics2D where to draw 87 * @param image the texture to apply 88 * @param ximg the x-coordinates of the triangle to use in the texture 89 * @param yimg the y-coordinates of the triangle to use in the texture 90 * @param xdest the x-coordinates of the destination triangle 91 * @param ydest the y-coordinates of the destination triangle 92 * @param key the rendering hint to use for interpolation 93 */ drawTriangleTexture(final Graphics2D g2d, final BufferedImage image, final double[] ximg, final double[] yimg, final double[] xdest, final double[] ydest, Object key)94 public static final void drawTriangleTexture(final Graphics2D g2d, final BufferedImage image, final double[] ximg, final double[] yimg, final double[] xdest, final double[] ydest, Object key) { 95 try { 96 double w = image.getWidth(); 97 double h = image.getHeight(); 98 99 Path2D.Double path = new Path2D.Double(); 100 path.moveTo(xdest[0], ydest[0]); 101 path.lineTo(xdest[1], ydest[1]); 102 path.lineTo(xdest[2], ydest[2]); 103 path.closePath(); 104 Area area = new Area(path); 105 area.add(new Area(stroke.createStrokedShape(path))); 106 107 boolean is1d = is1d(ximg, yimg); 108 109 // if we have a 1D texture we must slighlty modified the coordinates to use the algorithm below. 110 if (checkSourceCoordinates(ximg, yimg)) { 111 // three coordinates are the same in 1D texture 112 int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 113 int index = (int) Math.floor(w * ximg[0]); 114 Color color; 115 if (index >= pixels.length) { 116 color = new Color(pixels[pixels.length - 1]); 117 } else if (index < 0) { 118 color = new Color(pixels[0]); 119 } else { 120 color = new Color(pixels[index]); 121 } 122 //color = Color.PINK; 123 124 g2d.setColor(color); 125 g2d.fill(area); 126 return; 127 } 128 129 AffineTransform translationDest = AffineTransform.getTranslateInstance(xdest[0], ydest[0]); 130 AffineTransform translationImg = AffineTransform.getTranslateInstance(-w * ximg[0], -h * yimg[0]); 131 132 AffineTransform toDest = new AffineTransform(xdest[1] - xdest[0], ydest[1] - ydest[0], xdest[2] - xdest[0], ydest[2] - ydest[0], 0, 0); 133 AffineTransform fromImg = new AffineTransform(w * (ximg[1] - ximg[0]), h * (yimg[1] - yimg[0]), w * (ximg[2] - ximg[0]), h * (yimg[2] - yimg[0]), 0, 0).createInverse(); 134 135 AffineTransform transformation = new AffineTransform(); 136 transformation.concatenate(translationDest); 137 transformation.concatenate(toDest); 138 transformation.concatenate(fromImg); 139 transformation.concatenate(translationImg); 140 141 AffineTransform oldTransform = g2d.getTransform(); 142 143 // For now we don't enter in this 144 // SVGGraphics2D doesn't handle MultipleGradient :( 145 if (false && is1d && key == RenderingHints.VALUE_INTERPOLATION_BILINEAR) { 146 int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 147 float[] fractions = new float[pixels.length]; 148 for (int i = 0; i < fractions.length; i++) { 149 fractions[i] = (((float) i) / (fractions.length - 1)); 150 } 151 152 Color[] colors = new Color[pixels.length]; 153 for (int i = 0; i < colors.length; i++) { 154 colors[i] = new Color(pixels[i]); 155 } 156 LinearGradientPaint gradient = new LinearGradientPaint(new Point2D.Double(0, 0), new Point2D.Double(pixels.length, 0), fractions, colors, MultipleGradientPaint.CycleMethod.NO_CYCLE, MultipleGradientPaint.ColorSpaceType.SRGB, transformation); 157 Shape oldClip = g2d.getClip(); 158 g2d.clip(area); 159 g2d.setPaint(gradient); 160 g2d.fill(area); 161 g2d.setClip(oldClip); 162 } else { 163 clamp(g2d, ximg, yimg, xdest, ydest, transformation, image); 164 Object oldKey = g2d.getRenderingHint(RenderingHints.KEY_INTERPOLATION); 165 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, key); 166 g2d.setStroke(EMPTYSTROKE); 167 Shape oldClip = g2d.getClip(); 168 g2d.clip(area); 169 g2d.drawImage(image, transformation, null); 170 g2d.setClip(oldClip); 171 if (oldKey != null) { 172 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldKey); 173 } 174 } 175 176 g2d.setTransform(oldTransform); 177 } catch (NoninvertibleTransformException e) { 178 System.err.println(e); 179 } 180 } 181 182 /** 183 * Draw a texture (ie a BufferedImage) in a parallelogram 184 * @param g2d the Graphics2D where to draw 185 * @param image the texture to apply 186 * @param ximg the x-coordinates of the parallelogram to use in the texture 187 * @param yimg the y-coordinates of the parallelogram to use in the texture 188 * @param xdest the x-coordinates of the destination parallelogram 189 * @param ydest the y-coordinates of the destination parallelogram 190 * @param key the rendering hint to use for interpolation 191 */ drawParallelogramTexture(final Graphics2D g2d, final BufferedImage image, final double[] ximg, final double[] yimg, final double[] xdest, final double[] ydest, Object key)192 public static final void drawParallelogramTexture(final Graphics2D g2d, final BufferedImage image, final double[] ximg, final double[] yimg, final double[] xdest, final double[] ydest, Object key) { 193 try { 194 Object oldKey = g2d.getRenderingHint(RenderingHints.KEY_INTERPOLATION); 195 196 double w = image.getWidth(); 197 double h = image.getHeight(); 198 199 AffineTransform translationDest = AffineTransform.getTranslateInstance(xdest[0], ydest[0]); 200 AffineTransform translationImg = AffineTransform.getTranslateInstance(-w * ximg[0], -h * yimg[0]); 201 202 AffineTransform toDest = new AffineTransform(xdest[1] - xdest[0], ydest[1] - ydest[0], xdest[2] - xdest[0], ydest[2] - ydest[0], 0, 0); 203 AffineTransform fromImg = new AffineTransform(w * (ximg[1] - ximg[0]), h * (yimg[1] - yimg[0]), w * (ximg[2] - ximg[0]), h * (yimg[2] - yimg[0]), 0, 0).createInverse(); 204 205 AffineTransform transformation = new AffineTransform(); 206 transformation.concatenate(translationDest); 207 transformation.concatenate(toDest); 208 transformation.concatenate(fromImg); 209 transformation.concatenate(translationImg); 210 211 AffineTransform oldTransform = g2d.getTransform(); 212 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, key); 213 g2d.drawImage(image, transformation, null); 214 g2d.setTransform(oldTransform); 215 216 if (oldKey != null) { 217 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldKey); 218 } 219 } catch (NoninvertibleTransformException e) { } 220 } 221 222 /** 223 * Check and modify the coordinates if we have a 1D texture (i.e. all the y-coords are zero) 224 * @param x the x-coordinates 225 * @param y the y-coordinates 226 */ checkSourceCoordinates(final double[] x, final double[] y)227 private static final boolean checkSourceCoordinates(final double[] x, final double[] y) { 228 if (is1d(x, y)) { 229 if (!AbstractDrawable3DObject.isEqual(x[0], x[1]) && !AbstractDrawable3DObject.isEqual(x[1], x[2]) && !AbstractDrawable3DObject.isEqual(x[2], x[0])) { 230 y[0] = 1; 231 } else if (AbstractDrawable3DObject.isEqual(x[0], x[1]) && !AbstractDrawable3DObject.isEqual(x[1], x[2])) { 232 y[0] = 1; 233 } else if (AbstractDrawable3DObject.isEqual(x[0], x[2]) && !AbstractDrawable3DObject.isEqual(x[2], x[1])) { 234 y[2] = 1; 235 } else if (AbstractDrawable3DObject.isEqual(x[1], x[2]) && !AbstractDrawable3DObject.isEqual(x[2], x[0])) { 236 y[2] = 1; 237 } else { 238 return true; 239 } 240 241 y[0] = !AbstractDrawable3DObject.isNull(y[0]) ? 1 : 0; 242 y[1] = !AbstractDrawable3DObject.isNull(y[1]) ? 1 : 0; 243 y[2] = !AbstractDrawable3DObject.isNull(y[2]) ? 1 : 0; 244 } 245 246 return false; 247 } 248 249 /** 250 * Check if the triangle in texture is degenerate 251 * @param x x-coordinates 252 * @param y y-coordinates 253 * @return true if 1d 254 */ is1d(final double[] x, final double[] y)255 private static final boolean is1d(final double[] x, final double[] y) { 256 return AbstractDrawable3DObject.isNull(y[0]) && AbstractDrawable3DObject.isNull(y[1]) && AbstractDrawable3DObject.isNull(y[2]); 257 } 258 clamp(Graphics2D g2d, double[] ximg, double[] yimg, double[] xdest, double[] ydest, AffineTransform transformation, BufferedImage image)259 private static final void clamp(Graphics2D g2d, double[] ximg, double[] yimg, double[] xdest, double[] ydest, AffineTransform transformation, BufferedImage image) { 260 if (ximg[0] < 0 || ximg[1] < 0 || ximg[2] < 0 || ximg[0] > 1 || ximg[1] > 1 || ximg[2] > 1) { 261 double w = image.getWidth(); 262 double h = image.getHeight(); 263 int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 264 Path2D.Double path = new Path2D.Double(); 265 path.moveTo(w * ximg[0], h * yimg[0]); 266 path.lineTo(w * ximg[1], h * yimg[1]); 267 path.lineTo(w * ximg[2], h * yimg[2]); 268 path.closePath(); 269 Area tri = new Area(path); 270 Rectangle2D bounds = tri.getBounds2D(); 271 272 if (bounds.getX() < 0) { 273 Area rect = new Area(new Rectangle2D.Double(bounds.getX(), bounds.getY(), -bounds.getX(), bounds.getHeight())); 274 tri.intersect(rect); 275 if (!tri.isEmpty()) { 276 tri.transform(transformation); 277 g2d.setColor(new Color(pixels[0])); 278 g2d.fill(tri); 279 } 280 } 281 282 if (bounds.getX() + bounds.getWidth() > w) { 283 tri = new Area(path); 284 Area rect = new Area(new Rectangle2D.Double(w, bounds.getY(), bounds.getX() + bounds.getWidth() - w, bounds.getHeight())); 285 tri.intersect(rect); 286 if (!tri.isEmpty()) { 287 tri.transform(transformation); 288 g2d.setColor(new Color(pixels[pixels.length - 1])); 289 g2d.fill(tri); 290 } 291 } 292 } 293 } 294 295 /** 296 * Get the projection in 2D of point A on line (BC) 297 * @param A the point to project 298 * @param B a point of the line 299 * @param C another point of the line (different of B) 300 * @return the projected point 301 */ get2DProjection(final double xA, final double yA, final double xB, final double yB, final double xC, final double yC)302 private static final double[] get2DProjection(final double xA, final double yA, final double xB, final double yB, final double xC, final double yC) { 303 final double xBC = xC - xB; 304 final double yBC = yC - yB; 305 final double n = xBC * xBC + yBC * yBC; 306 final double s = (xBC * (xA - xB) + yBC * (yA - yB)) / n; 307 308 return new double[] {xB + s * xBC, yB + s * yBC}; 309 } 310 } 311