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