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