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 658Filter_greyc_item = class 659 Menupullright "_GREYCstoration" "noise-removing filter" { 660 Denoise_item = class 661 Menuaction "Denoise" "Noise-removing filter" { 662 action x = class 663 _result { 664 _vislevel = 3; 665 666 iterations = Scale "Iterations" 1 5 1; 667 amplitude = Scale "Amplitude" 1 100 40; 668 sharpness = Scale "Sharpness" 0 3 0.9; 669 anisotropy = Scale "Anisotropy" 0 1 0.15; 670 alpha = Scale "Noise scale" 0 5 0.6; 671 sigma = Scale "Geometry regularity" 0 2 1.1; 672 dl = Scale "Spatial integration step" 0 1 0.8; 673 da = Scale "Angular integration step" 0 90 30; 674 gauss_prec = Scale "Precision" 1 10 2; 675 interpolation = Option "Interpolation" 676 ["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0; 677 fast_approx = Toggle "Fast approximation" true; 678 679 _result = greyc 680 (to_real iterations) (to_real amplitude) (to_real sharpness) 681 (to_real anisotropy) (to_real alpha) (to_real sigma) 682 (to_real dl) (to_real da) 683 (to_real gauss_prec) (to_real interpolation) 684 (to_real fast_approx) x; 685 } 686 } 687 688 Enlarge_item = class 689 Menuaction "Enlarge" "Enlarge image" { 690 action x = class 691 _result { 692 _vislevel = 3; 693 694 scale = Scale "Enlarge" 1 10 3; 695 iterations = Scale "Iterations" 1 5 3; 696 amplitude = Scale "Amplitude" 1 100 20; 697 sharpness = Scale "Sharpness" 0 3 0.2; 698 anisotropy = Scale "Anisotropy" 0 1 0.9; 699 alpha = Scale "Noise scale" 0 5 0.1; 700 sigma = Scale "Geometry regularity" 0 2 1.5; 701 dl = Scale "Spatial integration step" 0 1 0.8; 702 da = Scale "Angular integration step" 0 90 30; 703 gauss_prec = Scale "Precision" 1 10 2; 704 interpolation = Option "Interpolation" 705 ["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0; 706 fast_approx = Toggle "Fast approximation" true; 707 708 _result = greyc 709 (to_real iterations) (to_real amplitude) (to_real sharpness) 710 (to_real anisotropy) (to_real alpha) (to_real sigma) 711 (to_real dl) (to_real da) 712 (to_real gauss_prec) (to_real interpolation) 713 (to_real fast_approx) 714 (resize (to_real scale) (to_real scale) 715 Interpolate.BILINEAR x); 716 } 717 } 718 719} 720 721#separator 722 723Filter_tilt_item = class 724 Menupullright "Ti_lt Brightness" "tilt brightness" { 725 Left_right_item = class 726 Menuaction "_Left to Right" "linear left-right brighten" { 727 action x = class 728 _result { 729 _vislevel = 3; 730 731 tilt = Scale "Left-right tilt" (-1) 1 0; 732 733 _result 734 = map_unary tilt_lr x 735 { 736 tilt_lr image 737 = image * scale 738 { 739 ramp = im_fgrey image.width image.height; 740 scale = (ramp - 0.5) * tilt + 1; 741 } 742 } 743 } 744 } 745 746 Top_bottom_item = class 747 Menuaction "_Top to Bottom" "linear top-bottom brighten" { 748 action x = class 749 _result { 750 _vislevel = 3; 751 752 tilt = Scale "Top-bottom tilt" (-1) 1 0; 753 754 _result 755 = map_unary tilt_tb x 756 { 757 tilt_tb image 758 = image * scale 759 { 760 ramp = rot90 761 (im_fgrey image.height image.width); 762 scale = (ramp - 0.5) * tilt + 1; 763 } 764 } 765 } 766 } 767 768 sep1 = Menuseparator; 769 770 Left_right_cos_item = class 771 Menuaction "Cosine Left-_right" "cosine left-right brighten" { 772 action x = class 773 _result { 774 _vislevel = 3; 775 776 tilt = Scale "Left-right tilt" (-1) 1 0; 777 shift = Scale "Shift by" (-1) 1 0; 778 779 _result 780 = map_unary tilt_lr x 781 { 782 tilt_lr image 783 = image * scale 784 { 785 ramp = im_fgrey image.width image.height - 0.5 - 786 shift.value; 787 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 788 } 789 } 790 } 791 } 792 793 Top_bottom_cos_item = class 794 Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" { 795 action x = class 796 _result { 797 _vislevel = 3; 798 799 tilt = Scale "Top-bottom tilt" (-1) 1 0; 800 shift = Scale "Shift by" (-1) 1 0; 801 802 _result 803 = map_unary tilt_tb x 804 { 805 tilt_tb image 806 = image * scale 807 { 808 ramp = rot90 (im_fgrey image.height image.width) - 0.5 - 809 shift.value; 810 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 811 } 812 } 813 } 814 } 815 816 sep2 = Menuseparator; 817 818 Circular_item = class 819 Menuaction "_Circular" "circular brighten" { 820 action x = class 821 _result { 822 _vislevel = 3; 823 824 tilt = Scale "Tilt" (-1) 1 0; 825 hshift = Scale "Horizontal shift by" (-1) 1 0; 826 vshift = Scale "Vertical shift by" (-1) 1 0; 827 828 _result 829 = map_unary tilt_tb x 830 { 831 tilt_tb image 832 = image * scale 833 { 834 hramp = im_fgrey image.width image.height - 0.5 - 835 hshift.value; 836 vramp = rot90 (im_fgrey image.height image.width) - 0.5 - 837 vshift.value; 838 ramp = (hramp ** 2 + vramp ** 2) ** 0.5; 839 scale = 0.5 * tilt.value * cos (ramp * 180) + 1; 840 } 841 } 842 } 843 } 844} 845 846Filter_blend_item = class 847 Menupullright "_Blend" "blend objects together" { 848 Scale_blend_item = class 849 Menuaction "_Scale" "blend two objects together with a scale" { 850 action a b = class 851 _result { 852 _vislevel = 3; 853 854 p = Scale "Blend position" 0 1 0.5; 855 856 _result 857 = map_binary process a b 858 { 859 process im1 im2 = im1 * (1 - p.value) + im2 * p.value; 860 } 861 } 862 } 863 864 Image_blend_item = class 865 Menuaction "_Image" "use an image to blend two objects" { 866 action a b c = class 867 _result { 868 _vislevel = 3; 869 870 i = Toggle "Invert mask" false; 871 872 _result 873 = map_trinary process a b c 874 { 875 process a b c 876 = blend condition in1 in2, !i 877 = blend (invert condition) in1 in2 878 { 879 compare a b 880 // prefer image as the condition 881 = false, 882 !has_image a && has_image b 883 // prefer mono images as the condition 884 = false, 885 has_bands a && has_bands b && 886 get_bands a > 1 && get_bands b == 1 887 // prefer uchar as the condition 888 = false, 889 has_format a && has_format b && 890 get_format a > Image_format.UCHAR && 891 get_format b == Image_format.UCHAR 892 = true; 893 [condition, in1, in2] = sortc compare [a, b, c]; 894 } 895 } 896 } 897 } 898 899 Line_blend_item = class 900 Menuaction "_Along Line" 901 "blend between image a and image b along a line" { 902 action a b = class 903 _result { 904 _vislevel = 3; 905 906 orientation = Option "Orientation" [ 907 "Left to Right", 908 "Top to Bottom" 909 ] 0; 910 blend_position = Scale "Blend position" 0 1 0.5; 911 blend_width = Scale "Blend width" 0 1 0.05; 912 913 _result 914 = map_binary process a b 915 { 916 process a b 917 = blend (Image condition) b a 918 { 919 output_width = max_pair a.width b.width; 920 output_height = max_pair a.height b.height; 921 range 922 = output_width, orientation == 0 923 = output_height; 924 blend_position' 925 = floor (range * blend_position.value); 926 blend_width' 927 = 1, blend_width.value == 0 928 = floor (range * blend_width.value); 929 start = blend_position' - blend_width' / 2; 930 931 background = (make_xy output_width output_height) >= 932 blend_position'; 933 ramp 934 = im_grey blend_width' output_height, orientation == 0 935 = rot90 (im_grey blend_width' output_width); 936 condition 937 = insert_noexpand start 0 ramp background?0, 938 orientation == 0 939 = insert_noexpand 0 start ramp background?1; 940 } 941 } 942 } 943 } 944 945 Blend_alpha_item = class 946 Menuaction "_Alpha" "blend images with optional alpha channels" { 947 // usage: layerit foreground background 948 // input images must be either 1 or 3 bands, optionally + 1 band 949 // which is used as the alpha channel 950 // rich lott 951 952 scale_mask im opacity 953 = (unsigned char) (to_real opacity / 255 * im); 954 955 // to mono 956 intensity = colour_transform_to Image_type.B_W; 957 958 // All the blend functions 959 // I am grateful to this page 960 // http://www.pegtop.net/delphi/blendmodes/ 961 // for most of the formulae. 962 963 blend_normal mask opacity fg bg 964 = blend (scale_mask mask opacity) fg bg; 965 966 blend_iflighter mask opacity fg bg 967 = blend (if fg' > bg' then mask' else 0) fg bg 968 { 969 fg' = intensity fg; 970 bg' = intensity bg; 971 mask' = scale_mask mask opacity ; 972 } 973 974 blend_ifdarker mask opacity fg bg 975 = blend (if fg' < bg' then mask' else 0) fg bg 976 { 977 fg' = intensity fg ; 978 bg' = intensity bg ; 979 mask' = scale_mask mask opacity ; 980 } 981 982 blend_multiply mask opacity fg bg 983 = blend (scale_mask mask opacity) fg' bg 984 { 985 fg' = fg / 255 * bg; 986 } 987 988 blend_add mask opacity fg bg 989 = blend mask fg' bg 990 { 991 fg' = opacity / 255 * fg + bg; 992 } 993 994 blend_subtract mask opacity fg bg 995 = blend mask fg' bg 996 { 997 fg' = bg - opacity / 255 * fg; 998 } 999 1000 blend_screen mask opacity fg bg 1001 = blend mask fg' bg 1002 { 1003 fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255; 1004 } 1005 1006 blend_burn mask opacity fg bg 1007 = blend mask fg'' bg 1008 { 1009 // fades to white which has no effect. 1010 fg' = (255 - opacity) + opacity * fg / 255; 1011 fg'' = 255 - 255 * (255 - bg) / fg'; 1012 } 1013 1014 blend_softlight mask opacity fg bg 1015 = blend mask' fg' bg 1016 { 1017 mask' = scale_mask mask opacity; 1018 fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255; 1019 } 1020 1021 blend_hardlight mask opacity fg bg 1022 = blend mask' fg' bg 1023 { 1024 mask' = scale_mask mask opacity; 1025 fg' 1026 = 2 / 255 * fg * bg, bg < 129 1027 = 255 - 2 * (255 - bg) * (255 - fg) / 255; 1028 } 1029 1030 blend_lighten mask opacity fg bg 1031 = blend mask' fg' bg 1032 { 1033 mask' = scale_mask mask opacity; 1034 fg' = if bg < fg then fg else bg; 1035 } 1036 1037 blend_darken mask opacity fg bg 1038 = blend mask' fg' bg 1039 { 1040 mask' = scale_mask mask opacity; 1041 fg' = if bg > fg then fg else bg; 1042 } 1043 1044 blend_dodge mask opacity fg bg 1045 = blend mask fg'' bg 1046 { 1047 // one added to avoid divide by zero 1048 fg' = 1 + 255 - (opacity / 255 * fg); 1049 fg'' = bg * 255 / fg'; 1050 } 1051 1052 blend_reflect mask opacity fg bg 1053 = blend mask' fg' bg 1054 { 1055 mask' = scale_mask mask opacity; 1056 fg' = bg * bg / (255 - fg); 1057 } 1058 1059 blend_freeze mask opacity fg bg 1060 = blend mask' fg' bg 1061 { 1062 mask' = scale_mask mask opacity; 1063 fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg); 1064 } 1065 1066 blend_or mask opacity fg bg 1067 = bg | (unsigned char) fg' 1068 { 1069 mask' = scale_mask mask opacity; 1070 fg' = fg * mask' / 255; 1071 } 1072 1073 blend_and mask opacity fg bg 1074 = bg & (unsigned char) fg' 1075 { 1076 mask' = scale_mask mask opacity; 1077 fg' = fg * mask' / 255; 1078 } 1079 1080 // blend types 1081 NORMAL = 0; 1082 IFLIGHTER = 1; 1083 IFDARKER = 2; 1084 MULTIPLY = 3; 1085 ADD = 4; 1086 SUBTRACT = 5; 1087 SCREEN = 6; 1088 BURN = 7; 1089 DODGE = 8; 1090 HARDLIGHT = 9; 1091 SOFTLIGHT = 10; 1092 LIGHTEN = 11; 1093 DARKEN = 12; 1094 REFLECT = 13; 1095 FREEZE = 14; 1096 OR = 15; 1097 AND = 16; 1098 1099 // names we show the user for blend types 1100 names = Enum [ 1101 [_ "Normal", NORMAL], 1102 [_ "If Lighter", IFLIGHTER], 1103 [_ "If Darker", IFDARKER], 1104 [_ "Multiply", MULTIPLY], 1105 [_ "Add", ADD], 1106 [_ "Subtract", SUBTRACT], 1107 [_ "Screen", SCREEN], 1108 [_ "Burn", BURN], 1109 [_ "Soft Light", SOFTLIGHT], 1110 [_ "Hard Light", HARDLIGHT], 1111 [_ "Lighten", LIGHTEN], 1112 [_ "Darken", DARKEN], 1113 [_ "Dodge", DODGE], 1114 [_ "Reflect", REFLECT], 1115 [_ "Freeze", FREEZE], 1116 [_ "Bitwise OR", OR], 1117 [_ "Bitwise AND", AND] 1118 ]; 1119 1120 // functions we call for each blend type 1121 actions = Table [ 1122 [NORMAL, blend_normal], 1123 [IFLIGHTER, blend_iflighter], 1124 [IFDARKER, blend_ifdarker], 1125 [MULTIPLY, blend_multiply], 1126 [ADD, blend_add], 1127 [SUBTRACT, blend_subtract], 1128 [SCREEN, blend_screen], 1129 [BURN, blend_burn], 1130 [SOFTLIGHT, blend_softlight], 1131 [HARDLIGHT, blend_hardlight], 1132 [LIGHTEN, blend_lighten], 1133 [DARKEN, blend_darken], 1134 [DODGE, blend_dodge], 1135 [REFLECT, blend_reflect], 1136 [FREEZE, blend_freeze], 1137 [OR, blend_or], 1138 [AND, blend_and] 1139 ]; 1140 1141 // make sure im has an alpha channel (set opaque if it hasn't) 1142 put_alpha im 1143 = im, im.bands == 4 || im.bands == 2 1144 = im ++ 255; 1145 1146 // make sure im has no alpha channel 1147 lose_alpha im 1148 = extract_bands 0 3 im, im.bands == 4 1149 = im?0, im.bands == 2 1150 = im; 1151 1152 // does im have al alpha channel? 1153 has_alpha im = im.bands == 2 || im.bands == 4; 1154 1155 // get the alpha (set opaque if no alpha) 1156 get_alpha img 1157 = img'?3, img.bands == 4 1158 = img'?1 1159 { 1160 img' = put_alpha img; 1161 } 1162 1163 // add an alpha ... cast the alpha image to match the main image 1164 append_alpha im alpha 1165 = im ++ clip2fmt im.format alpha; 1166 1167 // makes fg the same size as bg, displaced with u, v pixel offset 1168 moveit fg bg u v 1169 = insert_noexpand u v fg bg' 1170 { 1171 bg' = image_new bg.width bg.height 1172 fg.bands fg.format fg.coding fg.type 0 0 0; 1173 } 1174 1175 action bg fg = class 1176 _value { 1177 _vislevel = 3; 1178 1179 method = Option_enum names "Blend mode" "Normal"; 1180 opacity = Scale "Opacity" 0 255 255; 1181 hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0; 1182 vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0; 1183 1184 _value 1185 = append_alpha blended merged_alpha, has_alpha bg 1186 = blended 1187 { 1188 // displace and resize fg (need to displace alpha too) 1189 fg' = moveit (put_alpha fg) bg hmove vmove; 1190 1191 // transform to sRGB 1192 fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg'); 1193 bg' = colour_transform_to Image_type.sRGB (lose_alpha bg); 1194 1195 // alphas merged 1196 merged_alpha = get_alpha bg | get_alpha fg'; 1197 1198 // blend together 1199 blended = (actions.lookup 0 1 method.value_thing) 1200 (get_alpha fg') opacity.value fg'' bg'; 1201 } 1202 } 1203 } 1204} 1205 1206Filter_overlay_header_item = class 1207 Menuaction "_Overlay" 1208 "make a colour overlay of two monochrome images" { 1209 action a b = class 1210 _result { 1211 _vislevel = 3; 1212 1213 colour = Option "Colour overlay as" [ 1214 _ "Green over Red", 1215 _ "Blue over Red", 1216 _ "Red over Green", 1217 _ "Red over Blue", 1218 _ "Blue over Green", 1219 _ "Green over Blue" 1220 ] 0; 1221 1222 _result 1223 = map_binary overlay a b 1224 { 1225 overlay a b 1226 = image_set_type Image_type.sRGB 1227 [(a' ++ b' ++ 0), 1228 (a' ++ 0 ++ b'), 1229 (b' ++ a' ++ 0), 1230 (b' ++ 0 ++ a'), 1231 (0 ++ a' ++ b'), 1232 (0 ++ b' ++ a')]?colour 1233 { 1234 a' = colour_transform_to Image_type.B_W a; 1235 b' = colour_transform_to Image_type.B_W b; 1236 } 1237 } 1238 } 1239} 1240 1241Filter_colourize_item = class 1242 Menuaction "_Colourize" "use a colour image or patch to tint a mono image" { 1243 action a b = class 1244 _result { 1245 _vislevel = 3; 1246 1247 tint = Scale "Tint" 0 1 0.6; 1248 1249 _result 1250 = map_binary tintit a b 1251 { 1252 tintit a b 1253 = colour_transform_to (get_type colour) colourized' 1254 { 1255 // get the mono thing first 1256 [mono, colour] = 1257 sortc (const (is_colour_type @ get_type)) [a, b]; 1258 1259 colour' = tint * colour_transform_to Image_type.LAB colour; 1260 mono' = colour_transform_to Image_type.B_W mono; 1261 colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2; 1262 colourized' = image_set_type Image_type.LAB colourized; 1263 } 1264 } 1265 } 1266} 1267 1268Filter_browse_multiband_item = class 1269 Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" { 1270 Bandwise_item = class 1271 Menuaction "B_andwise" "browse through the bands of a multiband image" { 1272 action image = class 1273 _result { 1274 _vislevel = 3; 1275 1276 band = Scale "Band" 0 (image.bands - 1) 0; 1277 display = Option "Display as" [ 1278 _ "Grey", 1279 _ "Green over Red", 1280 _ "Blue over Red", 1281 _ "Red over Green", 1282 _ "Red over Blue", 1283 _ "Blue over Green", 1284 _ "Green over Blue" 1285 ] 0; 1286 1287 _result 1288 = output 1289 { 1290 down = (int) band.value; 1291 up = down + 1; 1292 remainder = band.value - down; 1293 1294 fade x a 1295 = Vector [0], x == 0 1296 = a * x; 1297 1298 a = fade remainder image?up; 1299 b = fade (1 - remainder) image?down; 1300 1301 output = [ 1302 a + b, 1303 a ++ b ++ 0, 1304 a ++ 0 ++ b, 1305 b ++ a ++ 0, 1306 b ++ 0 ++ a, 1307 0 ++ a ++ b, 1308 0 ++ b ++ a 1309 ] ? display; 1310 } 1311 } 1312 } 1313 1314 Bitwise_item = class 1315 Menuaction "Bi_twise" "browse through the bits of an image" { 1316 action x = class 1317 _result { 1318 _vislevel = 3; 1319 1320 bit 1321 = Islider "Bit" 0 (nbits - 1) (nbits - 1) 1322 { 1323 nbits 1324 = x.bits, is_Image x 1325 = 8; 1326 Islider c f t v = class 1327 scope.Scale c f t ((int) v) { 1328 Scale = Islider; 1329 } 1330 } 1331 1332 _result 1333 = map_unary process x 1334 { 1335 process im = (im & (0x1 << bit.value)) != 0; 1336 } 1337 } 1338 } 1339} 1340 1341#separator 1342 1343Filter_negative_item = class 1344 Menuaction "Photographic _Negative" "swap black and white" { 1345 action x 1346 = map_unary invert x 1347 { 1348 invert in 1349 = clip2fmt in.format (colour_transform_to (get_type in) rgb') 1350 { 1351 rgb = colour_transform_to Image_type.sRGB in; 1352 rgb' = 255 - rgb; 1353 } 1354 } 1355} 1356 1357Filter_solarize_item = class 1358 Menuaction "_Solarise" "invert colours above a threshold" { 1359 action x = class 1360 _result { 1361 _vislevel = 3; 1362 1363 kink = Scale "Kink" 0 1 0.5; 1364 1365 _result 1366 = map_unary process x 1367 { 1368 process image 1369 = hist_map tab'''' image 1370 { 1371 // max pixel value for this format 1372 mx = Image_format.maxval image.format; 1373 1374 // make a LUT ... just 8 and 16 bit 1375 tab 1376 = im_identity_ushort image.bands mx, 1377 image.format == 1378 Image_format.USHORT 1379 = im_identity image.bands; 1380 tab' = Image tab; 1381 1382 // make basic ^ shape 1383 tab'' 1384 = tab' * (1 / kink), tab' < mx * kink 1385 = (mx - tab') / (1 - kink); 1386 tab''' = clip2fmt image.format tab''; 1387 1388 // smooth a bit 1389 mask = matrix_blur (tab'''.width / 8); 1390 tab'''' = convsep mask tab'''; 1391 } 1392 } 1393 } 1394} 1395 1396Filter_diffuse_glow_item = class 1397 Menuaction "_Diffuse Glow" "add a halo to highlights" { 1398 action x = class 1399 _result { 1400 _vislevel = 3; 1401 1402 r = Scale "Radius" 0 50 5; 1403 highlights = Scale "Highlights" 0 100 95; 1404 glow = Scale "Glow" 0 1 0.5; 1405 colour = Colour_new_item.Widget_colour_item.action; 1406 1407 _result 1408 = map_unary process x 1409 { 1410 process image 1411 = image' 1412 { 1413 mono = (unsigned char) (colour_transform_to 1414 Image_type.B_W image); 1415 thresh = hist_thresh (highlights.value / 100) mono; 1416 mask = mono > thresh; 1417 blur = convsep (matrix_gaussian_blur r.value) mask; 1418 colour' = colour_transform_to image.type colour; 1419 image' = image + colour' * glow * (blur / 255); 1420 } 1421 } 1422 } 1423} 1424 1425Filter_drop_shadow_item = class 1426 Menuaction "Drop S_hadow" "add a drop shadow to an image" { 1427 action x = class 1428 _result { 1429 _vislevel = 3; 1430 1431 sx = Scale "Horizontal shadow" (-50) 50 5; 1432 sy = Scale "Vertical shadow" (-50) 50 5; 1433 ss = Scale "Shadow softness" 0 20 5; 1434 bg_colour = Expression "Background colour" 255; 1435 sd_colour = Expression "Shadow colour" 128; 1436 alpha = Toggle "Shadow in alpha channel" false; 1437 transparent = Toggle "Zero pixels are transparent" false; 1438 1439 _result 1440 = map_unary shadow x 1441 { 1442 shadow image 1443 = Image final 1444 { 1445 blur_size = ss.value * 2 + 1; 1446 1447 // matrix we blur with to soften shadows 1448 blur_matrix = matrix_gaussian_blur blur_size; 1449 matrix_size = blur_matrix.width; 1450 matrix_radius = (int) (matrix_size / 2) + 1; 1451 1452 // position and size of shadow image in input cods 1453 // before and after fuzzing 1454 shadow_rect = Rect sx.value sy.value 1455 image.width image.height; 1456 fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius; 1457 1458 // size and pos of final image, in input cods 1459 final_rect = image.rect.union fuzzy_shadow_rect; 1460 1461 // hard part of shadow in output cods 1462 shadow_rect' = Rect 1463 (shadow_rect.left - final_rect.left) 1464 (shadow_rect.top - final_rect.top) 1465 shadow_rect.width shadow_rect.height; 1466 1467 // make the shadow mask ... true for parts which cast 1468 // a shadow 1469 mask 1470 = (foldr1 bitwise_and @ bandsplit) (image.value != 0), 1471 transparent 1472 = image_new image.width image.height 1 Image_format.UCHAR 1473 Image_coding.NOCODING Image_type.B_W 255 0 0; 1474 mask' = embed 0 shadow_rect'.left shadow_rect'.top 1475 final_rect.width final_rect.height mask; 1476 mask'' = convsep blur_matrix mask'; 1477 1478 // use mask to fade between bg and shadow colour 1479 mk_background colour = image_new 1480 final_rect.width final_rect.height 1481 image.bands image.format image.coding image.type 1482 colour 0 0; 1483 1484 bg_image = mk_background bg_colour.expr; 1485 shadow_image = mk_background sd_colour.expr; 1486 bg = blend mask'' shadow_image bg_image; 1487 1488 // make a full size mask 1489 fg_mask = embed 0 1490 (image.rect.left - final_rect.left) 1491 (image.rect.top - final_rect.top) 1492 final_rect.width final_rect.height mask; 1493 1494 // wrap up the input image ... put the shadow colour 1495 // around it, so if we are outputting a separate 1496 // alpha the shadow colour will be set correctly 1497 fg = insert (image.rect.left - final_rect.left) 1498 (image.rect.top - final_rect.top) 1499 image.value shadow_image; 1500 1501 final 1502 // make a separate alpha 1503 = fg ++ mask'', alpha 1504 1505 // paste image over shadow 1506 = if fg_mask then fg else bg; 1507 } 1508 } 1509 } 1510} 1511 1512Filter_paint_text_item = class 1513 Menuaction "_Paint Text" "paint text into an image" { 1514 action x 1515 = paint_position, is_Group x 1516 = paint_area 1517 { 1518 paint_area = class 1519 _result { 1520 _check_args = [ 1521 [x, "x", check_Image] 1522 ] ++ super._check_args; 1523 _vislevel = 3; 1524 1525 text = String "Text to paint" "<i>Hello</i> world!"; 1526 font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; 1527 align = Option "Alignment" ["Left", "Centre", "Right"] 0; 1528 dpi = Expression "DPI" 300; 1529 colour = Expression "Text colour" 255; 1530 place = Region x (x.width / 4) (x.height / 4) 1531 (x.width / 2) (x.height / 2); 1532 1533 _result 1534 = insert_noexpand place.left place.top (blend txt' fg place) x 1535 { 1536 fg = image_new place.width place.height x.bands x.format 1537 x.coding x.type colour.expr 0 0; 1538 txt = Image (im_text text.value font.value 1539 place.width align.value (to_real dpi)); 1540 bg = im_black place.width place.height 1; 1541 txt' = insert_noexpand 0 0 txt bg; 1542 } 1543 } 1544 1545 paint_position = class 1546 _result { 1547 _vislevel = 3; 1548 1549 text = Pattern_images_item.Text_item.action; 1550 colour = Expression "Text colour" 255; 1551 position = Option "Position" [ 1552 _ "North-west", 1553 _ "North", 1554 _ "North-east", 1555 _ "West", 1556 _ "Centre", 1557 _ "East", 1558 _ "South-west", 1559 _ "South", 1560 _ "South-east", 1561 _ "Specify in pixels" 1562 ] 4; 1563 left = Expression "Pixels from left" 0; 1564 top = Expression "Pixels from top" 0; 1565 1566 _result 1567 = map_unary paint x 1568 { 1569 paint image 1570 = insert_noexpand x' y' place' image 1571 { 1572 xr = image.width - text.width; 1573 yr = image.height - text.height; 1574 x 1575 = left.expr, position == 9 1576 = [0, xr / 2, xr]?(position % 3); 1577 y 1578 = top.expr, position == 9 1579 = [0, yr / 2, yr]?(position / 3); 1580 x' = range 0 x (image.width - 1); 1581 y' = range 0 y (image.height - 1); 1582 w' = range 1 text.width (image.width - x'); 1583 h' = range 1 text.height (image.height - y'); 1584 1585 place = extract_area x' y' w' h' image; 1586 text' = insert_noexpand 0 0 text (im_black w' h' 1); 1587 fg = image_new w' h' image.bands image.format 1588 image.coding image.type colour.expr 0 0; 1589 place' = blend text' fg place; 1590 } 1591 } 1592 } 1593 } 1594} 1595