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