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