The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BackgroundImage.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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