True. So I converted them into beziers. When sliding the button totally to the right it’s easy to edit control points and anchors. Right mouse clicking the anchor points, will remove them, so I could almost reached the theoretical number 13. (if it were not for the case that I have a random start point which not can be removed). My next goal is to do this process by code. The first thing that came into my mind was to programmatically move the control-point around its anchor-point in a spiral way, keeping track of the matching pixels of the original line and the formed curve. This highest matching number would then indicate its best control-point location. But I hope you, or anyone else that eventually is reading this can give me another idea.
When writing this I am thinking that I could also write a shape editor including the many possibilities of the geomerative library. But I presume that your upcoming 2D/3D bezier library will contain such codes. Please let me know.
// Ramer Douglas Peucker algorithm by teachings Daniel Shiffman https://youtu.be/nSYw9GrakjY
// Catmull-Rom/Bezier convertion Peter Lagers and @antony74 https://forum.processing.org/one/topic/convert-between-curvevertex-and-beziervertex.html
// Actual link to paper Catmull-Rom/bezier convertion https://people.engr.tamu.edu/schaefer/research/catmull_rom.pdf
import gab.opencv.*;
import java.awt.Point;
import java.util.Queue;
import java.util.LinkedList;
import java.util.*;
float[][] cc = {{210, 168}, {430, 30}, {493, -35}, {410, 10}, {450, -75}, {400, 148}, {292, -75}, {185, 135}, {130, 110}, {180, 216}, {130, 215}, {210, 280}, {70, 185}}; // circle center
float[][] ca = {{-.15, .5}, {.18, .3}, {0.02, .12}, {.22, .35}, {.08, .16}, {.66, 1.56}, {-.29, -.02}, {-.67, -.23}, {-.18, -.08}, {-.54, -.08}, {-.07, .06}, {-.38, 0}, {0, .2}}; // circle arc {start,stop}
int[] radius = {300, 148, 160, 150, 177, 105, 255, 110, 110, 110, 110, 110, 250};
PImage img;
PShape shp1;
ArrayList<PVector> contol1 = new ArrayList<PVector>();
ArrayList<PVector> contol2 = new ArrayList<PVector>();
ArrayList<PVector> anchor1 = new ArrayList<PVector>();
ArrayList<PVector> anchor2 = new ArrayList<PVector>();
ArrayList<PVector> all_points = new ArrayList<PVector>();
ArrayList<PVector> rdp_points = new ArrayList<PVector>();
ArrayList<Contour> contours;
float loc = 15, epsilon = 1, x1, y1, x2, y2, x3, y3, x4, y4;
int control_point, control_index;
boolean curve_mode = true, bezier_mode;
OpenCV ocv;
CurveToBezier c2b = new CurveToBezier();
void setup() {
size(620, 520);
surface.setTitle("Bezier edit");
// Comment following lines to import a black shape on white background image instead
background(0);
stroke(255);
drawShapeBorder();
fillShape();
stroke(0);
drawShapeBorder();
// and uncomment these following lines
//img = loadImage("octo.png");
//img.resize(width, height);
//filter(INVERT);
ocv = new OpenCV(this, img);
contours = ocv.findContours();
println("number of contours = "+contours.size()); // Cheque if you have more then 1 !
// Bitmap created by parametric function above has 2 contours so be sure to selct the right one here.
Contour contour = contours.get(1);
for (PVector point : contour.getPoints()) {
point(point.x, point.y);
all_points.add(new PVector(point.x, point.y));
}
textSize(18);
}
void draw() {
background(255);
stroke(220, 220, 255);
strokeWeight(4);
noFill();
beginShape();
for (PVector v : all_points) {
vertex(v.x, v.y);
}
endShape();
if (curve_mode) {
rdp_points = new ArrayList<PVector>();
int total = all_points.size();
PVector start = all_points.get(0);
PVector end = all_points.get(total-1);
rdp_points.add(start);
rdp(0, total-1, all_points, rdp_points);
rdp_points.add(end);
stroke(0);
strokeWeight(1);
beginShape();
PVector v1 = new PVector();
v1 = rdp_points.get(rdp_points.size()-1);
curveVertex(v1.x, v1.y);
for (PVector v : rdp_points) {
curveVertex(v.x, v.y);
fill(255, 0, 0);
ellipse(v.x, v.y, 5, 5);
}
PVector v2 = new PVector();
v2 = rdp_points.get(0);
curveVertex(v2.x, v2.y);
noFill();
endShape();
fill(0);
text("epsilon: " + nf(epsilon, 2, 2), 150, 80);
text("Total Points: " + (rdp_points.size()-1), 50, 50);
drawSlider();
fill(0);
text("Edit", 510, 430);
} else if (bezier_mode) {
fill(0);
text("Total Points: "+(contol1.size()), 50, 50);
stroke(0);
strokeWeight(1);
beginShape();
vertex(rdp_points.get(1).x, rdp_points.get(1).y);
for (int i = 0; i < anchor2.size(); i++) {
x1 = anchor1.get(i).x;
y1 = anchor1.get(i).y;
x2 = contol1.get(i).x;
y2 = contol1.get(i).y;
x3 = contol2.get(i).x;
y3 = contol2.get(i).y;
x4 = anchor2.get(i).x;
y4 = anchor2.get(i).y;
fill(0, 255, 255);
circle(contol1.get(i).x, contol1.get(i).y, 8);
circle(contol2.get(i).x, contol2.get(i).y, 8);
stroke(150);
line(x4, y4, x3, y3);
line(x2, y2, x1, y1);
stroke(0);
fill(255, 130, 130);
circle(rdp_points.get(0).x, rdp_points.get(0).y, 7);
circle(anchor1.get(i).x, anchor1.get(i).y, 7);
circle(anchor2.get(i).x, anchor2.get(i).y, 7);
noFill();
bezierVertex(x2, y2, x3, y3, x4, y4);
}
endShape();
drawSlider();
fill(0);
text("Print", 510, 430);
}
noFill();
strokeWeight(1.3);
rect(490, 410, 80, 28);
}
void mousePressed() {
if (mouseX > 530 && mouseY < 50) {
println();
}
if (curve_mode) {
if (mouseX > 490 && mouseY > 410 && mouseY < 438) {
rdp_points.add(0, rdp_points.get(0));
rdp_points.add(rdp_points.get(0));
for (int i = 0; i < rdp_points.size(); i++) {
c2b.makeBezierVertex(rdp_points.get(i).x, rdp_points.get(i).y);
}
for (int i = 1; i < rdp_points.size(); i++) {
anchor1.add(new PVector(rdp_points.get(i).x, rdp_points.get(i).y));
}
curve_mode = false;
bezier_mode = true;
}
} else if (bezier_mode ) {
for (int i = 0; i < contol1.size(); i++) {
if (mouseX < contol1.get(i).x+6 && mouseX > contol1.get(i).x-6 &&
mouseY < contol1.get(i).y+6 && mouseY > contol1.get(i).y-6) {
control_point = 1;
control_index = i;
}
if (mouseX < contol2.get(i).x+6 && mouseX > contol2.get(i).x-6 &&
mouseY < contol2.get(i).y+6 && mouseY > contol2.get(i).y-6) {
control_point = 2;
control_index = i;
}
if (mouseX < anchor2.get(i).x+6 && mouseX > anchor2.get(i).x-6 &&
mouseY < anchor2.get(i).y+6 && mouseY > anchor2.get(i).y-6) {
control_point = 3;
control_index = i;
if (mouseButton == RIGHT && i != anchor2.size()-1) {
anchor1.remove(i+1);
anchor2.remove(i);
contol1.remove(i);
contol2.remove(i);
}
}
}
if (mouseX > 490 && mouseY > 410 && mouseY < 438) {
println();
println("shp1 = createShape(PShape.PATH);");
println("shp1.beginShape();");
println("shp1.vertex("+rdp_points.get(1).x+", "+rdp_points.get(1).y+");");
for (int i = 0; i < contol2.size(); i++) {
x1 = anchor1.get(i).x;
y1 = anchor1.get(i).y;
x2 = contol1.get(i).x;
y2 = contol1.get(i).y;
x3 = contol2.get(i).x;
y3 = contol2.get(i).y;
x4 = anchor2.get(i).x;
y4 = anchor2.get(i).y;
println("shp1.bezierVertex("+x2+", "+y2+", "+x3+", "+
y3+", "+x4+", "+y4+");");
}
println("shp1.endShape();");
}
}
}
void mouseDragged() {
if (curve_mode) {
loc = mouseX;
if (loc < 15) loc = 15;
if (loc > width-15) loc = width-15;
epsilon = int(map(loc-10.55, 0.0, width, 1.0, 17.0));
} else if (bezier_mode) {
if (control_point == 1) contol1.set(control_index, new PVector(mouseX, mouseY));
if (control_point == 2) contol2.set(control_index, new PVector(mouseX, mouseY));
if (control_point == 3 && control_index < anchor2.size()-1) {
anchor2.set(control_index, new PVector(mouseX, mouseY));
anchor1.set(control_index+1, new PVector(mouseX, mouseY));
float tx1 = contol1.get(control_index+1).x;
float ty1 = contol1.get(control_index+1).y;
float tx2 = contol2.get(control_index).x;
float ty2 = contol2.get(control_index).y;
contol1.set(control_index+1, new PVector(tx1+(mouseX-pmouseX), ty1+(mouseY-pmouseY)));
contol2.set(control_index, new PVector(tx2+(mouseX-pmouseX), ty2+(mouseY-pmouseY)));
}
}
}
void rdp(int start_index, int end_index, ArrayList<PVector> all_points, ArrayList<PVector> rdp_points) {
int next_index = findFurthest(all_points, start_index, end_index);
if (next_index > 0) {
if (start_index != next_index) {
rdp(start_index, next_index, all_points, rdp_points);
}
rdp_points.add(all_points.get(next_index));
if (end_index != next_index) {
rdp(next_index, end_index, all_points, rdp_points);
}
}
}
int findFurthest(ArrayList<PVector> points, int a, int b) {
float record_distance = -1;
PVector start = points.get(a);
PVector end = points.get(b);
int furthest_index = -1;
for (int i = a+1; i < b; i++) {
PVector current_point = points.get(i);
float d = lineDist(current_point, start, end);
if (d > record_distance) {
record_distance = d;
furthest_index = i;
}
}
if (record_distance > epsilon) {
return furthest_index;
} else {
return -1;
}
}
float lineDist(PVector c, PVector a, PVector b) {
PVector norm = scalarProjection(c, a, b);
return PVector.dist(c, norm);
}
PVector scalarProjection(PVector p, PVector a, PVector b) {
PVector ap = PVector.sub(p, a);
PVector ab = PVector.sub(b, a);
ab.normalize(); // Normalize the line
ab.mult(ap.dot(ab));
PVector normal_point = PVector.add(a, ab);
return normal_point;
}
void drawShapeBorder() {
float step = 2*PI/60;
float px = 0, py = 0;
for (int i = 0; i < 13; i++) {
push();
translate(cc[i][0], cc[i][1]);
int j = 0;
for (float t = ca[i][0]*PI; t < ca[i][1]*PI+step; t += step) {
float x = radius[i]*sin(t);
float y = radius[i]*cos(t);
if (j == 0) {
px = x;
py = y;
}
line(x, y, px, py);
px = x;
py = y;
j++;
}
pop();
}
}
void fillShape() {
img = get();
img.loadPixels();
Queue<Point> queue = new LinkedList<Point>();
queue.add(new Point(width/2, height/2));
while (!queue.isEmpty()) {
Point p = queue.remove();
if (check(p.x, p.y)) {
queue.add(new Point(p.x, p.y-1));
queue.add(new Point(p.x, p.y+1));
queue.add(new Point(p.x-1, p.y));
queue.add(new Point(p.x+1, p.y));
}
}
img.updatePixels();
}
boolean check(int x, int y) {
if (x < 0 || y < 0 || y >= img.height || x >= img.width) return false;
int pp = img.pixels[x+(y*img.width)];
if (pp != color(0)) return false;
img.pixels[x + (y * img.width)] = color(255);
return true;
}
void drawSlider() {
push();
noStroke();
fill(255);
rect(0, height-30, width, 30);
fill(200, 200, 255);
rect(10, height-15, width-20, 5);
fill(150, 150, 255);
ellipse(loc, height-13, 15, 15);
pop();
}
class CurveToBezier {
private PVector ring[] = new PVector[4];
private int count = 0;
CurveToBezier() {
ring[0] = new PVector();
ring[1] = new PVector();
ring[2] = new PVector();
ring[3] = new PVector();
}
void makeBezierVertex(float x, float y) {
ring[count % 4].x = x;
ring[count % 4].y = y;
if (count >= 3) makeBezierZero(ring[(count-3)%4], ring[(count-2)%4], ring[(count-1)%4], ring[count%4]);
count++;
}
private void makeBezierZero(PVector c0, PVector c1, PVector c2, PVector c3) {
PVector b1 = new PVector(0, 0);
PVector b2 = new PVector(0, 0);
b1.add(c2);
b1.sub(c0);
b1.add(PVector.mult(c1, 6));
b1.div(6);
b2.add(c1);
b2.sub(c3);
b2.add(PVector.mult(c2, 6));
b2.div(6);
contol1.add(b1);
contol2.add(b2);
anchor2.add(new PVector(c2.x, c2.y));
}
}