.
1 /*
2 Copyright (C) 2012, 2013, 2014 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16 using Math;
17
18 namespace BirdFont {
19
20 public class BackgroundImage {
21
22 public string name = "";
23 public Gee.ArrayList<BackgroundSelection> selections;
24
25 /** Image position in canvas coordinates. */
26 public double img_x = 0;
27 public double img_y = 0;
28
29 public double img_scale_x = 1;
30 public double img_scale_y = 1;
31
32 public double img_rotation = 0;
33 private int size = -1;
34
35 public int active_handle = -1;
36 public int selected_handle = -1;
37
38 private ImageSurface? contrast_image = null;
39 private ImageSurface? background_image = null;
40 private ImageSurface? original_image = null;
41
42 private string path;
43
44 public bool high_contrast = false;
45 private double trace_resolution = 1.0;
46 private double threshold = 1.0;
47 private double simplification = 0.5;
48 private Gee.ArrayList<TracedPoint> points = new Gee.ArrayList<TracedPoint> ();
49 private Gee.ArrayList<TracedPoint> start_points = new Gee.ArrayList<TracedPoint> ();
50
51 public signal void updated ();
52
53 public double img_offset_x {
54 get { return img_x + Glyph.xc (); }
55 set { img_x = value - Glyph.xc (); }
56 }
57
58 public double img_offset_y {
59 get { return Glyph.yc () - img_y; }
60 set { img_y = Glyph.yc () - value; }
61 }
62
63 public int size_margin {
64 get {
65 if (unlikely (size == -1)) {
66 size = (int) (Math.sqrt (Math.pow (get_img ().get_height (), 2)
67 + Math.pow (get_img ().get_width (), 2)) + 0.5);
68 }
69
70 return size;
71 }
72 }
73
74 public int margin_left {
75 get {
76 return size_margin - get_img ().get_width ();
77 }
78 }
79
80 public int margin_top {
81 get {
82 return size_margin - get_img ().get_height ();
83 }
84 }
85
86 public double img_middle_x {
87 get { return img_x + (size_margin * img_scale_x) / 2; }
88 set { img_x = value - (size_margin * img_scale_x) / 2; }
89 }
90
91 public double img_middle_y {
92 get { return img_y - (size_margin * img_scale_y) / 2; }
93 set { img_y = value + (size_margin * img_scale_y) / 2; }
94 }
95
96 public BackgroundImage (string file_name) {
97 path = file_name;
98 selections = new Gee.ArrayList<BackgroundSelection> ();
99 }
100
101 public BackgroundImage copy () {
102 BackgroundImage bg = new BackgroundImage (path);
103
104 bg.img_x = img_x;
105 bg.img_y = img_y;
106
107 bg.img_scale_x = img_scale_x;
108 bg.img_scale_y = img_scale_y;
109 bg.img_rotation = img_rotation;
110
111 bg.simplification = simplification;
112 bg.threshold = threshold;
113 bg.high_contrast = high_contrast;
114 bg.trace_resolution = trace_resolution;
115
116 foreach (BackgroundSelection b in selections) {
117 bg.selections.add (b);
118 }
119
120 return bg;
121 }
122
123 public void add_selection (BackgroundSelection bs) {
124 selections.add (bs);
125 }
126
127 public void set_trace_simplification (double s) {
128 simplification = s;
129 }
130
131 public void set_high_contrast (bool t) {
132 high_contrast = t;
133 }
134
135 public void set_trace_resolution (double t) {
136 trace_resolution = t;
137 }
138
139 public double get_margin_width () {
140 return ((size_margin - get_img ().get_width ()) / 2.0);
141 }
142
143 public double get_margin_height () {
144 return ((size_margin - get_img ().get_height ()) / 2.0);
145 }
146
147 public void set_img_offset (double x, double y) {
148 img_offset_x = x;
149 img_offset_y = y;
150 }
151
152 public void set_position (double coordinate_x, double coordinate_y) {
153 img_x = coordinate_x;
154 img_y = coordinate_y;
155 }
156
157 public ImageSurface get_img () {
158 if (!path.has_suffix (".png")) {
159 create_png ();
160 }
161
162 if (background_image == null) {
163 background_image = new ImageSurface.from_png (path);
164 original_image = new ImageSurface.from_png (path);
165 }
166
167 return (!) background_image;
168 }
169
170 public ImageSurface get_original () {
171 if (!path.has_suffix (".png")) {
172 create_png ();
173 }
174
175 if (background_image == null) {
176 background_image = new ImageSurface.from_png (path);
177 original_image = new ImageSurface.from_png (path);
178 }
179
180 return (!) original_image;
181 }
182
183 public bool is_valid () {
184 FileInfo file_info;
185 File file = File.new_for_path (path);
186
187 if (!file.query_exists ()) {
188 return false;
189 }
190
191 try {
192 file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
193
194 if (file_info.get_size () == 0) {
195 return false;
196 }
197 } catch (GLib.Error e) {
198 warning (e.message);
199 return false;
200 }
201
202 return true;
203 }
204
205 public string get_png_base64 () {
206 try {
207 File file = File.new_for_path (path);
208 FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
209 uint8[] buffer = new uint8[file_info.get_size ()];
210 FileInputStream file_stream;
211 DataInputStream png_stream;
212
213 if (!file.query_exists ()) {
214 warning (@"Can't to save image $path, file does not exist.");
215 return "";
216 }
217
218 if (is_null (buffer)) {
219 warning (@"Can not allocate a buffer of $(file_info.get_size ()) bytes to store $path.");
220 return "";
221 }
222
223 file_stream = file.read ();
224
225 png_stream = new DataInputStream (file_stream);
226 png_stream.read (buffer);
227
228 return Base64.encode (buffer);
229 } catch (GLib.Error e) {
230 warning (e.message);
231 }
232
233 return "";
234
235 }
236
237 public void create_background_folders (Font font) {
238 File dir;
239
240 dir = BirdFont.get_settings_directory ();
241 if (!dir.query_exists ()) {
242 DirUtils.create ((!) dir.get_path (), 0755);
243 }
244
245 dir = font.get_backgrounds_folder ();
246 if (!dir.query_exists ()) {
247 DirUtils.create ((!) dir.get_path (), 0755);
248 }
249
250 dir = get_child (font.get_backgrounds_folder (), "parts");
251 if (!dir.query_exists ()) {
252 DirUtils.create ((!) dir.get_path (), 0755);
253 }
254 }
255
256 public void copy_if_new (File destination) {
257 if (!destination.query_exists ()) {
258 copy_file (destination);
259 }
260 }
261
262 public void copy_file (File destination) {
263 File source;
264 FileInfo info;
265
266 try {
267 if (destination.query_exists ()) {
268 info = destination.query_info ("standard::*", FileQueryInfoFlags.NONE);
269 if (info.get_file_type () == FileType.DIRECTORY) {
270 warning (@"$((!) destination.get_path ()) is a directory.");
271 return;
272 }
273 }
274
275 if (!((!)destination.get_parent ()).query_exists ()) {
276 warning (@"Directory for file $((!) destination.get_path ()) is not created.");
277 return;
278 }
279
280 if (destination.query_exists ()) {
281 warning (@"Image $((!) destination.get_path ()) is already created.");
282 return;
283 }
284
285 source = File.new_for_path (path);
286 source.copy (destination, FileCopyFlags.NONE);
287 } catch (GLib.Error e) {
288 warning (e.message);
289 }
290 }
291
292 public string get_sha1 () {
293 try {
294 File file = File.new_for_path (path);
295 FileInfo file_info;
296 uint8[] buffer;
297 FileInputStream file_stream;
298 DataInputStream png_stream;
299
300 if (!file.query_exists ()) {
301 warning (@"Can't save $path file does not exist.");
302 return "";
303 }
304
305 file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
306
307 if (file_info.get_size () == 0) {
308 warning (@"length of image $path is zero");
309 return "";
310 }
311
312 buffer = new uint8[file_info.get_size ()];
313 file_stream = file.read ();
314 png_stream = new DataInputStream (file_stream);
315
316 png_stream.read (buffer);
317
318 return Checksum.compute_for_data (ChecksumType.SHA1, buffer);
319 } catch (GLib.Error e) {
320 warning (e.message);
321 }
322
323 return "";
324 }
325
326 private void create_png () {
327 string file_name = @"$path.png";
328 Font font = BirdFont.get_current_font ();
329 File folder = font.get_backgrounds_folder ();
330 File original = File.new_for_path (file_name);
331 File png_image = get_child (folder, @"full_$((!)original.get_basename ())");
332 bool converted;
333
334 if (png_image.query_exists ()) {
335 path = (!) png_image.get_path ();
336 return;
337 }
338
339 if (is_null (path)) {
340 warning ("Background image path is null.");
341 return;
342 }
343
344 converted = MainWindow.native_window.convert_to_png (path.dup (), ((!) png_image.get_path ()).dup ());
345
346 if (!converted) {
347 warning ("Failed to convert image: $(path)");
348 return;
349 }
350
351 path = (!) png_image.get_path ();
352 }
353
354 public void set_img_rotation_from_coordinate (double x, double y) {
355 double bcx, bcy;
356 double a, b, c, length;
357
358 bcx = img_middle_x;
359 bcy = img_middle_y;
360
361 a = bcx - x;
362 b = bcy - y;
363 c = a * a + b * b;
364
365 if (c == 0) {
366 return;
367 }
368
369 length = sqrt (fabs (c));
370
371 if (c < 0) {
372 length = -length;
373 }
374
375 img_rotation = (y > bcy) ? acos (a / length) + PI : -acos (a / length) + PI;
376
377 background_image = null;
378 }
379
380 public void set_img_scale (double xs, double ys) {
381 img_scale_x = xs;
382 img_scale_y = ys;
383 }
384
385 public void reset_scale (Glyph g) {
386 double w, h;
387
388 w = g.get_width ();
389 h = g.get_height ();
390
391 img_scale_x = 1;
392 img_scale_y = 1;
393
394 img_offset_x = g.get_line ("left").pos;
395 img_offset_y = g.get_line ("top").pos;
396 }
397
398 public void draw (Context cr, WidgetAllocation allocation, double view_offset_x, double view_offset_y, double view_zoom) {
399 double scale_x, scale_y;
400 double image_scale_x, image_scale_y;
401 ImageSurface s;
402 Surface st;
403 Context ct;
404 ImageSurface rotated_image;
405
406 if (high_contrast && contrast_image == null) {
407 contrast_image = get_contrast_image ();
408 }
409
410 if (unlikely (get_img ().status () != Cairo.Status.SUCCESS)) {
411 warning (@"Background image is invalid. (\"$path\")\n");
412 MainWindow.get_current_glyph ().set_background_visible (false);
413 return;
414 }
415
416 rotated_image = (ImageSurface) get_rotated_image ();
417
418 if (contrast_image == null) {
419 s = rotated_image;
420 image_scale_x = img_scale_x;
421 image_scale_y = img_scale_y;
422 } else {
423 s = (!) contrast_image;
424 image_scale_x = img_scale_x * ((double) rotated_image.get_width () / s.get_width ());
425 image_scale_y = img_scale_y * ((double) rotated_image.get_height () / s.get_height ());
426 }
427
428 st = new Surface.similar (s, s.get_content (), allocation.width, allocation.height);
429 ct = new Context (st);
430 ct.save ();
431
432 ct.set_source_rgba (1, 1, 1, 1);
433 ct.rectangle (0, 0, allocation.width, allocation.height);
434 ct.fill ();
435
436 // scale both canvas and image at the same time
437 scale_x = view_zoom * image_scale_x;
438 scale_y = view_zoom * image_scale_y;
439
440 ct.scale (scale_x, scale_y);
441 ct.translate (-view_offset_x / image_scale_x, -view_offset_y / image_scale_y);
442
443 ct.set_source_surface (s, img_offset_x / image_scale_x, img_offset_y / image_scale_y);
444
445 ct.paint ();
446 ct.restore ();
447
448 // add it
449 cr.save ();
450 cr.set_source_surface (st, 0, 0);
451 cr.paint ();
452 cr.restore ();
453 }
454
455 public Surface get_rotated_image () {
456 double x, y;
457 double iw, ih;
458 int h, w;
459 double oy, ox;
460
461 Surface o;
462
463 Surface s;
464 Context c;
465
466 Surface sg;
467 Context cg;
468
469 double wc, hc;
470
471 o = get_original ();
472
473 // add margin
474 sg = new Surface.similar (o, o.get_content (), size_margin, size_margin);
475 cg = new Context (sg);
476
477 wc = get_margin_width ();
478 hc = get_margin_height ();
479
480 cg.set_source_rgba (1, 1, 1, 1);
481 cg.rectangle (0, 0, size_margin, size_margin);
482 cg.fill ();
483
484 cg.set_source_surface (get_img (), wc, hc);
485 cg.paint ();
486
487 x = Glyph.reverse_path_coordinate_x (img_offset_x);
488 y = Glyph.reverse_path_coordinate_y (img_offset_y);
489
490 ih = (int) get_img ().get_height ();
491 iw = (int) get_img ().get_width ();
492
493 w = (int) iw;
494 h = (int) ih;
495
496 oy = size_margin;
497 ox = size_margin;
498
499 // rotate image
500 s = new Surface.similar (sg, sg.get_content (), (int) (ox), (int) (oy));
501 c = new Context (s);
502
503 c.save ();
504
505 c.set_source_rgba (1, 1, 1, 1);
506 c.rectangle (0, 0, size_margin, size_margin);
507 c.fill ();
508
509 c.translate (size_margin * 0.5, size_margin * 0.5);
510 c.rotate (img_rotation);
511 c.translate (-size_margin * 0.5, -size_margin * 0.5);
512
513 c.set_source_surface (cg.get_target (), 0, 0);
514 c.paint ();
515 c.restore ();
516
517 return s;
518 }
519
520 public void handler_release (double nx, double ny) {
521 selected_handle = 0;
522 handler_move (nx, ny);
523 }
524
525 public void handler_press (double nx, double ny) {
526 if (is_over_rotate (nx, ny)) {
527 selected_handle = 2;
528 } else if (is_over_resize (nx, ny)) {
529 selected_handle = 1;
530 } else {
531 selected_handle = 0;
532 }
533 }
534
535 bool is_over_rotate (double nx, double ny) {
536 double x, y, d;
537
538 x = Glyph.reverse_path_coordinate_x (img_middle_x);
539 y = Glyph.reverse_path_coordinate_y (img_middle_y);
540
541 x += cos (img_rotation) * 75;
542 y += sin (img_rotation) * 75;
543
544 d = Path.distance (x, nx, y, ny);
545
546 return d < 15 * MainWindow.units;
547 }
548
549 bool is_over_resize (double nx, double ny) {
550 double x, y, size;
551 bool inx, iny;
552
553 size = 12 * MainWindow.units;
554
555 x = img_middle_x - (img_scale_x * get_img ().get_width () / 2);
556 y = img_middle_y - (img_scale_y * get_img ().get_height () / 2);
557
558 x = Glyph.reverse_path_coordinate_x (x);
559 y = Glyph.reverse_path_coordinate_y (y);
560
561 inx = x - size <= nx <= x + size;
562 iny = y - size <= ny <= y + size;
563
564 return inx && iny;
565 }
566
567 public void handler_move (double nx, double ny) {
568 int prev_handle = active_handle;
569
570 if (is_over_rotate (nx, ny)) {
571 active_handle = 2;
572 } else if (is_over_resize (nx, ny)) {
573 active_handle = 1;
574 } else {
575 active_handle = 0;
576 }
577
578 if (prev_handle != active_handle) {
579 GlyphCanvas.redraw ();
580 }
581 }
582
583 public void draw_handle (Context cr, Glyph g) {
584 draw_resize_handle (cr, g);
585 draw_rotate_handle (cr, g);
586 }
587
588 public void draw_resize_handle (Context cr, Glyph g) {
589 double x, y;
590 cr.save ();
591
592 cr.set_source_rgba (1, 0, 0.3, 1);
593
594 x = img_middle_x - (img_scale_x * get_img ().get_width () / 2);
595 y = img_middle_y - (img_scale_y * get_img ().get_height () / 2);
596
597 x = Glyph.reverse_path_coordinate_x (x);
598 y = Glyph.reverse_path_coordinate_y (y);
599
600 draw_handle_triangle (x, y, cr, g, 6);
601
602 cr.restore ();
603 }
604
605 public void draw_rotate_handle (Context cr, Glyph g) {
606 double x, y, hx, hy, x2, y2;
607
608 double ivz = 1.0 / (g.view_zoom);
609
610 cr.save ();
611
612 cr.scale (g.view_zoom, g.view_zoom);
613
614 if (selected_handle == 2) cr.set_source_rgba (1, 0, 0.3, 1);
615 else if (active_handle == 2) cr.set_source_rgba (0, 0, 0.3, 1);
616 else cr.set_source_rgba (0.7, 0.7, 0.8, 1);
617
618 x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;
619 y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;
620
621 cr.rectangle (x, y, 5 * ivz, 5 * ivz);
622 cr.fill ();
623
624 hx = cos (img_rotation) * 75 * ivz;
625 hy = sin (img_rotation) * 75 * ivz;
626
627 x2 = x + hx;
628 y2 = y + hy;
629
630 cr.rectangle (x2, y2, 5 * ivz, 5 * ivz);
631 cr.fill ();
632
633 cr.set_line_width (ivz);
634 cr.move_to (x + 2.5 * ivz, y + 2.5 * ivz);
635 cr.line_to (x2 + 2.5 * ivz, y2 + 2.5 * ivz);
636 cr.stroke ();
637
638 cr.restore ();
639 }
640
641 void draw_handle_triangle (double x, double y, Context cr, Glyph g, int direction, double s = 1)
642 requires (0 < direction < 8)
643 {
644 double ivz = 1.0 / (g.view_zoom);
645 double size;
646
647 cr.save ();
648 cr.set_line_width (ivz);
649
650 if (selected_handle == 1) cr.set_source_rgba (1, 0, 0.3, 1);
651 else if (active_handle == 1) cr.set_source_rgba (0, 0, 0.3, 1);
652 else cr.set_source_rgba (0.7, 0.7, 0.8, 1);
653
654 size = (8) * s;
655
656 cr.scale (1, 1);
657 cr.new_path ();
658
659 // up + left
660 if (direction == 1) {
661 cr.move_to (x - size, y - size);
662 cr.line_to (x + size, y - size);
663 cr.line_to (x - size, y + size);
664 }
665
666 if (direction == 6) {
667 cr.move_to (x + size, y + size);
668 cr.line_to (x - size, y + size);
669 cr.line_to (x - size, y - size);
670 }
671
672 cr.close_path();
673 cr.fill ();
674
675 cr.restore ();
676
677 }
678
679 public void update_background () {
680 background_image = null;
681 contrast_image = null;
682
683 GlyphCanvas.redraw ();
684 updated ();
685 }
686
687 public void set_threshold (double t) {
688 threshold = t;
689 }
690
691 ImageSurface get_contrast_image () {
692 ImageSurface s;
693 Context c;
694
695 ImageSurface sg;
696 int scaled_width;
697
698 unowned uchar[] pix_buff;
699 int i, len;
700 double thres;
701 int stride;
702
703 ImageSurface img;
704 ImageSurface ns;
705
706 thres = (threshold - 0.5) * 255;
707
708 scaled_width = (int) (600 * trace_resolution);
709
710 s = new ImageSurface (Format.RGB24, scaled_width, scaled_width);
711 sg = (ImageSurface) get_rotated_image ();
712 c = new Context (s);
713
714 c.save ();
715 c.set_source_rgba (1, 1, 1, 1);
716 c.rectangle (0, 0, scaled_width, scaled_width);
717 c.fill ();
718
719 c.translate (scaled_width * 0.5, scaled_width * 0.5);
720 c.rotate (img_rotation);
721 c.translate (-scaled_width * 0.5, -scaled_width * 0.5);
722
723 c.scale ((double) scaled_width / sg.get_width (), (double) scaled_width / sg.get_height ());
724
725 c.set_source_surface (sg, 0, 0);
726 c.paint ();
727 c.restore ();
728
729 img = (ImageSurface) s;
730 pix_buff = img.get_data ();
731
732 len = s.get_height () * s.get_stride ();
733
734 uint8* outline_img = new uint8[len];
735
736 for (i = 0; i < len - 4; i += 4) {
737 uint8 o = (uint8) ((pix_buff[i] + pix_buff[i + 1] + pix_buff[i + 2]) / 3.0);
738 uint8 bw = (o < thres) ? 0 : 255;
739 outline_img[i] = bw;
740 outline_img[i + 1] = bw;
741 outline_img[i + 2] = bw;
742 outline_img[i + 3] = bw;
743 }
744
745 // fill blur with black
746 stride = s.get_stride ();
747 for (int m = 0; m < 2; m++) {
748 i = stride + 4;
749 while (i < len - 4 - stride) {
750 if ((outline_img[i] == 255 && outline_img[i + 4] == 0
751 && outline_img[i + stride] == 0 && outline_img[i + stride + 4] == 255)
752 || (outline_img[i] == 0 && outline_img[i + 4] == 255
753 && outline_img[i + stride] == 255 && outline_img[i + stride + 4] == 0)) {
754 outline_img[i] = 0;
755 outline_img[i + 4] = 0;
756 outline_img[i + stride] = 0;
757 outline_img[i + stride + 4] = 0;
758 }
759
760 if (outline_img[i] == 255 && outline_img[i + 4] == 0 && outline_img[i + 8] == 255
761 || outline_img[i] == 0 && outline_img[i + 4] == 255 && outline_img[i + 8] == 0) {
762 outline_img[i] = 0;
763 outline_img[i + 4] = 0;
764 outline_img[i + stride] = 0;
765 outline_img[i + stride + 4] = 0;
766 }
767 i += 4;
768 }
769 }
770
771 ns = new ImageSurface.for_data ((uchar[])outline_img, s.get_format (), s.get_width (), s.get_height (), s.get_stride ());
772 background_image = null;
773 original_image = null;
774 contrast_image = ns;
775
776 return (ImageSurface) ns;
777 }
778
779 public PathList autotrace () {
780 ImageSurface img;
781 int len, w, h, i, s;
782 Path p;
783 PathList pl;
784 uint8* outline_img;
785
786 ImageSurface scaled_image;
787 double scale;
788
789 p = new Path ();
790 pl = new PathList ();
791
792 get_img ();
793
794 if (background_image == null) {
795 return pl;
796 }
797
798 img = (contrast_image == null) ? get_contrast_image () : (!) contrast_image;
799
800 w = img.get_width();
801 h = img.get_height();
802
803 if (unlikely (img.status () != Cairo.Status.SUCCESS)) {
804 warning ("Error");
805 return pl;
806 }
807
808 if (img.get_format () != Format.RGB24) {
809 warning ("Wrong format");
810 return pl;
811 }
812
813 scaled_image = img;
814
815 img = (ImageSurface) scaled_image;
816 w = img.get_width ();
817 h = img.get_height ();
818 s = img.get_stride ();
819 len = s * h;
820
821 outline_img = (uint8*) img.get_data ();
822
823 start_points = new Gee.ArrayList<TracedPoint> ();
824 points = new Gee.ArrayList<TracedPoint> ();
825
826 int direction_vertical = 4;
827 int direction_horizontal = s; // FIXME: SET AT FIND START
828 int last_move = 0;
829 int pp = 0;
830
831 scale = 1;
832 i = 0;
833 while ((i = find_start_point (outline_img, len, s, i)) != -1) {
834 pp = 0;
835
836 while (4 + s <= i < len - 4 - s) {
837 pp++;
838 if (is_traced (i)) {
839 Path np = generate_path (outline_img, s, w, h, len);
840
841 if (np.points.size >= 3) {
842 if (Path.is_counter (pl, np)) {
843 np.force_direction (Direction.COUNTER_CLOCKWISE);
844 } else {
845 np.force_direction (Direction.CLOCKWISE);
846 }
847
848 pl.add (np);
849 }
850
851 break;
852 }
853
854 if (outline_img[i] == 255 && outline_img[i + 4] == 255
855 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
856 warning ("Lost path");
857 Path np = generate_path (outline_img, s, w, h, len);
858 pl.add (np);
859 break;
860 }
861
862 if (outline_img[i] == 0 && outline_img[i + 4] == 0
863 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
864 warning ("Lost path");
865 Path np = generate_path (outline_img, s, w, h, len);
866 pl.add (np);
867 break;
868 }
869
870 if (outline_img[i] == 0 && outline_img[i + 4] == 255
871 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
872 points.add (new TracedPoint (i));
873
874 if (last_move == direction_horizontal) {
875 direction_vertical = -s;
876 i += direction_vertical;
877 last_move = direction_vertical;
878 } else {
879 direction_horizontal = -4;
880 i += direction_horizontal;
881 last_move = direction_horizontal;
882 }
883
884 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255
885 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
886 points.add (new TracedPoint (i));
887
888 if (last_move == direction_horizontal) {
889 direction_vertical = -s;
890 i += direction_vertical;
891 last_move = direction_vertical;
892 } else {
893 direction_horizontal = -4;
894 i += direction_horizontal;
895 last_move = direction_horizontal;
896 }
897
898 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0
899 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
900 points.add (new TracedPoint (i));
901
902 if (last_move == direction_horizontal) {
903 direction_vertical = -s;
904 i += direction_vertical;
905 last_move = direction_vertical;
906 } else {
907 direction_horizontal = 4;
908 i += direction_horizontal;
909 last_move = direction_horizontal;
910 }
911
912 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0
913 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
914 points.add (new TracedPoint (i));
915
916 if (last_move == direction_horizontal) {
917 direction_vertical = s;
918 i += direction_vertical;
919 last_move = direction_vertical;
920 } else {
921 direction_horizontal = -4;
922 i += direction_horizontal;
923 last_move = direction_horizontal;
924 }
925
926 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
927 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
928 points.add (new TracedPoint (i));
929
930 if (last_move == direction_horizontal) {
931 direction_vertical = s;
932 i += direction_vertical;
933 last_move = direction_vertical;
934 } else {
935 direction_horizontal = 4;
936 i += direction_horizontal;
937 last_move = direction_horizontal;
938 }
939
940 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
941 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
942 points.add (new TracedPoint (i));
943
944 if (last_move == direction_horizontal) {
945 direction_vertical = s;
946 i += direction_vertical;
947 last_move = direction_vertical;
948 } else {
949 direction_horizontal = -4;
950 i += direction_horizontal;
951 last_move = direction_horizontal;
952 }
953
954 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0
955 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
956 points.add (new TracedPoint (i));
957
958 if (last_move == direction_horizontal) {
959 direction_vertical = -s;
960 i += direction_vertical;
961 last_move = direction_vertical;
962 } else {
963 direction_horizontal = -4;
964 i += direction_horizontal;
965 last_move = direction_horizontal;
966 }
967 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255
968 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
969 points.add (new TracedPoint (i));
970
971 if (last_move == direction_horizontal) {
972 direction_vertical = -s;
973 i += direction_vertical;
974 last_move = direction_vertical;
975 } else {
976 direction_horizontal = 4;
977 i += direction_horizontal;
978 last_move = direction_horizontal;
979 }
980 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0
981 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
982 points.add (new TracedPoint (i));
983
984 if (last_move == direction_horizontal) {
985 direction_vertical = s;
986 i += direction_vertical;
987 last_move = direction_vertical;
988 } else {
989 direction_horizontal = 4;
990 i += direction_horizontal;
991 last_move = direction_horizontal;
992 }
993 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
994 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
995 points.add (new TracedPoint (i));
996
997 if (last_move == direction_horizontal) {
998 direction_vertical = s;
999 i += direction_vertical;
1000 last_move = direction_vertical;
1001 } else {
1002 direction_horizontal = -4;
1003 i += direction_horizontal;
1004 last_move = direction_horizontal;
1005 }
1006 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
1007 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
1008 points.add (new TracedPoint (i));
1009
1010 i += direction_horizontal;
1011 last_move = direction_horizontal;
1012
1013 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0
1014 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
1015 points.add (new TracedPoint (i));
1016
1017 i += direction_horizontal;
1018 last_move = direction_horizontal;
1019 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0
1020 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
1021 points.add (new TracedPoint (i));
1022
1023 i += direction_vertical;
1024 last_move = direction_vertical;
1025 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255
1026 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
1027 points.add (new TracedPoint (i));
1028
1029 i += direction_vertical;
1030 last_move = direction_vertical;
1031 } else if ((outline_img[i] == 255 && outline_img[i + 4] == 0
1032 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255)
1033 || (outline_img[i] == 0 && outline_img[i + 4] == 255
1034 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0)) {
1035 points.add (new TracedPoint (i));
1036
1037 warning ("Bad edge");
1038 i += last_move;
1039
1040 } else {
1041 points.add (new TracedPoint (i));
1042 warning (@"No direction\n $(outline_img[i]) $(outline_img[i + 4])\n $(outline_img[i + s]) $(outline_img[i + s + 4])");
1043 i += 4;
1044 }
1045 }
1046 }
1047
1048 start_points.clear ();
1049 points.clear ();
1050
1051 return pl;
1052 }
1053
1054 int find_start_point (uint8* outline_img, int len, int s, int start_index) {
1055 // find start point
1056 int i = start_index;
1057
1058 if (i < s + 4) {
1059 i = s + 4;
1060 }
1061
1062 while (i < len - 4 - s) {
1063 if (outline_img[i] == 0 && outline_img[i + 4] == 255
1064 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255
1065 && !has_start_point (i)) {
1066 return i;
1067 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0
1068 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255
1069 && !has_start_point (i)) {
1070 return i;
1071 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
1072 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255
1073 && !has_start_point (i)) {
1074 return i;
1075 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255
1076 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0
1077 && !has_start_point (i)) {
1078 return i;
1079 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0
1080 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0
1081 && !has_start_point (i)) {
1082 return i;
1083 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255
1084 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0
1085 && !has_start_point (i)) {
1086 return i;
1087 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0
1088 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0
1089 && !has_start_point (i)) {
1090 return i;
1091 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0
1092 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255
1093 && !has_start_point (i)) {
1094 return i;
1095 }
1096
1097 i +=4;
1098 }
1099
1100 return -1;
1101 }
1102
1103 void find_corner (Path path, int point_index, int end, double points_per_unit, ref double x, ref double y) {
1104 TracedPoint tp0;
1105 double sx = 0;
1106 double sy = 0;
1107 double d = 0;
1108 double mind = double.MAX;
1109 int index = 0;
1110 int pi;
1111 EditPoint ep0, ep1;
1112 double dx, dy;
1113
1114 pi = point_index - 1;
1115 if (pi < 0) {
1116 pi += path.points.size;
1117 }
1118 ep0 = path.points.get (pi);
1119
1120 pi = point_index + 1;
1121 pi %= path.points.size;
1122 ep1 = path.points.get (pi);
1123
1124 Path.find_intersection_handle (ep0.get_left_handle (), ep1.get_right_handle (), out sx, out sy);
1125
1126 dx = x - ep0.x;
1127 dy = y - ep0.y;
1128
1129 sx += 3 * dx;
1130 sy += 3 * dy;
1131
1132 dx = x - ep1.x;
1133 dy = y - ep1.y;
1134
1135 sx += 3 * dx;
1136 sy += 3 * dy;
1137
1138 end += (int) (points_per_unit / 2.0);
1139 for (int i = 0; i < 2 * points_per_unit; i++) {
1140 index = end - i;
1141
1142 if (index < 0) {
1143 index += points.size;
1144 } else {
1145 index %= points.size;
1146 }
1147
1148 tp0 = points.get (index);
1149
1150 d = Path.distance (tp0.x, sx, tp0.y, sy);
1151 if (d < mind) {
1152 mind = d;
1153 x = tp0.x;
1154 y = tp0.y;
1155 }
1156 }
1157 }
1158
1159 Path generate_path (uint8* outline_img, int stride, int w, int h, int length) {
1160 double x, y, np;
1161 int i, index;
1162 double sumx, sumy, points_per_unit;
1163 Path path = new Path ();
1164 Gee.ArrayList<int?> sp = new Gee.ArrayList<int?> ();
1165 double corner;
1166 Gee.ArrayList<TracedPoint> traced = new Gee.ArrayList<TracedPoint> ();
1167 Gee.ArrayList<EditPoint> corners = new Gee.ArrayList<EditPoint> ();
1168 EditPoint ep;
1169 EditPointHandle r, l;
1170 double la, a;
1171 PointSelection ps;
1172 double image_scale_x;
1173 double image_scale_y;
1174 TracedPoint average_point;
1175 int pi;
1176 ImageSurface img;
1177
1178 img = (contrast_image == null) ? get_contrast_image () : (!) contrast_image;
1179
1180 image_scale_x = ((double) size_margin / img.get_width ());
1181 image_scale_y = ((double) size_margin / img.get_height ());
1182
1183 foreach (TracedPoint p in points) {
1184 start_points.add (p);
1185 }
1186
1187 sumx = 0;
1188 sumy = 0;
1189 np = 0;
1190
1191 points_per_unit = 9;
1192 corner = PI / 3.5;
1193
1194 i = 0;
1195 foreach (TracedPoint p in points) {
1196 index = p.index;
1197 x = -w * img_scale_x / 2 + (((index + 4) % stride) / 4) * img_scale_x;
1198 y = h * img_scale_y / 2 + -((index - x * 4) / stride) * img_scale_y;
1199
1200 x *= image_scale_x;
1201 y *= image_scale_y;
1202
1203 x += img_middle_x;
1204 y += img_middle_y;
1205
1206 p.x = x;
1207 p.y = y;
1208
1209 np++;
1210
1211 sumx += x;
1212 sumy += y;
1213
1214 if (np >= points_per_unit) {
1215 average_point = new TracedPoint (-1);
1216 average_point.x = sumx / np;
1217 average_point.y = sumy / np;
1218 traced.add (average_point);
1219
1220 sp.add (i);
1221
1222 np = 0;
1223 sumx = 0;
1224 sumy = 0;
1225 }
1226
1227 i++;
1228 }
1229
1230 if (np != 0) {
1231 average_point = new TracedPoint (-1);
1232 average_point.x = sumx / np;
1233 average_point.y = sumy / np;
1234 traced.add (average_point);
1235 sp.add (i);
1236 }
1237
1238 foreach (TracedPoint avgp in traced) {
1239 ep = new EditPoint (avgp.x, avgp.y);
1240
1241 path.points.add (ep);
1242
1243 if (DrawingTools.point_type == PointType.CUBIC) {
1244 ep.type = PointType.CUBIC;
1245 ep.get_right_handle ().type = PointType.LINE_CUBIC;
1246 ep.get_left_handle ().type = PointType.LINE_CUBIC;
1247 } else {
1248 ep.type = PointType.DOUBLE_CURVE;
1249 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
1250 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
1251 }
1252 }
1253
1254 path.close ();
1255 path.create_list ();
1256 path.recalculate_linear_handles ();
1257
1258 // Find corners
1259 pi = 0;
1260 for (i = 1; i < sp.size; i += 2) {
1261 return_val_if_fail (0 <= i < path.points.size, path);
1262 ep = path.points.get (i);
1263
1264 pi = i + 2;
1265 pi %= path.points.size;
1266 return_val_if_fail (0 <= pi < path.points.size, path);
1267 l = path.points.get (pi).get_left_handle ();
1268
1269 pi = i - 2;
1270 if (pi < 0) {
1271 pi += path.points.size;
1272 }
1273 return_val_if_fail (0 <= pi < path.points.size, path);
1274 r = path.points.get (pi).get_right_handle ();
1275
1276 la = l.angle - PI;
1277
1278 while (la < 0) {
1279 la += 2 * PI;
1280 }
1281
1282 if (r.angle > (2.0 / 3.0) * PI && la < PI / 2) {
1283 la += 2 * PI;
1284 } else if (la > (2.0 / 3.0) * PI && r.angle < PI / 2) {
1285 la -= 2 * PI;
1286 }
1287
1288 a = r.angle - la;
1289
1290 if (fabs (a) > corner) { // corner
1291 ep.set_tie_handle (false);
1292 find_corner (path, i, (!) sp.get (i), points_per_unit, ref ep.x, ref ep.y);
1293 corners.add (ep);
1294 } else {
1295 ep.set_tie_handle (true);
1296 }
1297 }
1298
1299 path.recalculate_linear_handles ();
1300 path.remove_points_on_points ();
1301 path.create_list ();
1302 foreach (EditPoint e in path.points) {
1303 if (e.tie_handles) {
1304 e.process_tied_handle ();
1305 }
1306 }
1307
1308 if (simplification > 0.01) {
1309 for (i = 0; i < path.points.size; i++) {
1310 ep = path.points.get (i);
1311 ps = new PointSelection (ep, path);
1312 if (corners.index_of (ep) == -1) {
1313 PenTool.remove_point_simplify (ps, simplification);
1314 }
1315 }
1316 }
1317
1318 for (i = 0; i < path.points.size; i++) {
1319 ep = path.points.get (i);
1320 ps = new PointSelection (ep, path);
1321 if (corners.index_of (ep) == -1) {
1322 ep.set_selected (true);
1323 }
1324 }
1325
1326 path = PenTool.simplify (path, true, simplification);
1327 points.clear ();
1328 path.update_region_boundaries ();
1329
1330 return path;
1331 }
1332
1333 bool has_start_point (int i) {
1334 foreach (TracedPoint p in start_points) {
1335 if (p.index == i) {
1336 return true;
1337 }
1338 }
1339 return false;
1340 }
1341
1342 bool is_traced (int i) {
1343 foreach (TracedPoint p in points) {
1344 if (p.index == i) {
1345 return true;
1346 }
1347 }
1348 return false;
1349 }
1350
1351 public void center_in_glyph (Glyph? glyph = null) {
1352 Glyph g;
1353 Font f = BirdFont.get_current_font ();
1354
1355 if (glyph != null) {
1356 g = (!) glyph;
1357 } else {
1358 g = MainWindow.get_current_glyph ();
1359 }
1360
1361 img_middle_x = g.left_limit + (g.right_limit - g.left_limit) / 2;
1362 img_middle_y = f.bottom_position + (f.top_position - f.bottom_position) / 2;
1363 }
1364
1365 class TracedPoint {
1366 public int index;
1367
1368 public double x = 0;
1369 public double y = 0;
1370
1371 public TracedPoint (int index) {
1372 this.index = index;
1373 }
1374 }
1375 }
1376
1377 }
1378