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