1/*
2 * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.java2d.pipe;
27
28import java.awt.Shape;
29import java.awt.BasicStroke;
30import java.awt.geom.Line2D;
31import java.awt.geom.Rectangle2D;
32import java.awt.geom.AffineTransform;
33import sun.java2d.SunGraphics2D;
34import sun.awt.SunHints;
35
36/**
37 * This class converts calls to the basic pixel rendering methods
38 * into calls to the methods on a ParallelogramPipe.
39 * Most calls are transformed into calls to the fill(Shape) method
40 * by the parent PixelToShapeConverter class, but some calls are
41 * transformed into calls to fill/drawParallelogram().
42 */
43public class PixelToParallelogramConverter extends PixelToShapeConverter
44    implements ShapeDrawPipe
45{
46    ParallelogramPipe outrenderer;
47    double minPenSize;
48    double normPosition;
49    double normRoundingBias;
50    boolean adjustfill;
51
52    /**
53     * @param shapepipe pipeline to forward shape calls to
54     * @param pgrampipe pipeline to forward parallelogram calls to
55     *                  (and drawLine calls if possible)
56     * @param minPenSize minimum pen size for dropout control
57     * @param normPosition sub-pixel location to normalize endpoints
58     *                     for STROKE_NORMALIZE cases
59     * @param adjustfill boolean to control whethere normalization
60     *                   constants are also applied to fill operations
61     *                   (normally true for non-AA, false for AA)
62     */
63    public PixelToParallelogramConverter(ShapeDrawPipe shapepipe,
64                                         ParallelogramPipe pgrampipe,
65                                         double minPenSize,
66                                         double normPosition,
67                                         boolean adjustfill)
68    {
69        super(shapepipe);
70        outrenderer = pgrampipe;
71        this.minPenSize = minPenSize;
72        this.normPosition = normPosition;
73        this.normRoundingBias = 0.5 - normPosition;
74        this.adjustfill = adjustfill;
75    }
76
77    public void drawLine(SunGraphics2D sg2d,
78                         int x1, int y1, int x2, int y2)
79    {
80        if (!drawGeneralLine(sg2d, x1, y1, x2, y2)) {
81            super.drawLine(sg2d, x1, y1, x2, y2);
82        }
83    }
84
85    public void drawRect(SunGraphics2D sg2d,
86                         int x, int y, int w, int h)
87    {
88        if (w >= 0 && h >= 0) {
89            if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) {
90                BasicStroke bs = ((BasicStroke) sg2d.stroke);
91                if (w > 0 && h > 0) {
92                    if (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
93                        bs.getDashArray() == null)
94                    {
95                        double lw = bs.getLineWidth();
96                        drawRectangle(sg2d, x, y, w, h, lw);
97                        return;
98                    }
99                } else {
100                    // Note: This calls the integer version which
101                    // will verify that the local drawLine optimizations
102                    // work and call super.drawLine(), if not.
103                    drawLine(sg2d, x, y, x+w, y+h);
104                    return;
105                }
106            }
107            super.drawRect(sg2d, x, y, w, h);
108        }
109    }
110
111    public void fillRect(SunGraphics2D sg2d,
112                         int x, int y, int w, int h)
113    {
114        if (w > 0 && h > 0) {
115            fillRectangle(sg2d, x, y, w, h);
116        }
117    }
118
119    public void draw(SunGraphics2D sg2d, Shape s) {
120        if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) {
121            BasicStroke bs = ((BasicStroke) sg2d.stroke);
122            if (s instanceof Rectangle2D) {
123                if (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
124                    bs.getDashArray() == null)
125                {
126                    Rectangle2D r2d = (Rectangle2D) s;
127                    double w = r2d.getWidth();
128                    double h = r2d.getHeight();
129                    double x = r2d.getX();
130                    double y = r2d.getY();
131                    if (w >= 0 && h >= 0) {
132                        double lw = bs.getLineWidth();
133                        drawRectangle(sg2d, x, y, w, h, lw);
134                    }
135                    return;
136                }
137            } else if (s instanceof Line2D) {
138                Line2D l2d = (Line2D) s;
139                if (drawGeneralLine(sg2d,
140                                    l2d.getX1(), l2d.getY1(),
141                                    l2d.getX2(), l2d.getY2()))
142                {
143                    return;
144                }
145            }
146        }
147
148        outpipe.draw(sg2d, s);
149    }
150
151    public void fill(SunGraphics2D sg2d, Shape s) {
152        if (s instanceof Rectangle2D) {
153            Rectangle2D r2d = (Rectangle2D) s;
154            double w = r2d.getWidth();
155            double h = r2d.getHeight();
156            if (w > 0 && h > 0) {
157                double x = r2d.getX();
158                double y = r2d.getY();
159                fillRectangle(sg2d, x, y, w, h);
160            }
161            return;
162        }
163
164        outpipe.fill(sg2d, s);
165    }
166
167    static double len(double x, double y) {
168        return ((x == 0) ? Math.abs(y)
169                : ((y == 0) ? Math.abs(x)
170                   : Math.sqrt(x * x + y * y)));
171    }
172
173    double normalize(double v) {
174        return Math.floor(v + normRoundingBias) + normPosition;
175    }
176
177    public boolean drawGeneralLine(SunGraphics2D sg2d,
178                                   double ux1, double uy1,
179                                   double ux2, double uy2)
180    {
181        if (sg2d.strokeState == SunGraphics2D.STROKE_CUSTOM ||
182            sg2d.strokeState == SunGraphics2D.STROKE_THINDASHED)
183        {
184            return false;
185        }
186        BasicStroke bs = (BasicStroke) sg2d.stroke;
187        int cap = bs.getEndCap();
188        if (cap == BasicStroke.CAP_ROUND || bs.getDashArray() != null) {
189            // TODO: we could construct the GeneralPath directly
190            // for CAP_ROUND and save a lot of processing in that case...
191            // And again, we would need to deal with dropout control...
192            return false;
193        }
194        double lw = bs.getLineWidth();
195        // Save the original dx, dy in case we need it to transform
196        // the linewidth as a perpendicular vector below
197        double dx = ux2 - ux1;
198        double dy = uy2 - uy1;
199        double x1, y1, x2, y2;
200        switch (sg2d.transformState) {
201        case SunGraphics2D.TRANSFORM_GENERIC:
202        case SunGraphics2D.TRANSFORM_TRANSLATESCALE:
203            {
204                double coords[] = {ux1, uy1, ux2, uy2};
205                sg2d.transform.transform(coords, 0, coords, 0, 2);
206                x1 = coords[0];
207                y1 = coords[1];
208                x2 = coords[2];
209                y2 = coords[3];
210            }
211            break;
212        case SunGraphics2D.TRANSFORM_ANY_TRANSLATE:
213        case SunGraphics2D.TRANSFORM_INT_TRANSLATE:
214            {
215                double tx = sg2d.transform.getTranslateX();
216                double ty = sg2d.transform.getTranslateY();
217                x1 = ux1 + tx;
218                y1 = uy1 + ty;
219                x2 = ux2 + tx;
220                y2 = uy2 + ty;
221            }
222            break;
223        case SunGraphics2D.TRANSFORM_ISIDENT:
224            x1 = ux1;
225            y1 = uy1;
226            x2 = ux2;
227            y2 = uy2;
228            break;
229        default:
230            throw new InternalError("unknown TRANSFORM state...");
231        }
232        if (sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE) {
233            if (sg2d.strokeState == SunGraphics2D.STROKE_THIN &&
234                outrenderer instanceof PixelDrawPipe)
235            {
236                // PixelDrawPipes will add sg2d.transXY so we need to factor
237                // that out...
238                int ix1 = (int) Math.floor(x1 - sg2d.transX);
239                int iy1 = (int) Math.floor(y1 - sg2d.transY);
240                int ix2 = (int) Math.floor(x2 - sg2d.transX);
241                int iy2 = (int) Math.floor(y2 - sg2d.transY);
242                ((PixelDrawPipe)outrenderer).drawLine(sg2d, ix1, iy1, ix2, iy2);
243                return true;
244            }
245            x1 = normalize(x1);
246            y1 = normalize(y1);
247            x2 = normalize(x2);
248            y2 = normalize(y2);
249        }
250        if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
251            // Transform the linewidth...
252            // calculate the scaling factor for a unit vector
253            // perpendicular to the original user space line.
254            double len = len(dx, dy);
255            if (len == 0) {
256                dx = len = 1;
257                // dy = 0; already
258            }
259            // delta transform the transposed (90 degree rotated) unit vector
260            double unitvector[] = {dy/len, -dx/len};
261            sg2d.transform.deltaTransform(unitvector, 0, unitvector, 0, 1);
262            lw *= len(unitvector[0], unitvector[1]);
263        }
264        lw = Math.max(lw, minPenSize);
265        dx = x2 - x1;
266        dy = y2 - y1;
267        double len = len(dx, dy);
268        double udx, udy;
269        if (len == 0) {
270            if (cap == BasicStroke.CAP_BUTT) {
271                return true;
272            }
273            udx = lw;
274            udy = 0;
275        } else {
276            udx = lw * dx / len;
277            udy = lw * dy / len;
278        }
279        double px = x1 + udy / 2.0;
280        double py = y1 - udx / 2.0;
281        if (cap == BasicStroke.CAP_SQUARE) {
282            px -= udx / 2.0;
283            py -= udy / 2.0;
284            dx += udx;
285            dy += udy;
286        }
287        outrenderer.fillParallelogram(sg2d, ux1, uy1, ux2, uy2,
288                                      px, py, -udy, udx, dx, dy);
289        return true;
290    }
291
292    public void fillRectangle(SunGraphics2D sg2d,
293                              double rx, double ry,
294                              double rw, double rh)
295    {
296        double px, py;
297        double dx1, dy1, dx2, dy2;
298        AffineTransform txform = sg2d.transform;
299        dx1 = txform.getScaleX();
300        dy1 = txform.getShearY();
301        dx2 = txform.getShearX();
302        dy2 = txform.getScaleY();
303        px = rx * dx1 + ry * dx2 + txform.getTranslateX();
304        py = rx * dy1 + ry * dy2 + txform.getTranslateY();
305        dx1 *= rw;
306        dy1 *= rw;
307        dx2 *= rh;
308        dy2 *= rh;
309        if (adjustfill &&
310            sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM &&
311            sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE)
312        {
313            double newx = normalize(px);
314            double newy = normalize(py);
315            dx1 = normalize(px + dx1) - newx;
316            dy1 = normalize(py + dy1) - newy;
317            dx2 = normalize(px + dx2) - newx;
318            dy2 = normalize(py + dy2) - newy;
319            px = newx;
320            py = newy;
321        }
322        outrenderer.fillParallelogram(sg2d, rx, ry, rx+rw, ry+rh,
323                                      px, py, dx1, dy1, dx2, dy2);
324    }
325
326    public void drawRectangle(SunGraphics2D sg2d,
327                              double rx, double ry,
328                              double rw, double rh,
329                              double lw)
330    {
331        double px, py;
332        double dx1, dy1, dx2, dy2;
333        double lw1, lw2;
334        AffineTransform txform = sg2d.transform;
335        dx1 = txform.getScaleX();
336        dy1 = txform.getShearY();
337        dx2 = txform.getShearX();
338        dy2 = txform.getScaleY();
339        px = rx * dx1 + ry * dx2 + txform.getTranslateX();
340        py = rx * dy1 + ry * dy2 + txform.getTranslateY();
341        // lw along dx1,dy1 scale by transformed length of dx2,dy2 vectors
342        // and vice versa
343        lw1 = len(dx1, dy1) * lw;
344        lw2 = len(dx2, dy2) * lw;
345        dx1 *= rw;
346        dy1 *= rw;
347        dx2 *= rh;
348        dy2 *= rh;
349        if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM &&
350            sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE)
351        {
352            double newx = normalize(px);
353            double newy = normalize(py);
354            dx1 = normalize(px + dx1) - newx;
355            dy1 = normalize(py + dy1) - newy;
356            dx2 = normalize(px + dx2) - newx;
357            dy2 = normalize(py + dy2) - newy;
358            px = newx;
359            py = newy;
360        }
361        lw1 = Math.max(lw1, minPenSize);
362        lw2 = Math.max(lw2, minPenSize);
363        double len1 = len(dx1, dy1);
364        double len2 = len(dx2, dy2);
365        if (lw1 >= len1 || lw2 >= len2) {
366            // The line widths are large enough to consume the
367            // entire hole in the middle of the parallelogram
368            // so we can just fill the outer parallelogram.
369            fillOuterParallelogram(sg2d,
370                                   rx, ry, rx+rw, ry+rh,
371                                   px, py, dx1, dy1, dx2, dy2,
372                                   len1, len2, lw1, lw2);
373        } else {
374            outrenderer.drawParallelogram(sg2d,
375                                          rx, ry, rx+rw, ry+rh,
376                                          px, py, dx1, dy1, dx2, dy2,
377                                          lw1 / len1, lw2 / len2);
378        }
379    }
380
381    /**
382     * This utility function handles the case where a drawRectangle
383     * operation discovered that the interior hole in the rectangle
384     * or parallelogram has been completely filled in by the stroke
385     * width.  It calculates the outer parallelogram of the stroke
386     * and issues a single fillParallelogram request to fill it.
387     */
388    public void fillOuterParallelogram(SunGraphics2D sg2d,
389                                       double ux1, double uy1,
390                                       double ux2, double uy2,
391                                       double px, double py,
392                                       double dx1, double dy1,
393                                       double dx2, double dy2,
394                                       double len1, double len2,
395                                       double lw1, double lw2)
396    {
397        double udx1 = dx1 / len1;
398        double udy1 = dy1 / len1;
399        double udx2 = dx2 / len2;
400        double udy2 = dy2 / len2;
401        if (len1 == 0) {
402            // len1 is 0, replace udxy1 with perpendicular of udxy2
403            if (len2 == 0) {
404                // both are 0, use a unit Y vector for udxy2
405                udx2 = 0;
406                udy2 = 1;
407            }
408            udx1 = udy2;
409            udy1 = -udx2;
410        } else if (len2 == 0) {
411            // len2 is 0, replace udxy2 with perpendicular of udxy1
412            udx2 = udy1;
413            udy2 = -udx1;
414        }
415        udx1 *= lw1;
416        udy1 *= lw1;
417        udx2 *= lw2;
418        udy2 *= lw2;
419        px -= (udx1 + udx2) / 2;
420        py -= (udy1 + udy2) / 2;
421        dx1 += udx1;
422        dy1 += udy1;
423        dx2 += udx2;
424        dy2 += udy2;
425
426        outrenderer.fillParallelogram(sg2d, ux1, uy1, ux2, uy2,
427                                      px, py, dx1, dy1, dx2, dy2);
428    }
429}
430