Преобразование кривой на основе точек. Преобразование кривой Безье в Android

У меня есть список точек, представляющий кривую, которую я рисую на холсте с помощью объекта 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);

Чего я хочу добиться, так это возможности устанавливать контрольные точки, которые пользователь может трогать и перемещать, и на основе этого движения мои точки будут трансформироваться. То же, что фотошоп делает с Pen Tool см. изображение: введите здесь описание изображения

Примечание: путь Android используется только для рисования, мне не нужно изменять путь, мне нужно изменить координаты. поэтому код выше можно заменить на

canvas.drawLine();

Это не имеет ничего общего с объектом Path.


person Vilen    schedule 18.06.2017    source источник
comment
прочитайте android.graphics.Path документы, они объясняют, как работать с кривыми Безье   -  person pskink    schedule 18.06.2017
comment
Я читал это, он только изменяет путь через матрицы, и, как вы можете видеть на картинке, то, что мне нужно, совершенно другое и не может быть достигнуто с помощью матричного преобразования.   -  person Vilen    schedule 18.06.2017
comment
вам нужно построить свой путь с нуля (метод reset, за которым следуют методы moveTo и пара методов cubicTo) всякий раз, когда изменяется какая-либо контрольная точка   -  person pskink    schedule 18.06.2017
comment
Пожалуйста, прочитайте вопрос еще раз, то, что вы предлагаете, не может решить мою проблему. Речь идет о модификации точек, чтобы путь можно было преобразовать в любом месте, а не о масштабе/смещении/перекосе.   -  person Vilen    schedule 18.06.2017
comment
Я не спрашиваю о том, как перерисовать / перерисовать путь, я прошу алгоритм, который изменяет точки, чтобы путь в реальной жизни вел себя как строка, когда вы перетаскиваете его, он меняет форму. Если вы все еще не поняли вопрос, попробуйте открыть фотошоп и посмотреть, как работает инструмент «Перо».   -  person Vilen    schedule 18.06.2017
comment
код находится под вопросом, но, пожалуйста, забудьте об объекте Path, речь идет не о пути, а о координатах. поэтому вопрос касается изменения координат, а путь используется только для рисования координат на холсте, речь идет даже не об андроиде, вопрос в общем о том, как изменить точки, чтобы они следовали за движением вашего пальца нелинейным образом.   -  person Vilen    schedule 18.06.2017
comment
о, пожалуйста. Мне не нужен путь, мне нужны координаты/пиксели. все дело в координатах и ​​пикселях. Мне нужен общий алгоритм Java, а объекта Path вообще нет. Я не знаю, как еще объяснить. нет пути, нет холста, просто манипулирование пикселями.   -  person Vilen    schedule 18.06.2017
comment
да, мне нужно вычислить каждый x, y, так как конечным результатом должен быть список координат. и да, это должна быть математическая формула. Вы видели, как работает инструмент «Перо» в Photoshop? youtu.be/dSdov7nVYdo?t=2m30s посмотрите, как он нажимает и перемещает указатель, обратите внимание, что это только одна точка, а их может быть много   -  person Vilen    schedule 18.06.2017
comment
Вы можете изменить это так, чтобы было 5 контрольных точек?   -  person Vilen    schedule 18.06.2017
comment
это был риторический вопрос, если вы не заметили   -  person Vilen    schedule 18.06.2017
comment
то, что вы предложили, не является решением. это то, что я пытаюсь объяснить. path.cubicTo имеет только две контрольные точки, и если вы попытаетесь создать цепочку, вы получите острые концы. попробуйте прочитать понять проблему, прежде чем предлагать решения, которые возвращает первый поиск Google.   -  person Vilen    schedule 18.06.2017
comment
о мой зазор. Я устал объяснять, что путевой подход не сработает. Если у вас есть рабочий ответ, который изменяет пиксели, опубликуйте его, иначе забудьте, пожалуйста.   -  person Vilen    schedule 18.06.2017
comment
конечно, да, у вас есть 4 контрольные точки и одна якорь, если вы измените точку привязки, вы увидите острую кромку, чтобы избежать острой кромки, вам нужно будет изменить контрольные точки, которые изменят культивацию, которая не ожидается. и в целом это не то решение, которое мне нужно. потому что, как я уже сказал, забудьте о пути, мне нужно манипулировать координатами. Я ценю ваши усилия, поэтому вы можете опубликовать свой код в качестве ответа, я подниму его.   -  person Vilen    schedule 18.06.2017
comment
да, я читал, и если вы не заметили, я повторю это еще раз, чтобы избежать острых краев, вам нужно будет изменить контрольные точки, которые изменят кривизну, которая не ожидается. это означает, что новая форма выглядит неестественно. Я не хочу использовать Path по основной причине, потому что он дает ограниченные возможности для изменения кривой. и это приносит дополнительную зависимость.   -  person Vilen    schedule 18.06.2017
comment
ок есть ответ - опубликуй. этот разговор стал слишком длинным для комментариев. Хорошая статья однако. Благодарю.   -  person Vilen    schedule 19.06.2017


Ответы (1)


это простое представление, в котором используется одна "якорная" точка и две контрольные точки, если вам нужно больше якорей, добавьте еще один 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);
    }
}
person pskink    schedule 19.06.2017
comment
хороший человек, это именно то, что я искал. Спасибо - person SeanDp32; 30.09.2018