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