The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BackgroundImage.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/BackgroundImage.vala.
Select all glyphs in overview tab
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 converted = MainWindow.native_window.convert_to_png (path.dup (), ((!) png_image.get_path ()).dup ()); 331 332 if (!converted) { 333 warning ("Failed to convert image: $(path)"); 334 return; 335 } 336 337 path = (!) png_image.get_path (); 338 } 339 340 public void set_img_rotation_from_coordinate (double x, double y) { 341 double bcx, bcy; 342 double a, b, c, length; 343 344 bcx = img_middle_x; 345 bcy = img_middle_y; 346 347 a = bcx - x; 348 b = bcy - y; 349 c = a * a + b * b; 350 351 if (c == 0) { 352 return; 353 } 354 355 length = sqrt (fabs (c)); 356 357 if (c < 0) { 358 length = -length; 359 } 360 361 img_rotation = (y > bcy) ? acos (a / length) + PI : -acos (a / length) + PI; 362 363 background_image = null; 364 } 365 366 public void set_img_scale (double xs, double ys) { 367 img_scale_x = xs; 368 img_scale_y = ys; 369 } 370 371 public void reset_scale (Glyph g) { 372 double w, h; 373 374 w = g.get_width (); 375 h = g.get_height (); 376 377 img_scale_x = 1; 378 img_scale_y = 1; 379 380 img_offset_x = g.get_line ("left").pos; 381 img_offset_y = g.get_line ("top").pos; 382 } 383 384 public void draw (Context cr, WidgetAllocation allocation, double view_offset_x, double view_offset_y, double view_zoom) { 385 double scale_x, scale_y; 386 double image_scale_x, image_scale_y; 387 ImageSurface s; 388 Surface st; 389 Context ct; 390 ImageSurface rotated_image; 391 392 if (high_contrast && contrast_image == null) { 393 contrast_image = get_contrast_image (); 394 } 395 396 if (unlikely (get_img ().status () != Cairo.Status.SUCCESS)) { 397 warning (@"Background image is invalid. (\"$path\")\n"); 398 MainWindow.get_current_glyph ().set_background_visible (false); 399 return; 400 } 401 402 rotated_image = (ImageSurface) get_rotated_image (); 403 404 if (contrast_image == null) { 405 s = rotated_image; 406 image_scale_x = img_scale_x; 407 image_scale_y = img_scale_y; 408 } else { 409 s = (!) contrast_image; 410 image_scale_x = img_scale_x * ((double) rotated_image.get_width () / s.get_width ()); 411 image_scale_y = img_scale_y * ((double) rotated_image.get_height () / s.get_height ()); 412 } 413 414 st = new Surface.similar (s, s.get_content (), allocation.width, allocation.height); 415 ct = new Context (st); 416 ct.save (); 417 418 ct.set_source_rgba (1, 1, 1, 1); 419 ct.rectangle (0, 0, allocation.width, allocation.height); 420 ct.fill (); 421 422 // scale both canvas and image at the same time 423 scale_x = view_zoom * image_scale_x; 424 scale_y = view_zoom * image_scale_y; 425 426 ct.scale (scale_x, scale_y); 427 ct.translate (-view_offset_x / image_scale_x, -view_offset_y / image_scale_y); 428 429 ct.set_source_surface (s, img_offset_x / image_scale_x, img_offset_y / image_scale_y); 430 431 ct.paint (); 432 ct.restore (); 433 434 // add it 435 cr.save (); 436 cr.set_source_surface (st, 0, 0); 437 cr.paint (); 438 cr.restore (); 439 } 440 441 public Surface get_rotated_image () { 442 double x, y; 443 double iw, ih; 444 int h, w; 445 double oy, ox; 446 447 Surface o; 448 449 Surface s; 450 Context c; 451 452 Surface sg; 453 Context cg; 454 455 double wc, hc; 456 457 o = get_original (); 458 459 // add margin 460 sg = new Surface.similar (o, o.get_content (), size_margin, size_margin); 461 cg = new Context (sg); 462 463 wc = get_margin_width (); 464 hc = get_margin_height (); 465 466 Theme.color (cg, "Background 1"); 467 cg.rectangle (0, 0, size_margin, size_margin); 468 cg.fill (); 469 470 cg.set_source_surface (get_img (), wc, hc); 471 cg.paint (); 472 473 x = Glyph.reverse_path_coordinate_x (img_offset_x); 474 y = Glyph.reverse_path_coordinate_y (img_offset_y); 475 476 ih = (int) get_img ().get_height (); 477 iw = (int) get_img ().get_width (); 478 479 w = (int) iw; 480 h = (int) ih; 481 482 oy = size_margin; 483 ox = size_margin; 484 485 // rotate image 486 s = new Surface.similar (sg, sg.get_content (), (int) (ox), (int) (oy)); 487 c = new Context (s); 488 489 c.save (); 490 491 Theme.color (c, "Background 1"); 492 c.rectangle (0, 0, size_margin, size_margin); 493 c.fill (); 494 495 c.translate (size_margin * 0.5, size_margin * 0.5); 496 c.rotate (img_rotation); 497 c.translate (-size_margin * 0.5, -size_margin * 0.5); 498 499 c.set_source_surface (cg.get_target (), 0, 0); 500 c.paint (); 501 c.restore (); 502 503 return s; 504 } 505 506 public void handler_release (double nx, double ny) { 507 selected_handle = 0; 508 handler_move (nx, ny); 509 } 510 511 public void handler_press (double nx, double ny) { 512 if (is_over_rotate (nx, ny)) { 513 selected_handle = 2; 514 } else if (is_over_resize (nx, ny)) { 515 selected_handle = 1; 516 } else { 517 selected_handle = 0; 518 } 519 } 520 521 bool is_over_rotate (double nx, double ny) { 522 double x, y, d; 523 524 x = Glyph.reverse_path_coordinate_x (img_middle_x); 525 y = Glyph.reverse_path_coordinate_y (img_middle_y); 526 527 x += cos (img_rotation) * 75; 528 y += sin (img_rotation) * 75; 529 530 d = Path.distance (x, nx, y, ny); 531 532 return d < 15 * MainWindow.units; 533 } 534 535 bool is_over_resize (double nx, double ny) { 536 double x, y, size; 537 bool inx, iny; 538 539 size = 12 * MainWindow.units; 540 541 x = img_middle_x - (img_scale_x * get_img ().get_width () / 2); 542 y = img_middle_y - (img_scale_y * get_img ().get_height () / 2); 543 544 x = Glyph.reverse_path_coordinate_x (x); 545 y = Glyph.reverse_path_coordinate_y (y); 546 547 inx = x - size <= nx <= x + size; 548 iny = y - size <= ny <= y + size; 549 550 return inx && iny; 551 } 552 553 public void handler_move (double nx, double ny) { 554 int prev_handle = active_handle; 555 556 if (is_over_rotate (nx, ny)) { 557 active_handle = 2; 558 } else if (is_over_resize (nx, ny)) { 559 active_handle = 1; 560 } else { 561 active_handle = 0; 562 } 563 564 if (prev_handle != active_handle) { 565 GlyphCanvas.redraw (); 566 } 567 } 568 569 public void draw_handle (Context cr, Glyph g) { 570 draw_resize_handle (cr, g); 571 draw_rotate_handle (cr, g); 572 } 573 574 public void draw_resize_handle (Context cr, Glyph g) { 575 double x, y; 576 cr.save (); 577 578 Theme.color (cr, "Menu Background"); 579 580 x = img_middle_x - (img_scale_x * get_img ().get_width () / 2); 581 y = img_middle_y - (img_scale_y * get_img ().get_height () / 2); 582 583 x = Glyph.reverse_path_coordinate_x (x); 584 y = Glyph.reverse_path_coordinate_y (y); 585 586 draw_handle_triangle (x, y, cr, g, 6); 587 588 cr.restore (); 589 } 590 591 public void draw_rotate_handle (Context cr, Glyph g) { 592 double x, y, hx, hy, x2, y2; 593 594 double ivz = 1.0 / (g.view_zoom); 595 596 cr.save (); 597 598 cr.scale (g.view_zoom, g.view_zoom); 599 600 if (selected_handle == 2) Theme.color (cr, "Highlighted 1"); 601 else if (active_handle == 2) Theme.color (cr, "Default Background"); 602 else Theme.color (cr, "Menu Background"); 603 604 x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x; 605 y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y; 606 607 cr.rectangle (x, y, 5 * ivz, 5 * ivz); 608 cr.fill (); 609 610 hx = cos (img_rotation) * 75 * ivz; 611 hy = sin (img_rotation) * 75 * ivz; 612 613 x2 = x + hx; 614 y2 = y + hy; 615 616 cr.rectangle (x2, y2, 5 * ivz, 5 * ivz); 617 cr.fill (); 618 619 cr.set_line_width (ivz); 620 cr.move_to (x + 2.5 * ivz, y + 2.5 * ivz); 621 cr.line_to (x2 + 2.5 * ivz, y2 + 2.5 * ivz); 622 cr.stroke (); 623 624 cr.restore (); 625 } 626 627 void draw_handle_triangle (double x, double y, Context cr, Glyph g, int direction, double s = 1) 628 requires (0 < direction < 8) 629 { 630 double ivz = 1.0 / (g.view_zoom); 631 double size; 632 633 cr.save (); 634 cr.set_line_width (ivz); 635 636 if (selected_handle == 1) Theme.color (cr, "Highlighted 1"); 637 else if (active_handle == 1) Theme.color (cr, "Default Background"); 638 else Theme.color (cr, "Menu Background"); 639 640 size = (8) * s; 641 642 cr.scale (1, 1); 643 cr.new_path (); 644 645 // up + left 646 if (direction == 1) { 647 cr.move_to (x - size, y - size); 648 cr.line_to (x + size, y - size); 649 cr.line_to (x - size, y + size); 650 } 651 652 if (direction == 6) { 653 cr.move_to (x + size, y + size); 654 cr.line_to (x - size, y + size); 655 cr.line_to (x - size, y - size); 656 } 657 658 cr.close_path(); 659 cr.fill (); 660 661 cr.restore (); 662 663 } 664 665 public void update_background () { 666 background_image = null; 667 contrast_image = null; 668 669 GlyphCanvas.redraw (); 670 updated (); 671 } 672 673 ImageSurface get_contrast_image () { 674 ImageSurface s; 675 Context c; 676 677 ImageSurface sg; 678 int scaled_width; 679 680 unowned uchar[] pix_buff; 681 int i, len; 682 double thres; 683 int stride; 684 685 ImageSurface img; 686 ImageSurface ns; 687 688 double trace_resolution = DrawingTools.auto_trace_resolution.get_value (); 689 double threshold = DrawingTools.background_threshold.get_value (); 690 double simplification = DrawingTools.auto_trace_simplify.get_value (); 691 692 thres = (threshold - 0.5) * 255; 693 694 scaled_width = (int) (600 * trace_resolution); 695 696 s = new ImageSurface (Format.RGB24, scaled_width, scaled_width); 697 sg = (ImageSurface) get_rotated_image (); 698 c = new Context (s); 699 700 c.save (); 701 Theme.color (c, "Background 1"); 702 c.rectangle (0, 0, scaled_width, scaled_width); 703 c.fill (); 704 705 c.translate (scaled_width * 0.5, scaled_width * 0.5); 706 c.rotate (img_rotation); 707 c.translate (-scaled_width * 0.5, -scaled_width * 0.5); 708 709 c.scale ((double) scaled_width / sg.get_width (), (double) scaled_width / sg.get_height ()); 710 711 c.set_source_surface (sg, 0, 0); 712 c.paint (); 713 c.restore (); 714 715 img = (ImageSurface) s; 716 pix_buff = img.get_data (); 717 718 len = s.get_height () * s.get_stride (); 719 720 uint8* outline_img = new uint8[len]; 721 722 for (i = 0; i < len - 4; i += 4) { 723 uint8 o = (uint8) ((pix_buff[i] + pix_buff[i + 1] + pix_buff[i + 2]) / 3.0); 724 uint8 bw = (o < thres) ? 0 : 255; 725 outline_img[i] = bw; 726 outline_img[i + 1] = bw; 727 outline_img[i + 2] = bw; 728 outline_img[i + 3] = bw; 729 } 730 731 // fill blur with black 732 stride = s.get_stride (); 733 for (int m = 0; m < 2; m++) { 734 i = stride + 4; 735 while (i < len - 4 - stride) { 736 if ((outline_img[i] == 255 && outline_img[i + 4] == 0 737 && outline_img[i + stride] == 0 && outline_img[i + stride + 4] == 255) 738 || (outline_img[i] == 0 && outline_img[i + 4] == 255 739 && outline_img[i + stride] == 255 && outline_img[i + stride + 4] == 0)) { 740 outline_img[i] = 0; 741 outline_img[i + 4] = 0; 742 outline_img[i + stride] = 0; 743 outline_img[i + stride + 4] = 0; 744 } 745 746 if (outline_img[i] == 255 && outline_img[i + 4] == 0 && outline_img[i + 8] == 255 747 || outline_img[i] == 0 && outline_img[i + 4] == 255 && outline_img[i + 8] == 0) { 748 outline_img[i] = 0; 749 outline_img[i + 4] = 0; 750 outline_img[i + stride] = 0; 751 outline_img[i + stride + 4] = 0; 752 } 753 i += 4; 754 } 755 } 756 757 ns = new ImageSurface.for_data ((uchar[])outline_img, s.get_format (), s.get_width (), s.get_height (), s.get_stride ()); 758 background_image = null; 759 original_image = null; 760 contrast_image = ns; 761 762 return (ImageSurface) ns; 763 } 764 765 public PathList autotrace () { 766 ImageSurface img; 767 int len, w, h, i, s; 768 Path p; 769 PathList pl; 770 uint8* outline_img; 771 772 ImageSurface scaled_image; 773 double scale; 774 775 p = new Path (); 776 pl = new PathList (); 777 778 get_img (); 779 780 if (background_image == null) { 781 return pl; 782 } 783 784 img = (contrast_image == null) ? get_contrast_image () : (!) contrast_image; 785 786 w = img.get_width(); 787 h = img.get_height(); 788 789 if (unlikely (img.status () != Cairo.Status.SUCCESS)) { 790 warning ("Error"); 791 return pl; 792 } 793 794 if (img.get_format () != Format.RGB24) { 795 warning ("Wrong format"); 796 return pl; 797 } 798 799 scaled_image = img; 800 801 img = (ImageSurface) scaled_image; 802 w = img.get_width (); 803 h = img.get_height (); 804 s = img.get_stride (); 805 len = s * h; 806 807 outline_img = (uint8*) img.get_data (); 808 809 start_points = new Gee.ArrayList<TracedPoint> (); 810 points = new Gee.ArrayList<TracedPoint> (); 811 812 int direction_vertical = 4; 813 int direction_horizontal = s; // FIXME: SET AT FIND START 814 int last_move = 0; 815 int pp = 0; 816 817 scale = 1; 818 i = 0; 819 while ((i = find_start_point (outline_img, len, s, i)) != -1) { 820 pp = 0; 821 822 while (4 + s <= i < len - 4 - s) { 823 pp++; 824 if (is_traced (i)) { 825 Path np = generate_path (outline_img, s, w, h, len); 826 827 if (np.points.size >= 3) { 828 if (Path.is_counter (pl, np)) { 829 np.force_direction (Direction.COUNTER_CLOCKWISE); 830 } else { 831 np.force_direction (Direction.CLOCKWISE); 832 } 833 834 pl.add (np); 835 } 836 837 break; 838 } 839 840 if (outline_img[i] == 255 && outline_img[i + 4] == 255 841 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) { 842 warning ("Lost path"); 843 Path np = generate_path (outline_img, s, w, h, len); 844 pl.add (np); 845 break; 846 } 847 848 if (outline_img[i] == 0 && outline_img[i + 4] == 0 849 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) { 850 warning ("Lost path"); 851 Path np = generate_path (outline_img, s, w, h, len); 852 pl.add (np); 853 break; 854 } 855 856 if (outline_img[i] == 0 && outline_img[i + 4] == 255 857 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) { 858 points.add (new TracedPoint (i)); 859 860 if (last_move == direction_horizontal) { 861 direction_vertical = -s; 862 i += direction_vertical; 863 last_move = direction_vertical; 864 } else { 865 direction_horizontal = -4; 866 i += direction_horizontal; 867 last_move = direction_horizontal; 868 } 869 870 } else 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] == 255 && outline_img[i + 4] == 0 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] == 0 && outline_img[i + 4] == 0 899 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) { 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] == 255 && outline_img[i + 4] == 255 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] == 0 && outline_img[i + s + 4] == 255) { 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] == 0 941 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) { 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 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255 954 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) { 955 points.add (new TracedPoint (i)); 956 957 if (last_move == direction_horizontal) { 958 direction_vertical = -s; 959 i += direction_vertical; 960 last_move = direction_vertical; 961 } else { 962 direction_horizontal = 4; 963 i += direction_horizontal; 964 last_move = direction_horizontal; 965 } 966 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0 967 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) { 968 points.add (new TracedPoint (i)); 969 970 if (last_move == direction_horizontal) { 971 direction_vertical = s; 972 i += direction_vertical; 973 last_move = direction_vertical; 974 } else { 975 direction_horizontal = 4; 976 i += direction_horizontal; 977 last_move = direction_horizontal; 978 } 979 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255 980 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) { 981 points.add (new TracedPoint (i)); 982 983 if (last_move == direction_horizontal) { 984 direction_vertical = s; 985 i += direction_vertical; 986 last_move = direction_vertical; 987 } else { 988 direction_horizontal = -4; 989 i += direction_horizontal; 990 last_move = direction_horizontal; 991 } 992 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255 993 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) { 994 points.add (new TracedPoint (i)); 995 996 i += direction_horizontal; 997 last_move = direction_horizontal; 998 999 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0 1000 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) { 1001 points.add (new TracedPoint (i)); 1002 1003 i += direction_horizontal; 1004 last_move = direction_horizontal; 1005 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0 1006 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) { 1007 points.add (new TracedPoint (i)); 1008 1009 i += direction_vertical; 1010 last_move = direction_vertical; 1011 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255 1012 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) { 1013 points.add (new TracedPoint (i)); 1014 1015 i += direction_vertical; 1016 last_move = direction_vertical; 1017 } else if ((outline_img[i] == 255 && outline_img[i + 4] == 0 1018 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) 1019 || (outline_img[i] == 0 && outline_img[i + 4] == 255 1020 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0)) { 1021 points.add (new TracedPoint (i)); 1022 1023 warning ("Bad edge"); 1024 i += last_move; 1025 1026 } else { 1027 points.add (new TracedPoint (i)); 1028 warning (@"No direction\n $(outline_img[i]) $(outline_img[i + 4])\n $(outline_img[i + s]) $(outline_img[i + s + 4])"); 1029 i += 4; 1030 } 1031 } 1032 } 1033 1034 start_points.clear (); 1035 points.clear (); 1036 1037 return pl; 1038 } 1039 1040 int find_start_point (uint8* outline_img, int len, int s, int start_index) { 1041 // find start point 1042 int i = start_index; 1043 1044 if (i < s + 4) { 1045 i = s + 4; 1046 } 1047 1048 while (i < len - 4 - s) { 1049 if (outline_img[i] == 0 && outline_img[i + 4] == 255 1050 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255 1051 && !has_start_point (i)) { 1052 return i; 1053 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0 1054 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 255 1055 && !has_start_point (i)) { 1056 return i; 1057 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255 1058 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255 1059 && !has_start_point (i)) { 1060 return i; 1061 } else if (outline_img[i] == 255 && outline_img[i + 4] == 255 1062 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0 1063 && !has_start_point (i)) { 1064 return i; 1065 } else if (outline_img[i] == 255 && outline_img[i + 4] == 0 1066 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0 1067 && !has_start_point (i)) { 1068 return i; 1069 } else if (outline_img[i] == 0 && outline_img[i + 4] == 255 1070 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 0 1071 && !has_start_point (i)) { 1072 return i; 1073 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0 1074 && outline_img[i + s] == 255 && outline_img[i + s + 4] == 0 1075 && !has_start_point (i)) { 1076 return i; 1077 } else if (outline_img[i] == 0 && outline_img[i + 4] == 0 1078 && outline_img[i + s] == 0 && outline_img[i + s + 4] == 255 1079 && !has_start_point (i)) { 1080 return i; 1081 } 1082 1083 i +=4; 1084 } 1085 1086 return -1; 1087 } 1088 1089 void find_corner (Path path, int point_index, int end, double points_per_unit, ref double x, ref double y) { 1090 TracedPoint tp0; 1091 double sx = 0; 1092 double sy = 0; 1093 double d = 0; 1094 double mind = double.MAX; 1095 int index = 0; 1096 int pi; 1097 EditPoint ep0, ep1; 1098 double dx, dy; 1099 1100 pi = point_index - 1; 1101 if (pi < 0) { 1102 pi += path.points.size; 1103 } 1104 ep0 = path.points.get (pi); 1105 1106 pi = point_index + 1; 1107 pi %= path.points.size; 1108 ep1 = path.points.get (pi); 1109 1110 Path.find_intersection_handle (ep0.get_left_handle (), ep1.get_right_handle (), out sx, out sy); 1111 1112 dx = x - ep0.x; 1113 dy = y - ep0.y; 1114 1115 sx += 3 * dx; 1116 sy += 3 * dy; 1117 1118 dx = x - ep1.x; 1119 dy = y - ep1.y; 1120 1121 sx += 3 * dx; 1122 sy += 3 * dy; 1123 1124 end += (int) (points_per_unit / 2.0); 1125 for (int i = 0; i < 2 * points_per_unit; i++) { 1126 index = end - i; 1127 1128 if (index < 0) { 1129 index += points.size; 1130 } else { 1131 index %= points.size; 1132 } 1133 1134 tp0 = points.get (index); 1135 1136 d = Path.distance (tp0.x, sx, tp0.y, sy); 1137 if (d < mind) { 1138 mind = d; 1139 x = tp0.x; 1140 y = tp0.y; 1141 } 1142 } 1143 } 1144 1145 Path generate_path (uint8* outline_img, int stride, int w, int h, int length) { 1146 double x, y, np; 1147 int i, index; 1148 double sumx, sumy, points_per_unit; 1149 Path path = new Path (); 1150 Gee.ArrayList<int?> sp = new Gee.ArrayList<int?> (); 1151 double corner; 1152 Gee.ArrayList<TracedPoint> traced = new Gee.ArrayList<TracedPoint> (); 1153 Gee.ArrayList<EditPoint> corners = new Gee.ArrayList<EditPoint> (); 1154 EditPoint ep; 1155 EditPointHandle r, l; 1156 double la, a; 1157 PointSelection ps; 1158 double image_scale_x; 1159 double image_scale_y; 1160 TracedPoint average_point; 1161 int pi; 1162 ImageSurface img; 1163 double simplification = DrawingTools.auto_trace_simplify.get_value (); 1164 1165 img = (contrast_image == null) ? get_contrast_image () : (!) contrast_image; 1166 1167 image_scale_x = ((double) size_margin / img.get_width ()); 1168 image_scale_y = ((double) size_margin / img.get_height ()); 1169 1170 foreach (TracedPoint p in points) { 1171 start_points.add (p); 1172 } 1173 1174 sumx = 0; 1175 sumy = 0; 1176 np = 0; 1177 1178 points_per_unit = 9; 1179 corner = PI / 3.5; 1180 1181 i = 0; 1182 foreach (TracedPoint p in points) { 1183 index = p.index; 1184 x = -w * img_scale_x / 2 + (((index + 4) % stride) / 4) * img_scale_x; 1185 y = h * img_scale_y / 2 + -((index - x * 4) / stride) * img_scale_y; 1186 1187 x *= image_scale_x; 1188 y *= image_scale_y; 1189 1190 x += img_middle_x; 1191 y += img_middle_y; 1192 1193 p.x = x; 1194 p.y = y; 1195 1196 np++; 1197 1198 sumx += x; 1199 sumy += y; 1200 1201 if (np >= points_per_unit) { 1202 average_point = new TracedPoint (-1); 1203 average_point.x = sumx / np; 1204 average_point.y = sumy / np; 1205 traced.add (average_point); 1206 1207 sp.add (i); 1208 1209 np = 0; 1210 sumx = 0; 1211 sumy = 0; 1212 } 1213 1214 i++; 1215 } 1216 1217 if (np != 0) { 1218 average_point = new TracedPoint (-1); 1219 average_point.x = sumx / np; 1220 average_point.y = sumy / np; 1221 traced.add (average_point); 1222 sp.add (i); 1223 } 1224 1225 foreach (TracedPoint avgp in traced) { 1226 ep = new EditPoint (avgp.x, avgp.y); 1227 1228 path.points.add (ep); 1229 1230 if (DrawingTools.point_type == PointType.CUBIC) { 1231 ep.type = PointType.CUBIC; 1232 ep.get_right_handle ().type = PointType.LINE_CUBIC; 1233 ep.get_left_handle ().type = PointType.LINE_CUBIC; 1234 } else { 1235 ep.type = PointType.DOUBLE_CURVE; 1236 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1237 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 1238 } 1239 } 1240 1241 path.close (); 1242 path.create_list (); 1243 path.recalculate_linear_handles (); 1244 1245 // Find corners 1246 pi = 0; 1247 for (i = 1; i < sp.size; i += 2) { 1248 return_val_if_fail (0 <= i < path.points.size, path); 1249 ep = path.points.get (i); 1250 1251 pi = i + 2; 1252 pi %= path.points.size; 1253 return_val_if_fail (0 <= pi < path.points.size, path); 1254 l = path.points.get (pi).get_left_handle (); 1255 1256 pi = i - 2; 1257 if (pi < 0) { 1258 pi += path.points.size; 1259 } 1260 return_val_if_fail (0 <= pi < path.points.size, path); 1261 r = path.points.get (pi).get_right_handle (); 1262 1263 la = l.angle - PI; 1264 1265 while (la < 0) { 1266 la += 2 * PI; 1267 } 1268 1269 if (r.angle > (2.0 / 3.0) * PI && la < PI / 2) { 1270 la += 2 * PI; 1271 } else if (la > (2.0 / 3.0) * PI && r.angle < PI / 2) { 1272 la -= 2 * PI; 1273 } 1274 1275 a = r.angle - la; 1276 1277 if (fabs (a) > corner) { // corner 1278 ep.set_tie_handle (false); 1279 find_corner (path, i, (!) sp.get (i), points_per_unit, ref ep.x, ref ep.y); 1280 corners.add (ep); 1281 } else { 1282 ep.set_tie_handle (true); 1283 } 1284 } 1285 1286 path.recalculate_linear_handles (); 1287 path.remove_points_on_points (); 1288 path.create_list (); 1289 foreach (EditPoint e in path.points) { 1290 if (e.tie_handles) { 1291 e.process_tied_handle (); 1292 } 1293 } 1294 1295 if (simplification > 0.01) { 1296 for (i = 0; i < path.points.size; i++) { 1297 ep = path.points.get (i); 1298 ps = new PointSelection (ep, path); 1299 if (corners.index_of (ep) == -1) { 1300 PenTool.remove_point_simplify (ps, simplification); 1301 } 1302 } 1303 } 1304 1305 for (i = 0; i < path.points.size; i++) { 1306 ep = path.points.get (i); 1307 ps = new PointSelection (ep, path); 1308 if (corners.index_of (ep) == -1) { 1309 ep.set_selected (true); 1310 } 1311 } 1312 1313 path = PenTool.simplify (path, true, simplification); 1314 points.clear (); 1315 path.update_region_boundaries (); 1316 1317 return path; 1318 } 1319 1320 bool has_start_point (int i) { 1321 foreach (TracedPoint p in start_points) { 1322 if (p.index == i) { 1323 return true; 1324 } 1325 } 1326 return false; 1327 } 1328 1329 bool is_traced (int i) { 1330 foreach (TracedPoint p in points) { 1331 if (p.index == i) { 1332 return true; 1333 } 1334 } 1335 return false; 1336 } 1337 1338 public void center_in_glyph (Glyph? glyph = null) { 1339 Glyph g; 1340 Font f = BirdFont.get_current_font (); 1341 1342 if (glyph != null) { 1343 g = (!) glyph; 1344 } else { 1345 g = MainWindow.get_current_glyph (); 1346 } 1347 1348 img_middle_x = g.left_limit + (g.right_limit - g.left_limit) / 2; 1349 img_middle_y = f.bottom_position + (f.top_position - f.bottom_position) / 2; 1350 } 1351 1352 class TracedPoint { 1353 public int index; 1354 1355 public double x = 0; 1356 public double y = 0; 1357 1358 public TracedPoint (int index) { 1359 this.index = index; 1360 } 1361 } 1362 } 1363 1364 } 1365