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