gpt4 book ai didi

ios - 从用户的触摸中画出一个完美的圆圈

转载 作者:IT王子 更新时间:2023-10-29 07:26:36 25 4
gpt4 key购买 nike

我有一个练习项目,允许用户在用手指触摸时在屏幕上绘图。非常简单的应用程序,我以前做过练习。我的小表弟冒昧地用我的 iPad 在这个应用程序上用他的手指画东西( child 画:圆、线等,任何他想到的)。然后他开始画圆,然后他让我把它画成一个“好圆”(据我理解:把画好的圆画得很圆,我们知道无论我们尝试用手指在屏幕上画得多么稳定,圆永远不会像圆应该的那样圆)。

所以我的问题是,在代码中有没有什么方法可以让我们首先检测到用户绘制的一条线,该线形成一个圆,然后通过在屏幕上使其完美圆形来生成大致相同大小的圆。使一条不太直线直线是我知道如何做的事情,但至于圆,我不太知道如何用 Quartz 或其他方法去做。

我的理由是,在用户抬起手指以证明他试图实际画圆这一事实后,线的起点和终点必须相互接触或交叉。

最佳答案

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

我会先展示我的结果,然后解释它们背后简单明了的想法。

enter image description here

您会在我的实现中看到,无需分析每个单独的点并进行复杂的计算。这个想法是发现一些有值(value)的元信息。我会用tangent举个例子:

enter image description here

让我们确定一个简单明了的图案,它是所选形状的典型图案:

enter image description here

因此,基于该思想实现圆检测机制并不难。请参阅下面的工作演示(抱歉,我使用 Java 作为最快的方式来提供这个快速但有点脏的示例):

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);
}
});
}
}

在 iOS 上实现类似的行为应该不是问题,因为您只需要几个事件和坐标。类似于以下内容(参见 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];
}

有几个可能的增强功能。

随时开始

由于以下简化,当前要求是从顶部中间点开始绘制圆:

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

请注意 index 使用默认值。通过形状的可用“部分”进行简单搜索将消除该限制。请注意,您需要使用圆形缓冲区来检测完整形状:

enter image description here

顺时针和逆时针

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

enter image description here

画一个椭圆

bounds 数组中已经有了您需要的一切。

enter image description here

只需使用该数据:

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

其他手势(可选)

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

enter image description here

更新

这个小PoC得到了相当高的关注,所以我做了一些代码更新,以使其顺利运行,并提供了一些绘图提示,高亮支撑点等:

enter image description here

代码如下:

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);
}

关于ios - 从用户的触摸中画出一个完美的圆圈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18934805/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com