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