Saya telah membuat sebuah algoritma yang mengubah setiap kurva yaitu jalur menjadi jumlah minimum poin sehingga saya dapat menyimpannya ke dalam file atau database.
Metode ini sederhana: memindahkan tiga titik dalam langkah yang sama dan mengukur sudut antara garis-garis ini membentuk titik-titik. Jika sudut lebih besar dari toleransi maka itu menciptakan kurva kubik baru ke titik itu. Kemudian ia memindahkan garis ke depan dan mengukur sudut lagi ...
Bagi mereka yang tahu Android Path Class - Perhatikan bahwa dstPath adalah kelas khusus, yang mencatat poin ke dalam Array sehingga saya dapat menyimpan poin nanti, sedangkan srcPath adalah hasil dari penyatuan Daerah dan karenanya tidak memiliki poin kunci untuk saya untuk menyimpan.
Masalahnya adalah bahwa lingkaran tidak terlihat mulus seperti yang Anda lihat pada gambar ini, dihasilkan oleh kode di bawah ini, di mana jalur sumber terdiri dari lingkaran dan persegi panjang yang sempurna. Saya mencoba mengubah sudut toleransi dan panjang langkah, tetapi tidak ada yang membantu. Saya ingin tahu apakah Anda dapat menyarankan perbaikan pada algoritma ini, atau pendekatan yang berbeda.
EDIT: Saya sekarang telah memposting seluruh kode untuk mereka yang menggunakan Android java, sehingga mereka dapat dengan mudah mencoba dan bereksperimen.
public class CurveSavePointsActivity extends Activity{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CurveView(this));
}
class CurveView extends View{
Path srcPath, dstPath;
Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CurveView(Context context) {
super(context);
srcPaint.setColor(Color.BLACK);
srcPaint.setStyle(Style.STROKE);
srcPaint.setStrokeWidth(2);
srcPaint.setTextSize(20);
dstPaint.setColor(Color.BLUE);
dstPaint.setStyle(Style.STROKE);
dstPaint.setStrokeWidth(2);
dstPaint.setTextSize(20);
srcPath = new Path();
dstPath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//make a circle path
srcPath.addCircle(w/4, h/2, w/6 - 30, Direction.CW);
//make a rectangle path
Path rectPath = new Path();
rectPath.addRect(new RectF(w/4, h/2 - w/16, w*0.5f, h/2 + w/16), Direction.CW);
//create a path union of circle and rectangle paths
RectF bounds = new RectF();
srcPath.computeBounds(bounds, true);
Region destReg = new Region();
Region clip = new Region();
clip.set(new Rect(0,0, w, h));
destReg.setPath(srcPath, clip);
Region srcReg = new Region();
srcReg.setPath(rectPath, clip);
Region resultReg = new Region();
resultReg.op(destReg, srcReg, Region.Op.UNION);
if(!resultReg.isEmpty()){
srcPath.reset();
srcPath.addPath(resultReg.getBoundaryPath());
}
//extract a new path from the region boundary path
extractOutlinePath();
//shift the resulting path bottom left, so they can be compared
Matrix matrix = new Matrix();
matrix.postTranslate(10, 30);
dstPath.transform(matrix);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.drawPath(srcPath, srcPaint);
canvas.drawPath(dstPath, dstPaint);
canvas.drawText("Source path", 40, 50, srcPaint);
canvas.drawText("Destination path", 40, 100, dstPaint);
}
public void extractOutlinePath() {
PathMeasure pm = new PathMeasure(srcPath, false); //get access to curve points
float p0[] = {0f, 0f}; //current position of the new polygon
float p1[] = {0f, 0f}; //beginning of the first line
float p2[] = {0f, 0f}; //end of the first & the beginning of the second line
float p3[] = {0f, 0f}; //end of the second line
float pxStep = 5; //sampling step for extracting points
float pxPlace = 0; //current place on the curve for taking x,y coordinates
float angleT = 5; //angle of tolerance
double a1 = 0; //angle of the first line
double a2 = 0; //angle of the second line
pm.getPosTan(0, p0, null); //get the beginning x,y of the original curve into p0
dstPath.moveTo(p0[0], p0[1]); //start new path from the beginning of the curve
p1 = p0.clone(); //set start of the first line
pm.getPosTan(pxStep, p2, null); //set end of the first line & the beginning of the second
pxPlace = pxStep * 2;
pm.getPosTan(pxPlace, p3, null); //set end of the second line
while(pxPlace < pm.getLength()){
a1 = 180 - Math.toDegrees(Math.atan2(p1[1] - p2[1], p1[0] - p2[0])); //angle of the first line
a2 = 180 - Math.toDegrees(Math.atan2(p2[1] - p3[1], p2[0] - p3[0])); //angle of the second line
//check the angle between the lines
if (Math.abs(a1-a2) > angleT){
//draw a straight line to the first point if the current p0 is not already there
if(p0[0] != p1[0] && p0[1] != p1[1]) dstPath.quadTo((p0[0] + p1[0])/2, (p0[1] + p1[1])/2, p1[0], p1[1]);
dstPath.quadTo(p2[0] , p2[1], p3[0], p3[1]); //create a curve to the third point through the second
//shift the three points by two steps forward
p0 = p3.clone();
p1 = p3.clone();
pxPlace += pxStep;
pm.getPosTan(pxPlace, p2, null);
pxPlace += pxStep;
pm.getPosTan(pxPlace, p3, null);
if (pxPlace > pm.getLength()) break;
}else{
//shift three points by one step towards the end of the curve
p1 = p2.clone();
p2 = p3.clone();
pxPlace += pxStep;
pm.getPosTan(pxPlace, p3, null);
}
}
dstPath.close();
}
}
}
Inilah perbandingan antara yang asli dan yang dihasilkan oleh algoritma saya:
Jawaban:
Saya pikir Anda memiliki dua masalah:
Titik kontrol non-simetris
Awalnya Anda mulai dengan jarak yang sama antara p0 ke p1 dan p1 ke p2. Jika sudut toleransi antara segmen garis tidak terpenuhi, Anda bergerak p1 dan p2 ke depan, tetapi tetap p0 di tempat itu. Ini meningkatkan jarak antara p0 ke p1 sambil menjaga jarak antara p1 ke p2 sama. Ketika Anda membuat kurva menggunakan p1 sebagai titik kontrol, itu bisa sangat bias terhadap p2 tergantung pada berapa banyak iterasi yang telah berlalu sejak kurva terakhir. Jika Anda akan memindahkan p2 dua kali jumlah dari p1, Anda akan mendapatkan jarak genap di antara titik-titik.
Kurva kuadratik
Seperti disebutkan dalam jawaban lain juga, kurva kuadratik tidak terlalu baik untuk kasus ini. Kurva yang berdekatan yang Anda buat harus berbagi titik kontrol dan garis singgung . Ketika data input Anda hanya poin, Catmull-Rom Spline adalah pilihan yang baik untuk tujuan itu. Ini adalah kurva Hermit kubik, di mana garis singgung untuk titik kontrol dihitung dari titik sebelumnya dan berikutnya.
Path API di Android mendukung kurva Bézier, yang sedikit berbeda dari kurva Hermite mengenai parameter. Untungnya kurva Hermite dapat dikonversi menjadi kurva Bézier. Berikut adalah contoh kode pertama yang saya temukan ketika Googling. Jawaban Stackoverflow ini juga tampaknya memberikan rumus.
Anda juga menyebutkan masalah ujung yang tajam. Dengan data input yang Anda miliki, tidak mungkin untuk mendeteksi apakah ada sudut tajam yang sebenarnya atau hanya kurva yang sangat curam. Jika ini menjadi masalah, Anda dapat membuat iterasi lebih adaptif dengan menambah / mengurangi langkah saat dibutuhkan.
Sunting: Setelah berpikir lebih lanjut kurva kuadrat dapat digunakan setelah semua. Alih-alih menggambar kurva kuadratik dari p0 ke p2 menggunakan p1 sebagai titik kontrol, gambarlah dari p0 ke p1 menggunakan titik baru p0_1 sebagai titik kontrol. Lihat gambar di bawah ini.
Jika p0_1 berada di persimpangan garis singgung dalam p0 dan p1, hasilnya harus halus. Lebih baik lagi, karena
PathMeasure.getPosTan()
pengembalian juga bersinggungan sebagai parameter ketiga, Anda dapat menggunakan garis singgung akurat yang sebenarnya alih-alih perkiraan dari titik yang berdekatan. Dengan pendekatan ini Anda perlu sedikit perubahan pada solusi yang ada.Berdasarkan jawaban ini , titik persimpangan dapat dihitung dengan rumus berikut:
Namun solusi ini hanya berfungsi jika u dan v tidak negatif. Lihat gambar kedua:
Di sini sinarnya tidak berpotongan meskipun garis-garisnya akan, karena kamu negatif. Dalam hal ini tidak mungkin untuk menggambar kurva kuadrat yang dengan lancar akan terhubung ke yang sebelumnya. Di sini Anda membutuhkan kurva bézier. Anda bisa menghitung titik kontrol untuk itu baik dengan metode yang diberikan sebelumnya dalam jawaban ini atau berasal langsung dari garis singgung. Memproyeksikan p0 ke sinar tangen p0 + u * t0 dan sebaliknya untuk sinar lainnya memberikan kedua titik kontrol c0 dan c1. Anda juga dapat menyesuaikan kurva dengan menggunakan titik mana saja antara p0 dan c0 alih-alih c0 selama itu terletak pada sinar tangen.
Sunting2: Jika posisi gambar Anda di p1, Anda dapat menghitung titik kontrol bezier ke p2 dengan kode pseudo berikut:
Dengan ini, Anda dapat menambahkan path dari p1 ke p2:
Ganti operasi vektor dengan operasi per komponen pada array float [ 2 ] untuk mencocokkan kode Anda. Anda mulai dengan menginisialisasi
p1 = start;
dan p2 dan p3 adalah poin berikutnya. p0 awalnya tidak terdefinisi. Untuk segmen pertama di mana Anda belum memiliki p0, Anda dapat menggunakan kurva kuadratik dari p1 ke p2 dengan cp2 sebagai titik kontrol. Hal yang sama untuk ujung jalur di mana Anda tidak memiliki p3, Anda dapat menggambar kurva kuadratik dari p1 ke p2 dengan cp1 sebagai titik kontrol. Atau Anda dapat menginisialisasi p0 = p1 untuk segmen pertama dan p3 = p2 untuk segmen terakhir. Setelah setiap segmen, Anda menggeser nilainyap0 = p1; p1 = p2; and p2 = p3;
saat bergerak maju.Ketika Anda menyimpan path, Anda hanya menyimpan semua poin p0 ... pN. Tidak perlu menyimpan titik kontrol cp1 dan cp2, karena mereka dapat dihitung sesuai kebutuhan.
Sunting3: Karena tampaknya sulit untuk mendapatkan nilai input yang bagus untuk pembuatan kurva, saya mengusulkan pendekatan lain: Gunakan serialisasi. Android Path tampaknya tidak mendukungnya, tetapi untungnya kelas Region melakukannya. Lihat jawaban ini untuk kodenya. Ini akan memberi Anda hasil yang tepat. Mungkin diperlukan beberapa ruang dalam bentuk serial jika tidak dioptimalkan, tetapi dalam kasus itu harus dikompres dengan sangat baik. Kompresi mudah di Android Java menggunakan GZIPOutputStream .
sumber
Apa yang akan dilakukan W3C?
Internet memiliki masalah ini. The World Wide Web Consortium melihat. Ini memiliki solusi standar yang direkomendasikan sejak 1999: Scalable Vector Graphics (SVG) . Ini adalah format file berbasis XML yang dirancang khusus untuk menyimpan bentuk 2D.
"Dapat diukur-apa? "
Grafik Vektor yang Dapat Dikukur !
Berikut spesifikasi teknis untuk SVG versi 1.1.
(Jangan takut dengan namanya; Ini sebenarnya menyenangkan untuk dibaca.)
Mereka telah menuliskan dengan tepat bagaimana bentuk dasar seperti lingkaran atau persegi panjang harus disimpan. Misalnya, persegi panjang memiliki sifat ini:
x
,y
,width
,height
,rx
,ry
. (Therx
danry
dapat digunakan untuk sudut bundar.)Inilah contoh persegi panjang mereka dalam SVG: (Ya, dua benar - satu untuk garis besar kanvas.)
Inilah yang diwakilinya:
Seperti spesifikasi mengatakan, Anda bebas untuk meninggalkan beberapa properti jika Anda tidak membutuhkannya. (Misalnya,
rx
danry
atribut tidak digunakan di sini.) Ya, ada satu ton cruft di bagian atasDOCTYPE
yang tidak Anda perlukan hanya untuk gim Anda. Mereka juga opsional.Jalan
Jalur SVG adalah "jalur" dalam arti bahwa jika Anda meletakkan pensil di atas kertas, memindahkannya dan akhirnya mengangkatnya lagi , Anda memiliki jalur. Mereka tidak memiliki harus ditutup , tapi mereka mungkin.
Setiap jalur memiliki
d
atribut (saya suka berpikir itu singkatan dari "draw"), yang berisi data path , urutan perintah untuk dasarnya hanya meletakkan pena ke kertas dan memindahkannya .Mereka memberi contoh segitiga:
Lihat
d
atribut dipath
?Ini
M
adalah perintah untuk Pindah ke (diikuti oleh koordinat),L
yaitu untuk Baris ke (dengan koordinat) danz
merupakan perintah untuk menutup jalur (yaitu menarik garis kembali ke lokasi pertama; yang tidak memerlukan koordinat).Garis lurus itu membosankan? Gunakan perintah Bézier kubik atau kuadrat !
Teori di balik kurva Bézier dibahas dengan baik di tempat lain (seperti di Wikipedia ), tetapi inilah ringkasan eksekutif: Béziers memiliki titik awal dan titik akhir, dengan kemungkinan banyak titik kontrol yang memengaruhi ke mana kurva di antaranya bergerak.
Spesifikasi ini juga memberikan instruksi untuk mengubah sebagian besar bentuk dasar menjadi jalur jika Anda mau.
Mengapa dan kapan harus menggunakan SVG
Putuskan dengan hati-hati jika Anda ingin turun jalan ini (pun intended), karena itu benar-benar sangat rumit untuk mewakili bentuk 2D sewenang-wenang dalam teks! Anda dapat membuat hidup Anda jauh lebih mudah jika Anda mis membatasi diri Anda hanya pada jalur yang terbuat dari (mungkin sangat banyak) garis lurus.
Tetapi jika Anda memutuskan ingin bentuk yang sewenang-wenang, SVG adalah caranya: Alat ini memiliki dukungan alat yang hebat: Anda dapat menemukan banyak pustaka untuk parsing XML di tingkat rendah dan alat editor SVG di tingkat tinggi.
Apapun, standar SVG memberikan contoh yang baik.
sumber
Kode Anda mengandung komentar yang menyesatkan:
Sebuah kurva Bezier kuadrat tidak tidak pergi melalui titik kedua. Jika Anda ingin melewati titik kedua, Anda memerlukan jenis kurva yang berbeda, seperti kurva hermite . Anda mungkin bisa mengubah kurva hermite menjadi beziers sehingga Anda bisa menggunakan kelas Path.
Saran lain adalah alih-alih mengambil sampel poin, gunakan rata-rata poin yang Anda lewatkan.
Saran lain adalah alih-alih menggunakan sudut sebagai ambang, gunakan perbedaan antara kurva aktual dan kurva perkiraan. Sudut bukan masalah sebenarnya; masalah sebenarnya adalah ketika set poin tidak cocok dengan kurva bezier.
Saran lain adalah menggunakan bezier kubik, dengan garis singgung satu cocok dengan garis singgung berikutnya. Kalau tidak (dengan kuadrat) saya pikir kurva Anda tidak akan cocok dengan lancar.
sumber
Untuk mendapatkan persimpangan dua jalur yang lebih mulus, Anda bisa memperbesarnya sebelum persimpangan dan menurunkannya setelahnya.
Saya tidak tahu apakah ini solusi yang baik, tetapi itu bekerja dengan baik untuk saya. Ini juga cepat. Dalam contoh saya, saya memotong lintasan bulat dengan pola yang saya buat (garis-garis). Ini terlihat bagus bahkan ketika ditingkatkan.
Ini kode saya:
Terlihat masih mulus saat melakukan zooming dengan canvas.scale ():
sumber
Lihatlah interpolasi poligon ( http://en.wikipedia.org/wiki/Polynomial_interpolation )
Pada dasarnya, Anda mengambil n equispaced node (interpolasi yang optimal tidak equispaced, tetapi untuk kasus Anda harus cukup baik dan mudah diimplementasikan)
Anda berakhir dengan poligon orde n yang mengurangi kesalahan antara kurva Anda jika (<- besar jika) garis Anda cukup halus .
Dalam kasus Anda, Anda melakukan interpolasi linier (urutan 1).
Kasus lain (seperti yang disarankan GriffinHeart ) adalah menggunakan Splines ( http://en.wikipedia.org/wiki/Spline_interpolation )
Kasus mana pun akan memberi Anda beberapa bentuk polinomial yang cocok untuk kurva Anda.
sumber
Jika tujuan konversi hanya untuk penyimpanan, dan saat Anda mengembalikannya di layar, Anda memerlukannya agar lancar, maka penyimpanan dengan kesetiaan tertinggi dapat Anda peroleh, sementara masih meminimalkan total penyimpanan yang diperlukan untuk bertahan pada kurva tertentu. untuk benar-benar menyimpan atribut lingkaran (atau busur, lebih tepatnya) dan menggambar ulang sesuai permintaan.
Asal. Radius. Mulai / hentikan sudut untuk menggambar busur.
Jika Anda perlu mengonversi lingkaran / lengkung menjadi titik-titik untuk dirender, maka Anda dapat melakukannya setelah memuatnya dari penyimpanan, sambil selalu menyimpan hanya atribut-atributnya.
sumber
Apakah ada alasan untuk membuat kurva yang bertentangan dengan garis lurus? Garis lurus lebih mudah untuk dikerjakan, dan dapat diterjemahkan secara efisien dalam perangkat keras.
Pendekatan lain yang layak dipertimbangkan adalah untuk menyimpan beberapa bit per piksel, menyatakan apakah itu di dalam, di luar atau pada garis bentuk. Ini harus dikompres dengan baik, dan mungkin lebih efisien daripada garis untuk pemilihan kompleks.
Anda juga mungkin menemukan artikel ini menarik / bermanfaat:
sumber
Lihatlah interpolasi kurva - ada beberapa jenis yang dapat Anda terapkan yang akan membantu memperlancar kurva Anda. Semakin banyak poin yang bisa Anda dapatkan di lingkaran itu, semakin baik. Penyimpanan cukup murah - jadi jika mengekstraksi 360 node tutup cukup murah (bahkan pada 8 byte untuk posisi; 360 node hampir tidak mahal untuk disimpan).
Anda dapat menempatkan beberapa sampel interpolasi di sini hanya dengan empat poin; dan hasilnya cukup baik (favorit saya adalah Bezier untuk kasus ini, meskipun yang lain mungkin berpadu tentang solusi efektif lainnya).
Anda dapat bermain-main di sini juga.
sumber