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