1/*
2 * Copyright (C) 2011 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 androidx.media.filterfw.geometry;
18
19import android.annotation.SuppressLint;
20import android.graphics.Matrix;
21import android.graphics.PointF;
22import android.graphics.RectF;
23
24/**
25 * The Quad class specifies a (possibly affine transformed) rectangle.
26 *
27 * A Quad instance holds 4 points that define its shape. The points may represent any rectangle that
28 * has been transformed by an affine transformation. This means that Quads can represent translated,
29 * scaled, rotated and sheared/skewed rectangles. As such, Quads are restricted to the set of
30 * parallelograms.
31 *
32 * Each point in the Quad represents a specific corner of the Quad. These are top-left, top-right,
33 * bottom-left, and bottom-right. These labels allow mapping a transformed Quad back to an up-right
34 * Quad, with the point-to-point mapping well-defined. They do not necessarily indicate that e.g.
35 * the top-left corner is actually at the top-left of coordinate space.
36 */
37@SuppressLint("FloatMath")
38public class Quad {
39
40    private final PointF mTopLeft;
41    private final PointF mTopRight;
42    private final PointF mBottomLeft;
43    private final PointF mBottomRight;
44
45    /**
46     * Returns the unit Quad.
47     * The unit Quad has its top-left point at (0, 0) and bottom-right point at (1, 1).
48     * @return the unit Quad.
49     */
50    public static Quad unitQuad() {
51        return new Quad(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f);
52    }
53
54    /**
55     * Return a Quad from the specified rectangle.
56     *
57     * @param rect a RectF instance.
58     * @return Quad that represents the passed rectangle.
59     */
60    public static Quad fromRect(RectF rect) {
61        return new Quad(new PointF(rect.left, rect.top),
62                        new PointF(rect.right, rect.top),
63                        new PointF(rect.left, rect.bottom),
64                        new PointF(rect.right, rect.bottom));
65    }
66
67    /**
68     * Return a Quad from the specified rectangle coordinates.
69     *
70     * @param x the top left x coordinate
71     * @param y the top left y coordinate
72     * @param width the width of the rectangle
73     * @param height the height of the rectangle
74     * @return Quad that represents the passed rectangle.
75     */
76    public static Quad fromRect(float x, float y, float width, float height) {
77        return new Quad(new PointF(x, y),
78                        new PointF(x + width, y),
79                        new PointF(x, y + height),
80                        new PointF(x + width, y + height));
81    }
82
83    /**
84     * Return a Quad that spans the specified points and height.
85     *
86     * The returned Quad has the specified top-left and top-right points, and the specified height
87     * while maintaining 90 degree angles on all 4 corners.
88     *
89     * @param topLeft the top-left of the quad
90     * @param topRight the top-right of the quad
91     * @param height the height of the quad
92     * @return Quad that spans the specified points and height.
93     */
94    public static Quad fromLineAndHeight(PointF topLeft, PointF topRight, float height) {
95        PointF dp = new PointF(topRight.x - topLeft.x, topRight.y - topLeft.y);
96        float len = dp.length();
97        PointF np = new PointF(height * (dp.y / len), height * (dp.x / len));
98        PointF p2 = new PointF(topLeft.x - np.x, topLeft.y + np.y);
99        PointF p3 = new PointF(topRight.x - np.x, topRight.y + np.y);
100        return new Quad(topLeft, topRight, p2, p3);
101    }
102
103    /**
104     * Return a Quad that represents the specified rotated rectangle.
105     *
106     * The Quad is rotated counter-clockwise around its centroid.
107     *
108     * @param rect the source rectangle
109     * @param angle the angle to rotate the source rectangle in radians
110     * @return the Quad representing the source rectangle rotated by the given angle.
111     */
112    public static Quad fromRotatedRect(RectF rect, float angle) {
113        return Quad.fromRect(rect).rotated(angle);
114    }
115
116    /**
117     * Return a Quad that represents the specified transformed rectangle.
118     *
119     * The transform is applied by multiplying each point (x, y, 1) by the matrix.
120     *
121     * @param rect the source rectangle
122     * @param matrix the transformation matrix
123     * @return the Quad representing the source rectangle transformed by the matrix
124     */
125    public static Quad fromTransformedRect(RectF rect, Matrix matrix) {
126        return Quad.fromRect(rect).transformed(matrix);
127    }
128
129    /**
130     * Returns the transformation matrix to transform the source Quad to the target Quad.
131     *
132     * @param source the source quad
133     * @param target the target quad
134     * @return the transformation matrix to map source to target.
135     */
136    public static Matrix getTransform(Quad source, Quad target) {
137        // We only use the first 3 points as they sufficiently specify the transform
138        Matrix transform = new Matrix();
139        transform.setPolyToPoly(source.asCoords(), 0, target.asCoords(), 0, 3);
140        return transform;
141    }
142
143    /**
144     * The top-left point of the Quad.
145     * @return top-left point of the Quad.
146     */
147    public PointF topLeft() {
148        return mTopLeft;
149    }
150
151    /**
152     * The top-right point of the Quad.
153     * @return top-right point of the Quad.
154     */
155    public PointF topRight() {
156        return mTopRight;
157    }
158
159    /**
160     * The bottom-left point of the Quad.
161     * @return bottom-left point of the Quad.
162     */
163    public PointF bottomLeft() {
164        return mBottomLeft;
165    }
166
167    /**
168     * The bottom-right point of the Quad.
169     * @return bottom-right point of the Quad.
170     */
171    public PointF bottomRight() {
172        return mBottomRight;
173    }
174
175    /**
176     * Rotate the quad by the given angle.
177     *
178     * The Quad is rotated counter-clockwise around its centroid.
179     *
180     * @param angle the angle to rotate in radians
181     * @return the rotated Quad
182     */
183    public Quad rotated(float angle) {
184        PointF center = center();
185        float cosa = (float) Math.cos(angle);
186        float sina = (float) Math.sin(angle);
187
188        PointF topLeft = rotatePoint(topLeft(), center, cosa, sina);
189        PointF topRight = rotatePoint(topRight(), center, cosa, sina);
190        PointF bottomLeft = rotatePoint(bottomLeft(), center, cosa, sina);
191        PointF bottomRight = rotatePoint(bottomRight(), center, cosa, sina);
192
193        return new Quad(topLeft, topRight, bottomLeft, bottomRight);
194    }
195
196    /**
197     * Transform the quad with the given transformation matrix.
198     *
199     * The transform is applied by multiplying each point (x, y, 1) by the matrix.
200     *
201     * @param matrix the transformation matrix
202     * @return the transformed Quad
203     */
204    public Quad transformed(Matrix matrix) {
205        float[] points = asCoords();
206        matrix.mapPoints(points);
207        return new Quad(points);
208    }
209
210    /**
211     * Returns the centroid of the Quad.
212     *
213     * The centroid of the Quad is where the two inner diagonals connecting the opposite corners
214     * meet.
215     *
216     * @return the centroid of the Quad.
217     */
218    public PointF center() {
219        // As the diagonals bisect each other, we can simply return the center of one of the
220        // diagonals.
221        return new PointF((mTopLeft.x + mBottomRight.x) / 2f,
222                          (mTopLeft.y + mBottomRight.y) / 2f);
223    }
224
225    /**
226     * Returns the quad as a float-array of coordinates.
227     * The order of coordinates is top-left, top-right, bottom-left, bottom-right. This is the
228     * default order of coordinates used in ImageShaders, so this method can be used to bind
229     * an attribute to the Quad.
230     */
231    public float[] asCoords() {
232        return new float[] { mTopLeft.x, mTopLeft.y,
233                             mTopRight.x, mTopRight.y,
234                             mBottomLeft.x, mBottomLeft.y,
235                             mBottomRight.x, mBottomRight.y };
236    }
237
238    /**
239     * Grow the Quad outwards by the specified factor.
240     *
241     * This method moves the corner points of the Quad outward along the diagonals that connect
242     * them to the centroid. A factor of 1.0 moves the quad outwards by the distance of the corners
243     * to the centroid.
244     *
245     * @param factor the growth factor
246     * @return the Quad grown by the specified amount
247     */
248    public Quad grow(float factor) {
249        PointF pc = center();
250        return new Quad(factor * (mTopLeft.x - pc.x) + pc.x,
251                        factor * (mTopLeft.y - pc.y) + pc.y,
252                        factor * (mTopRight.x - pc.x) + pc.x,
253                        factor * (mTopRight.y - pc.y) + pc.y,
254                        factor * (mBottomLeft.x - pc.x) + pc.x,
255                        factor * (mBottomLeft.y - pc.y) + pc.y,
256                        factor * (mBottomRight.x - pc.x) + pc.x,
257                        factor * (mBottomRight.y - pc.y) + pc.y);
258    }
259
260    /**
261     * Scale the Quad by the specified factor.
262     *
263     * @param factor the scaling factor
264     * @return the Quad instance scaled by the specified factor.
265     */
266    public Quad scale(float factor) {
267        return new Quad(mTopLeft.x * factor, mTopLeft.y * factor,
268                        mTopRight.x * factor, mTopRight.y * factor,
269                        mBottomLeft.x * factor, mBottomLeft.y * factor,
270                        mBottomRight.x * factor, mBottomRight.y * factor);
271    }
272
273    /**
274     * Scale the Quad by the specified factors in the x and y factors.
275     *
276     * @param sx the x scaling factor
277     * @param sy the y scaling factor
278     * @return the Quad instance scaled by the specified factors.
279     */
280    public Quad scale2(float sx, float sy) {
281        return new Quad(mTopLeft.x * sx, mTopLeft.y * sy,
282                        mTopRight.x * sx, mTopRight.y * sy,
283                        mBottomLeft.x * sx, mBottomLeft.y * sy,
284                        mBottomRight.x * sx, mBottomRight.y * sy);
285    }
286
287    /**
288     * Returns the Quad's left-to-right edge.
289     *
290     * Returns a vector that goes from the Quad's top-left to top-right (or bottom-left to
291     * bottom-right).
292     *
293     * @return the edge vector as a PointF.
294     */
295    public PointF xEdge() {
296        return new PointF(mTopRight.x - mTopLeft.x, mTopRight.y - mTopLeft.y);
297    }
298
299    /**
300     * Returns the Quad's top-to-bottom edge.
301     *
302     * Returns a vector that goes from the Quad's top-left to bottom-left (or top-right to
303     * bottom-right).
304     *
305     * @return the edge vector as a PointF.
306     */
307    public PointF yEdge() {
308        return new PointF(mBottomLeft.x - mTopLeft.x, mBottomLeft.y - mTopLeft.y);
309    }
310
311    @Override
312    public String toString() {
313        return "Quad(" + mTopLeft.x + ", " + mTopLeft.y + ", "
314                       + mTopRight.x + ", " + mTopRight.y + ", "
315                       + mBottomLeft.x + ", " + mBottomLeft.y + ", "
316                       + mBottomRight.x + ", " + mBottomRight.y + ")";
317    }
318
319    private Quad(PointF topLeft, PointF topRight, PointF bottomLeft, PointF bottomRight) {
320        mTopLeft = topLeft;
321        mTopRight = topRight;
322        mBottomLeft = bottomLeft;
323        mBottomRight = bottomRight;
324    }
325
326    private Quad(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
327        mTopLeft = new PointF(x0, y0);
328        mTopRight = new PointF(x1, y1);
329        mBottomLeft = new PointF(x2, y2);
330        mBottomRight = new PointF(x3, y3);
331    }
332
333    private Quad(float[] points) {
334        mTopLeft = new PointF(points[0], points[1]);
335        mTopRight = new PointF(points[2], points[3]);
336        mBottomLeft = new PointF(points[4], points[5]);
337        mBottomRight = new PointF(points[6], points[7]);
338    }
339
340    private static PointF rotatePoint(PointF p, PointF c, float cosa, float sina) {
341        float x = (p.x - c.x) * cosa - (p.y - c.y) * sina + c.x;
342        float y = (p.x - c.x) * sina + (p.y - c.y) * cosa + c.y;
343        return new PointF(x,y);
344    }
345}
346
347