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