com.android.ide.common.vectordrawable.EllipseSolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdk-common Show documentation
Show all versions of sdk-common Show documentation
sdk-common library used by other Android tools libraries.
/*
* Copyright (C) 2015 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 com.android.ide.common.vectordrawable;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.logging.Level;
import java.util.logging.Logger;
class EllipseSolver {
private static Logger logger = Logger.getLogger(EllipseSolver.class.getSimpleName());
// Final results:
private float mMajorAxis = 0;
private float mMinorAxis = 0;
private float mRotationDegree = 0;
private boolean mDirectionChanged;
// Intermediate results:
private Point2D.Float mMajorAxisPoint;
private Point2D.Float mMiddlePoint;
private Point2D.Float mMinorAxisPoint;
private Point2D.Float mDstMajorAxisPoint;
private Point2D.Float mDstMiddlePoint;
private Point2D.Float mDstMinorAxisPoint;
/**
* Rotate the Point2D by radians
*
* @return the rotated Point2D
*/
private Point2D.Float rotatePoint2D(Point2D.Float inPoint, float radians) {
Point2D.Float result = new Point2D.Float();
float cosine = (float) Math.cos(radians);
float sine = (float) Math.sin(radians);
result.x = inPoint.x * cosine - inPoint.y * sine;
result.y = inPoint.x * sine + inPoint.y * cosine;
return result;
}
/**
* Construct the solver with all necessary parameters, and all the output value will be
* ready after this constructor is called.
*
* Note that all the x y values are in absolute coordinates, such that we can apply the
* transform directly.
*/
public EllipseSolver(AffineTransform totalTransform,
float currentX, float currentY, float rx, float ry, float xAxisRotation,
float largeArcFlag, float sweepFlag, float destX, float destY) {
boolean largeArc = largeArcFlag != 0;
boolean sweep = sweepFlag != 0;
// Compute the cx and cy first.
Point2D.Float originalCenter = computeOriginalCenter(currentX, currentY, rx, ry,
xAxisRotation, largeArc, sweep, destX, destY);
// Compute 3 points from original ellipse
computeControlPoints(rx, ry, xAxisRotation, originalCenter.x, originalCenter.y);
// Transform 3 points and center point into destination.
mDstMiddlePoint = (Point2D.Float) totalTransform.transform(mMiddlePoint, null);
mDstMajorAxisPoint = (Point2D.Float) totalTransform.transform(mMajorAxisPoint, null);
mDstMinorAxisPoint = (Point2D.Float) totalTransform.transform(mMinorAxisPoint, null);
Point2D dstCenter = totalTransform.transform(originalCenter, null);
float dstCenterX = (float) dstCenter.getX();
float dstCenterY = (float) dstCenter.getY();
// Compute the relative 3 points:
float relativeDstMiddleX = mDstMiddlePoint.x - dstCenterX;
float relativeDstMiddleY = mDstMiddlePoint.y - dstCenterY;
float relativeDstMajorAxisPointX = mDstMajorAxisPoint.x - dstCenterX;
float relativeDstMajorAxisPointY = mDstMajorAxisPoint.y - dstCenterY;
float relativeDstMinorAxisPointX = mDstMinorAxisPoint.x - dstCenterX;
float relativeDstMinorAxisPointY = mDstMinorAxisPoint.y - dstCenterY;
// Check if the direction has change!
mDirectionChanged = computeDirectionChange(mMiddlePoint, mMajorAxisPoint,
mMinorAxisPoint,
mDstMiddlePoint, mDstMajorAxisPoint, mDstMinorAxisPoint);
// From 3 dest points, recompute the a, b and theta.
if (computeABThetaFromControlPoints(relativeDstMiddleX, relativeDstMiddleY,
relativeDstMajorAxisPointX,
relativeDstMajorAxisPointY, relativeDstMinorAxisPointX,
relativeDstMinorAxisPointY)) {
logger.log(Level.WARNING,
"Early return in the ellipse transformation computation!");
}
}
/**
* After a random transformation, the controls points may change its direction, left handed <->
* right handed. In this case, we better flip the flag for the ArcTo command.
*
* Here, we use the cross product to figure out the direction of the 3 control points for the
* src and dst ellipse.
*/
private boolean computeDirectionChange(final Point2D.Float middlePoint,
final Point2D.Float majorAxisPoint, final Point2D.Float minorAxisPoint,
final Point2D.Float dstMiddlePoint, final Point2D.Float dstMajorAxisPoint,
final Point2D.Float dstMinorAxisPoint) {
// Compute both Cross Product, then compare the sign.
float srcCrossProduct = getCrossProduct(middlePoint, majorAxisPoint, minorAxisPoint);
float dstCrossProduct = getCrossProduct(dstMiddlePoint, dstMajorAxisPoint,
dstMinorAxisPoint);
return srcCrossProduct * dstCrossProduct < 0 ? true : false;
}
private float getCrossProduct(final Point2D.Float middlePoint,
final Point2D.Float majorAxisPoint, final Point2D.Float minorAxisPoint) {
float majorMinusMiddleX = majorAxisPoint.x - middlePoint.x;
float majorMinusMiddleY = majorAxisPoint.y - middlePoint.y;
float minorMinusMiddleX = minorAxisPoint.x - middlePoint.x;
float minorMinusMiddleY = minorAxisPoint.y - middlePoint.y;
return (majorMinusMiddleX * minorMinusMiddleY) - (majorMinusMiddleY
* minorMinusMiddleX);
}
/**
* @return true if there is something error going on, either due to the ellipse is not setup
* correctly, or some computation error happens. This error is ignorable, but the
* output ellipse will not be correct.
*/
private boolean computeABThetaFromControlPoints(float relMiddleX, float relMiddleY,
float relativeMajorAxisPointX, float relativeMajorAxisPointY,
float relativeMinorAxisPointX, float relativeMinorAxisPointY) {
float m11 = relMiddleX * relMiddleX;
float m12 = relMiddleX * relMiddleY;
float m13 = relMiddleY * relMiddleY;
float m21 = relativeMajorAxisPointX * relativeMajorAxisPointX;
float m22 = relativeMajorAxisPointX * relativeMajorAxisPointY;
float m23 = relativeMajorAxisPointY * relativeMajorAxisPointY;
float m31 = relativeMinorAxisPointX * relativeMinorAxisPointX;
float m32 = relativeMinorAxisPointX * relativeMinorAxisPointY;
float m33 = relativeMinorAxisPointY * relativeMinorAxisPointY;
float det = -(m13 * m22 * m31 - m12 * m23 * m31 - m13 * m21 * m32
+ m11 * m23 * m32 + m12 * m21 * m33 - m11 * m22 * m33);
if (det == 0) {
return true;
}
float A = (-m13 * m22 + m12 * m23 + m13 * m32 - m23 * m32 - m12 * m33 + m22 * m33)
/ det;
float B = (m13 * m21 - m11 * m23 - m13 * m31 + m23 * m31 + m11 * m33 - m21 * m33) / det;
float C = (m12 * m21 - m11 * m22 - m12 * m31 + m22 * m31 + m11 * m32 - m21 * m32)
/ (-det);
// Now we know A = cos(t)^2 / a^2 + sin(t)^2 / b^2
// B = -2 cos(t) sin(t) (1/a^2 - 1/b^2)
// C = sin(t)^2 / a^2 + cos(t)^2 / b^2
// Solve it , we got
// 2*t = arctan ( B / (A - C);
if ((A - C) == 0) {
// We know that a == b now.
mMinorAxis = (float) Math.hypot(relativeMajorAxisPointX, relativeMajorAxisPointY);
mMajorAxis = mMinorAxis;
mRotationDegree = 0;
return false;
}
float doubleThetaInRadians = (float) Math.atan(B / (A - C));
float thetaInRadians = doubleThetaInRadians / 2;
if (Math.sin(doubleThetaInRadians) == 0) {
mMinorAxis = (float) Math.sqrt(1 / C);
mMajorAxis = (float) Math.sqrt(1 / A);
mRotationDegree = 0;
// This is a valid answer, so return false;
return false;
}
float bSqInv = (A + C + B / (float) Math.sin(doubleThetaInRadians)) / 2;
float aSqInv = A + C - bSqInv;
if (bSqInv == 0 || aSqInv == 0) {
return true;
}
mMinorAxis = (float) Math.sqrt(1 / bSqInv);
mMajorAxis = (float) Math.sqrt(1 / aSqInv);
mRotationDegree = (float) Math.toDegrees(Math.PI / 2 + thetaInRadians);
return false;
}
private void computeControlPoints(float a, float b, float rot, float cx, float cy) {
mMajorAxisPoint = new Point2D.Float(a, 0);
mMinorAxisPoint = new Point2D.Float(0, b);
mMajorAxisPoint = rotatePoint2D(mMajorAxisPoint, rot);
mMinorAxisPoint = rotatePoint2D(mMinorAxisPoint, rot);
mMajorAxisPoint.x += cx;
mMajorAxisPoint.y += cy;
mMinorAxisPoint.x += cx;
mMinorAxisPoint.y += cy;
float middleDegree = 45; // This can be anything b/t 0 to 90.
float middleRadians = (float) Math.toRadians(middleDegree);
float middleR = a * b / (float) Math.hypot(b * (float) Math.cos(middleRadians),
a * (float) Math.sin(middleRadians));
mMiddlePoint = new Point2D.Float(middleR * (float) Math.cos(middleRadians),
middleR * (float) Math.sin(middleRadians));
mMiddlePoint = rotatePoint2D(mMiddlePoint, rot);
mMiddlePoint.x += cx;
mMiddlePoint.y += cy;
}
private Point2D.Float computeOriginalCenter(float x1, float y1, float rx, float ry,
float phi, boolean largeArc, boolean sweep, float x2, float y2) {
Point2D.Float result = new Point2D.Float();
float cosPhi = (float) Math.cos(phi);
float sinPhi = (float) Math.sin(phi);
float xDelta = (x1 - x2) / 2;
float yDelta = (y1 - y2) / 2;
float tempX1 = cosPhi * xDelta + sinPhi * yDelta;
float tempY1 = -sinPhi * xDelta + cosPhi * yDelta;
float rxSq = rx * rx;
float rySq = ry * ry;
float tempX1Sq = tempX1 * tempX1;
float tempY1Sq = tempY1 * tempY1;
float tempCenterFactor = rxSq * rySq - rxSq * tempY1Sq - rySq * tempX1Sq;
tempCenterFactor /= rxSq * tempY1Sq + rySq * tempX1Sq;
tempCenterFactor = (float) Math.sqrt(tempCenterFactor);
if (largeArc == sweep) {
tempCenterFactor = -tempCenterFactor;
}
float tempCx = tempCenterFactor * rx * tempY1 / ry;
float tempCy = -tempCenterFactor * ry * tempX1 / rx;
float xCenter = (x1 + x2) / 2;
float yCenter = (y1 + y2) / 2;
float cx = cosPhi * tempCx - sinPhi * tempCy + xCenter;
float cy = sinPhi * tempCx + cosPhi * tempCy + yCenter;
result.x = cx;
result.y = cy;
return result;
}
public float getMajorAxis() {
return mMajorAxis;
}
public float getMinorAxis() {
return mMinorAxis;
}
public float getRotationDegree() {
return mRotationDegree;
}
public boolean getDirectionChanged() {
return mDirectionChanged;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy