从用户的触摸绘制一个完美的圆 [英] Draw a perfect circle from user's touch

查看:137
本文介绍了从用户的触摸绘制一个完美的圆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个练习项目,允许用户在他们用手指触摸时在屏幕上绘画。非常简单的应用程序我做运动的方式回来。
我的小表哥用自己的手指用我的iPad在这个应用程序(孩子们的图画:圆,线等,无论到底是什么)绘制的东西。
然后他开始画圈子,然后他要我把它做成好圈子(从我的理解:使绘制的圆完美圆,因为我们知道
无论我们如何稳定我们试图画东西用我们的手指在屏幕上,一个圆圈从来没有真正像圆圈一样圆)。



所以我的问题是,在代码中是否有任何方法,我们可以先检测用户绘制的一条线,形成一个圆,并生成大致相同的大小圆圈通过使其在屏幕上完美圆。使一条不那么直的直线是我会知道如何做,但对于圆,我不知道如何去做它与Quartz或其他方法。



我的推理是,在用户抬起手指后,线的开始和结束点必须相互接触或交叉,以证明他试图

解决方案

有时,花一些时间重新发明轮子真的很有用。正如你可能已经注意到有很多框架,但是实现一个简单但有用的解决方案并不难,不会引入所有复杂性。 (请不要误会我,为了任何严肃的目的,最好使用一些成熟的,被证明是稳定的框架)。



我将首先呈现我的结果,然后解释他们背后简单和直接的想法。



>



你会看到在我的实现没有必要分析每一个点,并做复杂的计算。这个想法是发现一些有价值的元信息。我将使用):

   - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [[event allTouches] anyObject]
}

- (void)handleTouch:(UIEvent *)event {
UITouch * touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self];

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch:event];
}

有几种增强功能。



随时开始



目前的要求是开始绘制圆由于以下简化的顶部中点:

  if(type == null || type!= newType){
if(newType!= shape [index]){
break;
}
bounds [index] = current;
detected [index ++] = newType;
}

请注意默认值 index 。通过形状的可用部分的简单搜索将去除该限制。请注意,您需要使用循环缓冲才能检测完整形状:





顺时针和逆时针



要支持这两种模式,您需要使用上一个增强功能的循环缓冲区,并在两个方向上搜索:





绘制椭圆 p>

您已经在 bounds 数组中拥有了所需的一切。



>



只需使用该数据:

  cWidth = bounds [ 0] .y; 
cHeight = bounds [3] .y-bounds [1] .y;

其他手势(可选)



最后,您只需要正确处理 dx (或 dy )相等的情况为零以支持其他手势:





更新



,所以我没有更新的代码,以使它工作顺利,并提供一些绘图提示,突出支持点等:





以下是代码:

  import java.awt.BasicStroke; 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class CircleGestureDemo extends JFrame {

enum类型{

RIGHT_DOWN,
LEFT_DOWN,
LEFT_UP,
RIGHT_UP,
UNDEFINED
}

private static final Type [] circleShape = {
Type.RIGHT_DOWN,
Type.LEFT_DOWN,
Type.LEFT_UP,
Type.RIGHT_UP};

public CircleGestureDemo()throws HeadlessException {
super(Circle gesture);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(BorderLayout.CENTER,new GesturePanel());
setPreferredSize(new Dimension(800,600));
pack();
}

public static class GesturePanel extends JPanel实现MouseListener,MouseMotionListener {

private boolean editing = false;
private Point [] bounds;
private point last = new Point(0,0);
private final List< Point> points = new ArrayList<>();

public GesturePanel(){
super(true);
addMouseListener(this);
addMouseMotionListener(this);
}

@Override
public void paint(Graphics Graphics){
super.paint(graphics);

Dimension d = getSize();
Graphics2D g =(Graphics2D)graphics;

RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
qualityHints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);

g.setRenderingHints(qualityHints);

if(!points.isEmpty()&& cD == 0){
isCircle(points,g);
g.setColor(HINT_COLOR);
if(bounds [2]!= null){
int r =(bounds [2] .y-bounds [0] .y)/ 2;
g.setStroke(new BasicStroke(r / 3 + 1));
g.drawOval(bounds [0] .x-r,bounds [0] .y,2 * r,2 * r);
} else if(bounds [1]!= null){
int r = bounds [1] .x - bounds [0] .x;
g.setStroke(new BasicStroke(r / 3 + 1));
g.drawOval(bounds [0] .x-r,bounds [0] .y,2 * r,2 * r);
}
}

g.setStroke(new BasicStroke(2));
g.setColor(Color.RED);

if(cD == 0){
Point b = null;
for(Point e:points){
if(null!= b){
g.drawLine(b.x,b.y,e.x,e.y);
}
b = e;
}

} else if(cD> 0){
g.setColor(Color.BLUE);
g.setStroke(new BasicStroke(3));
g.drawOval(cX,cY,cD,cD);
} else {
g.drawString(Uknown,30,50);
}
}

private类型getType(int dx,int dy){
Type result = Type.UNDEFINED;

if(dx> 0&& dy< 0){
result = Type.RIGHT_DOWN;
} else if(dx< 0&& dy< 0){
result = Type.LEFT_DOWN;
} else if(dx< 0& dy> 0){
result = Type.LEFT_UP;
} else if(dx> 0&& dy> 0){
result = Type.RIGHT_UP;
}

返回结果;
}

private boolean isCircle(List< Point> points,Graphics2D g){
boolean result = false;
Type [] shape = circleShape;
bounds = new Point [shape.length];

final int STEP = 5;
int index = 0;
int initial = 0;
Point current = points.get(0);
类型type = null;

for(int i = STEP; i< points.size(); i + = STEP){
final Point next = points.get(i);
final int dx = next.x - current.x;
final int dy = - (next.y - current.y);

if(dx == 0 || dy == 0){
continue;
}

final int marker = 8;
if(null!= g){
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
g.drawOval(current.x - marker / 2,
current.y - marker / 2,
marker,marker);
}

类型newType = getType(dx,dy);
if(type == null || type!= newType){
if(newType!= shape [index]){
break;
}
bounds [index ++] = current;
}

type = newType;
current = next;
initial = i;

if(index> = shape.length){
result = true;
break;
}
}
返回结果;
}

@Override
public void mousePressed(MouseEvent e){
cD = 0;
points.clear();
editing = true;
}

private int cX;
private int cY;
private int cD;

@Override
public void mouseReleased(MouseEvent e){
editing = false;
if(points.size()> 0){
if(isCircle(points,null)){
int r = Math.abs((bounds [2] .y-bounds [0] .y)/ 2);
cX = bounds [0] .x - r;
cY = bounds [0] .y;
cD = 2 * r;
} else {
cD = -1;
}
repaint();
}
}

@Override
public void mouseDragged(MouseEvent e){
Point newPoint = e.getPoint();
if(editing&&!last.equals(newPoint)){
points.add(newPoint);
last = newPoint;
repaint();
}
}

@Override
public void mouseMoved(MouseEvent e){
}

@Override
public void mouseEntered(MouseEvent e){
}

@Override
public void mouseExited(MouseEvent e){
}

@覆盖
public void mouseClicked(MouseEvent e){
}
}

public static void main(String [] args){
SwingUtilities.invokeLater new Runnable(){

@Override
public void run(){
CircleGestureDemo t = new CircleGestureDemo();
t.setVisible(true);
}
});
}

final static Color HINT_COLOR = new Color(0x55888888,true);
}


I have this practice project that allows the user to draw on the screen as they touch with their fingers. Very simple App I did as exercise way back. My little cousin took the liberty of drawing things with his finger with my iPad on this App (Kids drawings: circle, lines, etc, whatever came to his mind). Then he started to draw circles and then he asked me to make it "good circle" (from my understanding: make the drawn circle perfectly round, as we know no matter how stable we try to draw something with our finger on the screen, a circle is never really as rounded like a circle should be).

So my question here is that, is there any way in code where we can first detect a line drawn by the user that forms a circle and generate approximately the same size of the circle by making it perfectly round on the screen. Making a not so straight line straight is something I would know how to do, but as for circle, I don't quite know how to go about doing it with Quartz or other methods.

My reasoning is that, the start and the end point of the line must touch or cross each other after the user lifts his finger to justify the fact that he was trying to actually draw a circle.

解决方案

Sometimes it is really useful to spend some time reinventing the wheel. As you might have already noticed there are a lot of frameworks, but it is not that hard to implement a simple, but yet useful solution without introducing all that complexity. (Please don't get me wrong, for any serious purpose it is better to use some mature and proven to be stable framework).

I will present my results first and then explain the simple and straightforward idea behind them.

You'll see in my implementation there is no need to analyze every single point and do complex computations. The idea is to spot some valuable meta information. I will use tangent as an example:

Let's identify a simple and straightforward pattern, typical for the selected shape:

So it is not that hard to implement a circle detection mechanism based on that idea. See working demo below (Sorry, I'm using Java as the fastest way to provide this fast and a bit dirty example):

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class CircleGestureDemo extends JFrame implements MouseListener, MouseMotionListener {

    enum Type {
        RIGHT_DOWN,
        LEFT_DOWN,
        LEFT_UP,
        RIGHT_UP,
        UNDEFINED
    }

    private static final Type[] circleShape = {
        Type.RIGHT_DOWN,
        Type.LEFT_DOWN,
        Type.LEFT_UP,
        Type.RIGHT_UP};

    private boolean editing = false;
    private Point[] bounds;
    private Point last = new Point(0, 0);
    private List<Point> points = new ArrayList<>();

    public CircleGestureDemo() throws HeadlessException {
        super("Detect Circle");

        addMouseListener(this);
        addMouseMotionListener(this);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setPreferredSize(new Dimension(800, 600));
        pack();
    }

    @Override
    public void paint(Graphics graphics) {
        Dimension d = getSize();
        Graphics2D g = (Graphics2D) graphics;

        super.paint(g);

        RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHints(qualityHints);

        g.setColor(Color.RED);
        if (cD == 0) {
            Point b = null;
            for (Point e : points) {
                if (null != b) {
                    g.drawLine(b.x, b.y, e.x, e.y);
                }
                b = e;
            }
        }else if (cD > 0){
            g.setColor(Color.BLUE);
            g.setStroke(new BasicStroke(3));
            g.drawOval(cX, cY, cD, cD);
        }else{
            g.drawString("Uknown",30,50);
        }
    }


    private Type getType(int dx, int dy) {
        Type result = Type.UNDEFINED;

        if (dx > 0 && dy < 0) {
            result = Type.RIGHT_DOWN;
        } else if (dx < 0 && dy < 0) {
            result = Type.LEFT_DOWN;
        } else if (dx < 0 && dy > 0) {
            result = Type.LEFT_UP;
        } else if (dx > 0 && dy > 0) {
            result = Type.RIGHT_UP;
        }

        return result;
    }

    private boolean isCircle(List<Point> points) {
        boolean result = false;
        Type[] shape = circleShape;
        Type[] detected = new Type[shape.length];
        bounds = new Point[shape.length];

        final int STEP = 5;

        int index = 0;        
        Point current = points.get(0);
        Type type = null;

        for (int i = STEP; i < points.size(); i += STEP) {
            Point next = points.get(i);
            int dx = next.x - current.x;
            int dy = -(next.y - current.y);

            if(dx == 0 || dy == 0) {
                continue;
            }

            Type newType = getType(dx, dy);
            if(type == null || type != newType) {
                if(newType != shape[index]) {
                    break;
                }
                bounds[index] = current;
                detected[index++] = newType;
            }
            type = newType;            
            current = next;

            if (index >= shape.length) {
                result = true;
                break;
            }
        }

        return result;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        cD = 0;
        points.clear();
        editing = true;
    }

    private int cX;
    private int cY;
    private int cD;

    @Override
    public void mouseReleased(MouseEvent e) {
        editing = false;
        if(points.size() > 0) {
            if(isCircle(points)) {
                cX = bounds[0].x + Math.abs((bounds[2].x - bounds[0].x)/2);
                cY = bounds[0].y;
                cD = bounds[2].y - bounds[0].y;
                cX = cX - cD/2;

                System.out.println("circle");
            }else{
                cD = -1;
                System.out.println("unknown");
            }
            repaint();
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        Point newPoint = e.getPoint();
        if (editing && !last.equals(newPoint)) {
            points.add(newPoint);
            last = newPoint;
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                CircleGestureDemo t = new CircleGestureDemo();
                t.setVisible(true);
            }
        });
    }
}

It should not be a problem to implement similar behavior on iOS, since you just need several events and coordinates. Something like the following (see example):

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
}

- (void)handleTouch:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
    CGPoint location = [touch locationInView:self];

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [self handleTouch: event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self handleTouch: event];    
}

There are several enhancements possible.

Start at any point

Current requirement is to start drawing a circle from the top middle point due to the following simplification:

        if(type == null || type != newType) {
            if(newType != shape[index]) {
                break;
            }
            bounds[index] = current;
            detected[index++] = newType;
        }

Please notice the default value of index is used. A simple search through the available "parts" of the shape will remove that limitation. Please note you'll need to use a circular buffer in order to detect a full shape:

Clockwise and counterclockwise

In order to support both modes you will need to use the circular buffer from the previous enhancement and search in both directions:

Draw an ellipse

You have everything you need already in the bounds array.

Simply use that data:

cWidth = bounds[2].y - bounds[0].y;
cHeight = bounds[3].y - bounds[1].y;

Other gestures (optional)

Finally, you just need to properly handle a situation when dx (or dy) is equal to zero in order to support other gestures:

Update

This small PoC got quite a high attention, so I did update the code a bit in order to make it work smoothly and provide some drawing hints, highlight supporting points, etc:

