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