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