Skip to content Skip to sidebar Skip to footer

Try to Draw a Perfect Circle

Sometimes information technology is really useful to spend some time reinventing the bicycle. As you might take already noticed there are a lot of frameworks, merely it is non that hard to implement a uncomplicated, but yet useful solution without introducing all that complexity. (Delight don't get me incorrect, for whatsoever serious purpose it is better to use some mature and proven to be stable framework).

I volition nowadays my results get-go so explain the unproblematic and straightforward idea behind them.

enter image description here

Yous'll see in my implementation there is no need to analyze every unmarried betoken and do complex computations. The thought is to spot some valuable meta information. I volition apply tangent every bit an instance:

enter image description here

Let's place a simple and straightforward blueprint, typical for the selected shape:

enter image description here

So information technology is not that hard to implement a circumvolve detection machinery based on that idea. See working demo below (Sad, I'm using Java every bit the fastest way to provide this fast and a fleck dirty case):

          import coffee.awt.BasicStroke; import coffee.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import coffee.awt.HeadlessException; import java.awt.Point; import coffee.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     }      individual static final Type[] circleShape = {         Type.RIGHT_DOWN,         Type.LEFT_DOWN,         Blazon.LEFT_UP,         Type.RIGHT_UP};      individual boolean editing = false;     private Point[] bounds;     individual Signal 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 k = (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);          yard.setColor(Colour.RED);         if (cD == 0) {             Betoken b = null;             for (Signal eastward : points) {                 if (nada != b) {                     chiliad.drawLine(b.x, b.y, e.x, eastward.y);                 }                 b = due east;             }         }else if (cD > 0){             one thousand.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 upshot = Blazon.UNDEFINED;          if (dx > 0 && dy < 0) {             effect = Type.RIGHT_DOWN;         } else if (dx < 0 && dy < 0) {             issue = 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<Betoken> points) {         boolean consequence = false;         Blazon[] shape = circleShape;         Type[] detected = new Type[shape.length];         bounds = new Signal[shape.length];          terminal int Step = 5;          int index = 0;                 Point electric current = points.become(0);         Blazon type = null;          for (int i = STEP; i < points.size(); i += STEP) {             Indicate next = points.get(i);             int dx = adjacent.10 - current.ten;             int dy = -(next.y - current.y);              if(dx == 0 || dy == 0) {                 continue;             }              Type newType = getType(dx, dy);             if(type == zip || type != newType) {                 if(newType != shape[index]) {                     interruption;                 }                 premises[alphabetize] = electric current;                 detected[index++] = newType;             }             type = newType;                         electric current = side by side;              if (index >= shape.length) {                 issue = true;                 interruption;             }         }          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 eastward) {         editing = false;         if(points.size() > 0) {             if(isCircle(points)) {                 cX = bounds[0].x + Math.abs((premises[2].x - bounds[0].x)/ii);                 cY = bounds[0].y;                 cD = bounds[2].y - premises[0].y;                 cX = cX - cD/ii;                  Arrangement.out.println("circle");             }else{                 cD = -ane;                 System.out.println("unknown");             }             repaint();         }     }      @Override     public void mouseDragged(MouseEvent e) {         Indicate newPoint = e.getPoint();         if (editing && !last.equals(newPoint)) {             points.add together(newPoint);             concluding = newPoint;             repaint();         }     }      @Override     public void mouseMoved(MouseEvent eastward) {     }      @Override     public void mouseEntered(MouseEvent e) {     }      @Override     public void mouseExited(MouseEvent e) {     }      @Override     public void mouseClicked(MouseEvent e) {     }      public static void principal(String[] args) {         SwingUtilities.invokeLater(new Runnable() {              @Override             public void run() {                 CircleGestureDemo t = new CircleGestureDemo();                 t.setVisible(true);             }         });     } }                  

It should not be a trouble to implement similar beliefs on iOS, since you merely demand several events and coordinates. Something similar the following (meet instance):

          - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)outcome {     UITouch* touch on = [[event allTouches] anyObject]; }  - (void)handleTouch:(UIEvent *)event {     UITouch* bear on = [[outcome allTouches] anyObject];     CGPoint location = [bear on locationInView:self];  }  - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {     [cocky handleTouch: outcome]; }  - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)effect {     [cocky handleTouch: event];     }                  

There are several enhancements possible.

Beginning at any point

Electric current requirement is to commencement drawing a circle from the tiptop heart point due to the post-obit simplification:

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

Please observe the default value of alphabetize 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:

enter image description here

Clockwise and counterclockwise

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

enter image description here

Draw an ellipse

You accept everything you demand already in the bounds assortment.

enter image description here

Only employ that data:

          cWidth = bounds[ii].y - bounds[0].y; cHeight = premises[3].y - bounds[i].y;                  

