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