1pub struct Canvas {
2 pub width: usize,
3 pub height: usize,
4 pub file_path: String,
5 pub tags: Vec<String>,
6}
7
8impl crate::canvas_svg::Canvas {
9 pub fn new(file_path: String, size: (usize, usize)) -> Self {
10 crate::canvas_svg::Canvas {
11 width: size.0,
12 height: size.1,
13 file_path,
14 tags: vec![],
15 }
16 }
17
18 pub fn write(&self) {
19 let mut file = std::fs::File::create(self.file_path.as_str()).expect("file not found.");
20 use std::io::Write;
21 writeln!(
22 file,
23 "<svg width=\"{}\" height=\"{}\" xmlns=\"http://www.w3.org/2000/svg\">",
24 self.width, self.height
25 )
26 .expect("cannot write.");
27 for s in &self.tags {
28 writeln!(file, "{}", s).expect("cannot write");
29 }
30 writeln!(file, "</svg>").expect("cannot write");
31 }
32
33 pub fn polyloop(
34 &mut self,
35 vtx2xy: &[f32],
36 transform_xy2pix: &[f32; 9],
37 stroke_color: Option<i32>,
38 stroke_width: Option<f32>,
39 fill: Option<i32>,
40 ) {
41 let s = format!(
42 "<polygon points=\"{}\" {} {} {} />",
43 polyloop2_to_svg(vtx2xy, transform_xy2pix),
44 if stroke_color.is_some() {
45 format!("stroke=\"#{:06X}\"", stroke_color.unwrap())
46 } else {
47 "stroke=\"none\"".to_owned()
48 },
49 if stroke_width.is_some() {
50 format!("stroke-width=\"{}\"", stroke_width.unwrap())
51 } else {
52 "".to_owned()
53 },
54 if fill.is_some() {
55 format!("fill=\"#{:06X}\"", fill.unwrap())
56 } else {
57 "fill=\"none\"".to_owned()
58 }
59 );
60 self.tags.push(s);
61 }
62
63 pub fn circle(
64 &mut self,
65 x: f32,
66 y: f32,
67 transform_xy2pix: &[f32; 9],
68 radius: f32,
69 color: &str,
70 ) {
71 let p = [x, y, 1.];
72 let q = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p);
73 let s = format!(
74 "<circle cx=\"{}\" cy=\"{}\" r=\"{}\" fill=\"{}\" />",
75 q[0] / q[2],
76 q[1] / q[2],
77 radius,
78 color
79 );
80 self.tags.push(s);
81 }
82
83 pub fn line(
84 &mut self,
85 x1: f32,
86 y1: f32,
87 x2: f32,
88 y2: f32,
89 transform_xy2pix: &[f32; 9],
90 stroke_width: Option<f32>,
91 ) {
92 let p1 = [x1, y1, 1.];
93 let q1 = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p1);
94 let p2 = [x2, y2, 1.];
95 let q2 = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p2);
96 let s = format!(
97 "<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"black\" {} />",
98 q1[0] / q1[2],
99 q1[1] / q1[2],
100 q2[0] / q2[2],
101 q2[1] / q2[2],
102 if stroke_width.is_some() {
103 format!("stroke-width=\"{}\"", stroke_width.unwrap())
104 } else {
105 "".to_owned()
106 }
107 );
108 self.tags.push(s);
109 }
110}
111
112#[test]
113fn hoge() {
114 let str2 = "M 457.60409,474.77081 H 347.66161 \
115 L 208.25942,282.21963 q -15.48914,0.60741 -25.20781,0.60741 \
116 -3.94821,0 -8.50384,0 -4.55562,-0.3037 -9.41496,-0.60741 \
117 v 119.66114 q 0,38.87469 8.50384,48.28965 11.54092,13.36318 34.62277,13.36318 \
118 h 16.09655 v 11.23721 H 47.901331 V 463.5336 h 15.489133 \
119 q 26.118931,0 37.356146,-17.00768 6.37788,-9.41496 6.37788,-44.64515 V 135.83213 \
120 q 0,-38.874683 -8.50384,-48.289646 Q 86.776018,74.17931 63.390464,74.17931 H 47.901331 \
121 V 62.942096 H 197.93333 q 65.60103,0 96.5793,9.718671 31.28197,9.414964 52.84528,35.230183 \
122 21.86701,25.51152 21.86701,61.04541 0,37.96356 -24.9041,65.90474 -24.60039,27.94118 -76.53454,39.48211 \
123 l 85.03838,118.1426 q 29.15601,40.69694 50.1119,54.06011 20.95589,13.36318 54.66753,17.00768 z \
124 M 165.13281,263.08599 q 5.77046,0 10.02238,0.30371 4.25192,0 6.9853,0 58.91944,0 88.68288,-25.51151 \
125 30.06714,-25.51152 30.06714,-64.99362 0,-38.57098 -24.29668,-62.56395 -23.99297,-24.296679 \
126 -63.77879,-24.296679 -17.61509,0 -47.68223,5.770461 z";
127 let strs = svg_outline_path_from_shape(str2);
128 let loops = svg_loops_from_outline_path(&strs);
130 let (width, height) = (512usize, 512usize);
133 let mut img_data = vec![255u8; height * width];
134 for i_w in 0..width {
136 for i_h in 0..height {
137 let p = [i_w as f32 + 0.5f32, i_h as f32 + 0.5f32];
138 let mut wn = 0.0f32;
139 for i_loop in 0..loops.len() {
140 use slice_of_array::SliceFlatExt;
141 let loop0 = loops[i_loop].0.flat();
142 wn += crate::rasterize::polygon2::winding_number(loop0, &p);
143 }
144 if wn.round() as i64 != 0 {
145 img_data[i_h * width + i_w] = 128;
146 }
147 }
148 }
149 for (vtx2xy, _seg2vtx, _is_close) in &loops {
151 let num_vtx = vtx2xy.len();
152 for i_vtx in 0..num_vtx {
153 let j_vtx = (i_vtx + 1) % num_vtx;
154 let p0 = &vtx2xy[i_vtx];
155 let p1 = &vtx2xy[j_vtx];
156 crate::rasterize::line2::draw_pixcenter(
157 &mut img_data,
158 width,
159 p0,
160 p1,
161 &[1., 0., 0., 0., 1., 0., 0., 0., 1.],
162 3.0,
163 0,
164 );
165 }
166 }
167 let file = std::fs::File::create("target/r0.png").unwrap();
168 let w = std::io::BufWriter::new(file);
169 let mut encoder = png::Encoder::new(w, width.try_into().unwrap(), height.try_into().unwrap()); encoder.set_color(png::ColorType::Grayscale);
171 encoder.set_depth(png::BitDepth::Eight);
172 let mut writer = encoder.write_header().unwrap();
173 writer.write_image_data(&img_data).unwrap(); }
175
176#[test]
177fn hoge1() {
178 let str2 = "M 457.60409,474.77081 H 347.66161 \
179 L 208.25942,282.21963 q -15.48914,0.60741 -25.20781,0.60741 \
180 -3.94821,0 -8.50384,0 -4.55562,-0.3037 -9.41496,-0.60741 \
181 v 119.66114 q 0,38.87469 8.50384,48.28965 11.54092,13.36318 34.62277,13.36318 \
182 h 16.09655 v 11.23721 H 47.901331 V 463.5336 h 15.489133 \
183 q 26.118931,0 37.356146,-17.00768 6.37788,-9.41496 6.37788,-44.64515 V 135.83213 \
184 q 0,-38.874683 -8.50384,-48.289646 Q 86.776018,74.17931 63.390464,74.17931 H 47.901331 \
185 V 62.942096 H 197.93333 q 65.60103,0 96.5793,9.718671 31.28197,9.414964 52.84528,35.230183 \
186 21.86701,25.51152 21.86701,61.04541 0,37.96356 -24.9041,65.90474 -24.60039,27.94118 -76.53454,39.48211 \
187 l 85.03838,118.1426 q 29.15601,40.69694 50.1119,54.06011 20.95589,13.36318 54.66753,17.00768 z \
188 M 165.13281,263.08599 q 5.77046,0 10.02238,0.30371 4.25192,0 6.9853,0 58.91944,0 88.68288,-25.51151 \
189 30.06714,-25.51152 30.06714,-64.99362 0,-38.57098 -24.29668,-62.56395 -23.99297,-24.296679 \
190 -63.77879,-24.296679 -17.61509,0 -47.68223,5.770461 z";
191 let strs = svg_outline_path_from_shape(str2);
192 let loops = svg_loops_from_outline_path(&strs);
193 let (width, height) = (512usize, 512usize);
195 let mut img_data = vec![255u8; height * width];
196 for (vtx2xy, seg2vtx, is_close) in &loops {
197 let vtxp2xy = polybezier2polyloop(&vtx2xy, &seg2vtx, *is_close, 0.01);
198 for i_vtx in 0..vtxp2xy.len() {
199 let j_vtx = (i_vtx + 1) % vtxp2xy.len();
200 let p0 = vtxp2xy[i_vtx];
201 let p1 = vtxp2xy[j_vtx];
202 crate::rasterize::line2::draw_dda_pixel_coordinate(&mut img_data, width, &p0, &p1, 0);
203 }
204 }
205 let file = std::fs::File::create("target/r1.png").unwrap();
206 let w = std::io::BufWriter::new(file);
207 let mut encoder = png::Encoder::new(w, width.try_into().unwrap(), height.try_into().unwrap()); encoder.set_color(png::ColorType::Grayscale);
209 encoder.set_depth(png::BitDepth::Eight);
210 let mut writer = encoder.write_header().unwrap();
211 writer.write_image_data(&img_data).unwrap(); }
213
214pub fn polyloop2_to_svg<Real>(vtx2xy: &[Real], transform: &[Real; 9]) -> String
215where
216 Real: std::fmt::Display + Copy + num_traits::Float,
217{
218 let mut res = String::new();
219 for ivtx in 0..vtx2xy.len() / 2 {
220 let x = vtx2xy[ivtx * 2];
221 let y = vtx2xy[ivtx * 2 + 1];
222 let a = del_geo_core::mat3_col_major::transform_homogeneous(transform, &[x, y]).unwrap();
223 res += format!("{} {}", a[0], a[1]).as_str();
224 if ivtx != vtx2xy.len() / 2 - 1 {
225 res += ",";
226 }
227 }
228 res
229}
230
231pub fn svg_outline_path_from_shape(s0: &str) -> Vec<String> {
232 let s0 = s0.as_bytes();
233 let mut imark = 0;
234 let mut strs = Vec::<String>::new();
235 for i in 0..s0.len() {
236 if s0[i].is_ascii_digit() {
237 continue;
238 }
239 if s0[i] == b',' {
240 strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
241 imark = i + 1; }
243 if s0[i] == b' ' {
244 if i > imark {
246 strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
247 }
248 imark = i + 1; }
250 if s0[i] == b'-' {
251 if i > imark {
252 strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
253 }
254 imark = i;
255 }
256 if s0[i].is_ascii_alphabetic() {
257 if i > imark {
258 strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
259 }
260 strs.push(std::str::from_utf8(&[s0[i]]).unwrap().to_string()); imark = i + 1;
262 }
263 }
264 if s0.len() > imark {
265 strs.push(
266 std::str::from_utf8(&s0[imark..s0.len()])
267 .unwrap()
268 .to_string(),
269 );
270 }
271 strs
272}
273
274#[allow(clippy::identity_op)]
275pub fn svg_loops_from_outline_path(strs: &[String]) -> Vec<(Vec<[f32; 2]>, Vec<usize>, bool)> {
276 use del_geo_core::vec2::Vec2;
277 let hoge = |s0: &str, s1: &str| [s0.parse::<f32>().unwrap(), s1.parse::<f32>().unwrap()];
278 let mut loops: Vec<(Vec<[f32; 2]>, Vec<usize>, bool)> = vec![];
279 let mut vtxl2xy: Vec<[f32; 2]> = vec![];
280 let mut seg2vtxl: Vec<usize> = vec![0];
281 assert!(strs[0] == "M" || strs[0] == "m");
282 let mut pos_cur = [0f32; 2];
284 let mut is = 0;
285 loop {
286 if strs[is] == "M" {
287 is += 1;
289 pos_cur = hoge(&strs[is + 0], &strs[is + 1]);
290 vtxl2xy.push(pos_cur);
291 is += 2;
292 } else if strs[is] == "m" {
293 is += 1;
295 pos_cur = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
296 vtxl2xy.push(pos_cur);
297 is += 2;
298 } else if strs[is] == "l" {
299 is += 1;
301 loop {
302 seg2vtxl.push(vtxl2xy.len());
303 let p1 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
304 vtxl2xy.push(p1);
305 pos_cur = p1;
306 is += 2;
307 if strs[is].as_bytes()[0].is_ascii_alphabetic() {
308 break;
309 }
310 }
311 } else if strs[is] == "L" {
312 is += 1;
314 loop {
315 seg2vtxl.push(vtxl2xy.len());
316 let p1 = hoge(&strs[is + 0], &strs[is + 1]);
317 vtxl2xy.push(p1);
318 pos_cur = p1;
319 is += 2;
320 if strs[is].as_bytes()[0].is_ascii_alphabetic() {
321 break;
322 }
323 }
324 } else if strs[is] == "v" {
325 seg2vtxl.push(vtxl2xy.len());
327 let p1 = pos_cur.add(&[0., strs[is + 1].parse::<f32>().unwrap()]);
328 vtxl2xy.push(p1);
329 pos_cur = p1;
330 is += 2;
331 } else if strs[is] == "V" {
332 seg2vtxl.push(vtxl2xy.len());
334 let p1 = [pos_cur[0], strs[is + 1].parse::<f32>().unwrap()];
335 vtxl2xy.push(p1);
336 pos_cur = p1;
337 is += 2;
338 } else if strs[is] == "H" {
339 seg2vtxl.push(vtxl2xy.len());
341 let p1 = [strs[is + 1].parse::<f32>().unwrap(), pos_cur[1]];
342 vtxl2xy.push(p1);
343 pos_cur = p1;
344 is += 2;
345 } else if strs[is] == "h" {
346 seg2vtxl.push(vtxl2xy.len());
348 let dh = strs[is + 1].parse::<f32>().unwrap();
349 let p1 = pos_cur.add(&[dh, 0.]);
350 vtxl2xy.push(p1);
351 pos_cur = p1;
352 is += 2;
353 } else if strs[is] == "q" {
354 is += 1;
356 loop {
357 let pm0 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
359 let p1 = pos_cur.add(&hoge(&strs[is + 2], &strs[is + 3]));
360 vtxl2xy.push(pm0);
361 seg2vtxl.push(vtxl2xy.len());
362 vtxl2xy.push(p1);
363 pos_cur = p1;
364 is += 4;
365 if is == strs.len() {
366 loops.push((vtxl2xy.clone(), seg2vtxl.clone(), false));
367 break;
368 }
369 if strs[is].as_bytes()[0].is_ascii_alphabetic() {
370 break;
371 }
372 }
373 } else if strs[is] == "Q" {
374 is += 1;
376 loop {
377 let pm0 = hoge(&strs[is + 0], &strs[is + 1]);
379 let p1 = hoge(&strs[is + 2], &strs[is + 3]);
380 vtxl2xy.push(pm0);
381 seg2vtxl.push(vtxl2xy.len());
382 vtxl2xy.push(p1);
383 pos_cur = p1;
384 is += 4;
385 if strs[is].as_bytes()[0].is_ascii_alphabetic() {
386 break;
387 }
388 }
389 } else if strs[is] == "c" {
390 is += 1;
392 loop {
393 let pm0 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
395 let pm1 = pos_cur.add(&hoge(&strs[is + 2], &strs[is + 3]));
396 let p1 = pos_cur.add(&hoge(&strs[is + 4], &strs[is + 5]));
397 vtxl2xy.push(pm0);
398 vtxl2xy.push(pm1);
399 seg2vtxl.push(vtxl2xy.len());
400 vtxl2xy.push(p1);
401 pos_cur = p1;
402 is += 6;
403 if is == strs.len() {
404 loops.push((vtxl2xy.clone(), seg2vtxl.clone(), false));
405 break;
406 }
407 if strs[is].as_bytes()[0].is_ascii_alphabetic() {
408 break;
409 }
410 }
411 } else if strs[is] == "z" || strs[is] == "Z" {
412 let pe = vtxl2xy[0];
413 let ps = vtxl2xy[vtxl2xy.len() - 1];
414 let _dist0 = ps.sub(&pe).norm();
415 loops.push((vtxl2xy.clone(), seg2vtxl.clone(), true));
416 vtxl2xy.clear();
417 seg2vtxl = vec![0];
418 is += 1;
419 } else {
420 dbg!("error!--> ", &strs[is]);
421 break;
422 }
423 if is == strs.len() {
424 break;
425 }
426 }
427 loops
428}
429pub fn polybezier2polyloop(
430 vtx2xy: &[[f32; 2]],
431 seg2vtx: &[usize],
432 is_close: bool,
433 edge_length: f32,
434) -> Vec<[f32; 2]> {
435 use del_geo_core::vec2::Vec2;
436 let mut ret: Vec<[f32; 2]> = vec![];
437 let num_seg = seg2vtx.len() - 1;
438 for i_seg in 0..num_seg {
439 let (is_vtx, ie_vtx) = (seg2vtx[i_seg], seg2vtx[i_seg + 1]);
440 let ps = &vtx2xy[is_vtx];
441 let pe = &vtx2xy[ie_vtx];
442 if ie_vtx - is_vtx == 1 {
443 let len = pe.sub(ps).norm();
444 let ndiv = (len / edge_length) as usize;
445 for i in 0..ndiv {
446 let r = i as f32 / ndiv as f32;
447 let p = ps.scale(1f32 - r).add(&pe.scale(r));
448 ret.push(p);
449 }
450 } else if ie_vtx - is_vtx == 2 {
451 let pc = &vtx2xy[is_vtx + 1];
453 let ndiv = 10;
454 for idiv in 0..ndiv {
455 let t0 = idiv as f32 / ndiv as f32;
456 let p0 = del_geo_core::bezier_quadratic::eval(ps, pc, pe, t0);
457 ret.push(p0);
458 }
459 } else if ie_vtx - is_vtx == 3 {
460 let pc0 = &vtx2xy[is_vtx + 1];
462 let pc1 = &vtx2xy[is_vtx + 2];
463 let samples = del_geo_core::bezier_cubic::sample_uniform_length(
464 del_geo_core::bezier_cubic::ControlPoints::<'_, f32, 2> {
465 p0: ps,
466 p1: pc0,
467 p2: pc1,
468 p3: pe,
469 },
470 edge_length,
471 true,
472 false,
473 30,
474 );
475 ret.extend(samples);
476 }
477 }
478 if is_close {
479 let ps = &vtx2xy[vtx2xy.len() - 1];
480 let pe = &vtx2xy[0];
481 let len = pe.sub(ps).norm();
482 let ndiv = (len / edge_length) as usize;
483 for i in 0..ndiv {
484 let r = i as f32 / ndiv as f32;
485 let p = ps.scale(1f32 - r).add(&pe.scale(r));
486 ret.push(p);
487 }
488 }
489 ret
490}