Here is the code:

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class CircleGestureDemo extends JFrame {

    enum Type {

        RIGHT_DOWN,
        LEFT_DOWN,
        LEFT_UP,
        RIGHT_UP,
        UNDEFINED
    }

    private static final Type[] circleShape = {
        Type.RIGHT_DOWN,
        Type.LEFT_DOWN,
        Type.LEFT_UP,
        Type.RIGHT_UP};

    public CircleGestureDemo() throws HeadlessException {
        super("Circle gesture");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        add(BorderLayout.CENTER, new GesturePanel());
        setPreferredSize(new Dimension(800, 600));
        pack();
    }

    public static class GesturePanel extends JPanel implements MouseListener, MouseMotionListener {

        private boolean editing = false;
        private Point[] bounds;
        private Point last = new Point(0, 0);
        private final List<Point> points = new ArrayList<>();

        public GesturePanel() {
            super(true);
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        @Override
        public void paint(Graphics graphics) {
            super.paint(graphics);

            Dimension d = getSize();
            Graphics2D g = (Graphics2D) graphics;

            RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g.setRenderingHints(qualityHints);

            if (!points.isEmpty() && cD == 0) {
                isCircle(points, g);
                g.setColor(HINT_COLOR);
                if (bounds[2] != null) {
                    int r = (bounds[2].y - bounds[0].y) / 2;
                    g.setStroke(new BasicStroke(r / 3 + 1));
                    g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
                } else if (bounds[1] != null) {
                    int r = bounds[1].x - bounds[0].x;
                    g.setStroke(new BasicStroke(r / 3 + 1));
                    g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
                }
            }

            g.setStroke(new BasicStroke(2));
            g.setColor(Color.RED);

            if (cD == 0) {
                Point b = null;
                for (Point e : points) {
                    if (null != b) {
                        g.drawLine(b.x, b.y, e.x, e.y);
                    }
                    b = e;
                }

            } else if (cD > 0) {
                g.setColor(Color.BLUE);
                g.setStroke(new BasicStroke(3));
                g.drawOval(cX, cY, cD, cD);
            } else {
                g.drawString("Uknown", 30, 50);
            }
        }

        private Type getType(int dx, int dy) {
            Type result = Type.UNDEFINED;

            if (dx > 0 && dy < 0) {
                result = Type.RIGHT_DOWN;
            } else if (dx < 0 && dy < 0) {
                result = Type.LEFT_DOWN;
            } else if (dx < 0 && dy > 0) {
                result = Type.LEFT_UP;
            } else if (dx > 0 && dy > 0) {
                result = Type.RIGHT_UP;
            }

            return result;
        }

        private boolean isCircle(List<Point> points, Graphics2D g) {
            boolean result = false;
            Type[] shape = circleShape;
            bounds = new Point[shape.length];

            final int STEP = 5;
            int index = 0;
            int initial = 0;
            Point current = points.get(0);
            Type type = null;

            for (int i = STEP; i < points.size(); i += STEP) {
                final Point next = points.get(i);
                final int dx = next.x - current.x;
                final int dy = -(next.y - current.y);

                if (dx == 0 || dy == 0) {
                    continue;
                }

                final int marker = 8;
                if (null != g) {
                    g.setColor(Color.BLACK);
                    g.setStroke(new BasicStroke(2));
                    g.drawOval(current.x - marker/2, 
                               current.y - marker/2, 
                               marker, marker);
                }

                Type newType = getType(dx, dy);
                if (type == null || type != newType) {
                    if (newType != shape[index]) {
                        break;
                    }
                    bounds[index++] = current;
                }

                type = newType;
                current = next;
                initial = i;

                if (index >= shape.length) {
                    result = true;
                    break;
                }
            }
            return result;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            cD = 0;
            points.clear();
            editing = true;
        }

        private int cX;
        private int cY;
        private int cD;

        @Override
        public void mouseReleased(MouseEvent e) {
            editing = false;
            if (points.size() > 0) {
                if (isCircle(points, null)) {
                    int r = Math.abs((bounds[2].y - bounds[0].y) / 2);
                    cX = bounds[0].x - r;
                    cY = bounds[0].y;
                    cD = 2 * r;
                } else {
                    cD = -1;
                }
                repaint();
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            Point newPoint = e.getPoint();
            if (editing && !last.equals(newPoint)) {
                points.add(newPoint);
                last = newPoint;
                repaint();
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mouseClicked(MouseEvent e) {
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                CircleGestureDemo t = new CircleGestureDemo();
                t.setVisible(true);
            }
        });
    }

    final static Color HINT_COLOR = new Color(0x55888888, true);
}

这篇关于从用户的触摸绘制一个完美的圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