Filter_conv_item = class Menupullright "_Convolution" "various spatial convolution filters" { /* Some useful masks. */ filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]]; filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]]; filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]]; filter_laplacian = Matrix_con 1 128 [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]; filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]]; filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]]; Blur_item = class Menuaction "_Blur" "3x3 blur of image" { action x = map_unary (conv filter_blur) x; } Sharpen_item = class Menuaction "_Sharpen" "3x3 sharpen of image" { action x = map_unary (conv filter_sharp) x; } Emboss_item = class Menuaction "_Emboss" "1 pixel displace emboss" { action x = map_unary (conv filter_emboss) x; } Laplacian_item = class Menuaction "_Laplacian" "3x3 laplacian edge detect" { action x = map_unary (conv filter_laplacian) x; } Sobel_item = class Menuaction "So_bel" "3x3 Sobel edge detect" { action x = map_unary sobel x { sobel im = abs (a - 128) + abs (b - 128) { a = conv filter_sobel im; b = conv (rot270 filter_sobel) im; } } } /* 3x3 line detect of image diagonals should be scaled down by root(2) I guess Kirk */ Linedet_item = class Menuaction "Li_ne Detect" "3x3 line detect" { action x = map_unary lindet x { lindet im = foldr1 max_pair images { masks = take 4 (iterate rot45 filter_lindet); images = map (converse conv im) masks; } } } Usharp_item = class Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" { action x = class _result { _vislevel = 3; size = Option "Radius" [ "3 pixels", "5 pixels", "7 pixels", "9 pixels", "11 pixels", "51 pixels" ] 0; smooth_threshold = Slider 0 5 1.5; brighten_max = Slider 1 50 10; darken_max = Slider 1 50 50; flat_sharp = Slider (-2) 5 1; jaggy_sharp = Slider (-2) 5 2; _result = map_unary process x { process in = Image in''' { in' = colour_transform_to Image_type.LABS in.value; in'' = sharpen [3, 5, 7, 9, 11, 51]?size smooth_threshold brighten_max darken_max flat_sharp jaggy_sharp in'; in''' = colour_transform_to (get_type in) in''; } } } } sep1 = Menuseparator; Custom_blur_item = class Menuaction "Custom B_lur" "blur with tuneable parameters" { action x = class _result { _vislevel = 3; radius = Slider 1 50 1; shape = Option "Mask shape" [ "Square", "Gaussian" ] 0; _result = map_unary process x { process in = convsep mask in { mask = matrix_blur radius.value, shape.value == 0 = matrix_gaussian_blur radius.value; } } } } Custom_conv_item = class Menuaction "Custom C_onvolution" "convolution filter with tuneable parameters" { action x = class _result { _vislevel = 3; matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]]; separable = Toggle "Seperable convolution" false, matrix.width == 1 || matrix.height == 1 = false; type = Option "Convolution type" ["Int", "Float"] 0; _result = map_unary process x { process in = Image in' { conv_fn = im_conv, !separable && type == 0 = im_convsep, separable && type == 0 = im_convf, !separable && type == 1 = im_convsepf, separable && type == 1 = error "boink!"; in' = conv_fn in.value matrix; } } } } } Filter_rank_item = class Menupullright "_Rank" "various rank filters" { Median_item = class Menuaction "_Median" "3x3 median rank filter" { action x = map_unary (rank 3 3 5) x; } Image_rank_item = class Menuaction "_Image Rank" "pixelwise rank a list or group of images" { action x = class _result { _vislevel = 3; select = Expression "Rank" ((int) ((guess_size + 1) / 2)) { guess_size = len x, is_list x = len x.value, is_Group x = 0; } // can't really iterate over groups ... since we allow a group // argument _result = rank_image select x; } } Custom_rank_item = class Menuaction "Custom _Rank" "rank filter with tuneable parameters" { action x = class _result { _vislevel = 3; width = Expression "Window width" 3; height = Expression "Window height" 3; select = Expression "Rank" ((int) ((to_real width * to_real height + 1) / 2)); _result = map_unary process x { process in = rank width height select in; } } } } Filter_morphology_item = class Menupullright "_Morphology" "various morphological filters" { /* Some useful masks. */ mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]]; mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]]; mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]]; thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]]; Threshold_item = class Menuaction "Thres_hold" "simple image threshold" { action x = class _result { _vislevel = 3; threshold = Slider 0 mx (mx / 2) { mx = Image_format.maxval x.format, is_Image x = 255; } _result = map_unary (more threshold.value) x; } } sep1 = Menuseparator; Dilate8_item = class Menuaction "Dilate _8-connected" "dilate with an 8-connected mask" { action x = map_unary (dilate mask8) x; } Dilate4_item = class Menuaction "Dilate _4-connected" "dilate with a 4-connected mask" { action x = map_unary (dilate mask4) x; } Erode8_item = class Menuaction "_Erode 8-connected" "erode with an 8-connected mask" { action x = map_unary (erode mask8) x; } Erode4_item = class Menuaction "E_rode 4-connected" "erode with a 4-connected mask" { action x = map_unary (erode mask4) x; } Custom_morph_item = class Menuaction "Custom _Morphology" "convolution morphological operator" { action x = class _result { _vislevel = 3; mask = mask4; type = Option "Operation" ["Erode", "Dilate"] 1; apply = Expression "Number of times to apply mask" 1; _result = map_unary morph x { morph image = Image value' { fatmask = (iterate (dilate mask) mask)?(to_real apply - 1); value' = im_erode image.value fatmask, type.value == 0 = im_dilate image.value fatmask; } } } } sep2 = Menuseparator; Open_item = class Menuaction "_Open" "open with an 8-connected mask" { action x = map_unary (dilate mask8 @ erode mask8) x; } Close_item = class Menuaction "_Close" "close with an 8-connected mask" { action x = map_unary (erode mask8 @ dilate mask8) x; } Clean_item = class Menuaction "C_lean" "remove 8-connected isolated points" { action x = map_unary clean x { clean x = x ^ erode mask1 x; } } Thin_item = class Menuaction "_Thin" "thin once" { action x = map_unary thinall x { masks = take 8 (iterate rot45 thin); thin1 m x = x ^ erode m x; thinall x = foldr thin1 x masks; } } } Filter_fourier_item = class Menupullright "_Fourier" "various Fourier filters" { preview_size = 64; sense_option = Option "Sense" [ "Pass", "Reject" ] 0; // make a visualisation image make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn) (im_create_fmask preview_size preview_size); // make the process function process fn in = (Image @ fn) (im_flt_image_freq in.value); New_ideal_item = class Menupullright "_Ideal" "various ideal Fourier filters" { High_low_item = class Menuaction "_High or Low Pass" "highpass/lowpass ideal Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f sense.value frequency_cutoff.value 0 0 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Ring_item = class Menuaction "_Ring Pass or Ring Reject" "ring pass/reject ideal Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; ring_width = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f (sense.value + 6) frequency_cutoff.value ring_width.value 0 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Band_item = class Menuaction "_Band Pass or Band Reject" "band pass/reject ideal Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff_x = Slider 0.01 0.99 0.5; frequency_cutoff_y = Slider 0.01 0.99 0.5; radius = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f (sense.value + 12) frequency_cutoff_x.value frequency_cutoff_y.value radius.value 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } } New_gaussian_item = class Menupullright "_Gaussian" "various Gaussian Fourier filters" { High_low_item = class Menuaction "_High or Low Pass" "highpass/lowpass Gaussian Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f (sense.value + 4) frequency_cutoff.value amplitude_cutoff.value 0 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Ring_item = class Menuaction "_Ring Pass or Ring Reject" "ring pass/reject Gaussian Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; ring_width = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f (sense.value + 10) frequency_cutoff.value ring_width.value amplitude_cutoff.value 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Band_item = class Menuaction "_Band Pass or Band Reject" "band pass/reject Gaussian Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff_x = Slider 0.01 0.99 0.5; frequency_cutoff_y = Slider 0.01 0.99 0.5; radius = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; // call a freq func with our parameters _params f = f (sense.value + 16) frequency_cutoff_x.value frequency_cutoff_y.value radius.value amplitude_cutoff.value 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } } New_butterworth_item = class Menupullright "_Butterworth" "various Butterworth Fourier filters" { High_low_item = class Menuaction "_High or Low Pass" "highpass/lowpass Butterworth Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; order = Slider 1 10 2; // call a freq func with our parameters _params f = f (sense.value + 2) order.value frequency_cutoff.value amplitude_cutoff.value 0 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Ring_item = class Menuaction "_Ring Pass or Ring Reject" "ring pass/reject Butterworth Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; ring_width = Slider 0.01 0.99 0.5; order = Slider 1 10 2; // call a freq func with our parameters _params f = f (sense.value + 8) order.value frequency_cutoff.value ring_width.value amplitude_cutoff.value 0; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } Band_item = class Menuaction "_Band Pass or Band Reject" "band pass/reject Butterworth Fourier filter" { action x = class _result { _vislevel = 3; sense = sense_option; frequency_cutoff_x = Slider 0.01 0.99 0.5; frequency_cutoff_y = Slider 0.01 0.99 0.5; radius = Slider 0.01 0.99 0.5; amplitude_cutoff = Slider 0.01 0.99 0.5; order = Slider 1 10 2; // call a freq func with our parameters _params f = f (sense.value + 14) order.value frequency_cutoff_x.value frequency_cutoff_y.value radius.value amplitude_cutoff.value; visualize_mask = make_vis _params; _result = map_unary (process _params) x; } } } } Filter_enhance_item = class Menupullright "_Enhance" "various enhancement filters" { Falsecolour_item = class Menuaction "_False Colour" "false colour a mono image" { action x = class _result { _vislevel = 3; offset = Slider (-255) 255 0; clip = Toggle "Clip colour range" false; _result = map_unary process x { process im = falsecolour mono'' { mono = colour_transform_to Image_type.B_W im; mono' = mono + offset; mono'' = (unsigned char) mono', clip = (unsigned char) (mono' & 0xff); } } } } Statistical_diff_item = class Menuaction "_Statistical Difference" "statistical difference of an image" { action x = class _result { _vislevel = 3; wsize = Expression "Window size" 11; tmean = Expression "Target mean" 128; mean_weight = Slider 0 1 0.8; tdev = Expression "Target deviation" 50; dev_weight = Slider 0 1 0.8; border = Toggle "Output image matches input image in size" true; _result = map_unary process x { process in = Image in'' { in' = colour_transform_to Image_type.B_W in.value; fn = im_stdif, border = im_stdif_raw; in'' = fn in' mean_weight.value tmean.expr dev_weight.value tdev.expr wsize.expr wsize.expr; } } } } Hist_equal_item = class Menupullright "_Equalise Histogram" "equalise contrast" { Global_item = class Menuaction "_Global" "equalise contrast globally" { action x = map_unary hist_equalize x; } Local_item = class Menuaction "_Local" "equalise contrast within a roving window" { action x = class _result { _vislevel = 3; width = Expression "Window width" 20; height = Expression "Window height" 20; _result = map_unary process x { process in = hist_equalize_local width.expr height.expr in; } } } } } #separator Filter_tilt_item = class Menupullright "Ti_lt Brightness" "tilt brightness" { Left_right_item = class Menuaction "_Left to Right" "linear left-right brighten" { action x = class _result { _vislevel = 3; tilt = Slider (-1) 1 0; _result = map_unary tilt_lr x { tilt_lr image = image * scale { ramp = im_fgrey image.width image.height; scale = (ramp - 0.5) * tilt + 1; } } } } Top_bottom_item = class Menuaction "_Top to Bottom" "linear top-bottom brighten" { action x = class _result { _vislevel = 3; tilt = Slider (-1) 1 0; _result = map_unary tilt_tb x { tilt_tb image = image * scale { ramp = rot90 (im_fgrey image.height image.width); scale = (ramp - 0.5) * tilt + 1; } } } } sep1 = Menuseparator; Left_right_cos_item = class Menuaction "Cosine Left-_right" "cosine left-right brighten" { action x = class _result { _vislevel = 3; tilt = Slider (-1) 1 0; shift = Slider (-1) 1 0; _result = map_unary tilt_lr x { tilt_lr image = image * scale { ramp = im_fgrey image.width image.height - 0.5 - shift.value; scale = 0.5 * tilt.value * cos (ramp * 180) + 1; } } } } Top_bottom_cos_item = class Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" { action x = class _result { _vislevel = 3; tilt = Slider (-1) 1 0; shift = Slider (-1) 1 0; _result = map_unary tilt_tb x { tilt_tb image = image * scale { ramp = rot90 (im_fgrey image.height image.width) - 0.5 - shift.value; scale = 0.5 * tilt.value * cos (ramp * 180) + 1; } } } } sep2 = Menuseparator; Circular_item = class Menuaction "_Circular" "circular brighten" { action x = class _result { _vislevel = 3; tilt = Slider (-1) 1 0; hshift = Slider (-1) 1 0; vshift = Slider (-1) 1 0; _result = map_unary tilt_tb x { tilt_tb image = image * scale { hramp = im_fgrey image.width image.height - 0.5 - hshift.value; vramp = rot90 (im_fgrey image.height image.width) - 0.5 - vshift.value; ramp = (hramp ** 2 + vramp ** 2) ** 0.5; scale = 0.5 * tilt.value * cos (ramp * 180) + 1; } } } } } Filter_blend_item = class Menupullright "_Blend" "blend objects together" { Slider_blend_item = class Menuaction "_Slider Blend" "blend two objects together with a slider" { action a b = class _result { _vislevel = 3; blend = Slider 0 1 0.5; _result = map_binary process a b { process im1 im2 = im1 * (1 - blend) + im2 * blend; } } } Image_blend_item = class Menuaction "_Image Blend" "use an image to blend two objects" { action a b c = map_trinary process a b c { process a b c = blend condition in1 in2 { compare a b // prefer image as the condition = false, !has_image a && has_image b // prefer mono images as the condition = false, has_bands a && has_bands b && get_bands a > 1 && get_bands b == 1 // prefer uchar as the condition = false, has_format a && has_format b && get_format a > Image_format.UCHAR && get_format b == Image_format.UCHAR = true; args' = sortc compare [a, b, c]; condition = args'?0; in1 = args'?1; in2 = args'?2; } } } Line_blend_item = class Menuaction "_Along Line" "blend between image a and image b along a line" { action a b = class _result { _vislevel = 3; orientation = Option "Orientation" [ "Left to Right", "Top to Bottom" ] 0; blend_position = Slider 0 1 0.5; blend_width = Slider 0 1 0.05; _result = map_binary process a b { process a b = blend (Image condition) b a { output_width = max_pair a.width b.width; output_height = max_pair a.height b.height; range = output_width, orientation == 0 = output_height; blend_position' = floor (range * blend_position.value); blend_width' = 1, blend_width.value == 0 = floor (range * blend_width.value); start = blend_position' - blend_width' / 2; background = (make_xy output_width output_height) >= blend_position'; ramp = im_grey blend_width' output_height, orientation == 0 = rot90 (im_grey blend_width' output_width); condition = insert_noexpand start 0 ramp background?0, orientation == 0 = insert_noexpand 0 start ramp background?1; } } } } } Filter_overlay_header_item = class Menuaction "_Overlay" "make a colour overlay of two monochrome images" { action a b = class _result { _vislevel = 3; colour = Option "Colour overlay as" [ "Green over Red", "Blue over Red", "Red over Green", "Red over Blue", "Blue over Green", "Green over Blue" ] 0; _result = map_binary overlay a b { overlay a b = image_set_type Image_type.sRGB [(a' ++ b' ++ 0), (a' ++ 0 ++ b'), (b' ++ a' ++ 0), (b' ++ 0 ++ a'), (0 ++ a' ++ b'), (0 ++ b' ++ a')]?colour { a' = colour_transform_to Image_type.B_W a; b' = colour_transform_to Image_type.B_W b; } } } } Filter_colourize_item = class Menuaction "_Colourize" "use a colour image or patch to tint a mono image" { action a b = class _result { _vislevel = 3; tint = Slider 0 1 0.6; _result = map_binary tintit a b { tintit a b = colour_transform_to (get_type colour) colourized' { // get the mono thing first args = sortc (const (is_colour_type @ get_type)) [a, b]; mono = args?0; colour = args?1; colour' = tint * colour_transform_to Image_type.LAB colour; mono' = colour_transform_to Image_type.B_W mono; colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2; colourized' = image_set_type Image_type.LAB colourized; } } } } Filter_browse_multiband_item = class Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" { Bandwise_item = class Menuaction "B_andwise" "browse through the bands of a multiband image" { action image = class _result { _vislevel = 3; band = Slider 0 (image.bands - 1) 0; display = Option "Display as" [ "Grey", "Green over Red", "Blue over Red", "Red over Green", "Red over Blue", "Blue over Green", "Green over Blue" ] 0; _result = output { down = (int) band.value; up = down + 1; remainder = band.value - down; fade x a = Vector [0], x == 0 = a * x; a = fade remainder image?up; b = fade (1 - remainder) image?down; output = [ a + b, a ++ b ++ 0, a ++ 0 ++ b, b ++ a ++ 0, b ++ 0 ++ a, 0 ++ a ++ b, 0 ++ b ++ a ] ? display; } } } Bitwise_item = class Menuaction "Bi_twise" "browse through the bits of an image" { action x = class _result { _vislevel = 3; bit = Islider 0 (nbits - 1) (nbits - 1) { nbits = x.bits, is_Image x = 8; Islider f t v = class scope.Slider f t ((int) v) { Slider = Islider; } } _result = map_unary process x { process im = (im & (0x1 << bit.value)) != 0; } } } } #separator Filter_negative_item = class Menuaction "Photographic _Negative" "swap black and white" { action x = map_unary invert x { invert in = clip2fmt in.format (colour_transform_to (get_type in) rgb') { rgb = colour_transform_to Image_type.sRGB in; rgb' = 255 - rgb; } } } Filter_solarize_item = class Menuaction "_Solarise" "invert colours above a threshold" { action x = class _result { _vislevel = 3; kink = Slider 0 1 0.5; _result = map_unary process x { process image = hist_map tab'''' image { // max pixel value for this format mx = Image_format.maxval image.format; // make a LUT ... just 8 and 16 bit tab = im_identity_ushort image.bands mx, image.format == Image_format.USHORT = im_identity image.bands; tab' = Image tab; // make basic ^ shape tab'' = tab' * (1 / kink), tab' < mx * kink = (mx - tab') / (1 - kink); tab''' = clip2fmt image.format tab''; // smooth a bit mask = matrix_blur (tab'''.width / 8); tab'''' = convsep mask tab'''; } } } } Filter_diffuse_glow_item = class Menuaction "_Diffuse Glow" "add a halo to highlights" { action x = class _result { _vislevel = 3; radius = Slider 0 50 5; highlights = Slider 0 100 95; glow = Slider 0 1 0.5; colour = Colour_new_item.Widget_colour_item.action; _result = map_unary process x { process image = image' { mono = (unsigned char) (colour_transform_to Image_type.B_W image); thresh = hist_thresh (highlights.value / 100) mono; mask = mono > thresh; blur = convsep (matrix_gaussian_blur radius.value) mask; colour' = colour_transform_to image.type colour; image' = image + colour' * glow * (blur / 255); } } } } Filter_drop_shadow_item = class Menuaction "Drop S_hadow" "add a drop shadow to an image" { action x = class _result { _vislevel = 3; shadow_x = Slider (-50) 50 5; shadow_y = Slider (-50) 50 5; shadow_softness = Slider 0 20 5; bg_colour = Expression "Background colour" 255; sd_colour = Expression "Shadow colour" 128; alpha = Toggle "Shadow in alpha channel" false; transparent = Toggle "Zero pixels are transparent" false; _result = map_unary shadow x { shadow image = Image final { blur_size = shadow_softness.value * 2 + 1; // matrix we blur with to soften shadows blur_matrix = matrix_gaussian_blur blur_size; matrix_size = blur_matrix.width; matrix_radius = (int) (matrix_size / 2) + 1; // position and size of shadow image in input cods // before and after fuzzing shadow_rect = Rect shadow_x.value shadow_y.value image.width image.height; fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius; // size and pos of final image, in input cods final_rect = image.rect.union fuzzy_shadow_rect; // hard part of shadow in output cods shadow_rect' = Rect (shadow_rect.left - final_rect.left) (shadow_rect.top - final_rect.top) shadow_rect.width shadow_rect.height; // make the shadow mask ... true for parts which cast // a shadow mask = (foldr1 bitwise_and @ bandsplit) (image.value != 0), transparent = image_new image.width image.height 1 Image_format.UCHAR Image_coding.NOCODING Image_type.B_W 255 0 0; mask' = embed 0 shadow_rect'.left shadow_rect'.top final_rect.width final_rect.height mask; mask'' = convsep blur_matrix mask'; // use mask to fade between bg and shadow colour mk_background colour = image_new final_rect.width final_rect.height image.bands image.format image.coding image.type colour 0 0; bg_image = mk_background bg_colour.expr; shadow_image = mk_background sd_colour.expr; bg = blend mask'' shadow_image bg_image; // make a full size mask fg_mask = embed 0 (image.rect.left - final_rect.left) (image.rect.top - final_rect.top) final_rect.width final_rect.height mask; // wrap up the input image ... put the shadow colour // around it, so if we are outputting a separate // alpha the shadow colour will be set correctly fg = insert (image.rect.left - final_rect.left) (image.rect.top - final_rect.top) image.value shadow_image; final // make a separate alpha = fg ++ mask'', alpha // paste image over shadow = if fg_mask then fg else bg; } } } } Filter_paint_text_item = class Menuaction "_Paint Text" "paint text into an image" { action x = paint_position, is_Group x = paint_area { paint_area = class _result { _check_args = [ [x, "x", check_Image] ]; _vislevel = 3; text = String "Text to paint" "Hello world!"; font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; align = Option "Alignment" ["Left", "Centre", "Right"] 0; dpi = Expression "DPI" 300; colour = Expression "Text colour" 255; place = Region x (x.width / 4) (x.height / 4) (x.width / 2) (x.height / 2); _result = insert_noexpand place.left place.top (blend txt' fg place) x { fg = image_new place.width place.height x.bands x.format x.coding x.type colour.expr 0 0; txt = Image (im_text text.value font.value place.width align.value (to_real dpi)); bg = im_black place.width place.height 1; txt' = insert_noexpand 0 0 txt bg; } } paint_position = class _result { _vislevel = 3; text = Pattern_images_item.Text_item.action; colour = Expression "Text colour" 255; position = Option "Position" [ "North-west", "North", "North-east", "West", "Centre", "East", "South-west", "South", "South-east", "Specify in pixels" ] 4; left = Expression "Pixels from left" 0; top = Expression "Pixels from top" 0; _result = map_unary paint x { paint image = insert_noexpand x' y' place' image { xr = image.width - text.width; yr = image.height - text.height; x = left.expr, position == 9 = [0, xr / 2, xr]?(position % 3); y = top.expr, position == 9 = [0, yr / 2, yr]?(position / 3); x' = range 0 x (image.width - 1); y' = range 0 y (image.height - 1); w' = range 1 text.width (image.width - x'); h' = range 1 text.height (image.height - y'); place = extract_area x' y' w' h' image; text' = insert_noexpand 0 0 text (im_black w' h' 1); fg = image_new w' h' image.bands image.format image.coding image.type colour.expr 0 0; place' = blend text' fg place; } } } } }