如何在不包括贝塞尔曲线控制点的情况下计算 Android 中路径的边界?
How to calculate the bounds of a Path in Android without including the control points of Bezier curves?
Android 有一个路径 class 和一个方法 computeBounds()
。
但是在贝塞尔曲线路径的情况下,边界包括曲线的控制点。
这并不理想,因为我只想计算贝塞尔曲线创建的实际形状的边界。
似乎 Skia(Android 使用的底层库)有一个 computeTightBounds 方法可以满足我的要求,但 Android 似乎没有公开它。
https://api.skia.org/classSkPath.html#a597c8fcc5e4750542e2688b057a14e9e
如何在不包括控制点的情况下计算边界?
没有任何直接的方法。
我的解决方案是在绘制过程中根据贝塞尔曲线的终点更新一个RectF。
final RectF mRect = new RectF(999999,999999,-1,-1);
public void updateBounds(final float endPointX, final float endPointY) {
if (mRect.left == 999999) mRect.set(endPointX, endPointY, endPointX, endPointY);
else mRect.union(endPointX, endPointY);
}
但是,此解决方案不考虑 StrokeWidth 计数,而只考虑 virtual/invisible 路径(“宽度”等于零)。因此,如果您的需求是“计算有效绘制路径边界”,则需要subtract/sum一半“Path.getStrokewidth() " 在边界计算期间。
没有办法直接获取
要获得一组高精度的边界,您需要自己计算,方法是遍历路径段并计算每个贝塞尔曲线、直线、圆弧等的实际边界。不幸的是 Android不提供在 Path
创建后单步执行元素的方法。所以如果你需要一个高精度的结果,希望你在做之前知道路径的组成。
不过,有一种方法可以使用 PathMeasure
获得具有可配置精度的结果。
尝试以下操作。 此代码尚未经过测试。
void computeExactBounds(RectF bounds, Path path, int accuracy)
{
bool first = true;
// Make sure accuracy defaults to something reasonable
if (accuracy < 2)
accuracy = 100;
PathMeasure pm = new PathMeasure(path, false);
// Step through all the subpaths of this path
while (nextContour())
{
float pathLen = pm.getLength();
// To compute the bounds, we calculate the position of 'accuracy'
// number of positions along the path. The higher that accuracy is,
// the more accurate the result, but the longer it will take.
// But note that, in some extreme cases, it is possible that the
// real path bounds may be slightly larger than the bounds that
// this method returns.
float pos = new float[];
// Initialise bounds to the path start coordinates
pm.getPosTan(0, pos, null);
if (first) {
bounds.set(pos[0], pos[1], pos[0], pos[1]);
first = false;
}
else
bounds.union(pos[0], pos[1]);
// Step through 'accuracy' positions along the path,
// making sure bounds includes all those positions
for (int i=1; i <= accuracy; i++) {
pm.getPosTan(pathLen * i / accuracy, pos, null);
bounds.union(pos[0], pos[1]);
}
}
}
您可以通过使用二进制搜索自适应拆分来改进此代码
子路径分成更小的段(而不是沿着路径以小的固定增量陡峭)。这就是 path.approximate()
做。见下文。
如果您使用 API 26 或更高版本,您可以使用 path.approximate()
计算位置来加快此代码的速度。
这段代码也没有经过测试:)
void computeExactBounds(RectF bounds, Path path, float acceptableError)
{
// A typical value of acceptableError might be 0.5. But if, say,
// your path uses extremely small or large coordinate values, you
// might need to adjust this. Choose a value that works out as
// equivalent to around half a pixel at the current scale;
float[] pos = path.approximate(acceptableError);
for (int i = 0; i < pos.length; i += 3)
{
if (i == 0)
bounds.set(pos[i+1], pos[i+2], pos[i+1], pos[i+2]);
else
bounds.union(pos[i+1], pos[i+2]);
}
}
Android 有一个路径 class 和一个方法 computeBounds()
。
但是在贝塞尔曲线路径的情况下,边界包括曲线的控制点。
这并不理想,因为我只想计算贝塞尔曲线创建的实际形状的边界。
似乎 Skia(Android 使用的底层库)有一个 computeTightBounds 方法可以满足我的要求,但 Android 似乎没有公开它。
https://api.skia.org/classSkPath.html#a597c8fcc5e4750542e2688b057a14e9e
如何在不包括控制点的情况下计算边界?
没有任何直接的方法。 我的解决方案是在绘制过程中根据贝塞尔曲线的终点更新一个RectF。
final RectF mRect = new RectF(999999,999999,-1,-1);
public void updateBounds(final float endPointX, final float endPointY) {
if (mRect.left == 999999) mRect.set(endPointX, endPointY, endPointX, endPointY);
else mRect.union(endPointX, endPointY);
}
但是,此解决方案不考虑 StrokeWidth 计数,而只考虑 virtual/invisible 路径(“宽度”等于零)。因此,如果您的需求是“计算有效绘制路径边界”,则需要subtract/sum一半“Path.getStrokewidth() " 在边界计算期间。
没有办法直接获取
要获得一组高精度的边界,您需要自己计算,方法是遍历路径段并计算每个贝塞尔曲线、直线、圆弧等的实际边界。不幸的是 Android不提供在 Path
创建后单步执行元素的方法。所以如果你需要一个高精度的结果,希望你在做之前知道路径的组成。
不过,有一种方法可以使用 PathMeasure
获得具有可配置精度的结果。
尝试以下操作。 此代码尚未经过测试。
void computeExactBounds(RectF bounds, Path path, int accuracy)
{
bool first = true;
// Make sure accuracy defaults to something reasonable
if (accuracy < 2)
accuracy = 100;
PathMeasure pm = new PathMeasure(path, false);
// Step through all the subpaths of this path
while (nextContour())
{
float pathLen = pm.getLength();
// To compute the bounds, we calculate the position of 'accuracy'
// number of positions along the path. The higher that accuracy is,
// the more accurate the result, but the longer it will take.
// But note that, in some extreme cases, it is possible that the
// real path bounds may be slightly larger than the bounds that
// this method returns.
float pos = new float[];
// Initialise bounds to the path start coordinates
pm.getPosTan(0, pos, null);
if (first) {
bounds.set(pos[0], pos[1], pos[0], pos[1]);
first = false;
}
else
bounds.union(pos[0], pos[1]);
// Step through 'accuracy' positions along the path,
// making sure bounds includes all those positions
for (int i=1; i <= accuracy; i++) {
pm.getPosTan(pathLen * i / accuracy, pos, null);
bounds.union(pos[0], pos[1]);
}
}
}
您可以通过使用二进制搜索自适应拆分来改进此代码
子路径分成更小的段(而不是沿着路径以小的固定增量陡峭)。这就是 path.approximate()
做。见下文。
如果您使用 API 26 或更高版本,您可以使用 path.approximate()
计算位置来加快此代码的速度。
这段代码也没有经过测试:)
void computeExactBounds(RectF bounds, Path path, float acceptableError)
{
// A typical value of acceptableError might be 0.5. But if, say,
// your path uses extremely small or large coordinate values, you
// might need to adjust this. Choose a value that works out as
// equivalent to around half a pixel at the current scale;
float[] pos = path.approximate(acceptableError);
for (int i = 0; i < pos.length; i += 3)
{
if (i == 0)
bounds.set(pos[i+1], pos[i+2], pos[i+1], pos[i+2]);
else
bounds.union(pos[i+1], pos[i+2]);
}
}