基于点的曲线变换。在Android中进行贝塞尔曲线变换。

4

我有一个点列表,代表一条曲线,我使用Path对象在画布上绘制它。

path.moveTo(x, y);
for (int i = 0; i < points.size(); i++) {
    path.lineTo(points.get(i).x, points.get(i).y);
}
canvas.drawPath(path, paint);

我想实现的是能够设置控制点,用户可以触摸和移动这些点,基于这些移动,我的点将被转换。就像Photoshop使用Pen Tool所做的一样,如图所示:enter image description here 注意:Android Path仅用于绘图,我不需要修改Path,我需要修改坐标。因此,上面的代码可以替换为:
canvas.drawLine();

这与Path对象无关。


我已经阅读过了,它只能通过矩阵修改路径,正如您从图片中所看到的,我需要的完全不同,无法通过矩阵变换实现。 - Vilen
1
每当控制点发生变化时,您需要从头开始构建路径(使用“重置”方法,然后是“移动到”和几个“立方体曲线到”方法)。 - pskink
是的,我需要计算每一个x,y,因为最终结果需要是一系列坐标。是的,它需要是一个数学公式。你看过Photoshop中的钢笔工具是如何工作的吗?https://youtu.be/dSdov7nVYdo?t=2m30s请看他如何点击和移动指针,注意那只是一个点,可以有很多个点。 - Vilen
你能否将它更改为有5个控制点? - Vilen
如果你没有注意到,那是一个修辞问题。 - Vilen
显示剩余10条评论
1个回答

5

这是一个简单的视图,它使用一个“锚点”和两个控制点,如果你需要更多的锚点,请在路径中添加另一个cubicTo

class V extends View {
    static final float RADIUS = 32;
    Path path = new Path();
    Paint pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint controlPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    PointF ctrl1 = new PointF();
    PointF ctrl2 = new PointF();
    PointF ctrl3 = new PointF();
    PointF ctrl4 = new PointF();
    PointF anchor = new PointF();
    GestureDetector detector;
    Layout layout;

    public V(Context context) {
        super(context);
        pathPaint.setColor(Color.RED);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setStrokeWidth(16);
        controlPaint.setColor(Color.GREEN);
        controlPaint.setAlpha(128);
        detector = new GestureDetector(context, listener);
    }

    GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
        PointF target;

        @Override
        public boolean onDown(MotionEvent e) {
            PointF[] targets = { ctrl2, ctrl3, anchor };
            for (PointF t : targets) {
                if (Math.hypot(t.x - e.getX(), t.y - e.getY()) < RADIUS) {
                    target = t;
                    return true;
                }
            }
            target = null;
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (target == null) return false;

            target.offset(-distanceX, -distanceY);
            if (target == ctrl2 || target == ctrl3) {
                PointF otherControl = target == ctrl2 ? ctrl3 : ctrl2;
                // anchor just between points
                double a = Math.atan2(anchor.y - target.y, anchor.x - target.x);
                double r = Math.hypot(otherControl.x - anchor.x, otherControl.y - anchor.y);
                otherControl.set((float) (anchor.x + r * Math.cos(a)), (float) (anchor.y + r * Math.sin(a)));

                // anchor always in the center
//                otherControl.set(2 * anchor.x - target.x, 2 * anchor.y - target.y);
            } else {
                ctrl2.offset(-distanceX, -distanceY);
                ctrl3.offset(-distanceX, -distanceY);
            }
            rebuildPath();
            invalidate();
            return true;
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        ctrl1.set(w * 0.0f, h * 1.0f);
        ctrl2.set(w * 0.1f, h * 0.5f);
        ctrl3.set(w * 0.9f, h * 0.5f);
        ctrl4.set(w * 1.0f, h * 1.0f);
        anchor.set(w * 0.5f, h * 0.5f);
        rebuildPath();
        CharSequence src = "you can drag any green circle: the both control points or the anchor point\n\n" +
                "notice that the control points can be adjusted individually - the only constraint for a smooth line is that the anchor point is between them (but not necessarily in the exact center)";
        TextPaint tp = new TextPaint();
        tp.setColor(Color.WHITE);
        tp.setTextSize(32);
        layout = new StaticLayout(src, tp, w - 64, Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
    }

    private void rebuildPath() {
        path.reset();
        path.moveTo(ctrl1.x, ctrl1.y);
        path.cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, anchor.x, anchor.y);
        path.cubicTo(ctrl3.x, ctrl3.y, ctrl4.x, ctrl4.y, ctrl4.x, ctrl4.y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        detector.onTouchEvent(event);
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.translate(32, 32);
        layout.draw(canvas);
        canvas.restore();
        canvas.drawPath(path, pathPaint);
        controlPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(anchor.x, anchor.y, RADIUS, controlPaint);
        canvas.drawCircle(ctrl2.x, ctrl2.y, RADIUS, controlPaint);
        canvas.drawCircle(ctrl3.x, ctrl3.y, RADIUS, controlPaint);
        controlPaint.setStyle(Paint.Style.STROKE);
        canvas.drawLine(ctrl2.x, ctrl2.y, ctrl3.x, ctrl3.y, controlPaint);
    }
}

干得好,伙计!这正是我一直在寻找的。谢谢。 - SeanDp32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接