1Filter_conv_item = class 2 Menupullright "_Convolution" "various spatial convolution filters" { 3 /* Some useful masks. 4 */ 5 filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]]; 6 filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]]; 7 filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]]; 8 filter_laplacian = Matrix_con 1 128 9 [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]; 10 filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]]; 11 filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]]; 12 13 Blur_item = class 14 Menuaction "_Blur" "3x3 blur of image" { 15 action x = map_unary (conv filter_blur) x; 16 } 17 18 Sharpen_item = class 19 Menuaction "_Sharpen" "3x3 sharpen of image" { 20 action x = map_unary (conv filter_sharp) x; 21 } 22 23 Emboss_item = class 24 Menuaction "_Emboss" "1 pixel displace emboss" { 25 action x = map_unary (conv filter_emboss) x; 26 } 27 28 Laplacian_item = class 29 Menuaction "_Laplacian" "3x3 laplacian edge detect" { 30 action x = map_unary (conv filter_laplacian) x; 31 } 32 33 Sobel_item = class 34 Menuaction "So_bel" "3x3 Sobel edge detect" { 35 action x 36 = map_unary sobel x 37 { 38 sobel im 39 = abs (a - 128) + abs (b - 128) 40 { 41 a = conv filter_sobel im; 42 b = conv (rot270 filter_sobel) im; 43 } 44 } 45 } 46 47/* 3x3 line detect of image 48diagonals should be scaled down by root(2) I guess 49Kirk 50*/ 51 Linedet_item = class 52 Menuaction "Li_ne Detect" "3x3 line detect" { 53 action x 54 = map_unary lindet x 55 { 56 lindet im 57 = foldr1 max_pair images 58 { 59 masks = take 4 (iterate rot45 filter_lindet); 60 images = map (converse conv im) masks; 61 } 62 } 63 } 64 65 Usharp_item = class 66 Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" { 67 action x = class 68 _result { 69 _vislevel = 3; 70 71 size = Option "Radius" [ 72 "3 pixels", 73 "5 pixels", 74 "7 pixels", 75 "9 pixels", 76 "11 pixels", 77 "51 pixels" 78 ] 0; 79 80 st = Scale "Smoothness threshold" 0 5 2; 81 bm = Scale "Brighten by at most" 1 50 10; 82 dm = Scale "Darken by at most" 1 50 20; 83 fs = Scale "Sharpen flat areas by" 0 5 0.5; 84 js = Scale "Sharpen jaggy areas by" 0 5 1; 85 86 _result 87 = map_unary process x 88 { 89 process in 90 = Image in''' 91 { 92 in' = colour_transform_to Image_type.LABS in.value; 93 in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in'; 94 in''' = colour_transform_to (get_type in) in''; 95 } 96 } 97 } 98 } 99 100 sep1 = Menuseparator; 101 102 Custom_blur_item = class 103 Menuaction "Custom B_lur / Sharpen" 104 "blur or sharpen with tuneable parameters" { 105 action x = class 106 _result { 107 _vislevel = 3; 108 109 type = Option "Type" ["Blur", "Sharpen"] 0; 110 r = Scale "Radius" 1 100 1; 111 fac = Scale "Amount" 0 1 1; 112 layers = Scale "Layers" 1 100 10; 113 shape = Option "Mask shape" [ 114 "Square", 115 "Gaussian" 116 ] 0; 117 prec = Option "Precision" ["Int", "Float", "Approximate"] 0; 118 119 _result 120 = map_unary process x 121 { 122 process in 123 = clip2fmt blur.format proc 124 { 125 mask 126 = matrix_blur r.value, shape.value == 0 127 = matrix_gaussian_blur r.value; 128 blur = [convsep, convsepf, aconvsep layers]?prec mask in; 129 proc 130 = in + fac * (in - blur), type == 1 131 = blur * fac + in * (1 - fac); 132 } 133 } 134 } 135 } 136 137 Custom_conv_item = class 138 Menuaction "Custom C_onvolution" 139 "convolution filter with tuneable parameters" { 140 action x = class 141 _result { 142 _vislevel = 3; 143 144 matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]]; 145 separable 146 = Toggle "Seperable convolution" false, 147 matrix.width == 1 || matrix.height == 1 148 = false; 149 type = Option "Convolution type" ["Int", "Float"] 0; 150 rotate = Option "Rotate" [ 151 "Don't rotate", 152 "4 x 45 degrees", 153 "8 x 45 degrees", 154 "2 x 90 degrees" 155 ] 0; 156 157 _result 158 = map_unary process x 159 { 160 process in 161 = in.Image in' 162 { 163 conv_fn 164 = im_lindetect, !separable && type == 0 && rotate == 1 165 = im_compass, !separable && type == 0 && rotate == 2 166 = im_gradient, !separable && type == 0 && rotate == 3 167 = im_conv, !separable && type == 0 168 = im_convsep, separable && type == 0 169 = im_conv_f, !separable && type == 1 170 = im_convsep_f, separable && type == 1 171 = error "boink!"; 172 in' = conv_fn in.value matrix; 173 } 174 } 175 } 176 } 177} 178 179Filter_rank_item = class 180 Menupullright "_Rank" "various rank filters" { 181 Median_item = class 182 Menuaction "_Median" "3x3 median rank filter" { 183 action x = map_unary (rank 3 3 4) x; 184 } 185 186 Image_rank_item = class 187 Menuaction "_Image Rank" "pixelwise rank a list or group of images" { 188 action x = class 189 _result { 190 _vislevel = 3; 191 192 select 193 = Expression "Rank" ((int) (guess_size / 2)) 194 { 195 guess_size 196 = len x, is_list x 197 = len x.value, is_Group x 198 = 0; 199 } 200 201 // can't really iterate over groups ... since we allow a group 202 // argument 203 _result = rank_image select x; 204 } 205 } 206 207 Custom_rank_item = class 208 Menuaction "Custom _Rank" "rank filter with tuneable parameters" { 209 action x = class 210 _result { 211 _vislevel = 3; 212 213 window_width = Expression "Window width" 3; 214 window_height = Expression "Window height" 3; 215 select = Expression "Rank" 216 ((int) ((to_real window_width * to_real window_height) / 2)); 217 218 _result 219 = map_unary process x 220 { 221 process in 222 = rank window_width window_height select in; 223 } 224 } 225 } 226} 227 228Filter_morphology_item = class 229 Menupullright "_Morphology" "various morphological filters" { 230 /* Some useful masks. 231 */ 232 mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]]; 233 mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]]; 234 mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]]; 235 thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]]; 236 237 Threshold_item = Select_item.Threshold_item; 238 239 sep1 = Menuseparator; 240 241 Dilate_item = class 242 Menupullright "_Dilate" "morphological dilate" { 243 Dilate8_item = class 244 Menuaction "_8-connected" "dilate with an 8-connected mask" { 245 action x = map_unary (dilate mask8) x; 246 } 247 248 Dilate4_item = class 249 Menuaction "_4-connected" "dilate with a 4-connected mask" { 250 action x = map_unary (dilate mask4) x; 251 } 252 } 253 254 Erode_item = class 255 Menupullright "_Erode" "morphological erode" { 256 Erode8_item = class 257 Menuaction "_8-connected" "erode with an 8-connected mask" { 258 action x = map_unary (erode mask8) x; 259 } 260 261 Erode4_item = class 262 Menuaction "_4-connected" "erode with a 4-connected mask" { 263 action x = map_unary (erode mask4) x; 264 } 265 } 266 267 Custom_morph_item = class 268 Menuaction "Custom _Morphology" 269 "convolution morphological operator" { 270 action x = class 271 _result { 272 _vislevel = 3; 273 274 mask = mask4; 275 type = Option "Operation" ["Erode", "Dilate"] 1; 276 apply = Expression "Number of times to apply mask" 1; 277 278 _result 279 = map_unary morph x 280 { 281 morph image 282 = Image value' 283 { 284 fatmask = (iterate (dilate mask) mask)?(to_real apply - 1); 285 286 value' 287 = im_erode image.value fatmask, type.value == 0 288 = im_dilate image.value fatmask; 289 } 290 } 291 } 292 } 293 294 sep2 = Menuseparator; 295 296 Open_item = class 297 Menuaction "_Open" "open with an 8-connected mask" { 298 action x = map_unary (dilate mask8 @ erode mask8) x; 299 } 300 301 Close_item = class 302 Menuaction "_Close" "close with an 8-connected mask" { 303 action x = map_unary (erode mask8 @ dilate mask8) x; 304 } 305 306 Clean_item = class 307 Menuaction "C_lean" "remove 8-connected isolated points" { 308 action x 309 = map_unary clean x 310 { 311 clean x = x ^ erode mask1 x; 312 } 313 } 314 315 Thin_item = class 316 Menuaction "_Thin" "thin once" { 317 action x 318 = map_unary thinall x 319 { 320 masks = take 8 (iterate rot45 thin); 321 thin1 m x = x ^ erode m x; 322 thinall x = foldr thin1 x masks; 323 } 324 } 325 326} 327 328Filter_fourier_item = class 329 Menupullright "_Fourier" "various Fourier filters" { 330 preview_size = 64; 331 332 sense_option = Option "Sense" [ 333 "Pass", 334 "Reject" 335 ] 0; 336 337 // make a visualisation image 338 make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn) 339 (im_create_fmask preview_size preview_size); 340 341 // make the process function 342 process fn in 343 = (Image @ fn) (im_flt_image_freq in.value); 344 345 New_ideal_item = class 346 Menupullright "_Ideal" "various ideal Fourier filters" { 347 High_low_item = class 348 Menuaction "_High or Low Pass" 349 "highpass/lowpass ideal Fourier filter" { 350 action x = class 351 _result { 352 _vislevel = 3; 353 354 sense = sense_option; 355 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 356 357 // call a freq func with our parameters 358 _params f = f sense.value fc.value 0 0 0 0; 359 360 visualize_mask = make_vis _params; 361 362 _result = map_unary (process _params) x; 363 } 364 } 365 366 Ring_item = class 367 Menuaction "_Ring Pass or Ring Reject" 368 "ring pass/reject ideal Fourier filter" { 369 action x = class 370 _result { 371 _vislevel = 3; 372 373 sense = sense_option; 374 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 375 rw = Scale "Ring width" 0.01 0.99 0.5; 376 377 // call a freq func with our parameters 378 _params f = f (sense.value + 6) fc.value 379 rw.value 0 0 0; 380 381 visualize_mask = make_vis _params; 382 383 _result = map_unary (process _params) x; 384 } 385 } 386 387 Band_item = class 388 Menuaction "_Band Pass or Band Reject" 389 "band pass/reject ideal Fourier filter" { 390 action x = class 391 _result { 392 _vislevel = 3; 393 394 sense = sense_option; 395 fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; 396 fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; 397 r = Scale "Radius" 0.01 0.99 0.5; 398 399 // call a freq func with our parameters 400 _params f = f (sense.value + 12) fcx.value fcy.value 401 r.value 0 0; 402 403 visualize_mask = make_vis _params; 404 405 _result = map_unary (process _params) x; 406 } 407 } 408 } 409 410 New_gaussian_item = class 411 Menupullright "_Gaussian" "various Gaussian Fourier filters" { 412 High_low_item = class 413 Menuaction "_High or Low Pass" 414 "highpass/lowpass Gaussian Fourier filter" { 415 action x = class 416 _result { 417 _vislevel = 3; 418 419 sense = sense_option; 420 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 421 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 422 423 // call a freq func with our parameters 424 _params f = f (sense.value + 4) fc.value 425 ac.value 0 0 0; 426 427 visualize_mask = make_vis _params; 428 429 _result = map_unary (process _params) x; 430 } 431 } 432 433 Ring_item = class 434 Menuaction "_Ring Pass or Ring Reject" 435 "ring pass/reject Gaussian Fourier filter" { 436 action x = class 437 _result { 438 _vislevel = 3; 439 440 sense = sense_option; 441 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 442 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 443 rw = Scale "Ring width" 0.01 0.99 0.5; 444 445 // call a freq func with our parameters 446 _params f = f (sense.value + 10) fc.value 447 rw.value ac.value 0 0; 448 449 visualize_mask = make_vis _params; 450 451 _result = map_unary (process _params) x; 452 } 453 } 454 455 Band_item = class 456 Menuaction "_Band Pass or Band Reject" 457 "band pass/reject Gaussian Fourier filter" { 458 action x = class 459 _result { 460 _vislevel = 3; 461 462 sense = sense_option; 463 fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; 464 fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; 465 r = Scale "Radius" 0.01 0.99 0.5; 466 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 467 468 // call a freq func with our parameters 469 _params f = f (sense.value + 16) fcx.value fcy.value 470 r.value ac.value 0; 471 472 visualize_mask = make_vis _params; 473 474 _result = map_unary (process _params) x; 475 } 476 } 477 } 478 479 New_butterworth_item = class 480 Menupullright "_Butterworth" 481 "various Butterworth Fourier filters" { 482 High_low_item = class 483 Menuaction "_High or Low Pass" 484 "highpass/lowpass Butterworth Fourier filter" { 485 action x = class 486 _result { 487 _vislevel = 3; 488 489 sense = sense_option; 490 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 491 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 492 o = Scale "Order" 1 10 2; 493 494 // call a freq func with our parameters 495 _params f = f (sense.value + 2) o.value fc.value ac.value 496 0 0; 497 498 visualize_mask = make_vis _params; 499 500 _result = map_unary (process _params) x; 501 } 502 } 503 504 Ring_item = class 505 Menuaction "_Ring Pass or Ring Reject" 506 "ring pass/reject Butterworth Fourier filter" { 507 action x = class 508 _result { 509 _vislevel = 3; 510 511 sense = sense_option; 512 fc = Scale "Frequency cutoff" 0.01 0.99 0.5; 513 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 514 rw = Scale "Ring width" 0.01 0.99 0.5; 515 o = Scale "Order" 1 10 2; 516 517 // call a freq func with our parameters 518 _params f = f (sense.value + 8) o.value fc.value rw.value 519 ac.value 0; 520 521 visualize_mask = make_vis _params; 522 523 _result = map_unary (process _params) x; 524 } 525 } 526 527 Band_item = class 528 Menuaction "_Band Pass or Band Reject" 529 "band pass/reject Butterworth Fourier filter" { 530 action x = class 531 _result { 532 _vislevel = 3; 533 534 sense = sense_option; 535 fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; 536 fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; 537 r = Scale "Radius" 0.01 0.99 0.5; 538 ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; 539 o = Scale "Order" 1 10 2; 540 541 // call a freq func with our parameters 542 _params f = f (sense.value + 14) o.value fcx.value fcy.value 543 r.value ac.value; 544 545 visualize_mask = make_vis _params; 546 547 _result = map_unary (process _params) x; 548 } 549 } 550 } 551} 552 553Filter_enhance_item = class 554 Menupullright "_Enhance" "various enhancement filters" { 555 Falsecolour_item = class 556 Menuaction "_False Colour" "false colour a mono image" { 557 action x = class 558 _result { 559 _vislevel = 3; 560 561 o = Scale "Offset" (-255) 255 0; 562 clip = Toggle "Clip colour range" false; 563 564 _result 565 = map_unary process x 566 { 567 process im 568 = falsecolour mono'' 569 { 570 mono = colour_transform_to Image_type.B_W im; 571 mono' = mono + o; 572 mono'' 573 = (unsigned char) mono', clip 574 = (unsigned char) (mono' & 0xff); 575 } 576 } 577 } 578 } 579 580 Statistical_diff_item = class 581 Menuaction "_Statistical Difference" 582 "statistical difference of an image" { 583 action x = class 584 _result { 585 _vislevel = 3; 586 587 wsize = Expression "Window size" 11; 588 tmean = Expression "Target mean" 128; 589 mean_weight = Scale "Mean weight" 0 1 0.8; 590 tdev = Expression "Target deviation" 50; 591 dev_weight = Scale "Deviation weight" 0 1 0.8; 592 border = Toggle "Output image matches input image in size" true; 593 594 _result 595 = map_unary process x 596 { 597 process in 598 = Image in'' 599 { 600 in' = colour_transform_to Image_type.B_W in.value; 601 fn 602 = im_stdif, border 603 = im_stdif_raw; 604 in'' = fn in' 605 mean_weight.value tmean.expr 606 dev_weight.value tdev.expr 607 wsize.expr wsize.expr; 608 } 609 } 610 } 611 } 612 613 Hist_equal_item = class 614 Menupullright "_Equalise Histogram" "equalise contrast" { 615 Global_item = class 616 Menuaction "_Global" "equalise contrast globally" { 617 action x = map_unary hist_equalize x; 618 } 619 620 Local_item = class 621 Menuaction "_Local" "equalise contrast within a roving window" { 622 action x = class 623 _result { 624 _vislevel = 3; 625 626 window_width = Expression "Window width" 20; 627 window_height = Expression "Window height" 20; 628 629 _result 630 = map_unary process x 631 { 632 process in 633 = hist_equalize_local 634 window_width.expr window_height.expr in; 635 } 636 } 637 } 638 } 639} 640 641Filter_correlate_item = class 642 Menupullright "Spatial _Correlation" "calculate correlation surfaces" { 643 Correlate_item = class 644 Menuaction "_Correlate" "calculate correlation coefficient" { 645 action a b 646 = map_binary corr a b 647 { 648 corr a b 649 = correlate a b, 650 a.width <= b.width && a.height <= b.height 651 = correlate b a; 652 } 653 } 654 655 Correlate_fast_item = class 656 Menuaction "_Simple Difference" 657 "calculate sum of squares of differences" { 658 action a b 659 = map_binary corr a b 660 { 661 corr a b 662 = correlate_fast a b, 663 a.width <= b.width && a.height <= b.height 664 = correlate_fast b a; 665 } 666 } 667} 668 669Filter_hough_item = class 670 Menupullright "_Hough Transform" "transform to parameter space" { 671 Line_item = class 672 Menuaction "_Line" "find straight line Hough transform" { 673 action a = class 674 _result { 675 _vislevel = 3; 676 677 pspace_width = Expression "Parameter space width" 64; 678 pspace_height = Expression "Parameter space height" 64; 679 680 _result 681 = map_unary line a 682 { 683 line a 684 = hough_line 685 (to_real pspace_width) (to_real pspace_height) a; 686 } 687 } 688 } 689 690 Circle_item = class 691 Menuaction "_Circle" "find circle Hough transform" { 692 action a = class 693 _result { 694 _vislevel = 3; 695 696 scale = Expression "Scale down parameter space by" 10; 697 min_radius = Expression "Minimum radius" 10; 698 max_radius = Expression "Maximum radius" 30; 699 700 _result 701 = map_unary circle a 702 { 703 circle a 704 = hough_circle (to_real scale) (to_real min_radius) 705 (to_real max_radius) a; 706 } 707 } 708 } 709} 710 711Filter_coordinate_item = class 712 Menupullright "_Coordinate Transform" "various coordinate transforms" { 713 // run a function which wants a complex arg on a non-complex two-band 714 // image 715 run_cmplx fn x 716 = re x' ++ im x' 717 { 718 x' = fn (x?0, x?1); 719 } 720 721 Polar_item = class 722 Menuaction "_Polar" "transform to polar coordinates" { 723 action a = class 724 _result { 725 _vislevel = 3; 726 727 interp = Interpolate_picker Interpolate_type.BILINEAR; 728 729 _result 730 = map_unary to_polar a 731 { 732 to_polar im 733 = mapim interp.value map' im 734 { 735 // xy image, origin in the centre, scaled to fit image to 736 // a circle 737 xy = make_xy im.width im.height; 738 xy' = xy - Vector [im.width / 2, im.height / 2]; 739 scale = min [im.width, im.height] / im.width; 740 xy'' = 2 * xy' / scale; 741 742 // to polar, scale vertical axis to 360 degrees 743 map = run_cmplx polar xy''; 744 map' = map * Vector [1, im.height / 360]; 745 } 746 } 747 } 748 } 749 750 Rectangular_item = class 751 Menuaction "_Rectangular" "transform to rectangular coordinates" { 752 action a = class 753 _result { 754 _vislevel = 3; 755 756 interp = Interpolate_picker Interpolate_type.BILINEAR; 757 758 _result 759 = map_unary to_rect a 760 { 761 to_rect im 762 = mapim interp.value map'' im 763 { 764 // xy image, vertical scaled to 360 degrees 765 xy = make_xy im.width im.height; 766 xy' = xy * Vector [1, 360 / im.height]; 767 768 // to rect, scale to image rect 769 map = run_cmplx rectangular xy'; 770 scale = min [im.width, im.height] / im.width; 771 map' = map * scale / 2; 772 773 map'' = map' + Vector [im.width / 2, im.height / 2]; 774 } 775 } 776 } 777 } 778} 779 780#separator 781 782Filter_tilt_item = class 783 Menupullright "Ti_lt Brightness" "tilt brightness" { 784 Left_right_item = class 785 Menuaction "_Left to Right" "linear left-right brighten" { 786 action x = class 787 _result { 788 _vislevel = 3; 789 790 tilt = Scale "Left-right tilt" (-1) 1 0; 791 792 _result 793 = map_unary tilt_lr x 794 { 795 tilt_lr image 796 = image * scale 797 { 798 ramp = im_fgrey image.width image.height; 799 scale = (ramp - 0.5) * tilt + 1; 800 } 801 } 802 } 803 } 804 805 Top_bottom_item = class 806 Menuaction "_Top to Bottom" "linear top-bottom brighten" { 807 action x = class 808 _result { 809 _vislevel = 3; 810 811 tilt = Scale "Top-bottom tilt" (-1) 1 0; 812 813 _result 814 = map_unary tilt_tb x 815 { 816 tilt_tb image 817 = image * scale 818 { 819 ramp = rot90 820 (im_fgrey image.height image.width); 821 scale = (ramp - 0.5) * tilt + 1; 822 } 823 } 824 } 825 } 826 827 sep1 = Menuseparator; 828 829 Left_right_cos_item = class 830 Menuaction "Cosine Left-_right" "cosine left-right brighten" { 831 action x = class 832 _result { 833 _vislevel = 3; 834 835 tilt = Scale "Left-right tilt" (-1) 1 0; 836 shift = Scale "Shift by" (-1) 1 0; 837 838 _result 839 = map_unary tilt_lr x 840 { 841 tilt_lr image 842 = image * scale 843 { 844 ramp = im_fgrey image.width image.height - 0.5 - 845 shift.value; 846 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 847 } 848 } 849 } 850 } 851 852 Top_bottom_cos_item = class 853 Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" { 854 action x = class 855 _result { 856 _vislevel = 3; 857 858 tilt = Scale "Top-bottom tilt" (-1) 1 0; 859 shift = Scale "Shift by" (-1) 1 0; 860 861 _result 862 = map_unary tilt_tb x 863 { 864 tilt_tb image 865 = image * scale 866 { 867 ramp = rot90 (im_fgrey image.height image.width) - 0.5 - 868 shift.value; 869 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 870 } 871 } 872 } 873 } 874 875 sep2 = Menuseparator; 876 877 Circular_item = class 878 Menuaction "_Circular" "circular brighten" { 879 action x = class 880 _result { 881 _vislevel = 3; 882 883 tilt = Scale "Tilt" (-1) 1 0; 884 hshift = Scale "Horizontal shift by" (-1) 1 0; 885 vshift = Scale "Vertical shift by" (-1) 1 0; 886 887 _result 888 = map_unary tilt_tb x 889 { 890 tilt_tb image 891 = image * scale 892 { 893 hramp = im_fgrey image.width image.height - 0.5 - 894 hshift.value; 895 vramp = rot90 (im_fgrey image.height image.width) - 0.5 - 896 vshift.value; 897 ramp = (hramp ** 2 + vramp ** 2) ** 0.5; 898 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 899 } 900 } 901 } 902 } 903} 904 905Filter_blend_item = class 906 Menupullright "_Blend" "blend objects together" { 907 Scale_blend_item = class 908 Menuaction "_Scale" "blend two objects together with a scale" { 909 action a b = class 910 _result { 911 _vislevel = 3; 912 913 p = Scale "Blend position" 0 1 0.5; 914 915 _result 916 = map_binary process a b 917 { 918 process im1 im2 = im1 * (1 - p.value) + im2 * p.value; 919 } 920 } 921 } 922 923 Image_blend_item = class 924 Menuaction "_Image" "use an image to blend two objects" { 925 action a b c = class 926 _result { 927 _vislevel = 3; 928 929 i = Toggle "Invert mask" false; 930 931 _result 932 = map_trinary process a b c 933 { 934 process a b c 935 = blend condition in1 in2, !i 936 = blend (invert condition) in1 in2 937 { 938 compare a b 939 // prefer image as the condition 940 = false, 941 !has_image a && has_image b 942 // prefer mono images as the condition 943 = false, 944 has_bands a && has_bands b && 945 get_bands a > 1 && get_bands b == 1 946 // prefer uchar as the condition 947 = false, 948 has_format a && has_format b && 949 get_format a > Image_format.UCHAR && 950 get_format b == Image_format.UCHAR 951 = true; 952 [condition, in1, in2] = sortc compare [a, b, c]; 953 } 954 } 955 } 956 } 957 958 Line_blend_item = class 959 Menuaction "_Along Line" 960 "blend between image a and image b along a line" { 961 action a b = class 962 _result { 963 _vislevel = 3; 964 965 orientation = Option "Orientation" [ 966 "Left to Right", 967 "Top to Bottom" 968 ] 0; 969 blend_position = Scale "Blend position" 0 1 0.5; 970 blend_width = Scale "Blend width" 0 1 0.05; 971 972 _result 973 = map_binary process a b 974 { 975 process a b 976 = blend (Image condition) b a 977 { 978 output_width = max_pair a.width b.width; 979 output_height = max_pair a.height b.height; 980 range 981 = output_width, orientation == 0 982 = output_height; 983 blend_position' 984 = floor (range * blend_position.value); 985 blend_width' 986 = 1, blend_width.value == 0 987 = floor (range * blend_width.value); 988 start = blend_position' - blend_width' / 2; 989 990 background = (make_xy output_width output_height) >= 991 blend_position'; 992 ramp 993 = im_grey blend_width' output_height, orientation == 0 994 = rot90 (im_grey blend_width' output_width); 995 condition 996 = insert_noexpand start 0 ramp background?0, 997 orientation == 0 998 = insert_noexpand 0 start ramp background?1; 999 } 1000 } 1001 } 1002 } 1003 1004 Blend_alpha_item = class 1005 Menuaction "_Alpha" "blend images with optional alpha channels" { 1006 // usage: layerit foreground background 1007 // input images must be either 1 or 3 bands, optionally + 1 band 1008 // which is used as the alpha channel 1009 // rich lott 1010 1011 scale_mask im opacity 1012 = (unsigned char) (to_real opacity / 255 * im); 1013 1014 // to mono 1015 intensity = colour_transform_to Image_type.B_W; 1016 1017 // All the blend functions 1018 // I am grateful to this page 1019 // http://www.pegtop.net/delphi/blendmodes/ 1020 // for most of the formulae. 1021 1022 blend_normal mask opacity fg bg 1023 = blend (scale_mask mask opacity) fg bg; 1024 1025 blend_iflighter mask opacity fg bg 1026 = blend (if fg' > bg' then mask' else 0) fg bg 1027 { 1028 fg' = intensity fg; 1029 bg' = intensity bg; 1030 mask' = scale_mask mask opacity ; 1031 } 1032 1033 blend_ifdarker mask opacity fg bg 1034 = blend (if fg' < bg' then mask' else 0) fg bg 1035 { 1036 fg' = intensity fg ; 1037 bg' = intensity bg ; 1038 mask' = scale_mask mask opacity ; 1039 } 1040 1041 blend_multiply mask opacity fg bg 1042 = blend (scale_mask mask opacity) fg' bg 1043 { 1044 fg' = fg / 255 * bg; 1045 } 1046 1047 blend_add mask opacity fg bg 1048 = blend mask fg' bg 1049 { 1050 fg' = opacity / 255 * fg + bg; 1051 } 1052 1053 blend_subtract mask opacity fg bg 1054 = blend mask fg' bg 1055 { 1056 fg' = bg - opacity / 255 * fg; 1057 } 1058 1059 blend_screen mask opacity fg bg 1060 = blend mask fg' bg 1061 { 1062 fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255; 1063 } 1064 1065 blend_burn mask opacity fg bg 1066 = blend mask fg'' bg 1067 { 1068 // fades to white which has no effect. 1069 fg' = (255 - opacity) + opacity * fg / 255; 1070 fg'' = 255 - 255 * (255 - bg) / fg'; 1071 } 1072 1073 blend_softlight mask opacity fg bg 1074 = blend mask' fg' bg 1075 { 1076 mask' = scale_mask mask opacity; 1077 fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255; 1078 } 1079 1080 blend_hardlight mask opacity fg bg 1081 = blend mask' fg' bg 1082 { 1083 mask' = scale_mask mask opacity; 1084 fg' 1085 = 2 / 255 * fg * bg, bg < 129 1086 = 255 - 2 * (255 - bg) * (255 - fg) / 255; 1087 } 1088 1089 blend_lighten mask opacity fg bg 1090 = blend mask' fg' bg 1091 { 1092 mask' = scale_mask mask opacity; 1093 fg' = if bg < fg then fg else bg; 1094 } 1095 1096 blend_darken mask opacity fg bg 1097 = blend mask' fg' bg 1098 { 1099 mask' = scale_mask mask opacity; 1100 fg' = if bg > fg then fg else bg; 1101 } 1102 1103 blend_dodge mask opacity fg bg 1104 = blend mask fg'' bg 1105 { 1106 // one added to avoid divide by zero 1107 fg' = 1 + 255 - (opacity / 255 * fg); 1108 fg'' = bg * 255 / fg'; 1109 } 1110 1111 blend_reflect mask opacity fg bg 1112 = blend mask' fg' bg 1113 { 1114 mask' = scale_mask mask opacity; 1115 fg' = bg * bg / (255 - fg); 1116 } 1117 1118 blend_freeze mask opacity fg bg 1119 = blend mask' fg' bg 1120 { 1121 mask' = scale_mask mask opacity; 1122 fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg); 1123 } 1124 1125 blend_or mask opacity fg bg 1126 = bg | (unsigned char) fg' 1127 { 1128 mask' = scale_mask mask opacity; 1129 fg' = fg * mask' / 255; 1130 } 1131 1132 blend_and mask opacity fg bg 1133 = bg & (unsigned char) fg' 1134 { 1135 mask' = scale_mask mask opacity; 1136 fg' = fg * mask' / 255; 1137 } 1138 1139 // blend types 1140 NORMAL = 0; 1141 IFLIGHTER = 1; 1142 IFDARKER = 2; 1143 MULTIPLY = 3; 1144 ADD = 4; 1145 SUBTRACT = 5; 1146 SCREEN = 6; 1147 BURN = 7; 1148 DODGE = 8; 1149 HARDLIGHT = 9; 1150 SOFTLIGHT = 10; 1151 LIGHTEN = 11; 1152 DARKEN = 12; 1153 REFLECT = 13; 1154 FREEZE = 14; 1155 OR = 15; 1156 AND = 16; 1157 1158 // names we show the user for blend types 1159 names = Enum [ 1160 _ "Normal" => NORMAL, 1161 _ "If Lighter" => IFLIGHTER, 1162 _ "If Darker" => IFDARKER, 1163 _ "Multiply" => MULTIPLY, 1164 _ "Add" => ADD, 1165 _ "Subtract" => SUBTRACT, 1166 _ "Screen" => SCREEN, 1167 _ "Burn" => BURN, 1168 _ "Soft Light" => SOFTLIGHT, 1169 _ "Hard Light" => HARDLIGHT, 1170 _ "Lighten" => LIGHTEN, 1171 _ "Darken" => DARKEN, 1172 _ "Dodge" => DODGE, 1173 _ "Reflect" => REFLECT, 1174 _ "Freeze" => FREEZE, 1175 _ "Bitwise OR" => OR, 1176 _ "Bitwise AND" => AND 1177 ]; 1178 1179 // functions we call for each blend type 1180 actions = Table [ 1181 [NORMAL, blend_normal], 1182 [IFLIGHTER, blend_iflighter], 1183 [IFDARKER, blend_ifdarker], 1184 [MULTIPLY, blend_multiply], 1185 [ADD, blend_add], 1186 [SUBTRACT, blend_subtract], 1187 [SCREEN, blend_screen], 1188 [BURN, blend_burn], 1189 [SOFTLIGHT, blend_softlight], 1190 [HARDLIGHT, blend_hardlight], 1191 [LIGHTEN, blend_lighten], 1192 [DARKEN, blend_darken], 1193 [DODGE, blend_dodge], 1194 [REFLECT, blend_reflect], 1195 [FREEZE, blend_freeze], 1196 [OR, blend_or], 1197 [AND, blend_and] 1198 ]; 1199 1200 // make sure im has an alpha channel (set opaque if it hasn't) 1201 put_alpha im 1202 = im, im.bands == 4 || im.bands == 2 1203 = im ++ 255; 1204 1205 // make sure im has no alpha channel 1206 lose_alpha im 1207 = extract_bands 0 3 im, im.bands == 4 1208 = im?0, im.bands == 2 1209 = im; 1210 1211 // does im have al alpha channel? 1212 has_alpha im = im.bands == 2 || im.bands == 4; 1213 1214 // get the alpha (set opaque if no alpha) 1215 get_alpha img 1216 = img'?3, img.bands == 4 1217 = img'?1 1218 { 1219 img' = put_alpha img; 1220 } 1221 1222 // add an alpha ... cast the alpha image to match the main image 1223 append_alpha im alpha 1224 = im ++ clip2fmt im.format alpha; 1225 1226 // makes fg the same size as bg, displaced with u, v pixel offset 1227 moveit fg bg u v 1228 = insert_noexpand u v fg bg' 1229 { 1230 bg' = image_new bg.width bg.height 1231 fg.bands fg.format fg.coding fg.type 0 0 0; 1232 } 1233 1234 action bg fg = class 1235 _value { 1236 _vislevel = 3; 1237 1238 method = Option_enum "Blend mode" names "Normal"; 1239 opacity = Scale "Opacity" 0 255 255; 1240 hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0; 1241 vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0; 1242 1243 _value 1244 = append_alpha blended merged_alpha, has_alpha bg 1245 = blended 1246 { 1247 // displace and resize fg (need to displace alpha too) 1248 fg' = moveit (put_alpha fg) bg hmove vmove; 1249 1250 // transform to sRGB 1251 fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg'); 1252 bg' = colour_transform_to Image_type.sRGB (lose_alpha bg); 1253 1254 // alphas merged 1255 merged_alpha = get_alpha bg | get_alpha fg'; 1256 1257 // blend together 1258 blended = (actions.lookup 0 1 method.value_thing) 1259 (get_alpha fg') opacity.value fg'' bg'; 1260 } 1261 } 1262 } 1263} 1264 1265Filter_overlay_header_item = class 1266 Menuaction "_Overlay" 1267 "make a colour overlay of two monochrome images" { 1268 action a b = class 1269 _result { 1270 _vislevel = 3; 1271 1272 colour = Option "Colour overlay as" [ 1273 _ "Green over Red", 1274 _ "Blue over Red", 1275 _ "Red over Green", 1276 _ "Red over Blue", 1277 _ "Blue over Green", 1278 _ "Green over Blue" 1279 ] 0; 1280 1281 _result 1282 = map_binary overlay a b 1283 { 1284 overlay a b 1285 = image_set_type Image_type.sRGB 1286 [(a' ++ b' ++ 0), 1287 (a' ++ 0 ++ b'), 1288 (b' ++ a' ++ 0), 1289 (b' ++ 0 ++ a'), 1290 (0 ++ a' ++ b'), 1291 (0 ++ b' ++ a')]?colour 1292 { 1293 a' = colour_transform_to Image_type.B_W a; 1294 b' = colour_transform_to Image_type.B_W b; 1295 } 1296 } 1297 } 1298} 1299 1300Filter_colourize_item = class 1301 Menuaction "_Colourize" "use a colour image or patch to tint a mono image" { 1302 action a b = class 1303 _result { 1304 _vislevel = 3; 1305 1306 tint = Scale "Tint" 0 1 0.6; 1307 1308 _result 1309 = map_binary tintit a b 1310 { 1311 tintit a b 1312 = colour_transform_to (get_type colour) colourized' 1313 { 1314 // get the mono thing first 1315 [mono, colour] = 1316 sortc (const (is_colour_type @ get_type)) [a, b]; 1317 1318 colour' = tint * colour_transform_to Image_type.LAB colour; 1319 mono' = colour_transform_to Image_type.B_W mono; 1320 colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2; 1321 colourized' = image_set_type Image_type.LAB colourized; 1322 } 1323 } 1324 } 1325} 1326 1327Filter_browse_multiband_item = class 1328 Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" { 1329 Bandwise_item = class 1330 Menuaction "B_andwise" "browse through the bands of a multiband image" { 1331 action image = class 1332 _result { 1333 _vislevel = 3; 1334 1335 band = Scale "Band" 0 (image.bands - 1) 0; 1336 display = Option "Display as" [ 1337 _ "Grey", 1338 _ "Green over Red", 1339 _ "Blue over Red", 1340 _ "Red over Green", 1341 _ "Red over Blue", 1342 _ "Blue over Green", 1343 _ "Green over Blue" 1344 ] 0; 1345 1346 _result 1347 = output 1348 { 1349 down = (int) band.value; 1350 up = down + 1; 1351 remainder = band.value - down; 1352 1353 fade x a 1354 = Vector [0], x == 0 1355 = a * x; 1356 1357 a = fade remainder image?up; 1358 b = fade (1 - remainder) image?down; 1359 1360 output = [ 1361 a + b, 1362 a ++ b ++ 0, 1363 a ++ 0 ++ b, 1364 b ++ a ++ 0, 1365 b ++ 0 ++ a, 1366 0 ++ a ++ b, 1367 0 ++ b ++ a 1368 ] ? display; 1369 } 1370 } 1371 } 1372 1373 Bitwise_item = class 1374 Menuaction "Bi_twise" "browse through the bits of an image" { 1375 action x = class 1376 _result { 1377 _vislevel = 3; 1378 1379 bit 1380 = Islider "Bit" 0 (nbits - 1) (nbits - 1) 1381 { 1382 nbits 1383 = x.bits, is_Image x 1384 = 8; 1385 Islider c f t v = class 1386 scope.Scale c f t ((int) v) { 1387 Scale = Islider; 1388 } 1389 } 1390 1391 _result 1392 = map_unary process x 1393 { 1394 process im = (im & (0x1 << bit.value)) != 0; 1395 } 1396 } 1397 } 1398} 1399 1400#separator 1401 1402Filter_negative_item = class 1403 Menuaction "Photographic _Negative" "swap black and white" { 1404 action x 1405 = map_unary invert x 1406 { 1407 invert in 1408 = clip2fmt in.format (colour_transform_to (get_type in) rgb') 1409 { 1410 rgb = colour_transform_to Image_type.sRGB in; 1411 rgb' = 255 - rgb; 1412 } 1413 } 1414} 1415 1416Filter_solarize_item = class 1417 Menuaction "_Solarise" "invert colours above a threshold" { 1418 action x = class 1419 _result { 1420 _vislevel = 3; 1421 1422 kink = Scale "Kink" 0 1 0.5; 1423 1424 _result 1425 = map_unary process x 1426 { 1427 process image 1428 = hist_map tab'''' image 1429 { 1430 // max pixel value for this format 1431 mx = Image_format.maxval image.format; 1432 1433 // make a LUT ... just 8 and 16 bit 1434 tab 1435 = im_identity_ushort image.bands mx, 1436 image.format == 1437 Image_format.USHORT 1438 = im_identity image.bands; 1439 tab' = Image tab; 1440 1441 // make basic ^ shape 1442 tab'' 1443 = tab' * (1 / kink), tab' < mx * kink 1444 = (mx - tab') / (1 - kink); 1445 tab''' = clip2fmt image.format tab''; 1446 1447 // smooth a bit 1448 mask = matrix_blur (tab'''.width / 8); 1449 tab'''' = convsep mask tab'''; 1450 } 1451 } 1452 } 1453} 1454 1455Filter_diffuse_glow_item = class 1456 Menuaction "_Diffuse Glow" "add a halo to highlights" { 1457 action x = class 1458 _result { 1459 _vislevel = 3; 1460 1461 r = Scale "Radius" 0 50 5; 1462 highlights = Scale "Highlights" 0 100 95; 1463 glow = Scale "Glow" 0 1 0.5; 1464 colour = Colour_new_item.Widget_colour_item.action; 1465 1466 _result 1467 = map_unary process x 1468 { 1469 process image 1470 = image' 1471 { 1472 mono = (unsigned char) (colour_transform_to 1473 Image_type.B_W image); 1474 thresh = hist_thresh (highlights.value / 100) mono; 1475 mask = mono > thresh; 1476 blur = convsep (matrix_gaussian_blur r.value) mask; 1477 colour' = colour_transform_to image.type colour; 1478 image' = image + colour' * glow * (blur / 255); 1479 } 1480 } 1481 } 1482} 1483 1484Filter_drop_shadow_item = class 1485 Menuaction "Drop S_hadow" "add a drop shadow to an image" { 1486 action x = class 1487 _result { 1488 _vislevel = 3; 1489 1490 sx = Scale "Horizontal shadow" (-50) 50 5; 1491 sy = Scale "Vertical shadow" (-50) 50 5; 1492 ss = Scale "Shadow softness" 0 20 5; 1493 bg_colour = Expression "Background colour" 255; 1494 sd_colour = Expression "Shadow colour" 128; 1495 alpha = Toggle "Shadow in alpha channel" false; 1496 transparent = Toggle "Zero pixels are transparent" false; 1497 1498 _result 1499 = map_unary shadow x 1500 { 1501 shadow image 1502 = Image final 1503 { 1504 blur_size = ss.value * 2 + 1; 1505 1506 // matrix we blur with to soften shadows 1507 blur_matrix = matrix_gaussian_blur blur_size; 1508 matrix_size = blur_matrix.width; 1509 matrix_radius = (int) (matrix_size / 2) + 1; 1510 1511 // position and size of shadow image in input cods 1512 // before and after fuzzing 1513 shadow_rect = Rect sx.value sy.value 1514 image.width image.height; 1515 fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius; 1516 1517 // size and pos of final image, in input cods 1518 final_rect = image.rect.union fuzzy_shadow_rect; 1519 1520 // hard part of shadow in output cods 1521 shadow_rect' = Rect 1522 (shadow_rect.left - final_rect.left) 1523 (shadow_rect.top - final_rect.top) 1524 shadow_rect.width shadow_rect.height; 1525 1526 // make the shadow mask ... true for parts which cast 1527 // a shadow 1528 mask 1529 = (foldr1 bitwise_and @ bandsplit) (image.value != 0), 1530 transparent 1531 = image_new image.width image.height 1 Image_format.UCHAR 1532 Image_coding.NOCODING Image_type.B_W 255 0 0; 1533 mask' = embed 0 shadow_rect'.left shadow_rect'.top 1534 final_rect.width final_rect.height mask; 1535 mask'' = convsep blur_matrix mask'; 1536 1537 // use mask to fade between bg and shadow colour 1538 mk_background colour = image_new 1539 final_rect.width final_rect.height 1540 image.bands image.format image.coding image.type 1541 colour 0 0; 1542 1543 bg_image = mk_background bg_colour.expr; 1544 shadow_image = mk_background sd_colour.expr; 1545 bg = blend mask'' shadow_image bg_image; 1546 1547 // make a full size mask 1548 fg_mask = embed 0 1549 (image.rect.left - final_rect.left) 1550 (image.rect.top - final_rect.top) 1551 final_rect.width final_rect.height mask; 1552 1553 // wrap up the input image ... put the shadow colour 1554 // around it, so if we are outputting a separate 1555 // alpha the shadow colour will be set correctly 1556 fg = insert (image.rect.left - final_rect.left) 1557 (image.rect.top - final_rect.top) 1558 image.value shadow_image; 1559 1560 final 1561 // make a separate alpha 1562 = fg ++ mask'', alpha 1563 1564 // paste image over shadow 1565 = if fg_mask then fg else bg; 1566 } 1567 } 1568 } 1569} 1570 1571Filter_paint_text_item = class 1572 Menuaction "_Paint Text" "paint text into an image" { 1573 action x 1574 = paint_position, is_Group x 1575 = paint_area 1576 { 1577 paint_area = class 1578 _result { 1579 _check_args = [ 1580 [x, "x", check_Image] 1581 ]; 1582 _vislevel = 3; 1583 1584 text = String "Text to paint" "<i>Hello</i> world!"; 1585 font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; 1586 align = Option "Alignment" ["Left", "Centre", "Right"] 0; 1587 dpi = Expression "DPI" 300; 1588 colour = Expression "Text colour" 255; 1589 place = Region x (x.width / 4) (x.height / 4) 1590 (x.width / 2) (x.height / 2); 1591 1592 _result 1593 = insert_noexpand place.left place.top (blend txt' fg place) x 1594 { 1595 fg = image_new place.width place.height x.bands x.format 1596 x.coding x.type colour.expr 0 0; 1597 txt = Image (im_text text.value font.value 1598 place.width align.value (to_real dpi)); 1599 bg = im_black place.width place.height 1; 1600 txt' = insert_noexpand 0 0 txt bg; 1601 } 1602 } 1603 1604 paint_position = class 1605 _result { 1606 _vislevel = 3; 1607 1608 text = Pattern_images_item.Text_item.action; 1609 colour = Expression "Text colour" 255; 1610 position = Option "Position" [ 1611 _ "North-west", 1612 _ "North", 1613 _ "North-east", 1614 _ "West", 1615 _ "Centre", 1616 _ "East", 1617 _ "South-west", 1618 _ "South", 1619 _ "South-east", 1620 _ "Specify in pixels" 1621 ] 4; 1622 left = Expression "Pixels from left" 0; 1623 top = Expression "Pixels from top" 0; 1624 1625 _result 1626 = map_unary paint x 1627 { 1628 paint image 1629 = insert_noexpand x' y' place' image 1630 { 1631 xr = image.width - text.width; 1632 yr = image.height - text.height; 1633 x 1634 = left.expr, position == 9 1635 = [0, xr / 2, xr]?(position % 3); 1636 y 1637 = top.expr, position == 9 1638 = [0, yr / 2, yr]?(position / 3); 1639 x' = range 0 x (image.width - 1); 1640 y' = range 0 y (image.height - 1); 1641 w' = range 1 text.width (image.width - x'); 1642 h' = range 1 text.height (image.height - y'); 1643 1644 place = extract_area x' y' w' h' image; 1645 text' = insert_noexpand 0 0 text (im_black w' h' 1); 1646 fg = image_new w' h' image.bands image.format 1647 image.coding image.type colour.expr 0 0; 1648 place' = blend text' fg place; 1649 } 1650 } 1651 } 1652 } 1653} 1654 1655Autotrace_item = class 1656 Menuaction "_Trace" "convert a bitmap to an SVG file" { 1657 action x = class 1658 _result { 1659 _vislevel = 3; 1660 1661 despeckle = Scale "Despeckle level" 1 20 1; 1662 line = Scale "Line threshold" 1 20 1; 1663 center = Toggle "Trace centreline" false; 1664 scale = Scale "SVG scale" 0.1 10 1; 1665 1666 command 1667 = "autotrace %s " ++ join_sep " " 1668 [ofmt, ofile, desp, lint, cent] 1669 { 1670 prog = search_for_error "autotrace"; 1671 ofmt = "-output-format svg"; 1672 ofile = "-output-file %s"; 1673 desp = "-despeckle-level " ++ print despeckle.value; 1674 lint = "-line-threshold " ++ print line.value; 1675 cent = if center then "-centerline " else ""; 1676 } 1677 1678 _result 1679 = Image output 1680 { 1681 [output] = vips_call "system" 1682 [command] 1683 [$in => [x.value], 1684 $in_format => "%s.ppm", 1685 $out => true, 1686 $out_format => "%s.svg[scale=" ++ print scale.value ++ "]" 1687 ]; 1688 } 1689 } 1690} 1691 1692