/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.media.filterfw.geometry; import android.annotation.SuppressLint; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.RectF; /** * The Quad class specifies a (possibly affine transformed) rectangle. * * A Quad instance holds 4 points that define its shape. The points may represent any rectangle that * has been transformed by an affine transformation. This means that Quads can represent translated, * scaled, rotated and sheared/skewed rectangles. As such, Quads are restricted to the set of * parallelograms. * * Each point in the Quad represents a specific corner of the Quad. These are top-left, top-right, * bottom-left, and bottom-right. These labels allow mapping a transformed Quad back to an up-right * Quad, with the point-to-point mapping well-defined. They do not necessarily indicate that e.g. * the top-left corner is actually at the top-left of coordinate space. */ @SuppressLint("FloatMath") public class Quad { private final PointF mTopLeft; private final PointF mTopRight; private final PointF mBottomLeft; private final PointF mBottomRight; /** * Returns the unit Quad. * The unit Quad has its top-left point at (0, 0) and bottom-right point at (1, 1). * @return the unit Quad. */ public static Quad unitQuad() { return new Quad(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f); } /** * Return a Quad from the specified rectangle. * * @param rect a RectF instance. * @return Quad that represents the passed rectangle. */ public static Quad fromRect(RectF rect) { return new Quad(new PointF(rect.left, rect.top), new PointF(rect.right, rect.top), new PointF(rect.left, rect.bottom), new PointF(rect.right, rect.bottom)); } /** * Return a Quad from the specified rectangle coordinates. * * @param x the top left x coordinate * @param y the top left y coordinate * @param width the width of the rectangle * @param height the height of the rectangle * @return Quad that represents the passed rectangle. */ public static Quad fromRect(float x, float y, float width, float height) { return new Quad(new PointF(x, y), new PointF(x + width, y), new PointF(x, y + height), new PointF(x + width, y + height)); } /** * Return a Quad that spans the specified points and height. * * The returned Quad has the specified top-left and top-right points, and the specified height * while maintaining 90 degree angles on all 4 corners. * * @param topLeft the top-left of the quad * @param topRight the top-right of the quad * @param height the height of the quad * @return Quad that spans the specified points and height. */ public static Quad fromLineAndHeight(PointF topLeft, PointF topRight, float height) { PointF dp = new PointF(topRight.x - topLeft.x, topRight.y - topLeft.y); float len = dp.length(); PointF np = new PointF(height * (dp.y / len), height * (dp.x / len)); PointF p2 = new PointF(topLeft.x - np.x, topLeft.y + np.y); PointF p3 = new PointF(topRight.x - np.x, topRight.y + np.y); return new Quad(topLeft, topRight, p2, p3); } /** * Return a Quad that represents the specified rotated rectangle. * * The Quad is rotated counter-clockwise around its centroid. * * @param rect the source rectangle * @param angle the angle to rotate the source rectangle in radians * @return the Quad representing the source rectangle rotated by the given angle. */ public static Quad fromRotatedRect(RectF rect, float angle) { return Quad.fromRect(rect).rotated(angle); } /** * Return a Quad that represents the specified transformed rectangle. * * The transform is applied by multiplying each point (x, y, 1) by the matrix. * * @param rect the source rectangle * @param matrix the transformation matrix * @return the Quad representing the source rectangle transformed by the matrix */ public static Quad fromTransformedRect(RectF rect, Matrix matrix) { return Quad.fromRect(rect).transformed(matrix); } /** * Returns the transformation matrix to transform the source Quad to the target Quad. * * @param source the source quad * @param target the target quad * @return the transformation matrix to map source to target. */ public static Matrix getTransform(Quad source, Quad target) { // We only use the first 3 points as they sufficiently specify the transform Matrix transform = new Matrix(); transform.setPolyToPoly(source.asCoords(), 0, target.asCoords(), 0, 3); return transform; } /** * The top-left point of the Quad. * @return top-left point of the Quad. */ public PointF topLeft() { return mTopLeft; } /** * The top-right point of the Quad. * @return top-right point of the Quad. */ public PointF topRight() { return mTopRight; } /** * The bottom-left point of the Quad. * @return bottom-left point of the Quad. */ public PointF bottomLeft() { return mBottomLeft; } /** * The bottom-right point of the Quad. * @return bottom-right point of the Quad. */ public PointF bottomRight() { return mBottomRight; } /** * Rotate the quad by the given angle. * * The Quad is rotated counter-clockwise around its centroid. * * @param angle the angle to rotate in radians * @return the rotated Quad */ public Quad rotated(float angle) { PointF center = center(); float cosa = (float) Math.cos(angle); float sina = (float) Math.sin(angle); PointF topLeft = rotatePoint(topLeft(), center, cosa, sina); PointF topRight = rotatePoint(topRight(), center, cosa, sina); PointF bottomLeft = rotatePoint(bottomLeft(), center, cosa, sina); PointF bottomRight = rotatePoint(bottomRight(), center, cosa, sina); return new Quad(topLeft, topRight, bottomLeft, bottomRight); } /** * Transform the quad with the given transformation matrix. * * The transform is applied by multiplying each point (x, y, 1) by the matrix. * * @param matrix the transformation matrix * @return the transformed Quad */ public Quad transformed(Matrix matrix) { float[] points = asCoords(); matrix.mapPoints(points); return new Quad(points); } /** * Returns the centroid of the Quad. * * The centroid of the Quad is where the two inner diagonals connecting the opposite corners * meet. * * @return the centroid of the Quad. */ public PointF center() { // As the diagonals bisect each other, we can simply return the center of one of the // diagonals. return new PointF((mTopLeft.x + mBottomRight.x) / 2f, (mTopLeft.y + mBottomRight.y) / 2f); } /** * Returns the quad as a float-array of coordinates. * The order of coordinates is top-left, top-right, bottom-left, bottom-right. This is the * default order of coordinates used in ImageShaders, so this method can be used to bind * an attribute to the Quad. */ public float[] asCoords() { return new float[] { mTopLeft.x, mTopLeft.y, mTopRight.x, mTopRight.y, mBottomLeft.x, mBottomLeft.y, mBottomRight.x, mBottomRight.y }; } /** * Grow the Quad outwards by the specified factor. * * This method moves the corner points of the Quad outward along the diagonals that connect * them to the centroid. A factor of 1.0 moves the quad outwards by the distance of the corners * to the centroid. * * @param factor the growth factor * @return the Quad grown by the specified amount */ public Quad grow(float factor) { PointF pc = center(); return new Quad(factor * (mTopLeft.x - pc.x) + pc.x, factor * (mTopLeft.y - pc.y) + pc.y, factor * (mTopRight.x - pc.x) + pc.x, factor * (mTopRight.y - pc.y) + pc.y, factor * (mBottomLeft.x - pc.x) + pc.x, factor * (mBottomLeft.y - pc.y) + pc.y, factor * (mBottomRight.x - pc.x) + pc.x, factor * (mBottomRight.y - pc.y) + pc.y); } /** * Scale the Quad by the specified factor. * * @param factor the scaling factor * @return the Quad instance scaled by the specified factor. */ public Quad scale(float factor) { return new Quad(mTopLeft.x * factor, mTopLeft.y * factor, mTopRight.x * factor, mTopRight.y * factor, mBottomLeft.x * factor, mBottomLeft.y * factor, mBottomRight.x * factor, mBottomRight.y * factor); } /** * Scale the Quad by the specified factors in the x and y factors. * * @param sx the x scaling factor * @param sy the y scaling factor * @return the Quad instance scaled by the specified factors. */ public Quad scale2(float sx, float sy) { return new Quad(mTopLeft.x * sx, mTopLeft.y * sy, mTopRight.x * sx, mTopRight.y * sy, mBottomLeft.x * sx, mBottomLeft.y * sy, mBottomRight.x * sx, mBottomRight.y * sy); } /** * Returns the Quad's left-to-right edge. * * Returns a vector that goes from the Quad's top-left to top-right (or bottom-left to * bottom-right). * * @return the edge vector as a PointF. */ public PointF xEdge() { return new PointF(mTopRight.x - mTopLeft.x, mTopRight.y - mTopLeft.y); } /** * Returns the Quad's top-to-bottom edge. * * Returns a vector that goes from the Quad's top-left to bottom-left (or top-right to * bottom-right). * * @return the edge vector as a PointF. */ public PointF yEdge() { return new PointF(mBottomLeft.x - mTopLeft.x, mBottomLeft.y - mTopLeft.y); } @Override public String toString() { return "Quad(" + mTopLeft.x + ", " + mTopLeft.y + ", " + mTopRight.x + ", " + mTopRight.y + ", " + mBottomLeft.x + ", " + mBottomLeft.y + ", " + mBottomRight.x + ", " + mBottomRight.y + ")"; } private Quad(PointF topLeft, PointF topRight, PointF bottomLeft, PointF bottomRight) { mTopLeft = topLeft; mTopRight = topRight; mBottomLeft = bottomLeft; mBottomRight = bottomRight; } private Quad(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) { mTopLeft = new PointF(x0, y0); mTopRight = new PointF(x1, y1); mBottomLeft = new PointF(x2, y2); mBottomRight = new PointF(x3, y3); } private Quad(float[] points) { mTopLeft = new PointF(points[0], points[1]); mTopRight = new PointF(points[2], points[3]); mBottomLeft = new PointF(points[4], points[5]); mBottomRight = new PointF(points[6], points[7]); } private static PointF rotatePoint(PointF p, PointF c, float cosa, float sina) { float x = (p.x - c.x) * cosa - (p.y - c.y) * sina + c.x; float y = (p.x - c.x) * sina + (p.y - c.y) * cosa + c.y; return new PointF(x,y); } }