Other gestures (optional)

Finally, you just demand to properly handle a situation when dx (or dy) is equal to zip in club to back up other gestures:

enter image description here

Update

This small-scale PoC got quite a loftier attention, and then I did update the code a bit in order to make information technology piece of work smoothly and provide some cartoon hints, highlight supporting points, etc:

enter image description here

Here is the code:

          import coffee.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Colour; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import coffee.awt.HeadlessException; import java.awt.Point; import java.awt.RenderingHints; import coffee.awt.consequence.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import coffee.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     }      individual static final Type[] circleShape = {         Blazon.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 = faux;         private Point[] premises;         individual Point last = new Point(0, 0);         private final Listing<Bespeak> points = new ArrayList<>();          public GesturePanel() {             super(truthful);             addMouseListener(this);             addMouseMotionListener(this);         }          @Override         public void pigment(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);                 grand.setColor(HINT_COLOR);                 if (bounds[2] != zippo) {                     int r = (bounds[2].y - bounds[0].y) / 2;                     m.setStroke(new BasicStroke(r / 3 + one));                     g.drawOval(bounds[0].ten - r, premises[0].y, 2 * r, two * r);                 } else if (premises[one] != nix) {                     int r = bounds[1].10 - bounds[0].x;                     yard.setStroke(new BasicStroke(r / iii + 1));                     g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);                 }             }              g.setStroke(new BasicStroke(2));             yard.setColor(Colour.RED);              if (cD == 0) {                 Betoken b = null;                 for (Point e : points) {                     if (goose egg != b) {                         g.drawLine(b.x, b.y, e.x, e.y);                     }                     b = e;                 }              } else if (cD > 0) {                 g.setColor(Color.Blueish);                 1000.setStroke(new BasicStroke(3));                 grand.drawOval(cX, cY, cD, cD);             } else {                 g.drawString("Uknown", 30, l);             }         }          private Blazon getType(int dx, int dy) {             Type result = Type.UNDEFINED;              if (dx > 0 && dy < 0) {                 effect = Blazon.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) {                 consequence = Blazon.RIGHT_UP;             }              return result;         }          private boolean isCircle(List<Point> points, Graphics2D g) {             boolean consequence = false;             Type[] shape = circleShape;             bounds = new Point[shape.length];              final int Pace = five;             int index = 0;             int initial = 0;             Point current = points.get(0);             Type type = null;              for (int i = STEP; i < points.size(); i += Pace) {                 terminal Point next = points.get(i);                 final int dx = next.ten - current.x;                 concluding int dy = -(next.y - current.y);                  if (dx == 0 || dy == 0) {                     continue;                 }                  final int mark = 8;                 if (naught != yard) {                     g.setColor(Colour.Black);                     chiliad.setStroke(new BasicStroke(2));                     g.drawOval(electric current.x - marker/two,                                 current.y - marker/2,                                 marking, mark);                 }                  Blazon newType = getType(dx, dy);                 if (type == cypher || type != newType) {                     if (newType != shape[alphabetize]) {                         break;                     }                     bounds[alphabetize++] = current;                 }                  type = newType;                 current = next;                 initial = i;                  if (index >= shape.length) {                     result = truthful;                     break;                 }             }             render result;         }          @Override         public void mousePressed(MouseEvent east) {             cD = 0;             points.clear();             editing = truthful;         }          private int cX;         private int cY;         private int cD;          @Override         public void mouseReleased(MouseEvent eastward) {             editing = false;             if (points.size() > 0) {                 if (isCircle(points, zero)) {                     int r = Math.abs((bounds[two].y - bounds[0].y) / 2);                     cX = bounds[0].10 - r;                     cY = bounds[0].y;                     cD = 2 * r;                 } else {                     cD = -one;                 }                 repaint();             }         }          @Override         public void mouseDragged(MouseEvent due east) {             Point newPoint = e.getPoint();             if (editing && !final.equals(newPoint)) {                 points.add(newPoint);                 last = newPoint;                 repaint();             }         }          @Override         public void mouseMoved(MouseEvent e) {         }          @Override         public void mouseEntered(MouseEvent due east) {         }          @Override         public void mouseExited(MouseEvent e) {         }          @Override         public void mouseClicked(MouseEvent e) {         }     }      public static void chief(Cord[] args) {         SwingUtilities.invokeLater(new Runnable() {              @Override             public void run() {                 CircleGestureDemo t = new CircleGestureDemo();                 t.setVisible(true);             }         });     }      last static Colour HINT_COLOR = new Color(0x55888888, true); }                  

mickelsonficiones.blogspot.com

Source: https://stackoverflow.com/questions/18934805/draw-a-perfect-circle-from-users-touch

Post a Comment for "Try to Draw a Perfect Circle"