Path_Delegate.java revision 56222cfbe9973c518f7e8c9113c614de80b5a4b2
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics;
18
19import com.android.layoutlib.bridge.Bridge;
20import com.android.layoutlib.bridge.BridgeConstants;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22
23import android.graphics.Path.Direction;
24import android.graphics.Path.FillType;
25
26import java.awt.Shape;
27import java.awt.geom.AffineTransform;
28import java.awt.geom.Arc2D;
29import java.awt.geom.Area;
30import java.awt.geom.GeneralPath;
31import java.awt.geom.PathIterator;
32import java.awt.geom.Point2D;
33import java.awt.geom.Rectangle2D;
34
35/**
36 * Delegate implementing the native methods of android.graphics.Path
37 *
38 * Through the layoutlib_create tool, the original native methods of Path have been replaced
39 * by calls to methods of the same name in this delegate class.
40 *
41 * This class behaves like the original native implementation, but in Java, keeping previously
42 * native data into its own objects and mapping them to int that are sent back and forth between
43 * it and the original Path class.
44 *
45 * @see DelegateManager
46 *
47 */
48public final class Path_Delegate {
49
50    // ---- delegate manager ----
51    private static final DelegateManager<Path_Delegate> sManager =
52            new DelegateManager<Path_Delegate>();
53
54    // ---- delegate data ----
55    private FillType mFillType = FillType.WINDING;
56    private GeneralPath mPath = new GeneralPath();
57
58    private float mLastX = 0;
59    private float mLastY = 0;
60
61    // ---- Public Helper methods ----
62
63    public static Path_Delegate getDelegate(int nPath) {
64        return sManager.getDelegate(nPath);
65    }
66
67    public Shape getJavaShape() {
68        return mPath;
69    }
70
71    public void setJavaShape(Shape shape) {
72        mPath.reset();
73        mPath.append(shape, false /*connect*/);
74    }
75
76    public void reset() {
77        mPath.reset();
78    }
79
80    public void setPathIterator(PathIterator iterator) {
81        mPath.reset();
82        mPath.append(iterator, false /*connect*/);
83    }
84
85    // ---- native methods ----
86
87    /*package*/ static int init1() {
88        // create the delegate
89        Path_Delegate newDelegate = new Path_Delegate();
90
91        return sManager.addDelegate(newDelegate);
92    }
93
94    /*package*/ static int init2(int nPath) {
95        // create the delegate
96        Path_Delegate newDelegate = new Path_Delegate();
97
98        // get the delegate to copy, which could be null if nPath is 0
99        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
100        if (pathDelegate != null) {
101            newDelegate.set(pathDelegate);
102        }
103
104        return sManager.addDelegate(newDelegate);
105    }
106
107    /*package*/ static void native_reset(int nPath) {
108        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
109        if (pathDelegate == null) {
110            return;
111        }
112
113        pathDelegate.mPath.reset();
114    }
115
116    /*package*/ static void native_rewind(int nPath) {
117        // call out to reset since there's nothing to optimize in
118        // terms of data structs.
119        native_reset(nPath);
120    }
121
122    /*package*/ static void native_set(int native_dst, int native_src) {
123        Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
124        if (pathDstDelegate == null) {
125            return;
126        }
127
128        Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
129        if (pathSrcDelegate == null) {
130            return;
131        }
132
133        pathDstDelegate.set(pathSrcDelegate);
134    }
135
136    /*package*/ static int native_getFillType(int nPath) {
137        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
138        if (pathDelegate == null) {
139            return 0;
140        }
141
142        return pathDelegate.mFillType.nativeInt;
143    }
144
145    /*package*/ static void native_setFillType(int nPath, int ft) {
146        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
147        if (pathDelegate == null) {
148            return;
149        }
150
151        pathDelegate.mFillType = Path.sFillTypeArray[ft];
152    }
153
154    /*package*/ static boolean native_isEmpty(int nPath) {
155        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
156        if (pathDelegate == null) {
157            return true;
158        }
159
160        return pathDelegate.isEmpty();
161    }
162
163    /*package*/ static boolean native_isRect(int nPath, RectF rect) {
164        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
165        if (pathDelegate == null) {
166            return false;
167        }
168
169        // create an Area that can test if the path is a rect
170        Area area = new Area(pathDelegate.mPath);
171        if (area.isRectangular()) {
172            if (rect != null) {
173                pathDelegate.fillBounds(rect);
174            }
175
176            return true;
177        }
178
179        return false;
180    }
181
182    /*package*/ static void native_computeBounds(int nPath, RectF bounds) {
183        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
184        if (pathDelegate == null) {
185            return;
186        }
187
188        pathDelegate.fillBounds(bounds);
189    }
190
191    /*package*/ static void native_incReserve(int nPath, int extraPtCount) {
192        // since we use a java2D path, there's no way to pre-allocate new points,
193        // so we do nothing.
194    }
195
196    /*package*/ static void native_moveTo(int nPath, float x, float y) {
197        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
198        if (pathDelegate == null) {
199            return;
200        }
201
202        pathDelegate.moveTo(x, y);
203    }
204
205    /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) {
206        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
207        if (pathDelegate == null) {
208            return;
209        }
210
211        pathDelegate.rMoveTo(dx, dy);
212    }
213
214    /*package*/ static void native_lineTo(int nPath, float x, float y) {
215        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
216        if (pathDelegate == null) {
217            return;
218        }
219
220        pathDelegate.lineTo(x, y);
221    }
222
223    /*package*/ static void native_rLineTo(int nPath, float dx, float dy) {
224        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
225        if (pathDelegate == null) {
226            return;
227        }
228
229        pathDelegate.rLineTo(dx, dy);
230    }
231
232    /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) {
233        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
234        if (pathDelegate == null) {
235            return;
236        }
237
238        pathDelegate.quadTo(x1, y1, x2, y2);
239    }
240
241    /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) {
242        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
243        if (pathDelegate == null) {
244            return;
245        }
246
247        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
248    }
249
250    /*package*/ static void native_cubicTo(int nPath, float x1, float y1,
251            float x2, float y2, float x3, float y3) {
252        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
253        if (pathDelegate == null) {
254            return;
255        }
256
257        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
258    }
259
260    /*package*/ static void native_rCubicTo(int nPath, float x1, float y1,
261            float x2, float y2, float x3, float y3) {
262        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
263        if (pathDelegate == null) {
264            return;
265        }
266
267        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
268    }
269
270    /*package*/ static void native_arcTo(int nPath, RectF oval,
271                    float startAngle, float sweepAngle, boolean forceMoveTo) {
272        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
273        if (pathDelegate == null) {
274            return;
275        }
276
277        pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
278    }
279
280    /*package*/ static void native_close(int nPath) {
281        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
282        if (pathDelegate == null) {
283            return;
284        }
285
286        pathDelegate.close();
287    }
288
289    /*package*/ static void native_addRect(int nPath, RectF rect, int dir) {
290        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
291        if (pathDelegate == null) {
292            return;
293        }
294
295        pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
296    }
297
298    /*package*/ static void native_addRect(int nPath,
299            float left, float top, float right, float bottom, int dir) {
300        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
301        if (pathDelegate == null) {
302            return;
303        }
304
305        pathDelegate.addRect(left, top, right, bottom, dir);
306    }
307
308    /*package*/ static void native_addOval(int nPath, RectF oval, int dir) {
309        // FIXME
310        throw new UnsupportedOperationException();
311    }
312
313    /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) {
314        // FIXME
315        throw new UnsupportedOperationException();
316    }
317
318    /*package*/ static void native_addArc(int nPath, RectF oval,
319            float startAngle, float sweepAngle) {
320        // FIXME
321        throw new UnsupportedOperationException();
322    }
323
324    /*package*/ static void native_addRoundRect(int nPath, RectF rect,
325            float rx, float ry, int dir) {
326        // FIXME
327        throw new UnsupportedOperationException();
328    }
329
330    /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) {
331        // FIXME
332        throw new UnsupportedOperationException();
333    }
334
335    /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) {
336        // FIXME
337        throw new UnsupportedOperationException();
338    }
339
340    /*package*/ static void native_addPath(int nPath, int src) {
341        native_addPath(nPath, src, 0, 0);
342    }
343
344    /*package*/ static void native_addPath(int nPath, int src, int matrix) {
345        // FIXME
346        throw new UnsupportedOperationException();
347    }
348
349    /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) {
350        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
351        if (pathDelegate == null) {
352            return;
353        }
354
355        // could be null if the int is 0;
356        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
357
358        pathDelegate.offset(dx, dy, dstDelegate);
359    }
360
361    /*package*/ static void native_offset(int nPath, float dx, float dy) {
362        native_offset(nPath, dx, dy, 0);
363    }
364
365    /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) {
366        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
367        if (pathDelegate == null) {
368            return;
369        }
370
371        pathDelegate.mLastX = dx;
372        pathDelegate.mLastY = dy;
373    }
374
375    /*package*/ static void native_transform(int nPath, int matrix,
376                                                int dst_path) {
377        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
378        if (pathDelegate == null) {
379            return;
380        }
381
382        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
383        if (matrixDelegate == null) {
384            return;
385        }
386
387        // this can be null if dst_path is 0
388        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
389
390        pathDelegate.transform(matrixDelegate, dstDelegate);
391    }
392
393    /*package*/ static void native_transform(int nPath, int matrix) {
394        native_transform(nPath, matrix, 0);
395    }
396
397    /*package*/ static void finalizer(int nPath) {
398        sManager.removeDelegate(nPath);
399    }
400
401
402    // ---- Private helper methods ----
403
404    private void set(Path_Delegate delegate) {
405        mPath.reset();
406        setFillType(delegate.mFillType);
407        mPath.append(delegate.mPath, false /*connect*/);
408    }
409
410    private void setFillType(FillType fillType) {
411        mFillType = fillType;
412        mPath.setWindingRule(getWindingRule(fillType));
413    }
414
415    /**
416     * Returns the Java2D winding rules matching a given Android {@link FillType}.
417     * @param type the android fill type
418     * @return the matching java2d winding rule.
419     */
420    private static int getWindingRule(FillType type) {
421        switch (type) {
422            case WINDING:
423            case INVERSE_WINDING:
424                return GeneralPath.WIND_NON_ZERO;
425            case EVEN_ODD:
426            case INVERSE_EVEN_ODD:
427                return GeneralPath.WIND_EVEN_ODD;
428        }
429
430        assert false;
431        throw new IllegalArgumentException();
432    }
433
434    private static Direction getDirection(int direction) {
435        for (Direction d : Direction.values()) {
436            if (direction == d.nativeInt) {
437                return d;
438            }
439        }
440
441        assert false;
442        return null;
443    }
444
445    /**
446     * Returns whether the path is empty.
447     * @return true if the path is empty.
448     */
449    private boolean isEmpty() {
450        return mPath.getCurrentPoint() == null;
451    }
452
453    /**
454     * Fills the given {@link RectF} with the path bounds.
455     * @param bounds the RectF to be filled.
456     */
457    private void fillBounds(RectF bounds) {
458        Rectangle2D rect = mPath.getBounds2D();
459        bounds.left = (float)rect.getMinX();
460        bounds.right = (float)rect.getMaxX();
461        bounds.top = (float)rect.getMinY();
462        bounds.bottom = (float)rect.getMaxY();
463    }
464
465    /**
466     * Set the beginning of the next contour to the point (x,y).
467     *
468     * @param x The x-coordinate of the start of a new contour
469     * @param y The y-coordinate of the start of a new contour
470     */
471    private void moveTo(float x, float y) {
472        mPath.moveTo(mLastX = x, mLastY = y);
473    }
474
475    /**
476     * Set the beginning of the next contour relative to the last point on the
477     * previous contour. If there is no previous contour, this is treated the
478     * same as moveTo().
479     *
480     * @param dx The amount to add to the x-coordinate of the end of the
481     *           previous contour, to specify the start of a new contour
482     * @param dy The amount to add to the y-coordinate of the end of the
483     *           previous contour, to specify the start of a new contour
484     */
485    private void rMoveTo(float dx, float dy) {
486        dx += mLastX;
487        dy += mLastY;
488        mPath.moveTo(mLastX = dx, mLastY = dy);
489    }
490
491    /**
492     * Add a line from the last point to the specified point (x,y).
493     * If no moveTo() call has been made for this contour, the first point is
494     * automatically set to (0,0).
495     *
496     * @param x The x-coordinate of the end of a line
497     * @param y The y-coordinate of the end of a line
498     */
499    private void lineTo(float x, float y) {
500        mPath.lineTo(mLastX = x, mLastY = y);
501    }
502
503    /**
504     * Same as lineTo, but the coordinates are considered relative to the last
505     * point on this contour. If there is no previous point, then a moveTo(0,0)
506     * is inserted automatically.
507     *
508     * @param dx The amount to add to the x-coordinate of the previous point on
509     *           this contour, to specify a line
510     * @param dy The amount to add to the y-coordinate of the previous point on
511     *           this contour, to specify a line
512     */
513    private void rLineTo(float dx, float dy) {
514        if (isEmpty()) {
515            mPath.moveTo(mLastX = 0, mLastY = 0);
516        }
517        dx += mLastX;
518        dy += mLastY;
519        mPath.lineTo(mLastX = dx, mLastY = dy);
520    }
521
522    /**
523     * Add a quadratic bezier from the last point, approaching control point
524     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
525     * this contour, the first point is automatically set to (0,0).
526     *
527     * @param x1 The x-coordinate of the control point on a quadratic curve
528     * @param y1 The y-coordinate of the control point on a quadratic curve
529     * @param x2 The x-coordinate of the end point on a quadratic curve
530     * @param y2 The y-coordinate of the end point on a quadratic curve
531     */
532    private void quadTo(float x1, float y1, float x2, float y2) {
533        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
534    }
535
536    /**
537     * Same as quadTo, but the coordinates are considered relative to the last
538     * point on this contour. If there is no previous point, then a moveTo(0,0)
539     * is inserted automatically.
540     *
541     * @param dx1 The amount to add to the x-coordinate of the last point on
542     *            this contour, for the control point of a quadratic curve
543     * @param dy1 The amount to add to the y-coordinate of the last point on
544     *            this contour, for the control point of a quadratic curve
545     * @param dx2 The amount to add to the x-coordinate of the last point on
546     *            this contour, for the end point of a quadratic curve
547     * @param dy2 The amount to add to the y-coordinate of the last point on
548     *            this contour, for the end point of a quadratic curve
549     */
550    private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
551        if (isEmpty()) {
552            mPath.moveTo(mLastX = 0, mLastY = 0);
553        }
554        dx1 += mLastX;
555        dy1 += mLastY;
556        dx2 += mLastX;
557        dy2 += mLastY;
558        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
559    }
560
561    /**
562     * Add a cubic bezier from the last point, approaching control points
563     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
564     * made for this contour, the first point is automatically set to (0,0).
565     *
566     * @param x1 The x-coordinate of the 1st control point on a cubic curve
567     * @param y1 The y-coordinate of the 1st control point on a cubic curve
568     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
569     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
570     * @param x3 The x-coordinate of the end point on a cubic curve
571     * @param y3 The y-coordinate of the end point on a cubic curve
572     */
573    private void cubicTo(float x1, float y1, float x2, float y2,
574                        float x3, float y3) {
575        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
576    }
577
578    /**
579     * Same as cubicTo, but the coordinates are considered relative to the
580     * current point on this contour. If there is no previous point, then a
581     * moveTo(0,0) is inserted automatically.
582     */
583    private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
584                         float dx3, float dy3) {
585        if (isEmpty()) {
586            mPath.moveTo(mLastX = 0, mLastY = 0);
587        }
588        dx1 += mLastX;
589        dy1 += mLastY;
590        dx2 += mLastX;
591        dy2 += mLastY;
592        dx3 += mLastX;
593        dy3 += mLastY;
594        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
595    }
596
597    /**
598     * Append the specified arc to the path as a new contour. If the start of
599     * the path is different from the path's current last point, then an
600     * automatic lineTo() is added to connect the current contour to the
601     * start of the arc. However, if the path is empty, then we call moveTo()
602     * with the first point of the arc. The sweep angle is tread mod 360.
603     *
604     * @param oval        The bounds of oval defining shape and size of the arc
605     * @param startAngle  Starting angle (in degrees) where the arc begins
606     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
607     *                    mod 360.
608     * @param forceMoveTo If true, always begin a new contour with the arc
609     */
610    private void arcTo(RectF oval, float startAngle, float sweepAngle,
611                      boolean forceMoveTo) {
612        Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle,
613                sweepAngle, Arc2D.OPEN);
614        mPath.append(arc, true /*connect*/);
615
616        resetLastPointFromPath();
617    }
618
619    /**
620     * Close the current contour. If the current point is not equal to the
621     * first point of the contour, a line segment is automatically added.
622     */
623    private void close() {
624        mPath.closePath();
625    }
626
627    private void resetLastPointFromPath() {
628        Point2D last = mPath.getCurrentPoint();
629        mLastX = (float) last.getX();
630        mLastY = (float) last.getY();
631    }
632
633    /**
634     * Add a closed rectangle contour to the path
635     *
636     * @param left   The left side of a rectangle to add to the path
637     * @param top    The top of a rectangle to add to the path
638     * @param right  The right side of a rectangle to add to the path
639     * @param bottom The bottom of a rectangle to add to the path
640     * @param dir    The direction to wind the rectangle's contour
641     */
642    private void addRect(float left, float top, float right, float bottom,
643                        int dir) {
644        moveTo(left, top);
645
646        Direction direction = getDirection(dir);
647
648        switch (direction) {
649            case CW:
650                lineTo(right, top);
651                lineTo(right, bottom);
652                lineTo(left, bottom);
653                break;
654            case CCW:
655                lineTo(left, bottom);
656                lineTo(right, bottom);
657                lineTo(right, top);
658                break;
659        }
660
661        close();
662
663        resetLastPointFromPath();
664    }
665
666    /**
667     * Offset the path by (dx,dy), returning true on success
668     *
669     * @param dx  The amount in the X direction to offset the entire path
670     * @param dy  The amount in the Y direction to offset the entire path
671     * @param dst The translated path is written here. If this is null, then
672     *            the original path is modified.
673     */
674    public void offset(float dx, float dy, Path_Delegate dst) {
675        GeneralPath newPath = new GeneralPath();
676
677        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
678
679        newPath.append(iterator, false /*connect*/);
680
681        if (dst != null) {
682            dst.mPath = newPath;
683        } else {
684            mPath = newPath;
685        }
686    }
687
688    /**
689     * Transform the points in this path by matrix, and write the answer
690     * into dst. If dst is null, then the the original path is modified.
691     *
692     * @param matrix The matrix to apply to the path
693     * @param dst    The transformed path is written here. If dst is null,
694     *               then the the original path is modified
695     */
696    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
697        if (matrix.hasPerspective()) {
698            assert false;
699            Bridge.getLog().fidelityWarning(BridgeConstants.TAG_MATRIX,
700                    "android.graphics.Path#transform() only " +
701                    "supports affine transformations.", null);
702        }
703
704        GeneralPath newPath = new GeneralPath();
705
706        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
707
708        newPath.append(iterator, false /*connect*/);
709
710        if (dst != null) {
711            dst.mPath = newPath;
712        } else {
713            mPath = newPath;
714        }
715    }
716}
717