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