diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3809c..27dd31a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## master +- pattern matching rewrite +- add multiple defs and parameter pattern match to compiler +- add filemodel::new_from_file, rework filemodel_open +- reloading a toolkit now deletes all defs, then does load ... adding a new + def to a func across a parse unit is now an error + ## 9.0.14 2025/10/25 - fix load cancel diff --git a/TODO b/TODO index ee319da..8dc59d1 100644 --- a/TODO +++ b/TODO @@ -1,38 +1,54 @@ -- should allow vips_image ++ Image +- nicholas's bug report -- remove remove prefs workspace stuff? +- lcomps leave stray unreffed symbols in "list unresolved" - things like max heap size should be settable ... maybe pres are useful? + Image_transform_item.Resize_item.Size_within_item.action.within ($SAVEDIR/start/Image.def:509) refers to undefined symbol h + Image_transform_item.Resize_item.Size_within_item.action.within ($SAVEDIR/start/Image.def:509) refers to undefined symbol w -- add multiple definitions, finish function argument destructuring +- rework pattern matching in lcomps - sym has a field for "next definition", initially NULL +- what does the programming window do for multiple defs? -` add_defining looks for an existing sym with this name, if it finds - one, add a new sym called "{name}-$$4" or whatever + probably need to follow next_def and concatenate them all - chase to the end of "next definition" on the existing sym, append our - new sym +- dir should hide $$vars - during compile, generate code like +- programming window is showing generated syms, like $$pattern_lhs0? - fred a b c - = destructured_fred, args_match - = fred-$$1 a b c - { - destructured_fred = rhs of fred - args_match = a_matches && b_matches && c_matches - } +- ban patterns in class parameters + + or could we allow multiple class defs? + + Fred (Image x) = class { ... }; - if a func has many RHS and the last RHS uses destrucuring, generate a - final def with + probably only useful for trivial classes + +- what about + + fred (list x) = ...; + fred (complex x) = ...; + + ie. allow names of builtin types as well as class names + + maybe: - fred a b c - = error "no def of fred matches args a b c"; + fred (is_list x) = ...; - - error if more than one def of fred has no destructuring - - error if defs don't all have the same number of args - - error if a def with no destructuring isn't the last def + ie. a predicate before the arg, as well as a class name? + +- do we allow eg. + + fred [a, b ..] = a + b; + + equivalent to + + fred a:b:x = a + b; + + + +- remove remove prefs workspace stuff? + + things like max heap size should be settable ... maybe pres are useful? - try < > in the image titlebar diff --git a/meson.build b/meson.build index 31cdb43..f4f116b 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('nip4', 'c', # ie. a major after nip2 8.9 for workspace save file versioning - version: '9.0.14', + version: '9.0.15', license: 'GPL', meson_version: '>=0.64', default_options: [ diff --git a/share/nip4/start/Image.def b/share/nip4/start/Image.def index 1f670ab..2749a83 100644 --- a/share/nip4/start/Image.def +++ b/share/nip4/start/Image.def @@ -1,2375 +1,2372 @@ Image_new_item = class Menupullright "_New" "make new things" { - Image_black_item = class Menuaction "_Image" "make a new image" { - format_names = [ - "8-bit unsigned int - UCHAR", // 0 - "8-bit signed int - CHAR", // 1 - "16-bit unsigned int - USHORT", // 2 - "16-bit signed int - SHORT", // 3 - "32-bit unsigned int - UINT", // 4 - "32-bit signed int - INT", // 5 - "32-bit float - FLOAT", // 6 - "64-bit complex - COMPLEX", // 7 - "64-bit float - DOUBLE", // 8 - "128-bit complex - DPCOMPLEX" // 9 - ]; - - action = class - Image _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - nbands = Expression "Image bands" 1; - format_option = Option "Image format" format_names 0; - type_option = Option_enum "Image type" - Image_type.type_names "B_W"; - pixel = Expression "Pixel value" 0; - - _result - = image_new (to_real nwidth) (to_real nheight) (to_real nbands) - (to_real format_option) Image_coding.NOCODING - type_option.value_thing pixel.expr 0 0; - } - } - - Image_new_from_image_item = class - Menuaction "_From Image" "make a new image based on image x" { - action x = class - Image _result { - _vislevel = 3; - - pixel = Expression "Pixel value" 0; - - _result - = image_new x.width x.height x.bands - x.format x.coding x.type pixel.expr x.xoffset x.yoffset; - } - } - - Image_region_item = class - Menupullright "_Region on Image" "make a new region on an image" { - Region_item = class - Menuaction "_Region" "make a region on an image" { - action image = scope.Region_relative image 0.25 0.25 0.5 0.5; - } - - Mark_item = class - Menuaction "_Point" "make a point on an image" { - action image = scope.Mark_relative image 0.5 0.5; - } - - Arrow_item = class - Menuaction "_Arrow" "make an arrow on an image" { - action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5; - } - - HGuide_item = class - Menuaction "_Horizontal Guide" - "make a horizontal guide on an image" { - action image = scope.HGuide image 0.5; - } - - VGuide_item = class - Menuaction "_Vertical Guide" "make a vertical guide on an image" { - action image = scope.VGuide image 0.5; - } - - sep1 = Menuseparator; - - Move_item = class - Menuaction "From Region" - "new region on image using existing region as a guide" { - action a b - = map_binary process a b - { - process a b - = x.Region target x.left x.top x.width x.height, - is_Region x - = x.Arrow target x.left x.top x.width x.height, - is_Arrow x - = error "bad arguments to region-from-region" - { - // prefer image then region - compare a b - = false, - !is_Image a && is_Image b - = false, - is_Region a && !is_Region b - = true; - - [target, x] = sortc compare [a, b]; - } - } - } - } + Image_black_item = class Menuaction "_Image" "make a new image" { + format_names = [ + "8-bit unsigned int - UCHAR", // 0 + "8-bit signed int - CHAR", // 1 + "16-bit unsigned int - USHORT", // 2 + "16-bit signed int - SHORT", // 3 + "32-bit unsigned int - UINT", // 4 + "32-bit signed int - INT", // 5 + "32-bit float - FLOAT", // 6 + "64-bit complex - COMPLEX", // 7 + "64-bit float - DOUBLE", // 8 + "128-bit complex - DPCOMPLEX" // 9 + ]; + + action = class + Image _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + nbands = Expression "Image bands" 1; + format_option = Option "Image format" format_names 0; + type_option = Option_enum "Image type" + Image_type.type_names "B_W"; + pixel = Expression "Pixel value" 0; + + _result + = image_new (to_real nwidth) (to_real nheight) (to_real nbands) + (to_real format_option) Image_coding.NOCODING + type_option.value_thing pixel.expr 0 0; + } + } + + Image_new_from_image_item = class + Menuaction "_From Image" "make a new image based on image x" { + action x = class + Image _result { + _vislevel = 3; + + pixel = Expression "Pixel value" 0; + + _result + = image_new x.width x.height x.bands + x.format x.coding x.type pixel.expr x.xoffset x.yoffset; + } + } + + Image_region_item = class + Menupullright "_Region on Image" "make a new region on an image" { + Region_item = class + Menuaction "_Region" "make a region on an image" { + action image = scope.Region_relative image 0.25 0.25 0.5 0.5; + } + + Mark_item = class + Menuaction "_Point" "make a point on an image" { + action image = scope.Mark_relative image 0.5 0.5; + } + + Arrow_item = class + Menuaction "_Arrow" "make an arrow on an image" { + action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5; + } + + HGuide_item = class + Menuaction "_Horizontal Guide" + "make a horizontal guide on an image" { + action image = scope.HGuide image 0.5; + } + + VGuide_item = class + Menuaction "_Vertical Guide" "make a vertical guide on an image" { + action image = scope.VGuide image 0.5; + } + + sep1 = Menuseparator; + + Move_item = class + Menuaction "From Region" + "new region on image using existing region as a guide" { + action a b + = map_binary process a b + { + process a b + = x.Region target x.left x.top x.width x.height, + is_Region x + = x.Arrow target x.left x.top x.width x.height, + is_Arrow x + = error "bad arguments to region-from-region" + { + // prefer image then region + compare a b + = false, + !is_Image a && is_Image b + = false, + is_Region a && !is_Region b + = true; + + [target, x] = sortc compare [a, b]; + } + } + } + } } Image_convert_to_image_item = class - Menuaction "Con_vert to Image" "convert anything to an image" { - action x = to_image x; + Menuaction "Con_vert to Image" "convert anything to an image" { + action x = to_image x; } Image_number_format_item = class - Menupullright "_Format" "convert numeric format" { + Menupullright "_Format" "convert numeric format" { - U8_item = class - Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" { - action x = map_unary cast_unsigned_char x; - } + U8_item = class + Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" { + action x = map_unary cast_unsigned_char x; + } - U16_item = class - Menuaction "1_6 bit unsigned" - "convert to unsigned 16 bit [0, 65535]" { - action x = map_unary cast_unsigned_short x; - } + U16_item = class + Menuaction "1_6 bit unsigned" + "convert to unsigned 16 bit [0, 65535]" { + action x = map_unary cast_unsigned_short x; + } - U32_item = class - Menuaction "_32 bit unsigned" - "convert to unsigned 32 bit [0, 4294967295]" { - action x = map_unary cast_unsigned_int x; - } + U32_item = class + Menuaction "_32 bit unsigned" + "convert to unsigned 32 bit [0, 4294967295]" { + action x = map_unary cast_unsigned_int x; + } sep1 = Menuseparator; - S8_item = class - Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" { - action x = map_unary cast_signed_char x; - } + S8_item = class + Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" { + action x = map_unary cast_signed_char x; + } - S16_item = class - Menuaction "16 b_it signed" - "convert to signed 16 bit [-32768, 32767]" { - action x = map_unary cast_signed_short x; - } + S16_item = class + Menuaction "16 b_it signed" + "convert to signed 16 bit [-32768, 32767]" { + action x = map_unary cast_signed_short x; + } - S32_item = class - Menuaction "32 bi_t signed" - "convert to signed 32 bit [-2147483648, 2147483647]" { - action x = map_unary cast_signed_int x; - } + S32_item = class + Menuaction "32 bi_t signed" + "convert to signed 32 bit [-2147483648, 2147483647]" { + action x = map_unary cast_signed_int x; + } sep2 = Menuseparator; - Float_item = class - Menuaction "_Single precision float" - "convert to IEEE 32 bit float" { - action x = map_unary cast_float x; - } + Float_item = class + Menuaction "_Single precision float" + "convert to IEEE 32 bit float" { + action x = map_unary cast_float x; + } - Double_item = class - Menuaction "_Double precision float" - "convert to IEEE 64 bit float" { - action x = map_unary cast_double x; - } + Double_item = class + Menuaction "_Double precision float" + "convert to IEEE 64 bit float" { + action x = map_unary cast_double x; + } sep3 = Menuseparator; - Scmplxitem = class - Menuaction "Single _precision complex" - "convert to 2 x IEEE 32 bit float" { - action x = map_unary cast_complex x; - } - - Dcmplx_item = class - Menuaction "Double p_recision complex" - "convert to 2 x IEEE 64 bit float" { - action x = map_unary cast_double_complex x; - } + Scmplxitem = class + Menuaction "Single _precision complex" + "convert to 2 x IEEE 32 bit float" { + action x = map_unary cast_complex x; + } + + Dcmplx_item = class + Menuaction "Double p_recision complex" + "convert to 2 x IEEE 64 bit float" { + action x = map_unary cast_double_complex x; + } } Image_header_item = class - Menupullright "_Header" "do stuff to the image header" { - - Image_get_item = class - Menupullright "_Get" "get header fields" { - - // the header fields we can get - fields = class { - type = 0; - width = 1; - height = 2; - format = 3; - bands = 4; - xres = 5; - yres = 6; - xoffset = 7; - yoffset = 8; - coding = 9; - - field_names = Enum [ - $width => width, - $height => height, - $bands => bands, - $format => format, - $type => type, - $xres => xres, - $yres => yres, - $xoffset => xoffset, - $yoffset => yoffset, - $coding => coding - ]; - - field_option name = Option_enum (_ "Field") field_names name; - - field_funcs = Table [ - [type, get_type], - [width, get_width], - [height, get_height], - [format, get_format], - [bands, get_bands], - [xres, get_xres], - [yres, get_yres], - [xoffset, get_xoffset], - [yoffset, get_yoffset], - [coding, get_coding] - ]; - } - - get_field field_name x = class - _result { - _vislevel = 3; - - field = fields.field_option field_name; - - _result - = map_unary (Real @ - fields.field_funcs.lookup 0 1 field.value_thing) x; - } - - Width_item = class - Menuaction "_Width" "get width" { - action x = get_field "width" x; - } - - Height_item = class - Menuaction "_Height" "get height" { - action x = get_field "height" x; - } - - Bands_item = class - Menuaction "_Bands" "get bands" { - action x = get_field "bands" x; - } - - Format_item = class - Menuaction "_Format" "get format" { - action x = get_field "format" x; - } - - Type_item = class - Menuaction "_Type" "get type" { - action x = get_field "type" x; - } - - Xres_item = class - Menuaction "_Xres" "get X resolution" { - action x = get_field "xres" x; - } - - Yres_item = class - Menuaction "_Yres" "get Y resolution" { - action x = get_field "yres" x; - } - - Xoffset_item = class - Menuaction "X_offset" "get X offset" { - action x = get_field "xoffset" x; - } - - Yoffset_item = class - Menuaction "Yo_ffset" "get Y offset" { - action x = get_field "yoffset" x; - } - - Coding_item = class - Menuaction "_Coding" "get coding" { - action x = get_field "coding" x; - } - - sep1 = Menuseparator; - - Custom_item = class - Menuaction "C_ustom" "get any header field" { - action x = class - _result { - _vislevel = 3; - - field = String "Field" "Xsize"; - parse = Option "Parse" [ - "No parsing", - "Parse string as integer", - "Parse string as real", - "Parse string as hh:mm:ss" - ] 0; - - _result - = map_unary (wrap @ process @ get_header field.value) x - { - parse_str parse str = parse (split is_space str)?0; - - parse_field name cast parse x - = cast x, is_number x - = parse_str parse x, is_string x - = error ("not " ++ name); - - get_int = parse_field "int" - cast_signed_int parse_int; - get_float = parse_field "float" - cast_float parse_float; - get_time = parse_field "hh:mm:ss" - cast_signed_int parse_time; - - wrap x - = Real x, is_real x - = Vector x, is_real_list x - = Image x, is_image x - = Bool x, is_bool x - = Matrix x, is_matrix x - = String "String" x, is_string x - = List x, is_list x - = x; - - process = [ - id, - get_int, - get_float, - get_time - ]?parse; - } - } - } - } + Menupullright "_Header" "do stuff to the image header" { + + Image_get_item = class + Menupullright "_Get" "get header fields" { + + // the header fields we can get + fields = class { + type = 0; + width = 1; + height = 2; + format = 3; + bands = 4; + xres = 5; + yres = 6; + xoffset = 7; + yoffset = 8; + coding = 9; + + field_names = Enum [ + $width => width, + $height => height, + $bands => bands, + $format => format, + $type => type, + $xres => xres, + $yres => yres, + $xoffset => xoffset, + $yoffset => yoffset, + $coding => coding + ]; + + field_option name = Option_enum (_ "Field") field_names name; + + field_funcs = Table [ + [type, get_type], + [width, get_width], + [height, get_height], + [format, get_format], + [bands, get_bands], + [xres, get_xres], + [yres, get_yres], + [xoffset, get_xoffset], + [yoffset, get_yoffset], + [coding, get_coding] + ]; + } + + get_field field_name x = class + _result { + _vislevel = 3; + + field = fields.field_option field_name; + + _result + = map_unary (Real @ + fields.field_funcs.lookup 0 1 field.value_thing) x; + } + + Width_item = class + Menuaction "_Width" "get width" { + action x = get_field "width" x; + } + + Height_item = class + Menuaction "_Height" "get height" { + action x = get_field "height" x; + } + + Bands_item = class + Menuaction "_Bands" "get bands" { + action x = get_field "bands" x; + } + + Format_item = class + Menuaction "_Format" "get format" { + action x = get_field "format" x; + } + + Type_item = class + Menuaction "_Type" "get type" { + action x = get_field "type" x; + } + + Xres_item = class + Menuaction "_Xres" "get X resolution" { + action x = get_field "xres" x; + } + + Yres_item = class + Menuaction "_Yres" "get Y resolution" { + action x = get_field "yres" x; + } + + Xoffset_item = class + Menuaction "X_offset" "get X offset" { + action x = get_field "xoffset" x; + } + + Yoffset_item = class + Menuaction "Yo_ffset" "get Y offset" { + action x = get_field "yoffset" x; + } + + Coding_item = class + Menuaction "_Coding" "get coding" { + action x = get_field "coding" x; + } + + sep1 = Menuseparator; + + Custom_item = class + Menuaction "C_ustom" "get any header field" { + action x = class + _result { + _vislevel = 3; + + field = String "Field" "Xsize"; + parse = Option "Parse" [ + "No parsing", + "Parse string as integer", + "Parse string as real", + "Parse string as hh:mm:ss" + ] 0; + + _result + = map_unary (wrap @ process @ get_header field.value) x + { + parse_str parse str = parse (split is_space str)?0; + + parse_field name cast parse x + = cast x, is_number x + = parse_str parse x, is_string x + = error ("not " ++ name); + + get_int = parse_field "int" + cast_signed_int parse_int; + get_float = parse_field "float" + cast_float parse_float; + get_time = parse_field "hh:mm:ss" + cast_signed_int parse_time; + + wrap x + = Real x, is_real x + = Vector x, is_real_list x + = Image x, is_image x + = Bool x, is_bool x + = Matrix x, is_matrix x + = String "String" x, is_string x + = List x, is_list x + = x; + + process = [ + id, + get_int, + get_float, + get_time + ]?parse; + } + } + } + } sep1 = Menuseparator; - Image_set_meta_item = class - Menuaction "_Set" "set image metadata" { - action x = class - _result { - _vislevel = 3; - - fname = String "Field" "field-name"; - val = Expression "Value" 42; - - _result - = map_unary process x - { - process image - = set_header fname.value val.expr image; - } - } - } - - Image_edit_header_item = class - Menuaction "_Edit" "change advisory header fields of image" { - type_names = Image_type.type_names; - all_names = sort (map (extract 0) type_names.value); - - get_prop has get def x - = get x, has x - = def; - - action x = class - _result { - _vislevel = 3; - - nxres = Expression "Xres" (get_prop has_xres get_xres 1 x); - nyres = Expression "Yres" (get_prop has_yres get_yres 1 x); - nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x); - nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x); - - type_option - = Option_enum "Image type" Image_type.type_names - (Image_type.type_names.get_name type) - { - type - = x.type, is_Image x - = Image_type.MULTIBAND; - } - - _result - = map_unary process x - { - process image - = Image (im_copy_set image.value type_option.value_thing - (to_real nxres) (to_real nyres) - (to_real nxoff) (to_real nyoff)); - } - } - } + Image_set_meta_item = class + Menuaction "_Set" "set image metadata" { + action x = class + _result { + _vislevel = 3; + + fname = String "Field" "field-name"; + val = Expression "Value" 42; + + _result + = map_unary process x + { + process image + = set_header fname.value val.expr image; + } + } + } + + Image_edit_header_item = class + Menuaction "_Edit" "change advisory header fields of image" { + type_names = Image_type.type_names; + all_names = sort (map (extract 0) type_names.value); + + get_prop has get def x + = get x, has x + = def; + + action x = class + _result { + _vislevel = 3; + + nxres = Expression "Xres" (get_prop has_xres get_xres 1 x); + nyres = Expression "Yres" (get_prop has_yres get_yres 1 x); + nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x); + nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x); + + type_option + = Option_enum "Image type" Image_type.type_names + (Image_type.type_names.get_name type) + { + type + = x.type, is_Image x + = Image_type.MULTIBAND; + } + + _result + = map_unary process x + { + process image + = Image (im_copy_set image.value type_option.value_thing + (to_real nxres) (to_real nyres) + (to_real nxoff) (to_real nyoff)); + } + } + } } Image_cache_item = class - Menuaction "C_ache" "cache calculated image pixels" { - action x = class - _result { - _vislevel = 3; - - tile_width = Number "Tile width" 128; - tile_height = Number "Tile height" 128; - max_tiles = Number "Maximum number of tiles to cache" (-1); - - _result - = map_unary process x - { - process image - = cache (to_real tile_width) (to_real tile_height) - (to_real max_tiles) image; - } - } + Menuaction "C_ache" "cache calculated image pixels" { + action x = class + _result { + _vislevel = 3; + + tile_width = Number "Tile width" 128; + tile_height = Number "Tile height" 128; + max_tiles = Number "Maximum number of tiles to cache" (-1); + + _result + = map_unary process x + { + process image + = cache (to_real tile_width) (to_real tile_height) + (to_real max_tiles) image; + } + } } #separator Image_levels_item = class - Menupullright "_Levels" "change image levels" { - Scale_item = class - Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" { - action x = map_unary scale x; - } - - Linear_item = class - Menuaction "_Linear" "linear transform of image levels" { - action x = class - _result { - _vislevel = 3; - - scale = Scale "Scale" 0.001 3 1; - offset = Scale "Offset" (-128) 128 0; - - _result - = map_unary adj x - { - adj x - // only force back to input type if this is a thing - // with a type ... so we work for Colour / Matrix etc. - = clip2fmt x.format x', has_member "format" x - = x' - { - x' = x * scale + offset; - } - } - } - } - - Gamma_item = class - Menuaction "_Power" "power transform of image levels (gamma)" { - action x = class - _result { - _vislevel = 3; - - gamma = Scale "Gamma" 0.001 4 1; - image_maximum_hint = "You may need to change image_maximum if " ++ - "this is not an 8 bit image"; - im_mx - = Expression "Image maximum" mx - { - mx - = Image_format.maxval x.format, has_format x - = 255; - } - - _result - = map_unary gam x - { - gam x - = clip2fmt (get_format x) x', has_format x - = x' - { - x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma; - } - } - } - } - - Tone_item = class - Menuaction "_Tone Curve" "adjust tone curve" { - action x = class - _result { - _vislevel = 3; - - b = Scale "Black point" 0 100 0; - w = Scale "White point" 0 100 100; - - sp = Scale "Shadow point" 0.1 0.3 0.2; - mp = Scale "Mid-tone point" 0.4 0.6 0.5; - hp = Scale "Highlight point" 0.7 0.9 0.8; - - sa = Scale "Shadow adjust" (-15) 15 0; - ma = Scale "Mid-tone adjust" (-30) 30 0; - ha = Scale "Highlight adjust" (-15) 15 0; - - curve = tone_build x.format b w sp mp hp sa ma ha; - - _result = map_unary (hist_map curve) x; - } - } + Menupullright "_Levels" "change image levels" { + Scale_item = class + Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" { + action x = map_unary scale x; + } + + Linear_item = class + Menuaction "_Linear" "linear transform of image levels" { + action x = class + _result { + _vislevel = 3; + + scale = Scale "Scale" 0.001 3 1; + offset = Scale "Offset" (-128) 128 0; + + _result + = map_unary adj x + { + adj x + // only force back to input type if this is a thing + // with a type ... so we work for Colour / Matrix etc. + = clip2fmt x.format x', has_member "format" x + = x' + { + x' = x * scale + offset; + } + } + } + } + + Gamma_item = class + Menuaction "_Power" "power transform of image levels (gamma)" { + action x = class + _result { + _vislevel = 3; + + gamma = Scale "Gamma" 0.001 4 1; + image_maximum_hint = "You may need to change image_maximum if " ++ + "this is not an 8 bit image"; + im_mx + = Expression "Image maximum" mx + { + mx + = Image_format.maxval x.format, has_format x + = 255; + } + + _result + = map_unary gam x + { + gam x + = clip2fmt (get_format x) x', has_format x + = x' + { + x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma; + } + } + } + } + + Tone_item = class + Menuaction "_Tone Curve" "adjust tone curve" { + action x = class + _result { + _vislevel = 3; + + b = Scale "Black point" 0 100 0; + w = Scale "White point" 0 100 100; + + sp = Scale "Shadow point" 0.1 0.3 0.2; + mp = Scale "Mid-tone point" 0.4 0.6 0.5; + hp = Scale "Highlight point" 0.7 0.9 0.8; + + sa = Scale "Shadow adjust" (-15) 15 0; + ma = Scale "Mid-tone adjust" (-30) 30 0; + ha = Scale "Highlight adjust" (-15) 15 0; + + curve = tone_build x.format b w sp mp hp sa ma ha; + + _result = map_unary (hist_map curve) x; + } + } } Image_transform_item = class - Menupullright "_Transform" "transform images" { - Rotate_item = class - Menupullright "Ro_tate" "rotate image" { - Fixed_item = class - Menupullright "_Fixed" "clockwise rotation by fixed angles" { - rotate_widget default x = class - _result { - _vislevel = 3; - - angle = Option "Rotate by" [ - "Don't rotate", - "90 degrees clockwise", - "180 degrees", - "90 degrees anticlockwise" - ] default; - - _result - = map_unary process x - { - process = [ - // we can't use id here since we want to "declass" - // the members of x ... consider if x is a crop class, - // for example, we don't want to inherit from crop, we - // want to make a new image class - rot180 @ rot180, - rot90, - rot180, - rot270 - ] ? angle; - } - } - - Rot90_item = class - Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" { - action x = rotate_widget 1 x; - } - - Rot180_item = class - Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" { - action x = rotate_widget 2 x; - } - - Rot270_item = class - Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" { - action x = rotate_widget 3 x; - } - } - - Free_item = class - Menuaction "_Free" "clockwise rotation by any angle" { - action x = class - _result { - _vislevel = 3; - - angle = Scale "Angle" (-180) 180 0; - interp = Interpolate_picker Interpolate_type.BILINEAR; - - _result - = map_unary process x - { - process image - = rotate interp angle image; - } - } - } - - Straighten_item = class - Menuaction "_Straighten" - ("smallest rotation that makes an arrow either horizontal " ++ - "or vertical") { - action x = class - _result { - _vislevel = 3; - - interp = Interpolate_picker Interpolate_type.BILINEAR; - - _result - = map_unary straighten x - { - straighten arrow - = rotate interp angle'' arrow.image - { - x = arrow.width; - y = arrow.height; - - angle = im (polar (x, y)); - - angle' - = angle - 360, angle > 315 - = angle - 180, angle > 135 - = angle; - - angle'' - = -angle', angle' >= (-45) && angle' < 45 - = 90 - angle'; - } - } - } - } - } - - Flip_item = class - Menupullright "_Flip" "mirror left/right or up/down" { - Left_right_item = class - Menuaction "_Left Right" "mirror object left/right" { - action x = map_unary fliplr x; - } - - Top_bottom_item = class - Menuaction "_Top Bottom" "mirror object top/bottom" { - action x = map_unary fliptb x; - } - } - - Resize_item = class - Menupullright "_Resize" "change image size" { - Scale_item = class - Menuaction "_Scale" "scale image size by a factor" { - action x = class - _result { - _vislevel = 3; - - xfactor = Expression "Horizontal scale factor" 1; - yfactor = Expression "Vertical scale factor" 1; - kernel = Kernel_picker Kernel_type.LINEAR; - - _result - = map_unary process x - { - process image - = resize kernel xfactor yfactor image; - } - } - } - - Size_item = class - Menuaction "_Size To" "resize to a fixed size" { - action x = class - _result { - _vislevel = 3; - - which = Option "Resize axis" [ - "Shortest", - "Longest", - "Horizontal", - "Vertical" - ] 0; - size = Expression "Resize to (pixels)" 128; - aspect = Toggle "Break aspect ratio" false; - kernel = Kernel_picker Kernel_type.LINEAR; - - _result - = map_unary process x - { - process image - = resize kernel h v image, aspect - = resize kernel fac fac image - { - xfac = to_real size / image.width; - yfac = to_real size / image.height; - max_factor - = [xfac, 1], xfac > yfac - = [1, yfac]; - min_factor - = [xfac, 1], xfac < yfac - = [1, yfac]; - [h, v] = [ - max_factor, - min_factor, - [xfac, 1], - [1, yfac]]?which; - - fac - = h, v == 1 - = v; - } - } - } - } - - Size_within_item = class - Menuaction "Size _Within" "size to fit within a rectangle" { - action x = class - _result { - _vislevel = 3; - - // the rects we size to fit within - _rects = [ - [2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050], - [1280, 1024], [1024, 768], [800, 600], [640, 480] - ]; - - within = Option "Fit within (pixels)" ( - [print w ++ " x " ++ print h :: [w, h] <- _rects] ++ - ["Custom"] - ) 4; - custom_width = Expression "Custom width" 1000; - custom_height = Expression "Custom height" 1000; - size = Option "Page size" [ - "Full page", "Half page", "Quarter page" - ] 0; - kernel = Kernel_picker Kernel_type.LINEAR; - - _result - = map_unary process x - { - xdiv = [1, 2, 2]?size; - ydiv = [1, 1, 2]?size; - allrect = _rects ++ [ - [custom_width.expr, custom_height.expr] - ]; - [width, height] = allrect?within; - - process x - = resize kernel fac fac x, fac < 1 - = x - { - xfac = (width / xdiv) / x.width; - yfac = (height / ydiv) / x.height; - fac = min_pair xfac yfac; - } - } - } - } - - Resize_canvas_item = class - Menuaction "_Canvas" "change size of surrounding image" { - action x = class - _result { - _vislevel = 3; - - // try to guess a sensible size for the new image - _guess_size - = x.rect, is_Image x - = Rect 0 0 100 100; - - nwidth = Expression "New width (pixels)" _guess_size.width; - nheight = Expression "New height (pixels)" _guess_size.height; - bgcolour = Expression "Background colour" 0; - - 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 process x - { - process image - = insert_noexpand xp yp image background - { - width = image.width; - height = image.height; - coding = image.coding; - bands - = 3, coding == Image_coding.LABPACK - = image.bands; - format - = Image_format.FLOAT, coding == Image_coding.LABPACK - = image.format; - type = image.type; - - // placement vectors ... left, centre, right - xposv = [0, to_real nwidth / 2 - width / 2, - to_real nwidth - width]; - yposv = [0, to_real nheight / 2 - height / 2, - to_real nheight - height]; - xp - = left, position == 9 - = xposv?((int) (position % 3)); - yp - = top, position == 9 - = yposv?((int) (position / 3)); - - background = image_new nwidth nheight - bands format coding type bgcolour.expr 0 0; - } - } - } - } - } - - Image_map_item = class - Menuaction "_Map" "map an image through a 2D transform image" { - action a b = class - _result { - _vislevel = 3; - - interp = Interpolate_picker Interpolate_type.BILINEAR; - - _result - = map_binary trans a b - { - trans a b - = mapim interp.value in index - { - // get the index image first - [index, in] = sortc (const is_twocomponent) [a, b]; - - // is a two-component image, ie. one band complex, or - // two-band non-complex - is_twocomponent x - = is_nonc x || is_c x; - is_nonc x - = has_bands x && get_bands x == 2 && - has_format x && !is_complex_format (get_format x); - is_c x - = has_bands x && get_bands x == 1 && - has_format x && is_complex_format (get_format x); - is_complex_format f - = f == Image_format.COMPLEX || - f == Image_format.DPCOMPLEX; - } - } - } - } - - Image_perspective_item = Perspective_item; - - Image_rubber_item = class - Menupullright "Ru_bber Sheet" - "automatically warp images to superposition" { - rubber_interp = Option "Interpolation" ["Nearest", "Bilinear"] 1; - rubber_order = Option "Order" ["0", "1", "2", "3"] 1; - rubber_wrap = Toggle "Wrap image edges" false; - - // a transform ... a matrix, plus the size of the image the - // matrix was made for - Transform matrix image_width image_height = class - matrix { - // scale a transform ... if it worked for a m by n image, make - // it work for a (m * xfac) by (y * yfac) image - rescale xfac yfac - = Transform (Matrix (map2 (map2 multiply) matrix.value facs)) - (image_width * xfac) (image_height * yfac) - { - facs = [ - [xfac, yfac], - [1, 1], - [1, 1], - [1 / xfac, 1 / yfac], - [1 / xfac, 1 / yfac], - [1 / xfac, 1 / yfac] - ]; - } - } - - // yuk!!!! fix is_instanceof to not need absolute names - is_Transform = is_instanceof - "Image_transform_item.Image_rubber_item.Transform"; - - Find_item = class - Menuaction "_Find" - ("find a transform which will map sample image onto " ++ - "reference") { - action reference sample = class - _trn { - _vislevel = 3; - - // controls - order = rubber_order; - interp = rubber_interp; - wrap = rubber_wrap; - max_err = Expression "Maximum error" 0.3; - max_iter = Expression "Maximum iterations" 10; - - // transform - [sample', trn, err] = transform_search - max_err max_iter order interp wrap - sample reference; - transformed_image = Image sample'; - _trn = Transform trn reference.width reference.height; - final_error = err; - } - } - - Apply_item = class - Menuaction "_Apply" "apply a transform to an image" { - action a b = class - _result { - _vislevel = 3; - - // controls - interp = rubber_interp; - wrap = rubber_wrap; - - _result - = map_binary trans a b - { - trans a b - = transform interp wrap t' i - { - // get the transform arg first - [i, t] = sortc (const is_Transform) [a, b]; - t' = t.rescale (i.width / t.image_width) - (i.height / t.image_height); - } - } - } - } - } + Menupullright "_Transform" "transform images" { + Rotate_item = class + Menupullright "Ro_tate" "rotate image" { + Fixed_item = class + Menupullright "_Fixed" "clockwise rotation by fixed angles" { + rotate_widget default x = class + _result { + _vislevel = 3; + + angle = Option "Rotate by" [ + "Don't rotate", + "90 degrees clockwise", + "180 degrees", + "90 degrees anticlockwise" + ] default; + + _result + = map_unary process x + { + process = [ + // we can't use id here since we want to "declass" + // the members of x ... consider if x is a crop class, + // for example, we don't want to inherit from crop, we + // want to make a new image class + rot180 @ rot180, + rot90, + rot180, + rot270 + ] ? angle; + } + } - sep1 = Menuseparator; + Rot90_item = class + Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" { + action x = rotate_widget 1 x; + } - Match_item = class - Menuaction "_Linear Match" - "rotate and scale one image to match another" { - action x y = class - _result { - _vislevel = 3; - - // try to find an image ... for a group, get the first item - find_image x - = x, is_Image x - = find_image x?0, is_list x - = find_image x.value, is_class x && has_value x - = error "unable to find image"; - - _a = find_image x; - _b = find_image y; - - ap1 = Mark_relative _a 0.5 0.25; - bp1 = Mark_relative _b 0.5 0.25; - ap2 = Mark_relative _a 0.5 0.75; - bp2 = Mark_relative _b 0.5 0.75; - - refine = Toggle "Refine selected tie-points" false; - lock = Toggle "No resize" false; - - _result - = map_binary process x y - { - process a b - = Image b''' - { - _prefs = Workspaces.Preferences; - window = _prefs.MOSAIC_WINDOW_SIZE; - object = _prefs.MOSAIC_OBJECT_SIZE; - - a' = a.value; - b' = b.value; - - b'' = clip2fmt a.format b'; - - // return p2 ... if lock is set, return a p2 a standard - // distance along the vector joining p1 and p2 - norm p1 p2 - = Rect left' top' 0 0, lock - = p2 - { - v = (p2.left - p1.left, p2.top - p1.top); - // 100000 to give precision since we pass points as - // ints to match - n = 100000 * sign v; - left' = p1.left + re n; - top' = p1.top + im n; - } - - ap2'' = norm ap1 ap2; - bp2'' = norm bp1 bp2; - - b''' - = im_match_linear_search a' b'' - ap1.left ap1.top bp1.left bp1.top - ap2''.left ap2''.top bp2''.left bp2''.top - object window, - // we can't search if lock is on - refine && !lock - = im_match_linear a' b'' - ap1.left ap1.top bp1.left bp1.top - ap2''.left ap2''.top bp2''.left bp2''.top; - } - } - } - } - - Image_perspective_match_item = Perspective_match_item; -} + Rot180_item = class + Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" { + action x = rotate_widget 2 x; + } -Image_band_item = class - Menupullright "_Band" "manipulate image bands" { - // like extract_bands, but return [] for zero band image - // makes compose a bit simpler - exb b n x - = [], to_real n == 0 - = extract_bands b n x; - - Extract_item = class Menuaction "_Extract" "extract bands from image" { - action x = class - _result { - _vislevel = 3; - - first = Expression "Extract from band" 0; - number = Expression "Extract this many bands" 1; - - _result = map_unary (exb first number) x; - } - } - - Insert_item = class Menuaction "_Insert" "insert bands into image" { - action x y = class - _result { - _vislevel = 3; - - first = Expression "Insert at position" 0; - - _result - = map_binary process x y - { - process im1 im2 - = exb 0 f im1 ++ im2 ++ exb f (b - f) im1 - { - f = to_real first; - b = im1.bands; - } - } - } - } - - Delete_item = class Menuaction "_Delete" "delete bands from image" { - action x = class - _result { - _vislevel = 3; - - first = Expression "Delete from band" 0; - number = Expression "Delete this many bands" 1; - - _result - = map_unary process x - { - process im - = exb 0 f im ++ exb (f + n) (b - (f + n)) im - { - f = to_real first; - n = to_real number; - b = im.bands; - } - } - } - } - - Bandwise_item = Image_join_item.Bandwise_item; + Rot270_item = class + Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" { + action x = rotate_widget 3 x; + } + } - sep1a = Menuseparator; + Free_item = class + Menuaction "_Free" "clockwise rotation by any angle" { + action x = class + _result { + _vislevel = 3; - Bandand_item = class - Menuaction "Bitwise Band AND" "bitwise AND of image bands" { - action x = bandand x; - } + angle = Scale "Angle" (-180) 180 0; + interp = Interpolate_picker Interpolate_type.BILINEAR; - Bandor_item = class - Menuaction "Bitwise Band OR" "bitwise OR of image bands" { - action x = bandor x; - } + _result + = map_unary process x + { + process image + = rotate interp angle image; + } + } + } - sep2 = Menuseparator; + Straighten_item = class + Menuaction "_Straighten" + ("smallest rotation that makes an arrow either horizontal " ++ + "or vertical") { + action x = class + _result { + _vislevel = 3; - To_dimension_item = class - Menuaction "To D_imension" "convert bands to width or height" { - action x = class - _result { - _vislevel = 3; - - orientation = Option "Orientation" [ - "Horizontal", - "Vertical" - ] 0; - - _result - = map_unary process x - { - process im - = foldl1 [join_lr, join_tb]?orientation (bandsplit im); - } - } - } - - To_bands_item = class - Menuaction "To B_ands" "turn width or height to bands" { - action x = class - _result { - _vislevel = 3; - - orientation = Option "Orientation" [ - "Horizontal", - "Vertical" - ] 0; - - _result - = map_unary process x - { - process im - = bandjoin (map extract_column [0 .. im.width - 1]), - orientation == 0 - = bandjoin (map extract_row [0 .. im.height - 1]) - { - extract_column n - = extract_area n 0 1 im.height im; - extract_row n - = extract_area 0 n im.width 1 im; - } - } - } - } -} + interp = Interpolate_picker Interpolate_type.BILINEAR; -Image_alpha_item = class - Menupullright "_Alpha" "manipulate image alpha" { - - Add_item = class Menuaction "_Add" "add alpha" { - action x = class - _result { - _vislevel = 3; - - opacity = Expression "Opacity (255 == solid)" 255; - - _result = x ++ to_real opacity; - } - } - - Flatten_item = class Menuaction "_Flatten" "flatten alpha out of image" { - action x = class - _result { - _vislevel = 3; - - bg = Expression "Background" 0; - - _result = map_unary (flattenimage bg) x; - } - } - - Extract_item = class Menuaction "_Extract" "extract alpha" { - action x - = map_unary exb x - { - exb x = extract_bands (x.bands - 1) 1 x; - } - } - - Drop_item = class Menuaction "_Drop" "drop alpha" { - action x - = map_unary exb x - { - exb x = extract_bands 0 (x.bands - 1) x; - } - } + _result + = map_unary straighten x + { + straighten arrow + = rotate interp angle'' arrow.image + { + x = arrow.width; + y = arrow.height; + + angle = im (polar (x, y)); + + angle' + = angle - 360, angle > 315 + = angle - 180, angle > 135 + = angle; + + angle'' + = -angle', angle' >= (-45) && angle' < 45 + = 90 - angle'; + } + } + } + } + } - sep1 = Menuseparator; + Flip_item = class + Menupullright "_Flip" "mirror left/right or up/down" { + Left_right_item = class + Menuaction "_Left Right" "mirror object left/right" { + action x = map_unary fliplr x; + } - Premultiply_item = class Menuaction "_Premultiply" "premultiply alpha" { - action x = premultiply x; - } + Top_bottom_item = class + Menuaction "_Top Bottom" "mirror object top/bottom" { + action x = map_unary fliptb x; + } + } - Unpremultiply_item = class - Menuaction "_Unpremultiply" "unpremultiply alpha" { - action x = unpremultiply x; - } + Resize_item = class + Menupullright "_Resize" "change image size" { + Scale_item = class + Menuaction "_Scale" "scale image size by a factor" { + action x = class + _result { + _vislevel = 3; - sep2 = Menuseparator; + xfactor = Expression "Horizontal scale factor" 1; + yfactor = Expression "Vertical scale factor" 1; + kernel = Kernel_picker Kernel_type.LINEAR; - Composite2_item = class - Menuaction "_Composite two" "composite a pair of images" { - action x y = class - _result { - _vislevel = 3; - - blend = Option_enum (_ "Blend mode") modes "over" - { - modes = Blend_type.types; - } - compositing_space = Option_enum (_ "Compositing space") spaces "sRGB" - { - spaces = Image_type.image_colour_spaces; - } - premultiplied = Toggle (_ "Premultiplied") false; - - _result - = Image output - { - output = vips_composite [ - $compositing_space => compositing_space.value_thing, - $premultiplied => premultiplied.value - ] [y.value, x.value] blend.value; - } - } - } - - Composite3_item = class - Menuaction "_Composite three" "composite three images" { - action x y z = class - _result { - _vislevel = 3; - - blend1 = Option_enum (_ "Blend mode") modes "over" - { - modes = Blend_type.types; - } - blend2 = Option_enum (_ "Blend mode") modes "over" - { - modes = Blend_type.types; - } - compositing_space = Option_enum (_ "Compositing space") spaces "sRGB" - { - spaces = Image_type.image_colour_spaces; - } - premultiplied = Toggle (_ "Premultiplied") false; - - _result - = Image output - { - output = vips_composite [ - $compositing_space => compositing_space.value_thing, - $premultiplied => premultiplied.value - ] [z.value, y.value, x.value] [blend1.value, blend2.value]; - } - } - } -} + _result + = map_unary process x + { + process image + = resize kernel xfactor yfactor image; + } + } + } -Image_crop_item = class - Menuaction "_Crop" "extract a rectangular area from an image" { - action x - = crop x [l, t, w, h] - { - fields = [ - [has_left, get_left, 0], - [has_top, get_top, 0], - [has_width, get_width, 100], - [has_height, get_height, 100] - ]; - - [l, t, w, h] - = map get_default fields - { - get_default line - = get x, has x - = default - { - [has, get, default] = line; - } - } - } - - crop x geo = class - _result { - _vislevel = 3; - - l = Expression "Crop left" ((int) (geo?0 + geo?2 / 4)); - t = Expression "Crop top" ((int) (geo?1 + geo?3 / 4)); - w = Expression "Crop width" (max_pair 1 ((int) (geo?2 / 2))); - h = Expression "Crop height" (max_pair 1 ((int) (geo?3 / 2))); - - _result - = map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr] - { - extract im l t w h - = extract_area left' top' width' height' im - { - width' = min_pair (to_real w) im.width; - height' = min_pair (to_real h) im.height; - left' = range 0 (to_real l) (im.width - width'); - top' = range 0 (to_real t) (im.height - height'); - } - } - } -} + Size_item = class + Menuaction "_Size To" "resize to a fixed size" { + action x = class + _result { + _vislevel = 3; + + which = Option "Resize axis" [ + "Shortest", + "Longest", + "Horizontal", + "Vertical" + ] 0; + size = Expression "Resize to (pixels)" 128; + aspect = Toggle "Break aspect ratio" false; + kernel = Kernel_picker Kernel_type.LINEAR; + + _result + = map_unary process x + { + process image + = resize kernel h v image, aspect + = resize kernel fac fac image + { + xfac = to_real size / image.width; + yfac = to_real size / image.height; + max_factor + = [xfac, 1], xfac > yfac + = [1, yfac]; + min_factor + = [xfac, 1], xfac < yfac + = [1, yfac]; + [h, v] = [ + max_factor, + min_factor, + [xfac, 1], + [1, yfac]]?which; + + fac + = h, v == 1 + = v; + } + } + } + } -Trim_item = class Menuaction "_Trim" "crop away edges" { - action x = class - _result { - _vislevel = 3; - - thresh = Scale "threshold" 0 100 10; - background = Expression "Background" default_background - { - default_background - = map mean (bandsplit (extract_area 0 0 1 1 x)); - } - - _result - = Region x l t w h - { - [l, t, w, h] = vips_find_trim [ - $threshold => thresh.value, - $background => background.expr - ] x.value; - } - } -} + Size_within_item = class + Menuaction "Size _Within" "size to fit within a rectangle" { + action x = class + _result { + _vislevel = 3; + + // the rects we size to fit within + _rects = [ + [2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050], + [1280, 1024], [1024, 768], [800, 600], [640, 480] + ]; + + within = Option "Fit within (pixels)" ( + [print w ++ " x " ++ print h :: [w, h] <- _rects] ++ + ["Custom"] + ) 4; + custom_width = Expression "Custom width" 1000; + custom_height = Expression "Custom height" 1000; + size = Option "Page size" [ + "Full page", "Half page", "Quarter page" + ] 0; + kernel = Kernel_picker Kernel_type.LINEAR; + + _result + = map_unary process x + { + xdiv = [1, 2, 2]?size; + ydiv = [1, 1, 2]?size; + allrect = _rects ++ [ + [custom_width.expr, custom_height.expr] + ]; + [width, height] = allrect?within; + + process x + = resize kernel fac fac x, fac < 1 + = x + { + xfac = (width / xdiv) / x.width; + yfac = (height / ydiv) / x.height; + fac = min_pair xfac yfac; + } + } + } + } -Image_insert_item = class - Menuaction "_Insert" "insert a small image into a large image" { - action a b - = insert_position, is_Group a || is_Group b - = insert_area - { - insert_area = class - _result { - _check_args = [ - [a, "a", check_Image], - [b, "b", check_Image] - ]; - _vislevel = 3; - - // sort to get smallest first - _pred x y = x.width * x.height < y.width * y.height; - [_a', _b'] = sortc _pred [a, b]; - - place - = Area _b' left top width height - { - // be careful in case b is smaller than a - left = max_pair 0 ((_b'.width - _a'.width) / 2); - top = max_pair 0 ((_b'.height - _a'.height) / 2); - width = min_pair _a'.width _b'.width; - height = min_pair _a'.height _b'.height; - } - - _result - = insert_noexpand place.left place.top - (clip2fmt _b'.format a'') _b' - { - a'' = extract_area 0 0 place.width place.height _a'; - } - } - - insert_position = class - _result { - _vislevel = 3; - - 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_binary insert a b - { - insert a b - = insert_noexpand left top (clip2fmt b.format a) b, - position == 9 - = insert_noexpand xp yp (clip2fmt b.format a) b - { - xr = b.width - a.width; - yr = b.height - a.height; - xp = [0, xr / 2, xr]?((int) (position % 3)); - yp = [0, yr / 2, yr]?((int) (position / 3)); - } - } - } - } -} + Resize_canvas_item = class + Menuaction "_Canvas" "change size of surrounding image" { + action x = class + _result { + _vislevel = 3; + + // try to guess a sensible size for the new image + _guess_size + = x.rect, is_Image x + = Rect 0 0 100 100; + + nwidth = Expression "New width (pixels)" _guess_size.width; + nheight = Expression "New height (pixels)" _guess_size.height; + bgcolour = Expression "Background colour" 0; + + 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 process x + { + process image + = insert_noexpand xp yp image background + { + width = image.width; + height = image.height; + coding = image.coding; + bands + = 3, coding == Image_coding.LABPACK + = image.bands; + format + = Image_format.FLOAT, coding == Image_coding.LABPACK + = image.format; + type = image.type; + + // placement vectors ... left, centre, right + xposv = [0, to_real nwidth / 2 - width / 2, + to_real nwidth - width]; + yposv = [0, to_real nheight / 2 - height / 2, + to_real nheight - height]; + xp + = left, position == 9 + = xposv?((int) (position % 3)); + yp + = top, position == 9 + = yposv?((int) (position / 3)); + + background = image_new nwidth nheight + bands format coding type bgcolour.expr 0 0; + } + } + } + } + } -Image_select_item = Select_item; + Image_map_item = class + Menuaction "_Map" "map an image through a 2D transform image" { + action a b = class + _result { + _vislevel = 3; -Image_draw_item = class - Menupullright "_Draw" "draw lines, circles, rectangles, floods" { - Line_item = class Menuaction "_Line" "draw line on image" { - action x = class - _result { - _vislevel = 3; - - x1 = Expression "Start x" 0; - y1 = Expression "Start y" 0; - x2 = Expression "End x" 100; - y2 = Expression "End y" 100; - - i = Expression "Ink" [0]; - - _result - = map_unary line x - { - line im - = draw_line x1 y1 x2 y2 i.expr im; - } - } - } - - Rect_item = class Menuaction "_Rectangle" "draw rectangle on image" { - action x = class - _result { - _vislevel = 3; - - rx = Expression "Left" 50; - ry = Expression "Top" 50; - rw = Expression "Width" 100; - rh = Expression "Height" 100; - - f = Toggle "Fill" true; - - t = Scale "Line thickness" 1 50 3; - - i = Expression "Ink" [0]; - - _result - = map_unary rect x - { - rect im - = draw_rect_width rx ry rw rh f t i.expr im; - } - } - } - - Circle_item = class Menuaction "_Circle" "draw circle on image" { - action x = class - _result { - _vislevel = 3; - - cx = Expression "Centre x" 100; - cy = Expression "Centre y" 100; - r = Expression "Radius" 50; - - f = Toggle "Fill" true; - - i = Expression "Ink" [0]; - - _result - = map_unary circle x - { - circle im - = draw_circle cx cy r f i.expr im; - } - } - } - - Flood_item = class Menuaction "_Flood" "flood bounded area of image" { - action x = class - _result { - _vislevel = 3; - - sx = Expression "Start x" 0; - sy = Expression "Start y" 0; - - e = Option "Flood while" [ - "Not equal to ink", - "Equal to start point" - ] 0; - - // pick a default ink that won't flood, if we can - i - = Expression "Ink" default_ink - { - default_ink - = [0], ! has_image x - = pixel; - pixel = map mean (bandsplit (extract_area sx sy 1 1 im)); - im = get_image x; - } - - _result - = map_unary flood x - { - flood im - = draw_flood sx sy i.expr im, e == 0 - = draw_flood_blob sx sy i.expr im; - } - } - } - - Draw_scalebar_item = class Menuaction "_Scale" "draw scale bar" { - action x = class - _result { - _vislevel = 3; - - px = Expression "Left" 50; - py = Expression "Top" 50; - wid = Expression "Width" 100; - thick = Scale "Line thickness" 1 50 3; - text = String "Dimension text" "50μm"; - font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; - pos = Option "Position Text" ["Above", "Below"] 1; - vp = Option "Dimension by" [ - "Inner Vertical Edge", - "Centre of Vertical", - "Outer Vertical Edge" - ] 1; - dpi = Expression "DPI" 100; - ink = Colour "Lab" [50,0,0]; + interp = Interpolate_picker Interpolate_type.BILINEAR; _result - = map_unary process x + = map_binary trans a b { - process im - = blend (Image scale) ink' im + trans a b + = mapim interp.value in index { - // make an ink compatible with the image - ink' = colour_transform_to (get_type im) ink; + // get the index image first + [index, in] = sortc (const is_twocomponent) [a, b]; + + // is a two-component image, ie. one band complex, or + // two-band non-complex + is_twocomponent x + = is_nonc x || is_c x; + is_nonc x + = has_bands x && get_bands x == 2 && + has_format x && !is_complex_format (get_format x); + is_c x + = has_bands x && get_bands x == 1 && + has_format x && is_complex_format (get_format x); + is_complex_format f + = f == Image_format.COMPLEX || + f == Image_format.DPCOMPLEX; + } + } + } + } - x = to_real px; - y = to_real py; - w = to_real wid; - d = to_real dpi; + Image_perspective_item = Perspective_item; + + Image_rubber_item = class + Menupullright "Ru_bber Sheet" + "automatically warp images to superposition" { + rubber_interp = Option "Interpolation" ["Nearest", "Bilinear"] 1; + rubber_order = Option "Order" ["0", "1", "2", "3"] 1; + rubber_wrap = Toggle "Wrap image edges" false; + + // a transform ... a matrix, plus the size of the image the + // matrix was made for + Transform matrix image_width image_height = class + matrix { + // scale a transform ... if it worked for a m by n image, make + // it work for a (m * xfac) by (y * yfac) image + rescale xfac yfac + = Transform (Matrix (map2 (map2 multiply) matrix.value facs)) + (image_width * xfac) (image_height * yfac) + { + facs = [ + [xfac, yfac], + [1, 1], + [1, 1], + [1 / xfac, 1 / yfac], + [1 / xfac, 1 / yfac], + [1 / xfac, 1 / yfac] + ]; + } + } - t = floor thick; + // yuk!!!! fix is_instanceof to not need absolute names + is_Transform = is_instanceof + "Image_transform_item.Image_rubber_item.Transform"; + + Find_item = class + Menuaction "_Find" + ("find a transform which will map sample image onto " ++ + "reference") { + action reference sample = class + _trn { + _vislevel = 3; + + // controls + order = rubber_order; + interp = rubber_interp; + wrap = rubber_wrap; + max_err = Expression "Maximum error" 0.3; + max_iter = Expression "Maximum iterations" 10; + + // transform + [sample', trn, err] = transform_search + max_err max_iter order interp wrap + sample reference; + transformed_image = Image sample'; + _trn = Transform trn reference.width reference.height; + final_error = err; + } + } - bg = image_new (get_width im) (get_height im) (get_bands im) - (get_format im) (get_coding im) (get_type im) 0 0 0; - draw_block x y w t im = - draw_rect_width x y w t true 1 [255] im; - label = im_text text.value font.value w 1 d; - lw = get_width label; - lh = get_height label; - ly = [y - lh - t, y + 2 * t]?pos; - vx = [ - [x - t, x + w], - [x - t / 2, x + w - t / 2], - [x, x + w - t] - ]?vp; - - scale = (draw_block x y w t @ - draw_block vx?0 (y - 2 * t) t (t * 5) @ - draw_block vx?1 (y - 2 * t) t (t * 5) @ - insert_noexpand (x + w / 2 - lw / 2) ly label) - bg; - } - } - } - } -} + Apply_item = class + Menuaction "_Apply" "apply a transform to an image" { + action a b = class + _result { + _vislevel = 3; -Image_join_item = class - Menupullright "_Join" "join two or more images together" { - Bandwise_item = class - Menuaction "_Bandwise Join" "join two images bandwise" { - action a b = join a b; - } + // controls + interp = rubber_interp; + wrap = rubber_wrap; + + _result + = map_binary trans a b + { + trans a b + = transform interp wrap t' i + { + // get the transform arg first + [i, t] = sortc (const is_Transform) [a, b]; + t' = t.rescale (i.width / t.image_width) + (i.height / t.image_height); + } + } + } + } + } sep1 = Menuseparator; - join_lr shim bg align a b - = im2 - { - w = a.width + b.width + shim; - h = max_pair a.height b.height; - - back = image_new w h a.bands a.format a.coding a.type bg 0 0; - - ya = [0, max_pair 0 ((b.height - a.height)/2), - max_pair 0 (b.height - a.height)]; - yb = [0, max_pair 0 ((a.height - b.height)/2), - max_pair 0 (a.height - b.height)]; - - im1 = insert_noexpand 0 ya?align a back; - im2 = insert_noexpand (a.width + shim) yb?align b im1; - } - - join_tb shim bg align a b - = im2 - { - w = max_pair a.width b.width; - h = a.height + b.height + shim; - - back = image_new w h a.bands a.format a.coding a.type bg 0 0; - - xa = [0, max_pair 0 ((b.width - a.width)/2), - max_pair 0 (b.width - a.width)]; - xb = [0, max_pair 0 ((a.width - b.width)/2), - max_pair 0 (a.width - b.width)]; - - im1 = insert_noexpand xa?align 0 a back; - im2 = insert_noexpand xb?align (a.height + shim) b im1; - } - - halign_names = ["Top", "Centre", "Bottom"]; - valign_names = ["Left", "Centre", "Right"]; - - Left_right_item = class - Menuaction "_Left to Right" "join two images left-right" { - action a b = class - _result { - _vislevel = 3; - - shim = Scale "Spacing" 0 100 0; - bg_colour = Expression "Background colour" 0; - align = Option "Alignment" halign_names 1; - - _result = map_binary - (join_lr shim.value bg_colour.expr align.value) a b; - } - } - - Top_bottom_item = class - Menuaction "_Top to Bottom" "join two images top-bottom" { - action a b = class - _result { - _vislevel = 3; - - shim = Scale "Spacing" 0 100 0; - bg_colour = Expression "Background colour" 0; - align = Option "Alignment" valign_names 1; - - _result = map_binary - (join_tb shim.value bg_colour.expr align.value) a b; - } - } + Match_item = class + Menuaction "_Linear Match" + "rotate and scale one image to match another" { + action x y = class + _result { + _vislevel = 3; - sep2 = Menuseparator; + // try to find an image ... for a group, get the first item + find_image x + = x, is_Image x + = find_image x?0, is_list x + = find_image x.value, is_class x && has_value x + = error "unable to find image"; - Array_item = class - Menuaction "_Array" - "join a list of lists of images into a single image" { - action x = class - _result { - _vislevel = 3; - - hshim = Scale "Horizontal spacing" (-100) (100) 0; - vshim = Scale "Vertical spacing" (-100) (100) 0; - bg_colour = Expression "Background colour" 0; - halign = Option "Horizontal alignment" valign_names 1; - valign = Option "Vertical alignment" halign_names 1; - - // we can't use map_unary since chop-into-tiles returns a group of - // groups and we want to be able to reassemble that - // TODO: chop-into-tiles should return an array class which - // displays as group but does not have the looping behaviour? - _result - = (image_set_origin 0 0 @ - foldl1 (join_tb vshim.value bg_colour.expr halign.value) @ - map (foldl1 (join_lr hshim.value - bg_colour.expr valign.value))) (to_list (to_list x)); - } - } - - ArrayFL_item = class - Menuaction "_Array from List" - "join a list of images into a single image" { - action x = class - _result { - _vislevel = 3; - - ncol = Number "Max. Number of Columns" 1; - hshim = Scale "Horizontal spacing" (-100) (100) 0; - vshim = Scale "Vertical spacing" (-100) (100) 0; - bg_colour = Expression "Background colour" 0; - halign = Option "Horizontal alignment" valign_names 1; - valign = Option "Vertical alignment" halign_names 1; - snake = Toggle "Reverse the order of every other row" false; - - _l - = split_lines ncol.value x.value, is_Group x - = split_lines ncol.value x; - - _l' - = map2 reverse_if_odd [0..] _l, snake - = _l - { - reverse_if_odd n x - = reverse x, n % 2 == 1 - = x; - } - - _result - = (image_set_origin 0 0 @ - foldl1 (join_tb vshim.value bg_colour.expr halign.value) @ - map (foldl1 (join_lr hshim.value - bg_colour.expr valign.value))) (to_list (to_list _l')); - } - } -} + _a = find_image x; + _b = find_image y; -Image_tile_item = class - Menupullright "Til_e" "tile an image across and down" { - tile_widget default_type x = class - _result { - _vislevel = 3; - - across = Expression "Tiles across" 2; - down = Expression "Tiles down" 2; - repeat = Option "Tile type" - ["Replicate", "Four-way mirror"] default_type; - - _result - = map_unary process x - { - process image - = tile across down image, repeat == 0 - = tile across down image'' - { - image' = insert image.width 0 (fliplr image) image; - image'' = insert 0 image.height (fliptb image') image'; - } - } - } - - Replicate_item = class - Menuaction "_Replicate" "replicate image across and down" { - action x = tile_widget 0 x; - } - - Fourway_item = class - Menuaction "_Four-way Mirror" "four-way mirror across and down" { - action x = tile_widget 1 x; - } - - Chop_item = class - Menuaction "_Chop Into Tiles" "slice an image into tiles" { - action x = class - _result { - _vislevel = 3; - - tile_width = Expression "Tile width" 100; - tile_height = Expression "Tile height" 100; - hoverlap = Expression "Horizontal overlap" 0; - voverlap = Expression "Vertical overlap" 0; - - _result - = map_unary (Group @ map Group @ process) x - { - process x - = imagearray_chop tile_width tile_height - hoverlap voverlap x; - } - } - } -} + ap1 = Mark_relative _a 0.5 0.25; + bp1 = Mark_relative _b 0.5 0.25; + ap2 = Mark_relative _a 0.5 0.75; + bp2 = Mark_relative _b 0.5 0.75; -#separator + refine = Toggle "Refine selected tie-points" false; + lock = Toggle "No resize" false; -Pattern_images_item = class - Menupullright "_Patterns" "make a variety of useful patterns" { - Grey_item = class - Menuaction "Grey _Ramp" "make a smooth grey ramp" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - orientation = Option "Orientation" [ - "Horizontal", - "Vertical" - ] 0; - foption = Option "Format" ["8 bit", "float"] 0; - - _result - = Image im - { - gen - = im_grey, foption == 0 - = im_fgrey; - w = to_real nwidth; - h = to_real nheight; - im - = gen w h, orientation == 0 - = rot90 (gen h w); - } - } - } - - Xy_item = class - Menuaction "_XY Image" - "make a two band image whose pixel values are their coordinates" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - - _result = Image (make_xy nwidth nheight); - } - } - - Noise_item = class - Menupullright "_Noise" "various noise generators" { - Gaussian_item = class - Menuaction "_Gaussian" "make an image of gaussian noise" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - mean = Scale "Mean" 0 255 128; - deviation = Scale "Deviation" 0 128 50; - - _result = Image (gaussnoise nwidth nheight - mean.value deviation.value); - } - } - - Fractal_item = class - Menuaction "_Fractal" "make a fractal noise image" { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - dimension = Scale "Dimension" 2.001 2.999 2.001; - - _result = Image (im_fractsurf (to_real nsize) dimension.value); - } - } - - Perlin_item = class - Menuaction "_Perlin" "Perlin noise image" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - cell_size = Expression "Cell size (pixels)" 8; - eight = Toggle "Eight bit output" true; - - _result - = 128 * im + 128, eight - = im - { - im = perlin cell_size nwidth nheight; - } - } - } - - Worley_item = class - Menuaction "_Worley" "Worley noise image" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 512; - nheight = Expression "Image height (pixels)" 512; - cell_size = Expression "Cell size (pixels)" 256; - - _result - = worley cell_size nwidth nheight; - } - } - - } - - Checkerboard_item = class - Menuaction "_Checkerboard" "make a checkerboard image" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - hpsize = Expression "Horizontal patch size" 8; - vpsize = Expression "Vertical patch size" 8; - hpoffset = Expression "Horizontal patch offset" 0; - vpoffset = Expression "Vertical patch offset" 0; - - _result - = Image (xstripes ^ ystripes) - { - pixels = make_xy nwidth nheight; - xpixels = pixels?0 + to_real hpoffset; - ypixels = pixels?1 + to_real vpoffset; - - make_stripe pix swidth = pix % (swidth * 2) >= swidth; - - xstripes = make_stripe xpixels (to_real hpsize); - ystripes = make_stripe ypixels (to_real vpsize); - } - } - } - - Grid_item = class - Menuaction "Gri_d" "make a grid" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - hspace = Expression "Horizontal line spacing" 8; - vspace = Expression "Vertical line spacing" 8; - thick = Expression "Line thickness" 1; - hoff = Expression "Horizontal grid offset" 4; - voff = Expression "Vertical grid offset" 4; - - _result - = Image (xstripes | ystripes) - { - pixels = make_xy nwidth nheight; - xpixels = pixels?0 + to_real hoff; - ypixels = pixels?1 + to_real voff; - - make_stripe pix swidth = pix % swidth < to_real thick; - - xstripes = make_stripe xpixels (to_real hspace); - ystripes = make_stripe ypixels (to_real vspace); - } - } - } - - Text_item = class - Menuaction "_Text" "make a bitmap of some text" { - action = class - _result { - _vislevel = 3; - - text = String "Text to paint" "Hello world!"; - font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; - fontfile = Pathname "Font file" ""; - textw = Expression "Text width" 0; - texth = Expression "Text height" 0; - align = Option "Alignment" [ - "Left", - "Centre", - "Right" - ] 0; - dpi = Expression "DPI" 300; - fit = Toggle "Fit text to box" false; - spacing = Expression "Line spacing" 0; - - _result - = Image out - { - base_options = [ - $font => font.value, - $align => align.value - ]; - - set_option name default value - = [name => value], value != default - = []; - - options = base_options ++ concat [ - set_option $width 0 (to_real textw), - set_option $height 0 (to_real texth), - set_option $fontfile "" fontfile.value, - if !fit then set_option $dpi 72 (to_real dpi) else [], - set_option $spacing 0 (to_real spacing) - ]; - - out = vips_text options text.value; - } - } - } - - New_CIELAB_slice_item = class - Menuaction "CIELAB _Slice" "make a slice through CIELAB space" { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - L = Scale "L value" 0 100 50; - - _result = Image (lab_slice (to_real nsize) L.value); - } - } - - sense_option = Option "Sense" [ - "Pass", - "Reject" - ] 0; - - build fn size - = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn) - (im_create_fmask size size); - - New_ideal_item = class - Menupullright "_Ideal Fourier Mask" - "make various ideal Fourier filter masks" { - High_low_item = class - Menuaction "_High or Low Pass" - ("make a mask image for a highpass/lowpass " ++ - "ideal Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f sense.value fc.value 0 0 0 0; - } - } - } - - Ring_item = class - Menuaction "_Ring Pass or Ring Reject" - ("make a mask image for an ring pass/reject " ++ - "ideal Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - rw = Scale "Ring width" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 6) fc.value rw.value 0 0 0; - } - } - } - - Band_item = class - Menuaction "_Band Pass or Band Reject" - ("make a mask image for a band pass/reject " ++ - "ideal Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; - fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; - r = Scale "Radius" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 12) fcx.value fcy.value - r.value 0 0; - } - } - } - } - - New_gaussian_item = class - Menupullright "_Gaussian Fourier Mask" - "make various Gaussian Fourier filter masks" { - High_low_item = class - Menuaction "_High or Low Pass" - ("make a mask image for a highpass/lowpass " ++ - "Gaussian Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 4) fc.value ac.value 0 0 0; - } - } - } - - Ring_item = class - Menuaction "_Ring Pass or Ring Reject" - ("make a mask image for an ring pass/reject " ++ - "Gaussian Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - rw = Scale "Ring width" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 10) fc.value rw.value - ac.value 0 0; - } - } - } - - Band_item = class - Menuaction "_Band Pass or Band Reject" - ("make a mask image for a band pass/reject " ++ - "Gaussian Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; - fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; - r = Scale "Radius" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 16) fcx.value fcy.value - r.value ac.value 0; - } - } - } - } - - New_butterworth_item = class - Menupullright "_Butterworth Fourier Mask" - "make various Butterworth Fourier filter masks" { - High_low_item = class - Menuaction "_High or Low Pass" - ("make a mask image for a highpass/lowpass " ++ - "Butterworth Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - order = Scale "Order" 1 10 2; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 2) order.value fc.value - ac.value 0 0; - } - } - } - - Ring_item = class - Menuaction "_Ring Pass or Ring Reject" - ("make a mask image for an ring pass/reject " ++ - "Butterworth Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fc = Scale "Frequency cutoff" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - rw = Scale "Ring width" 0.01 0.99 0.5; - order = Scale "Order" 1 10 2; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 8) order.value fc.value - rw.value ac.value 0; - } - } - } - - Band_item = class - Menuaction "_Band Pass or Band Reject" - ("make a mask image for a band pass/reject " ++ - "Butterworth Fourier filter") { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - sense = sense_option; - fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; - fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; - r = Scale "Radius" 0.01 0.99 0.5; - ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; - order = Scale "Order" 1 10 2; - - _result - = build param (to_real nsize) - { - param f = f (sense.value + 14) order.value fcx.value - fcy.value r.value ac.value; - } - } - } - } + _result + = map_binary process x y + { + process a b + = Image b''' + { + _prefs = Workspaces.Preferences; + window = _prefs.MOSAIC_WINDOW_SIZE; + object = _prefs.MOSAIC_OBJECT_SIZE; + + a' = a.value; + b' = b.value; + + b'' = clip2fmt a.format b'; + + // return p2 ... if lock is set, return a p2 a standard + // distance along the vector joining p1 and p2 + norm p1 p2 + = Rect left' top' 0 0, lock + = p2 + { + v = (p2.left - p1.left, p2.top - p1.top); + // 100000 to give precision since we pass points as + // ints to match + n = 100000 * sign v; + left' = p1.left + re n; + top' = p1.top + im n; + } + + ap2'' = norm ap1 ap2; + bp2'' = norm bp1 bp2; + + b''' + = im_match_linear_search a' b'' + ap1.left ap1.top bp1.left bp1.top + ap2''.left ap2''.top bp2''.left bp2''.top + object window, + // we can't search if lock is on + refine && !lock + = im_match_linear a' b'' + ap1.left ap1.top bp1.left bp1.top + ap2''.left ap2''.top bp2''.left bp2''.top; + } + } + } + } + + Image_perspective_match_item = Perspective_match_item; } -Test_images_item = class - Menupullright "Test I_mages" "make a variety of test images" { - Eye_item = class - Menuaction "_Spatial Response" - "image for testing the eye's spatial response" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - nheight = Expression "Image height (pixels)" 64; - factor = Scale "Factor" 0.001 1 0.2; - - _result = Image (im_eye (to_real nwidth) (to_real nheight) - factor.value); - } - } - - Zone_plate = class - Menuaction "_Zone Plate" "make a zone plate" { - action = class - _result { - _vislevel = 3; - - nsize = Expression "Image size (pixels)" 64; - - _result = Image (im_zone (to_real nsize)); - } - } - - Frequency_test_chart_item = class - Menuaction "_Frequency Testchart" - "make a black/white frequency test pattern" { - action = class - _result { - _vislevel = 3; - - nwidth = Expression "Image width (pixels)" 64; - sheight = Expression "Strip height (pixels)" 10; - waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2]; - - _result - = imagearray_assemble 0 0 (transpose [strips]) - { - freq_slice wave = Image (sin (grey / wave) > 0); - strips = map freq_slice waves.expr; - grey = im_fgrey (to_real nwidth) (to_real sheight) * - 360 * (to_real nwidth); - } - } - } - - CRT_test_chart_item = class - Menuaction "CRT _Phosphor Chart" - "make an image for measuring phosphor colours" { - action = class - _result { - _vislevel = 3; - - brightness = Scale "Brightness" 0 255 200; - psize = Expression "Patch size (pixels)" 32; - - _result - = Image (imagearray_assemble 0 0 [[green, red], [blue, white]]) - { - - black = image_new (to_real psize) (to_real psize) 1 - Image_format.FLOAT Image_coding.NOCODING - Image_type.B_W 0 0 0; - notblack = black + brightness; - - green = black ++ notblack ++ black; - red = notblack ++ black ++ black; - blue = black ++ black ++ notblack; - white = notblack ++ notblack ++ notblack; - } - } - } - - Greyscale_chart_item = class - Menuaction "_Greyscale" "make a greyscale" { - action = class - _result { - _vislevel = 3; - - pwidth = Expression "Patch width" 8; - pheight = Expression "Patch height" 8; - npatches = Expression "Number of patches" 16; - - _result - = Image (image_set_type Image_type.B_W - (clip2fmt Image_format.UCHAR wedge)) - { - wedge - = 255 / (to_real npatches - 1) * - (int) (strip?0 / to_real pwidth) - { - strip = make_xy (to_real pwidth * to_real npatches) pheight; - } - } - } - } - - CMYK_test_chart_item = class - Menuaction "_CMYK Wedges" "make a set of CMYK wedges" { - action = class - _result { - _vislevel = 3; - - pwidth = Expression "Patch width" 8; - pheight = Expression "Patch height" 8; - npatches = Expression "Number of patches" 16; - - _result - = Image (image_set_type Image_type.CMYK - (clip2fmt Image_format.UCHAR strips)) - { - wedge - = 255 / (to_real npatches - 1) * - (int) (strip?0 / to_real pwidth) - { - strip = make_xy (to_real pwidth * to_real npatches) pheight; - } - - black = wedge * 0; - - C = wedge ++ black ++ black ++ black; - M = black ++ wedge ++ black ++ black; - Y = black ++ black ++ wedge ++ black; - K = black ++ black ++ black ++ wedge; - - strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]]; - } - } - } - - Colour_atlas_item = class - Menuaction "_Colour Atlas" - "make a grid of patches grouped around a colour" { - action = class - _result { - _vislevel = 3; - - start = Colour_picker "Lab" [50,0,0]; - nstep = Expression "Number of steps" 9; - ssize = Expression "Step size" 10; - psize = Expression "Patch size" 32; - sepsize = Expression "Separator size" 4; - - _result - = colour_transform_to (get_type start) blocks''' - { - size = (to_real nstep * 2 + 1) * to_real psize - - to_real sepsize; - xy = make_xy size size; - - xy_grid = (xy % to_real psize) < - (to_real psize - to_real sepsize); - grid = xy_grid?0 & xy_grid?1; - - blocks = (int) (to_real ssize * ((int) (xy / to_real psize))); - lab_start = colour_transform_to Image_type.LAB start; - blocks' = blocks - to_real nstep * to_real ssize + - Vector (tl lab_start.value); - L = image_new size size 1 - Image_format.FLOAT Image_coding.NOCODING - Image_type.B_W lab_start.value?0 0 0; - blocks'' = Image L ++ Image blocks'; - blocks''' - = image_set_type Image_type.LAB blocks'', Image grid - = 0; +Image_band_item = class + Menupullright "_Band" "manipulate image bands" { + // like extract_bands, but return [] for zero band image + // makes compose a bit simpler + exb b n x + = [], to_real n == 0 + = extract_bands b n x; + + Extract_item = class Menuaction "_Extract" "extract bands from image" { + action x = class + _result { + _vislevel = 3; + + first = Expression "Extract from band" 0; + number = Expression "Extract this many bands" 1; + + _result = map_unary (exb first number) x; + } + } + + Insert_item = class Menuaction "_Insert" "insert bands into image" { + action x y = class + _result { + _vislevel = 3; + + first = Expression "Insert at position" 0; + + _result + = map_binary process x y + { + process im1 im2 + = exb 0 f im1 ++ im2 ++ exb f (b - f) im1 + { + f = to_real first; + b = im1.bands; + } + } + } + } + + Delete_item = class Menuaction "_Delete" "delete bands from image" { + action x = class + _result { + _vislevel = 3; + + first = Expression "Delete from band" 0; + number = Expression "Delete this many bands" 1; + + _result + = map_unary process x + { + process im + = exb 0 f im ++ exb (f + n) (b - (f + n)) im + { + f = to_real first; + n = to_real number; + b = im.bands; + } + } + } + } + + Bandwise_item = Image_join_item.Bandwise_item; + + sep1a = Menuseparator; + + Bandand_item = class + Menuaction "Bitwise Band AND" "bitwise AND of image bands" { + action x = bandand x; + } + + Bandor_item = class + Menuaction "Bitwise Band OR" "bitwise OR of image bands" { + action x = bandor x; + } + + sep2 = Menuseparator; + + To_dimension_item = class + Menuaction "To D_imension" "convert bands to width or height" { + action x = class + _result { + _vislevel = 3; + + orientation = Option "Orientation" [ + "Horizontal", + "Vertical" + ] 0; + + _result + = map_unary process x + { + process im + = foldl1 [join_lr, join_tb]?orientation (bandsplit im); + } + } + } + + To_bands_item = class + Menuaction "To B_ands" "turn width or height to bands" { + action x = class + _result { + _vislevel = 3; + + orientation = Option "Orientation" [ + "Horizontal", + "Vertical" + ] 0; + + _result + = map_unary process x + { + process im + = bandjoin (map extract_column [0 .. im.width - 1]), + orientation == 0 + = bandjoin (map extract_row [0 .. im.height - 1]) + { + extract_column n + = extract_area n 0 1 im.height im; + extract_row n + = extract_area 0 n im.width 1 im; + } + } + } + } +} + +Image_alpha_item = class + Menupullright "_Alpha" "manipulate image alpha" { + + Add_item = class Menuaction "_Add" "add alpha" { + action x = class + _result { + _vislevel = 3; + + opacity = Expression "Opacity (255 == solid)" 255; + + _result = x ++ to_real opacity; + } + } + + Flatten_item = class Menuaction "_Flatten" "flatten alpha out of image" { + action x = class + _result { + _vislevel = 3; + + bg = Expression "Background" 0; + + _result = map_unary (flattenimage bg) x; + } + } + + Extract_item = class Menuaction "_Extract" "extract alpha" { + action x + = map_unary exb x + { + exb x = extract_bands (x.bands - 1) 1 x; + } + } + + Drop_item = class Menuaction "_Drop" "drop alpha" { + action x + = map_unary exb x + { + exb x = extract_bands 0 (x.bands - 1) x; + } + } + + sep1 = Menuseparator; + + Premultiply_item = class Menuaction "_Premultiply" "premultiply alpha" { + action x = premultiply x; + } + + Unpremultiply_item = class + Menuaction "_Unpremultiply" "unpremultiply alpha" { + action x = unpremultiply x; + } + + sep2 = Menuseparator; + + Composite2_item = class + Menuaction "_Composite two" "composite a pair of images" { + action x y = class + _result { + _vislevel = 3; + + blend = Option_enum (_ "Blend mode") modes "over" + { + modes = Blend_type.types; + } + compositing_space = Option_enum (_ "Compositing space") spaces "sRGB" + { + spaces = Image_type.image_colour_spaces; + } + premultiplied = Toggle (_ "Premultiplied") false; + + _result + = Image output + { + output = vips_composite [ + $compositing_space => compositing_space.value_thing, + $premultiplied => premultiplied.value + ] [y.value, x.value] blend.value; + } + } + } + + Composite3_item = class + Menuaction "_Composite three" "composite three images" { + action x y z = class + _result { + _vislevel = 3; + + blend1 = Option_enum (_ "Blend mode") modes "over" + { + modes = Blend_type.types; + } + blend2 = Option_enum (_ "Blend mode") modes "over" + { + modes = Blend_type.types; + } + compositing_space = Option_enum (_ "Compositing space") spaces "sRGB" + { + spaces = Image_type.image_colour_spaces; + } + premultiplied = Toggle (_ "Premultiplied") false; + + _result + = Image output + { + output = vips_composite [ + $compositing_space => compositing_space.value_thing, + $premultiplied => premultiplied.value + ] [z.value, y.value, x.value] [blend1.value, blend2.value]; + } + } + } +} + +Image_crop_item = class + Menuaction "_Crop" "extract a rectangular area from an image" { + action x + = crop x [l, t, w, h] + { + fields = [ + [has_left, get_left, 0], + [has_top, get_top, 0], + [has_width, get_width, 100], + [has_height, get_height, 100] + ]; + + [l, t, w, h] + = map get_default fields + { + get_default line + = get x, has x + = default + { + [has, get, default] = line; + } + } + } + + crop x geo = class + _result { + _vislevel = 3; + + l = Expression "Crop left" ((int) (geo?0 + geo?2 / 4)); + t = Expression "Crop top" ((int) (geo?1 + geo?3 / 4)); + w = Expression "Crop width" (max_pair 1 ((int) (geo?2 / 2))); + h = Expression "Crop height" (max_pair 1 ((int) (geo?3 / 2))); + + _result + = map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr] + { + extract im l t w h + = extract_area left' top' width' height' im + { + width' = min_pair (to_real w) im.width; + height' = min_pair (to_real h) im.height; + left' = range 0 (to_real l) (im.width - width'); + top' = range 0 (to_real t) (im.height - height'); + } + } + } +} + +Trim_item = class Menuaction "_Trim" "crop away edges" { + action x = class + _result { + _vislevel = 3; + + thresh = Scale "threshold" 0 100 10; + background = Expression "Background" default_background + { + default_background + = map mean (bandsplit (extract_area 0 0 1 1 x)); + } + + _result + = Region x l t w h + { + [l, t, w, h] = vips_find_trim [ + $threshold => thresh.value, + $background => background.expr + ] x.value; + } + } +} + +Image_insert_item = class + Menuaction "_Insert" "insert a small image into a large image" { + action a b + = insert_position, is_Group a || is_Group b + = insert_area + { + insert_area = class + _result { + _check_args = [ + [a, "a", check_Image], + [b, "b", check_Image] + ]; + _vislevel = 3; + + // sort to get smallest first + _pred x y = x.width * x.height < y.width * y.height; + [_a', _b'] = sortc _pred [a, b]; + + place + = Area _b' left top width height + { + // be careful in case b is smaller than a + left = max_pair 0 ((_b'.width - _a'.width) / 2); + top = max_pair 0 ((_b'.height - _a'.height) / 2); + width = min_pair _a'.width _b'.width; + height = min_pair _a'.height _b'.height; + } + + _result + = insert_noexpand place.left place.top + (clip2fmt _b'.format a'') _b' + { + a'' = extract_area 0 0 place.width place.height _a'; + } + } + + insert_position = class + _result { + _vislevel = 3; + + 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_binary insert a b + { + insert a b + = insert_noexpand left top (clip2fmt b.format a) b, + position == 9 + = insert_noexpand xp yp (clip2fmt b.format a) b + { + xr = b.width - a.width; + yr = b.height - a.height; + xp = [0, xr / 2, xr]?((int) (position % 3)); + yp = [0, yr / 2, yr]?((int) (position / 3)); + } + } + } + } +} + +Image_select_item = Select_item; + +Image_draw_item = class + Menupullright "_Draw" "draw lines, circles, rectangles, floods" { + Line_item = class Menuaction "_Line" "draw line on image" { + action x = class + _result { + _vislevel = 3; + + x1 = Expression "Start x" 0; + y1 = Expression "Start y" 0; + x2 = Expression "End x" 100; + y2 = Expression "End y" 100; + + i = Expression "Ink" [0]; + + _result + = map_unary line x + { + line im + = draw_line x1 y1 x2 y2 i.expr im; + } + } + } + + Rect_item = class Menuaction "_Rectangle" "draw rectangle on image" { + action x = class + _result { + _vislevel = 3; + + rx = Expression "Left" 50; + ry = Expression "Top" 50; + rw = Expression "Width" 100; + rh = Expression "Height" 100; + + f = Toggle "Fill" true; + + t = Scale "Line thickness" 1 50 3; + + i = Expression "Ink" [0]; + + _result + = map_unary rect x + { + rect im + = draw_rect_width rx ry rw rh f t i.expr im; + } + } + } + + Circle_item = class Menuaction "_Circle" "draw circle on image" { + action x = class + _result { + _vislevel = 3; + + cx = Expression "Centre x" 100; + cy = Expression "Centre y" 100; + r = Expression "Radius" 50; + + f = Toggle "Fill" true; + + i = Expression "Ink" [0]; + + _result + = map_unary circle x + { + circle im + = draw_circle cx cy r f i.expr im; + } + } + } + + Flood_item = class Menuaction "_Flood" "flood bounded area of image" { + action x = class + _result { + _vislevel = 3; + + sx = Expression "Start x" 0; + sy = Expression "Start y" 0; + + e = Option "Flood while" [ + "Not equal to ink", + "Equal to start point" + ] 0; + + // pick a default ink that won't flood, if we can + i + = Expression "Ink" default_ink + { + default_ink + = [0], ! has_image x + = pixel; + pixel = map mean (bandsplit (extract_area sx sy 1 1 im)); + im = get_image x; + } + + _result + = map_unary flood x + { + flood im + = draw_flood sx sy i.expr im, e == 0 + = draw_flood_blob sx sy i.expr im; + } + } + } + + Draw_scalebar_item = class Menuaction "_Scale" "draw scale bar" { + action x = class + _result { + _vislevel = 3; + + px = Expression "Left" 50; + py = Expression "Top" 50; + wid = Expression "Width" 100; + thick = Scale "Line thickness" 1 50 3; + text = String "Dimension text" "50μm"; + font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; + pos = Option "Position Text" ["Above", "Below"] 1; + vp = Option "Dimension by" [ + "Inner Vertical Edge", + "Centre of Vertical", + "Outer Vertical Edge" + ] 1; + dpi = Expression "DPI" 100; + ink = Colour "Lab" [50,0,0]; + + _result + = map_unary process x + { + process im + = blend (Image scale) ink' im + { + // make an ink compatible with the image + ink' = colour_transform_to (get_type im) ink; + + x = to_real px; + y = to_real py; + w = to_real wid; + d = to_real dpi; + + t = floor thick; + + bg = image_new (get_width im) (get_height im) (get_bands im) + (get_format im) (get_coding im) (get_type im) 0 0 0; + draw_block x y w t im = + draw_rect_width x y w t true 1 [255] im; + label = im_text text.value font.value w 1 d; + lw = get_width label; + lh = get_height label; + ly = [y - lh - t, y + 2 * t]?pos; + vx = [ + [x - t, x + w], + [x - t / 2, x + w - t / 2], + [x, x + w - t] + ]?vp; + + scale = (draw_block x y w t @ + draw_block vx?0 (y - 2 * t) t (t * 5) @ + draw_block vx?1 (y - 2 * t) t (t * 5) @ + insert_noexpand (x + w / 2 - lw / 2) ly label) + bg; + } + } + } + } +} + +Image_join_item = class + Menupullright "_Join" "join two or more images together" { + Bandwise_item = class + Menuaction "_Bandwise Join" "join two images bandwise" { + action a b = join a b; + } + + sep1 = Menuseparator; + + join_lr shim bg align a b + = im2 + { + w = a.width + b.width + shim; + h = max_pair a.height b.height; + + back = image_new w h a.bands a.format a.coding a.type bg 0 0; + + ya = [0, max_pair 0 ((b.height - a.height)/2), + max_pair 0 (b.height - a.height)]; + yb = [0, max_pair 0 ((a.height - b.height)/2), + max_pair 0 (a.height - b.height)]; + + im1 = insert_noexpand 0 ya?align a back; + im2 = insert_noexpand (a.width + shim) yb?align b im1; + } + + join_tb shim bg align a b + = im2 + { + w = max_pair a.width b.width; + h = a.height + b.height + shim; + + back = image_new w h a.bands a.format a.coding a.type bg 0 0; + + xa = [0, max_pair 0 ((b.width - a.width)/2), + max_pair 0 (b.width - a.width)]; + xb = [0, max_pair 0 ((a.width - b.width)/2), + max_pair 0 (a.width - b.width)]; + + im1 = insert_noexpand xa?align 0 a back; + im2 = insert_noexpand xb?align (a.height + shim) b im1; + } + + halign_names = ["Top", "Centre", "Bottom"]; + valign_names = ["Left", "Centre", "Right"]; + + Left_right_item = class + Menuaction "_Left to Right" "join two images left-right" { + action a b = class + _result { + _vislevel = 3; + + shim = Scale "Spacing" 0 100 0; + bg_colour = Expression "Background colour" 0; + align = Option "Alignment" halign_names 1; + + _result = map_binary + (join_lr shim.value bg_colour.expr align.value) a b; + } + } + + Top_bottom_item = class + Menuaction "_Top to Bottom" "join two images top-bottom" { + action a b = class + _result { + _vislevel = 3; + + shim = Scale "Spacing" 0 100 0; + bg_colour = Expression "Background colour" 0; + align = Option "Alignment" valign_names 1; + + _result = map_binary + (join_tb shim.value bg_colour.expr align.value) a b; + } + } + + sep2 = Menuseparator; + + Array_item = class + Menuaction "_Array" + "join a list of lists of images into a single image" { + action x = class + _result { + _vislevel = 3; + + hshim = Scale "Horizontal spacing" (-100) (100) 0; + vshim = Scale "Vertical spacing" (-100) (100) 0; + bg_colour = Expression "Background colour" 0; + halign = Option "Horizontal alignment" valign_names 1; + valign = Option "Vertical alignment" halign_names 1; + + // we can't use map_unary since chop-into-tiles returns a group of + // groups and we want to be able to reassemble that + // TODO: chop-into-tiles should return an array class which + // displays as group but does not have the looping behaviour? + _result + = (image_set_origin 0 0 @ + foldl1 (join_tb vshim.value bg_colour.expr halign.value) @ + map (foldl1 (join_lr hshim.value + bg_colour.expr valign.value))) (to_list (to_list x)); + } + } + + ArrayFL_item = class + Menuaction "_Array from List" + "join a list of images into a single image" { + action x = class + _result { + _vislevel = 3; + + ncol = Number "Max. Number of Columns" 1; + hshim = Scale "Horizontal spacing" (-100) (100) 0; + vshim = Scale "Vertical spacing" (-100) (100) 0; + bg_colour = Expression "Background colour" 0; + halign = Option "Horizontal alignment" valign_names 1; + valign = Option "Vertical alignment" halign_names 1; + snake = Toggle "Reverse the order of every other row" false; + + _l + = split_lines ncol.value x.value, is_Group x + = split_lines ncol.value x; + + _l' + = map2 reverse_if_odd [0..] _l, snake + = _l + { + reverse_if_odd n x + = reverse x, n % 2 == 1 + = x; + } + + _result + = (image_set_origin 0 0 @ + foldl1 (join_tb vshim.value bg_colour.expr halign.value) @ + map (foldl1 (join_lr hshim.value + bg_colour.expr valign.value))) (to_list (to_list _l')); + } + } +} + +Image_tile_item = class + Menupullright "Til_e" "tile an image across and down" { + tile_widget default_type x = class + _result { + _vislevel = 3; + + across = Expression "Tiles across" 2; + down = Expression "Tiles down" 2; + repeat = Option "Tile type" + ["Replicate", "Four-way mirror"] default_type; + + _result + = map_unary process x + { + process image + = tile across down image, repeat == 0 + = tile across down image'' + { + image' = insert image.width 0 (fliplr image) image; + image'' = insert 0 image.height (fliptb image') image'; + } + } + } + + Replicate_item = class + Menuaction "_Replicate" "replicate image across and down" { + action x = tile_widget 0 x; + } + + Fourway_item = class + Menuaction "_Four-way Mirror" "four-way mirror across and down" { + action x = tile_widget 1 x; + } + + Chop_item = class + Menuaction "_Chop Into Tiles" "slice an image into tiles" { + action x = class + _result { + _vislevel = 3; + + tile_width = Expression "Tile width" 100; + tile_height = Expression "Tile height" 100; + hoverlap = Expression "Horizontal overlap" 0; + voverlap = Expression "Vertical overlap" 0; + + _result + = map_unary (Group @ map Group @ process) x + { + process x + = imagearray_chop tile_width tile_height + hoverlap voverlap x; + } + } + } +} + +#separator + +Pattern_images_item = class + Menupullright "_Patterns" "make a variety of useful patterns" { + Grey_item = class + Menuaction "Grey _Ramp" "make a smooth grey ramp" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + orientation = Option "Orientation" [ + "Horizontal", + "Vertical" + ] 0; + foption = Option "Format" ["8 bit", "float"] 0; + + _result + = Image im + { + gen + = im_grey, foption == 0 + = im_fgrey; + w = to_real nwidth; + h = to_real nheight; + im + = gen w h, orientation == 0 + = rot90 (gen h w); + } + } + } + + Xy_item = class + Menuaction "_XY Image" + "make a two band image whose pixel values are their coordinates" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + + _result = Image (make_xy nwidth nheight); + } + } + + Noise_item = class + Menupullright "_Noise" "various noise generators" { + Gaussian_item = class + Menuaction "_Gaussian" "make an image of gaussian noise" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + mean = Scale "Mean" 0 255 128; + deviation = Scale "Deviation" 0 128 50; + + _result = Image (gaussnoise nwidth nheight + mean.value deviation.value); + } + } + + Fractal_item = class + Menuaction "_Fractal" "make a fractal noise image" { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + dimension = Scale "Dimension" 2.001 2.999 2.001; + + _result = Image (im_fractsurf (to_real nsize) dimension.value); + } + } + + Perlin_item = class + Menuaction "_Perlin" "Perlin noise image" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + cell_size = Expression "Cell size (pixels)" 8; + eight = Toggle "Eight bit output" true; + + _result + = 128 * im + 128, eight + = im + { + im = perlin cell_size nwidth nheight; + } + } + } + + Worley_item = class + Menuaction "_Worley" "Worley noise image" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 512; + nheight = Expression "Image height (pixels)" 512; + cell_size = Expression "Cell size (pixels)" 256; + + _result + = worley cell_size nwidth nheight; + } + } + + } + + Checkerboard_item = class + Menuaction "_Checkerboard" "make a checkerboard image" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + hpsize = Expression "Horizontal patch size" 8; + vpsize = Expression "Vertical patch size" 8; + hpoffset = Expression "Horizontal patch offset" 0; + vpoffset = Expression "Vertical patch offset" 0; + + _result + = Image (xstripes ^ ystripes) + { + pixels = make_xy nwidth nheight; + xpixels = pixels?0 + to_real hpoffset; + ypixels = pixels?1 + to_real vpoffset; + + make_stripe pix swidth = pix % (swidth * 2) >= swidth; + + xstripes = make_stripe xpixels (to_real hpsize); + ystripes = make_stripe ypixels (to_real vpsize); + } + } + } + + Grid_item = class + Menuaction "Gri_d" "make a grid" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + hspace = Expression "Horizontal line spacing" 8; + vspace = Expression "Vertical line spacing" 8; + thick = Expression "Line thickness" 1; + hoff = Expression "Horizontal grid offset" 4; + voff = Expression "Vertical grid offset" 4; + + _result + = Image (xstripes | ystripes) + { + pixels = make_xy nwidth nheight; + xpixels = pixels?0 + to_real hoff; + ypixels = pixels?1 + to_real voff; + + make_stripe pix swidth = pix % swidth < to_real thick; + + xstripes = make_stripe xpixels (to_real hspace); + ystripes = make_stripe ypixels (to_real vspace); + } + } + } + + Text_item = class + Menuaction "_Text" "make a bitmap of some text" { + action = class + _result { + _vislevel = 3; + + text = String "Text to paint" "Hello world!"; + font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT; + fontfile = Pathname "Font file" ""; + textw = Expression "Text width" 0; + texth = Expression "Text height" 0; + align = Option "Alignment" [ + "Left", + "Centre", + "Right" + ] 0; + dpi = Expression "DPI" 300; + fit = Toggle "Fit text to box" false; + spacing = Expression "Line spacing" 0; + + _result + = Image out + { + base_options = [ + $font => font.value, + $align => align.value + ]; + + set_option name default value + = [name => value], value != default + = []; + + options = base_options ++ concat [ + set_option $width 0 (to_real textw), + set_option $height 0 (to_real texth), + set_option $fontfile "" fontfile.value, + if !fit then set_option $dpi 72 (to_real dpi) else [], + set_option $spacing 0 (to_real spacing) + ]; + + out = vips_text options text.value; + } + } + } + + New_CIELAB_slice_item = class + Menuaction "CIELAB _Slice" "make a slice through CIELAB space" { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + L = Scale "L value" 0 100 50; + + _result = Image (lab_slice (to_real nsize) L.value); + } + } + + sense_option = Option "Sense" [ + "Pass", + "Reject" + ] 0; + + build fn size + = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn) + (im_create_fmask size size); + + New_ideal_item = class + Menupullright "_Ideal Fourier Mask" + "make various ideal Fourier filter masks" { + High_low_item = class + Menuaction "_High or Low Pass" + ("make a mask image for a highpass/lowpass " ++ + "ideal Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f sense.value fc.value 0 0 0 0; + } + } + } + + Ring_item = class + Menuaction "_Ring Pass or Ring Reject" + ("make a mask image for an ring pass/reject " ++ + "ideal Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + rw = Scale "Ring width" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 6) fc.value rw.value 0 0 0; + } + } + } + + Band_item = class + Menuaction "_Band Pass or Band Reject" + ("make a mask image for a band pass/reject " ++ + "ideal Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; + fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; + r = Scale "Radius" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 12) fcx.value fcy.value + r.value 0 0; + } + } + } + } + + New_gaussian_item = class + Menupullright "_Gaussian Fourier Mask" + "make various Gaussian Fourier filter masks" { + High_low_item = class + Menuaction "_High or Low Pass" + ("make a mask image for a highpass/lowpass " ++ + "Gaussian Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 4) fc.value ac.value 0 0 0; + } + } + } + + Ring_item = class + Menuaction "_Ring Pass or Ring Reject" + ("make a mask image for an ring pass/reject " ++ + "Gaussian Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + rw = Scale "Ring width" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 10) fc.value rw.value + ac.value 0 0; + } + } + } + + Band_item = class + Menuaction "_Band Pass or Band Reject" + ("make a mask image for a band pass/reject " ++ + "Gaussian Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; + fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; + r = Scale "Radius" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 16) fcx.value fcy.value + r.value ac.value 0; + } + } + } + } + + New_butterworth_item = class + Menupullright "_Butterworth Fourier Mask" + "make various Butterworth Fourier filter masks" { + High_low_item = class + Menuaction "_High or Low Pass" + ("make a mask image for a highpass/lowpass " ++ + "Butterworth Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + order = Scale "Order" 1 10 2; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 2) order.value fc.value + ac.value 0 0; + } + } + } + + Ring_item = class + Menuaction "_Ring Pass or Ring Reject" + ("make a mask image for an ring pass/reject " ++ + "Butterworth Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fc = Scale "Frequency cutoff" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + rw = Scale "Ring width" 0.01 0.99 0.5; + order = Scale "Order" 1 10 2; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 8) order.value fc.value + rw.value ac.value 0; + } + } + } + + Band_item = class + Menuaction "_Band Pass or Band Reject" + ("make a mask image for a band pass/reject " ++ + "Butterworth Fourier filter") { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + sense = sense_option; + fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5; + fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5; + r = Scale "Radius" 0.01 0.99 0.5; + ac = Scale "Amplitude cutoff" 0.01 0.99 0.5; + order = Scale "Order" 1 10 2; + + _result + = build param (to_real nsize) + { + param f = f (sense.value + 14) order.value fcx.value + fcy.value r.value ac.value; + } + } + } + } +} + +Test_images_item = class + Menupullright "Test I_mages" "make a variety of test images" { + Eye_item = class + Menuaction "_Spatial Response" + "image for testing the eye's spatial response" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + nheight = Expression "Image height (pixels)" 64; + factor = Scale "Factor" 0.001 1 0.2; + + _result = Image (im_eye (to_real nwidth) (to_real nheight) + factor.value); + } + } + + Zone_plate = class + Menuaction "_Zone Plate" "make a zone plate" { + action = class + _result { + _vislevel = 3; + + nsize = Expression "Image size (pixels)" 64; + + _result = Image (im_zone (to_real nsize)); + } + } + + Frequency_test_chart_item = class + Menuaction "_Frequency Testchart" + "make a black/white frequency test pattern" { + action = class + _result { + _vislevel = 3; + + nwidth = Expression "Image width (pixels)" 64; + sheight = Expression "Strip height (pixels)" 10; + waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2]; + + _result + = imagearray_assemble 0 0 (transpose [strips]) + { + freq_slice wave = Image (sin (grey / wave) > 0); + strips = map freq_slice waves.expr; + grey = im_fgrey (to_real nwidth) (to_real sheight) * + 360 * (to_real nwidth); + } + } + } + + CRT_test_chart_item = class + Menuaction "CRT _Phosphor Chart" + "make an image for measuring phosphor colours" { + action = class + _result { + _vislevel = 3; + + brightness = Scale "Brightness" 0 255 200; + psize = Expression "Patch size (pixels)" 32; + + _result + = Image (imagearray_assemble 0 0 [[green, red], [blue, white]]) + { + + black = image_new (to_real psize) (to_real psize) 1 + Image_format.FLOAT Image_coding.NOCODING + Image_type.B_W 0 0 0; + notblack = black + brightness; + + green = black ++ notblack ++ black; + red = notblack ++ black ++ black; + blue = black ++ black ++ notblack; + white = notblack ++ notblack ++ notblack; + } + } + } + + Greyscale_chart_item = class + Menuaction "_Greyscale" "make a greyscale" { + action = class + _result { + _vislevel = 3; + + pwidth = Expression "Patch width" 8; + pheight = Expression "Patch height" 8; + npatches = Expression "Number of patches" 16; + + _result + = Image (image_set_type Image_type.B_W + (clip2fmt Image_format.UCHAR wedge)) + { + wedge + = 255 / (to_real npatches - 1) * + (int) (strip?0 / to_real pwidth) + { + strip = make_xy (to_real pwidth * to_real npatches) pheight; + } + } + } + } + + CMYK_test_chart_item = class + Menuaction "_CMYK Wedges" "make a set of CMYK wedges" { + action = class + _result { + _vislevel = 3; + + pwidth = Expression "Patch width" 8; + pheight = Expression "Patch height" 8; + npatches = Expression "Number of patches" 16; + + _result + = Image (image_set_type Image_type.CMYK + (clip2fmt Image_format.UCHAR strips)) + { + wedge + = 255 / (to_real npatches - 1) * + (int) (strip?0 / to_real pwidth) + { + strip = make_xy (to_real pwidth * to_real npatches) pheight; + } + + black = wedge * 0; + + C = wedge ++ black ++ black ++ black; + M = black ++ wedge ++ black ++ black; + Y = black ++ black ++ wedge ++ black; + K = black ++ black ++ black ++ wedge; + + strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]]; + } + } + } + + Colour_atlas_item = class + Menuaction "_Colour Atlas" + "make a grid of patches grouped around a colour" { + action = class + _result { + _vislevel = 3; + + start = Colour_picker "Lab" [50,0,0]; + nstep = Expression "Number of steps" 9; + ssize = Expression "Step size" 10; + psize = Expression "Patch size" 32; + sepsize = Expression "Separator size" 4; + + _result + = colour_transform_to (get_type start) blocks''' + { + size = (to_real nstep * 2 + 1) * to_real psize - + to_real sepsize; + xy = make_xy size size; + + xy_grid = (xy % to_real psize) < + (to_real psize - to_real sepsize); + grid = xy_grid?0 & xy_grid?1; + + blocks = (int) (to_real ssize * ((int) (xy / to_real psize))); + lab_start = colour_transform_to Image_type.LAB start; + blocks' = blocks - to_real nstep * to_real ssize + + Vector (tl lab_start.value); + blocks'' = Vector [hd lab_start.value] ++ Image blocks'; + blocks''' + = image_set_type Image_type.LAB blocks'', Image grid + = 0; } } } diff --git a/share/nip4/start/_convert.def b/share/nip4/start/_convert.def index c1b41c5..b4dcf92 100644 --- a/share/nip4/start/_convert.def +++ b/share/nip4/start/_convert.def @@ -381,13 +381,6 @@ im_RGB162GREY16 in im_GREY162RGB16 in = image_set_type Image_type.RGB16 (in ++ in ++ in); -/* The vips8 scRGB functions. - */ -im_sRGB2scRGB in = vips_sRGB2scRGB [] in; -im_scRGB2sRGB in = vips_scRGB2sRGB [] in; -im_scRGB2XYZ in = vips_scRGB2XYZ [] in; -im_XYZ2scRGB in = vips_XYZ2scRGB [] in; - /* apply a func to an image ... make it 1 or 3 bands, and reapply other bands * on the way out. Except if it's LABPACK. */ diff --git a/share/nip4/start/_stdenv.def b/share/nip4/start/_stdenv.def index 07d34f0..f10b5d0 100644 --- a/share/nip4/start/_stdenv.def +++ b/share/nip4/start/_stdenv.def @@ -2545,17 +2545,6 @@ unpremultiply x unprem x = vips_unpremultiply [] x; } -hist_entropy x - = oo_unary_function hist_entropy_op x, is_class x - = entropy x, is_image x - = error (_ "bad arguments to " ++ "hist_entropy") -{ - hist_entropy_op = Operator "hist_entropy" - hist_entropy Operator_type.COMPOUND_REWRAP false; - - entropy x = vips_hist_entropy [] x; -} - canny sigma precision x = oo_unary_function canny_op x, is_class x = canny_im (to_real sigma) (to_int precision) x, is_image x diff --git a/share/nip4/start/_types.def b/share/nip4/start/_types.def index c00d332..2609142 100644 --- a/share/nip4/start/_types.def +++ b/share/nip4/start/_types.def @@ -272,8 +272,7 @@ Vector value = class is_Vector x && len value == len x.value && op.type == Operator_type.COMPOUND_REWRAP], - [x.Image (vec op'.op_name x.value value), - is_Image x], + // (is_Image x) is left for the Image class to handle [vec op'.op_name x value, is_image x], [op.fn this.value x, @@ -912,6 +911,8 @@ Image value = class has_image x], [wrap (op.fn this.value (get_number x)), has_number x], + [wrap (vec op.op_name this.value x.value), + is_Vector x], // if it's not a class on the RHS, handle here ... just apply and // rewrap [wrap (op.fn this.value x), diff --git a/share/nip4/start/_vips7compat.def b/share/nip4/start/_vips7compat.def index 121eda6..49296f1 100644 --- a/share/nip4/start/_vips7compat.def +++ b/share/nip4/start/_vips7compat.def @@ -84,7 +84,6 @@ im_less_vec im vec = vips_relational_const [] im "less" vec; im_lesseq_vec im vec = vips_relational_const [] im "lesseq" vec; im_more_vec im vec = vips_relational_const [] im "more" vec; im_moreeq_vec im vec = vips_relational_const [] im "moreeq" vec; -im_notequal_vec in vec = vips_relational_const [] in "noteq" vec; im_maxpos in = (x, y) @@ -112,14 +111,15 @@ im_LabS2LabQ x = vips_LabS2LabQ [] x; im_LabS2Lab x = vips_LabS2Lab [] x; im_LCh2UCS x = vips_LCh2CMC [] x; im_LCh2Lab x = vips_LCh2Lab [] x; -im_scRGB2XYZ x = vips_scRGB2XYZ [] x; im_scRGB2sRGB x = vips_scRGB2sRGB [] x; im_scRGB2XYZ x = vips_scRGB2XYZ [] x; im_UCS2LCh x = vips_CMC2LCh [] x; +im_XYZ2scRGB in = vips_XYZ2scRGB [] in; im_XYZ2Lab x = vips_XYZ2Lab [] x; im_XYZ2Lab_temp x x0 y0 z0 = vips_XYZ2Lab [$temp => [x0, y0, z0]] x; im_XYZ2Yxy x = vips_XYZ2Yxy [] x; im_Yxy2XYZ x = vips_Yxy2XYZ [] x; +im_sRGB2scRGB in = vips_sRGB2scRGB [] in; im_XYZ2UCS = im_LCh2UCS @ im_Lab2LCh @ im_XYZ2Lab; im_UCS2XYZ = im_Lab2XYZ @ im_LCh2Lab @ im_UCS2LCh; diff --git a/src/columnview.c b/src/columnview.c index dba3b5e..bbc6ef8 100644 --- a/src/columnview.c +++ b/src/columnview.c @@ -157,7 +157,7 @@ columnview_merge(Columnview *cview) workspace_column_select(ws, col); workspacegroup_set_load_type(wsg, WORKSPACEGROUP_LOAD_ROWS); - filemodel_open(GTK_WINDOW(main), FILEMODEL(wsg), _("Merge"), + filemodel_merge(GTK_WINDOW(main), FILEMODEL(wsg), _("Merge"), columnview_merge_next, columnview_saveas_error, cview, NULL); } diff --git a/src/compile.c b/src/compile.c index a8714e6..4ec7897 100644 --- a/src/compile.c +++ b/src/compile.c @@ -268,6 +268,7 @@ compile_finalize(GObject *gobject) (void) slist_map(compile->children, (SListMapFn) symbol_link_break, compile); VIPS_FREEF(g_slist_free, compile->children); + VIPS_FREEF(g_slist_free, compile->matchers); /* Remove static strings we created. */ @@ -1392,8 +1393,7 @@ compile_transform_share(HeapNode *hn, Compile *compile) VipsBuf buf = VIPS_BUF_STATIC(txt); graph_node(heap, &buf, hn1, TRUE); - printf("Found shared code: %s\n", - vips_buf_all(&buf)); + printf("Found shared code: %s\n", vips_buf_all(&buf)); } #endif /*DEBUG*/ @@ -1468,6 +1468,226 @@ compile_remove_subexpr(Compile *compile, PElement *root) return TRUE; } +/* This is a func with multiple defs, or which needs multiple defs. Check that: + * + * - all defs have the same number of args + * - no more than one def has no pattern matching args + * - if there is a no-pattern def, it must be the last one + */ +static gboolean +compile_defs_check(Compile *compile) +{ + g_assert(compile->sym->next_def || !compile->has_default); + + int nparam = -1; + int rhs = 1; + for (Symbol *p = compile->sym; p; p = p->next_def, rhs++) { + if (nparam == -1) + nparam = p->expr->compile->nparam; + else if (p->expr->compile->nparam != nparam) { + error_top(_("Argument numbers don't match")); + error_sub(_("definition %d of \"%s\" should have %d arguments"), + rhs, symbol_name(compile->sym), nparam); + return FALSE; + } + + if (p->expr->compile->params_include_patterns) { + /* This RHS has patterns, so it can't be defined after the + * default. + */ + if (compile->has_default) { + error_top(_("Default case already defined")); + error_sub(_("definition %d of \"%s\" follows the default case"), + rhs, symbol_name(compile->sym)); + return FALSE; + } + } + else { + /* This RHS has no patterns in the args, so it defines the + * default case. + */ + if (compile->has_default) { + error_top(_("Default case already defined")); + error_sub(_("definition %d of \"%s\" is a second default case"), + rhs, symbol_name(compile->sym)); + return FALSE; + } + + compile->has_default = TRUE; + } + } + + return TRUE; +} + +/* Generate a parsetree for a "pattern match failed" error. + */ +static ParseNode * +compile_pattern_error(Compile *compile) +{ + ParseNode *left; + ParseConst n; + ParseNode *right; + ParseNode *node; + + left = tree_leaf_new(compile, "error"); + n.type = PARSE_CONST_STR; + n.val.str = g_strdup(_("pattern match failed")); + right = tree_const_new(compile, n); + node = tree_appl_new(compile, left, right); + + return node; +} + +/* Append a default case to the set of defs. + */ +static gboolean +compile_defs_codegen_default(Compile *compile) +{ + g_assert(!compile->has_default); + + Symbol *parent = symbol_get_parent(compile->sym); + Symbol *def = symbol_new_defining(parent->expr->compile, + IOBJECT(compile->sym)->name); + (void) symbol_user_init(def); + (void) compile_new_local(def->expr); + symbol_made(def); + + for (int i = 0; i < compile->nparam; i++) { + char name[256]; + + g_snprintf(name, sizeof(name), "$$param%d", i); + Symbol *param = symbol_new_defining(def->expr->compile, name); + param->generated = TRUE; + symbol_parameter_init(param); + } + + def->expr->compile->tree = compile_pattern_error(def->expr->compile); + + compile->has_default = TRUE; + + /* We ref error, resolve outwards. + */ + compile_resolve_names(def->expr->compile, parent->expr->compile); + +#ifdef DEBUG + printf("compile_defs_codegen_default: generated "); + dump_compile(def->expr->compile); +#endif /*DEBUG*/ + + return TRUE; +} + +/* We need to: + * + * - if there's no default case, generate a final definition + * + * $$fred_def99 a b c = error "pattern match failed"; + * + * and link it on to the end of the chain of defs + * + * - for each pattern match def: + * - save the rhs tree somewhere, eg. for fred [a] = 1; keep the "1" + * - find all the local arg $$pattN on this compile + * + * we know compile->nargs, just loop and test? + * + * not all args will use patterns + * + * we have GSList *compile->param + * + * - make a new rhs with + * + * ifthenelse(compile_pattern_condition($$patt0) && + * compile_pattern_condition($$patt1) && + * ..., + * saved rhs, + * $$fred_defN) + * + * - for each $$pattN + * - for each leaf in the pattern + * - make an access var + * - remove the $$pattN locals + * + * So for: + * + * fred [a] = 12; + * + * We generate: + * + * fred $$arg0 + * = 12, if is_list $$arg0 && is_list_len 1 $$argv0 + * = $$fred_def0 $$arg0 + * { + * a = $$arg0?0 + * } + * + * $$fred_def0 $$arg0 = error "pattern match failed"; + * + */ +static gboolean +compile_defs_codegen(Compile *compile) +{ + /* Add the default case, if it's missing. + */ + if (!compile->has_default && + !compile_defs_codegen_default(compile)) + return FALSE; + + /* For all syms except the last in this set of defs. + * + * We don't need to gen the final one (always the no pattern default case). + */ + for (Symbol *sym = compile->sym; sym->next_def; sym = sym->next_def) { + Compile *this_compile = sym->expr->compile; + + /* AND all the matches together. + */ + g_assert(this_compile->matchers); + Symbol *match = SYMBOL(this_compile->matchers->data); + ParseNode *condition = tree_leafsym_new(this_compile, match); + for (GSList *p = this_compile->matchers->next; p; p = p->next) { + match = SYMBOL(p->data); + ParseNode *node = tree_leafsym_new(this_compile, match); + condition = tree_binop_new(this_compile, BI_LAND, condition, node); + } + + /* Generate the call to the next def in the chain. + */ + ParseNode *next_def = tree_leafsym_new(this_compile, sym->next_def); + for (GSList *p = this_compile->param; p; p = p->next) { + Symbol *param = SYMBOL(p->data); + + ParseNode *node = tree_leafsym_new(this_compile, param); + next_def = tree_appl_new(this_compile, next_def, node); + } + + /* Wrap the RHS in a condition, bounce to the next in case of fail. + */ + this_compile->tree = tree_ifelse_new(this_compile, + condition, + this_compile->tree, // the old RHS the user wrote + next_def); + + /* We may have generated lots of new refs and zombies in this def, + * resolve them outwards. + */ + compile_resolve_names(this_compile, compile_get_parent(this_compile)); + + /* Update recomp links in case this is a top-levelk sym + */ + symbol_made(sym); + +#ifdef DEBUG + printf("compile_defs_codegen: generated:\n"); + if (this_compile->tree) + dump_tree(this_compile->tree, 2); +#endif /*DEBUG*/ + } + + return TRUE; +} + /* Top-level compiler driver. */ @@ -1496,11 +1716,11 @@ compile_heap(Compile *compile) } #ifdef DEBUG - printf("*** compile_expr: about to compile "); + printf("*** compile_heap: about to compile "); symbol_name_print(compile->sym); printf("\n"); if (compile->tree) - dump_tree(compile->tree); + dump_tree(compile->tree, 2); #endif /*DEBUG*/ /* Compile function body. Tree can be NULL for classes. @@ -1608,28 +1828,66 @@ compile_object(Compile *compile) } static void * -compile_toolkit_sub(Tool *tool) +compile_codegen(Compile *compile) { - Compile *compile; + Symbol *sym = compile->sym; - if (tool->sym && tool->sym->expr && - (compile = tool->sym->expr->compile)) - /* Only if we have no code. + if (sym->needs_codegen) { +#ifdef DEBUG + printf("compile_codegen_sym: codegen for "); + symbol_name_print(sym); + printf("\n"); + printf("\tbefore codegen, AST is:\n"); + dump_compile(compile); +#endif /*DEBUG*/ + + /* For now the only codegen is for multiple defs. */ - if (compile->base.type == ELEMENT_NOVAL) - if (compile_object(compile)) - return tool; + if (sym->next_def || + !compile->has_default) { + if (!compile_defs_check(compile) || + !compile_defs_codegen(compile)) + return sym; + } + } + + sym->needs_codegen = FALSE; + + return NULL; +} + +/* This is a top-level def: search for any syms which need a codegen pass. For + * example, a top-level def might have locals with multiple defs. + */ +void * +compile_codegen_toplevel(Symbol *sym) +{ + if (sym->expr && + sym->expr->compile && + compile_map_all(sym->expr->compile, + (map_compile_fn) compile_codegen, NULL)) + return sym; + + return NULL; +} + +static void * +compile_codegen_tool(Tool *tool) +{ + if (tool->sym && + tool->sym->needs_codegen && + compile_codegen_toplevel(tool->sym)) + return tool; return NULL; } -/* Scan a toolkit and make sure all the symbols have been compiled. +/* Scan a toolkit and do any codegen. */ void * -compile_toolkit(Toolkit *kit) +compile_codegen_toolkit(Toolkit *kit) { - return toolkit_map(kit, - (tool_map_fn) compile_toolkit_sub, NULL, NULL); + return toolkit_map(kit, (tool_map_fn) compile_codegen_tool, NULL, NULL); } /* Parse support. @@ -2222,7 +2480,7 @@ compile_lcomp(Compile *compile) ParseNode *n1, *n2, *n3; #ifdef DEBUG_LCOMP - printf("before compile_lcomp:\n"); + printf("before compile_lcomp: "); dump_compile(compile); #endif /*DEBUG_LCOMP*/ @@ -2316,7 +2574,7 @@ compile_lcomp(Compile *compile) pattern = compile_lcomp_find_pattern(children, IOBJECT(element)->name); g_assert(pattern); - built_syms = compile_pattern_lhs(child->expr->compile, + built_syms = compile_pattern(child->expr->compile, param1, pattern->expr->compile->tree); g_slist_free(built_syms); @@ -2376,7 +2634,7 @@ compile_lcomp(Compile *compile) } #ifdef DEBUG_LCOMP - printf("after compile_lcomp:\n"); + printf("after compile_lcomp: "); dump_compile(compile); #endif /*DEBUG_LCOMP*/ @@ -2390,36 +2648,53 @@ compile_lcomp(Compile *compile) * * compiles to: * - * sym = x; - * a = if is_list sym && len sym == 1 then sym?0 else error ".."; + * $$valueN = x; + * $$matchN = is_list $$valueN && len $$valueN == 1; + * a = if $$matchN then $$valueN?0 else error "pattern match failed"; + * + * Also used for function argument pattern matching. */ -/* Generate code to access element n of a pattern trail. Eg, pattern is - * [[[a]]] - * the trail will be - * 0) LISTCONST 1) LISTCONST 2) LISTCONST 3) LEAF - * then access(0) will be - * leaf - * and access(1) will be - * leaf?0 - * and access(3) (to get the value for a) will be - * leaf?0?0?0 +/* Depth of trail we keep as we walk the pattern. + */ +#define MAX_TRAIL (50) + +/* Generate code to access element depth of a pattern trail from a value. + * + * Eg,: + * + * $$patt0 = [12, (13, a)] + * $$value0 = [12, (13, 14)] + * + * the trail for "a" is built as we recurse down $$patt0 AST, so: + * + * 0) LISTCONST 1) COMPLEX 2) IM + * + * with n == 3 meaning 3 items in trail. + * + * For access, we read from left, so: + * + * depth generate type + * + * 0 $$value0 list + * 1 $$value0?1 complex + * 2 im $$value0?1 ref to a + * */ static ParseNode * compile_pattern_access(Compile *compile, - Symbol *leaf, ParseNode **trail, int n) + Symbol *value, ParseNode **trail, int depth) { ParseNode *node; ParseNode *left; ParseNode *right; ParseConst c; - int i; /* The initial leaf ref we access from. */ - node = tree_leafsym_new(compile, leaf); + node = tree_leafsym_new(compile, value); - for (i = 0; i < n; i++) + for (int i = 0; i < depth; i++) switch (trail[i]->type) { case NODE_CONST: case NODE_PATTERN_CLASS: @@ -2472,184 +2747,181 @@ compile_pattern_access(Compile *compile, return node; } -/* Generate a parsetree for the condition test. The array of nodes represents - * the set of conditions we have to test, left to right. +/* Generate a parsetree for the match test. trail is the trail of parsenodes + * we have recursed down to find this pattern root is in trail[0]. + * + * Return NULL for no testing needed. */ static ParseNode * compile_pattern_condition(Compile *compile, - Symbol *leaf, ParseNode **trail, int depth) + Symbol *value, ParseNode **trail, int depth) { - ParseConst n; + g_assert(depth > 0); + ParseNode *patt = trail[depth - 1]; + + ParseConst c; ParseNode *node; ParseNode *node2; ParseNode *left; ParseNode *right; - int i; - - n.type = PARSE_CONST_BOOL; - n.val.bol = TRUE; - node = tree_const_new(compile, n); - - for (i = depth - 1; i >= 0; i--) { - switch (trail[i]->type) { - case NODE_LEAF: - break; - - case NODE_BINOP: - switch (trail[i]->biop) { - case BI_COMMA: - /* Generate is_complex x. - */ - left = tree_leaf_new(compile, "is_complex"); - right = compile_pattern_access(compile, - leaf, trail, i); - node2 = tree_appl_new(compile, left, right); - node = tree_binop_new(compile, - BI_LAND, node2, node); - break; + node = NULL; - case BI_CONS: - /* Generate is_list x && x != []. - */ - left = tree_leaf_new(compile, "is_list"); - right = compile_pattern_access(compile, - leaf, trail, i); - node2 = tree_appl_new(compile, left, right); - - node = tree_binop_new(compile, - BI_LAND, node2, node); - - left = compile_pattern_access(compile, - leaf, trail, i); - n.type = PARSE_CONST_ELIST; - right = tree_const_new(compile, n); - node2 = tree_binop_new(compile, - BI_NOTEQ, left, right); - - node = tree_binop_new(compile, - BI_LAND, node, node2); - break; + switch (patt->type) { + case NODE_LEAF: + break; - default: - g_assert(0); - } + case NODE_BINOP: + switch (patt->biop) { + case BI_COMMA: + /* Generate is_complex x. + */ + left = tree_leaf_new(compile, "is_complex"); + right = compile_pattern_access(compile, value, trail, depth - 1); + node = tree_appl_new(compile, left, right); break; - case NODE_LISTCONST: - /* Generate is_list x && is_list_len n x. + case BI_CONS: + /* Generate is_list x && x != []. */ left = tree_leaf_new(compile, "is_list"); - right = compile_pattern_access(compile, - leaf, trail, i); - node2 = tree_appl_new(compile, left, right); - - node = tree_binop_new(compile, BI_LAND, node2, node); + right = compile_pattern_access(compile, value, trail, depth - 1); + node = tree_appl_new(compile, left, right); - left = tree_leaf_new(compile, "is_list_len"); - n.type = PARSE_CONST_NUM; - n.val.num = g_slist_length(trail[i]->elist); - right = tree_const_new(compile, n); - left = tree_appl_new(compile, left, right); - right = compile_pattern_access(compile, - leaf, trail, i); - node2 = tree_appl_new(compile, left, right); + left = compile_pattern_access(compile, value, trail, depth - 1); + c.type = PARSE_CONST_ELIST; + right = tree_const_new(compile, c); + node2 = tree_binop_new(compile, BI_NOTEQ, left, right); node = tree_binop_new(compile, BI_LAND, node, node2); - break; - case NODE_CONST: - /* Generate x == n. + /* Recurse down the left and right sides in case there's something + * we must test there. */ - left = compile_pattern_access(compile, - leaf, trail, i); - right = tree_const_new(compile, trail[i]->con); - node2 = tree_binop_new(compile, BI_EQ, left, right); + g_assert(depth < MAX_TRAIL); + trail[depth] = patt->arg1; + node2 = compile_pattern_condition(compile, value, trail, depth + 1); + if (node2) + node = tree_binop_new(compile, BI_LAND, node, node2); - node = tree_binop_new(compile, BI_LAND, node2, node); - break; + trail[depth] = patt->arg2; + node2 = compile_pattern_condition(compile, value, trail, depth + 1); + if (node2) + node = tree_binop_new(compile, BI_LAND, node, node2); - case NODE_PATTERN_CLASS: - /* Generate is_instanceof "class-name" x. - */ - left = tree_leaf_new(compile, "is_instanceof"); - n.type = PARSE_CONST_STR; - n.val.str = g_strdup(trail[i]->tag); - right = tree_const_new(compile, n); - node2 = tree_appl_new(compile, left, right); - right = compile_pattern_access(compile, - leaf, trail, i); - node2 = tree_appl_new(compile, node2, right); - - node = tree_binop_new(compile, BI_LAND, node2, node); break; default: g_assert(0); } - } + break; - return node; -} + case NODE_LISTCONST: + /* Generate is_list x && is_list_len n x. + */ + left = tree_leaf_new(compile, "is_list"); + right = compile_pattern_access(compile, value, trail, depth - 1); + node = tree_appl_new(compile, left, right); + + left = tree_leaf_new(compile, "is_list_len"); + c.type = PARSE_CONST_NUM; + c.val.num = g_slist_length(patt->elist); + right = tree_const_new(compile, c); + left = tree_appl_new(compile, left, right); + right = compile_pattern_access(compile, value, trail, depth - 1); + node2 = tree_appl_new(compile, left, right); + node = tree_binop_new(compile, BI_LAND, node, node2); + + /* Recurse for each item in the list. + */ + for (GSList *p = patt->elist; p; p = p->next) { + ParseNode *item = (ParseNode *) p->data; + + g_assert(depth < MAX_TRAIL); + trail[depth] = item; + node2 = compile_pattern_condition(compile, value, trail, depth + 1); + if (node2) + node = tree_binop_new(compile, BI_LAND, node, node2); + } -/* Generate a parsetree for a "pattern match failed" error. - */ -static ParseNode * -compile_pattern_error(Compile *compile, Symbol *leaf) -{ - ParseNode *left; - ParseConst n; - ParseNode *right; - ParseNode *node; + break; - left = tree_leaf_new(compile, "error"); - n.type = PARSE_CONST_STR; - n.val.str = g_strdup(_("pattern match failed")); - right = tree_const_new(compile, n); - node = tree_appl_new(compile, left, right); + case NODE_CONST: + /* Generate x == n. + */ + left = compile_pattern_access(compile, value, trail, depth - 1); + right = tree_const_new(compile, patt->con); + node = tree_binop_new(compile, BI_EQ, left, right); + + break; + + case NODE_PATTERN_CLASS: + /* Generate is_instanceof "class-name" x. + */ + left = tree_leaf_new(compile, "is_instanceof"); + c.type = PARSE_CONST_STR; + c.val.str = g_strdup(patt->tag); + right = tree_const_new(compile, c); + node2 = tree_appl_new(compile, left, right); + right = compile_pattern_access(compile, value, trail, depth - 1); + node = tree_appl_new(compile, node2, right); + + break; + + default: + g_assert(0); + } return node; } -/* Depth of trail we keep as we walk the pattern. - */ -#define MAX_TRAIL (10) +typedef struct _PatternInfo { + Compile *compile; /* Scope in which we generate new symbols */ + + ParseNode *pattern; /* The pattern we are generating syms from */ + Symbol *value; /* The thing we fetch values from */ -typedef struct _PatternLhs { - Compile *compile; /* Scope in which we generate new symbols */ - Symbol *sym; /* Thing we access */ + /* The $$match condition. + */ + Symbol *match; /* The trail of nodes representing this slice of the pattern. */ ParseNode *trail[MAX_TRAIL]; int depth; + + /* The symbols we have built. + */ GSList *built_syms; -} PatternLhs; +} PatternInfo; /* Generate one reference. leaf is the new sym we generate. */ static void -compile_pattern_lhs_leaf(PatternLhs *lhs, Symbol *leaf) +compile_pattern_leaf(PatternInfo *info, Symbol *leaf) { - Symbol *sym; - Compile *compile; - - sym = symbol_new_defining(lhs->compile, IOBJECT(leaf)->name); + Symbol *sym = symbol_new_defining(info->compile, IOBJECT(leaf)->name); sym->generated = TRUE; (void) symbol_user_init(sym); (void) compile_new_local(sym->expr); - lhs->built_syms = g_slist_prepend(lhs->built_syms, sym); - compile = sym->expr->compile; + symbol_made(sym); + Compile *compile = sym->expr->compile; compile->tree = tree_ifelse_new(compile, - compile_pattern_condition(compile, - lhs->sym, lhs->trail, lhs->depth), + tree_leaf_new(compile, IOBJECT(info->match)->name), compile_pattern_access(compile, - lhs->sym, lhs->trail, lhs->depth), - compile_pattern_error(compile, leaf)); + info->value, info->trail, info->depth - 1), + compile_pattern_error(compile)); + + /* The access sym will contain refs to $$value, $$match etc. which must be + * resolved out one. + */ + compile_resolve_names(compile, info->compile); + + info->built_syms = g_slist_append(info->built_syms, sym); #ifdef DEBUG_PATTERN - printf("compile_pattern_lhs_leaf: generated\n"); + printf("compile_pattern_leaf: generated "); dump_compile(compile); #endif /*DEBUG_PATTERN*/ } @@ -2657,27 +2929,26 @@ compile_pattern_lhs_leaf(PatternLhs *lhs, Symbol *leaf) /* Recurse over the pattern generating references. */ static void * -compile_pattern_lhs_sub(ParseNode *node, PatternLhs *lhs) +compile_pattern_sub(ParseNode *node, PatternInfo *info) { - lhs->trail[lhs->depth++] = node; + info->trail[info->depth++] = node; switch (node->type) { case NODE_LEAF: - compile_pattern_lhs_leaf(lhs, node->leaf); + compile_pattern_leaf(info, node->leaf); break; case NODE_PATTERN_CLASS: - compile_pattern_lhs_sub(node->arg1, lhs); + compile_pattern_sub(node->arg1, info); break; case NODE_BINOP: - compile_pattern_lhs_sub(node->arg1, lhs); - compile_pattern_lhs_sub(node->arg2, lhs); + compile_pattern_sub(node->arg1, info); + compile_pattern_sub(node->arg2, info); break; case NODE_LISTCONST: - slist_map(node->elist, - (SListMapFn) compile_pattern_lhs_sub, lhs); + slist_map(node->elist, (SListMapFn) compile_pattern_sub, info); break; case NODE_CONST: @@ -2687,37 +2958,81 @@ compile_pattern_lhs_sub(ParseNode *node, PatternLhs *lhs) g_assert(0); } - lhs->depth--; + info->depth--; return NULL; } -/* Something like "[a] = [1];". sym is the $$pattern we are generating access - * syms for, node is the pattern tree, compile is the scope in which we - * generate the new defining symbols. Return a list of the syms we built: they - * will need any final finishing up and then having symbol_made() called on - * them. You need to free the list, too. +Symbol * +compile_pattern_match(Compile *compile, Symbol *value, ParseNode *pattern) +{ + static int match_id = 0; + + char name[256]; + g_snprintf(name, sizeof(name), "$$match%d", match_id++); + Symbol *match = symbol_new_defining(compile, name); + match->generated = TRUE; + (void) symbol_user_init(match); + (void) compile_new_local(match->expr); + symbol_made(match); + + ParseNode *trail[MAX_TRAIL]; + trail[0] = pattern; + ParseNode *node = + compile_pattern_condition(match->expr->compile, value, trail, 1); + if (node) + match->expr->compile->tree = node; + else { + ParseConst c; + + c.type = PARSE_CONST_BOOL; + c.val.bol = TRUE; + match->expr->compile->tree = tree_const_new(match->expr->compile, c); + } + + /* The $$match can call eg. is_list_len, we need to resolve these + * references. + */ + compile_resolve_names(match->expr->compile, compile); + +#ifdef DEBUG + printf("compile_pattern_match: generated "); + dump_compile(match->expr->compile); +#endif /*DEBUG*/ + + return match; +} + +/* Something like "[a] = [1];". sym is the $$value0 we fetch values from, + * node is the pattern tree, compile is the scope in which we + * generate the new defining symbols. + * + * $$match0 = if is_list $$value0 && ... + * a = if $$match0 then $$value0?0 else error "pattern match failed" + * b = ... + * + * Return a list of the new symbols we built, they will need finishing up. + * + * The first returned sym is the $$match test. */ GSList * -compile_pattern_lhs(Compile *compile, Symbol *sym, ParseNode *node) +compile_pattern(Compile *compile, Symbol *value, ParseNode *pattern) { - PatternLhs lhs; + PatternInfo info; -#ifdef DEBUG_PATTERN - printf("compile_pattern_lhs: building access fns for %s\n", - symbol_name(sym)); -#endif /*DEBUG_PATTERN*/ + info.compile = compile; + info.value = value; + info.depth = 0; + info.match = compile_pattern_match(compile, value, pattern); + info.built_syms = NULL; - lhs.compile = compile; - lhs.sym = sym; - lhs.depth = 0; - lhs.built_syms = NULL; + info.built_syms = g_slist_append(info.built_syms, info.match); - compile_pattern_lhs_sub(node, &lhs); + compile_pattern_sub(pattern, &info); - g_assert(lhs.depth == 0); + g_assert(info.depth == 0); - return lhs.built_syms; + return info.built_syms; } static ParseNode * diff --git a/src/compile.h b/src/compile.h index a3fa12a..2cdb423 100644 --- a/src/compile.h +++ b/src/compile.h @@ -55,25 +55,37 @@ struct _Compile { gboolean is_klass; /* True if this is a class */ gboolean has_super; /* True if has a super-class */ - char *text; /* The original text */ - char *prhstext; /* Parameters plus the RHS of the definition */ - char *rhstext; /* Just the RHS of the definition */ - - ParseNode *tree; /* Parse tree we built */ - GSList *treefrag; /* List of tree bits for easy freeing */ - Symbol *last_sym; /* The last child we added in this context */ - - int nparam; /* Number of real parameters */ - GSList *param; /* Pointers into locals for real params */ - int nsecret; /* Number of secret parameters */ - GSList *secret; /* Pointers into locals for secret params */ - Symbol *this; /* If we are a class, the "this" local */ - Symbol *super; /* If we are a class, the "super" local */ - GSList *children; /* Symbols which we directly refer to */ - - Element base; /* Base of compiled code */ - Heap *heap; /* Heap containing compiled code */ - GSList *statics; /* Static strings we built */ + /* TRUE on a RHS if one or more params have used patterns, ie. this + * cannot be a def of the default case. + */ + gboolean params_include_patterns; + + /* TRUE on the top compile for this def if the user has given a RHS with + * no patterns (ie. defined the default case). + */ + gboolean has_default; + + char *text; /* The original text */ + char *prhstext; /* Parameters plus the RHS of the definition */ + char *rhstext; /* Just the RHS of the definition */ + + ParseNode *tree; /* Parse tree we built */ + GSList *treefrag; /* List of tree bits for easy freeing */ + Symbol *last_sym; /* The last child we added in this context */ + + int nparam; /* Number of real parameters */ + GSList *param; /* Pointers into locals for real params */ + int nsecret; /* Number of secret parameters */ + GSList *secret; /* Pointers into locals for secret params */ + Symbol *this; /* If we are a class, the "this" local */ + Symbol *super; /* If we are a class, the "super" local */ + GSList *children; /* Symbols which we directly refer to */ + + Element base; /* Base of compiled code */ + Heap *heap; /* Heap containing compiled code */ + GSList *statics; /* Static strings we built */ + + GSList *matchers; /* The match synbols we built for pattern args */ }; typedef struct _CompileClass { @@ -106,7 +118,8 @@ Compile *compile_new_toplevel(Expr *expr); Compile *compile_new_local(Expr *expr); void *compile_object(Compile *compile); -void *compile_toolkit(Toolkit *kit); +void *compile_codegen_toolkit(Toolkit *kit); +void *compile_codegen_toplevel(Symbol *sym); void compile_error_set(Compile *compile); gboolean compile_check(Compile *compile); @@ -123,6 +136,7 @@ ParseNode *compile_copy_tree(Compile *fromscope, ParseNode *tree, void compile_lcomp(Compile *compile); -GSList *compile_pattern_lhs(Compile *compile, Symbol *sym, ParseNode *node); gboolean compile_pattern_has_leaf(ParseNode *node); gboolean compile_pattern_has_args(Compile *compile); +GSList *compile_pattern(Compile *compile, Symbol *value, ParseNode *pattern); + diff --git a/src/dump.c b/src/dump.c index a032ac6..b371db5 100644 --- a/src/dump.c +++ b/src/dump.c @@ -381,7 +381,7 @@ dump_compile(Compile *compile) if (compile->tree) { printf("%s->compile->tree = \n", IOBJECT(sym)->name); - (void) dump_tree(compile->tree); + (void) dump_tree(compile->tree, 2); } #ifdef VERBOSE printf("%s->compile->treefrag = %d pointers\n", IOBJECT(sym)->name, @@ -477,6 +477,17 @@ dump_symbol(Symbol *sym) IOBJECT(sym)->name, sym->ndirtychildren); printf("%s->leaf = %s\n", IOBJECT(sym)->name, bool_to_char(sym->leaf)); + printf("%s->needs_codegen = %s\n", + IOBJECT(sym)->name, bool_to_char(sym->needs_codegen)); + printf("%s->generated = %s\n", + IOBJECT(sym)->name, bool_to_char(sym->generated)); + + if (!sym->generated && sym->next_def) { + printf("%s->next_def = ", IOBJECT(sym)->name); + for (Symbol *p = sym->next_def; p; p = sym->next_def) + printf("%s ", IOBJECT(sym)->name); + printf("\n"); + } printf("%s->tool = kit ", IOBJECT(sym)->name); if (sym->tool) @@ -774,92 +785,117 @@ dump_parseconst(ParseConst *pc) } } +static void * +dump_list_element(ParseNode *n, int *indent) +{ + printf("%*c", *indent, ' '); + printf("ITEM\n"); + dump_tree(n, *indent + 2); + + return NULL; +} + /* Dump a parse tree. */ void * -dump_tree(ParseNode *n) +dump_tree(ParseNode *n, int indent) { switch (n->type) { case NODE_NONE: + printf("%*c", indent, ' '); printf("node->type == NODE_NONE\n"); break; case NODE_APPLY: + printf("%*c", indent, ' '); printf("Function application\n"); - printf("LHS = "); - (void) dump_tree(n->arg1); - printf("RHS = "); - (void) dump_tree(n->arg2); + printf("%*c", indent, ' '); + printf("LHS\n"); + (void) dump_tree(n->arg1, indent + 2); + printf("%*c", indent, ' '); + printf("RHS\n"); + (void) dump_tree(n->arg2, indent + 2); break; case NODE_CLASS: + printf("%*c", indent, ' '); printf("Class: "); (void) dump_compile_tiny(n->klass); printf("\n"); break; case NODE_LEAF: + printf("%*c", indent, ' '); printf("Leaf symbol (%p): ", n->leaf); (void) dump_tiny(n->leaf); printf("\n"); break; case NODE_TAG: + printf("%*c", indent, ' '); printf("Tag: %s\n", n->tag); break; case NODE_BINOP: + printf("%*c", indent, ' '); printf("Binary operator %s\n", decode_BinOp(n->biop)); - printf("Left expression:\n"); - (void) dump_tree(n->arg1); - printf("Right expression:\n"); - (void) dump_tree(n->arg2); + printf("%*c", indent, ' '); + printf("LHS\n"); + (void) dump_tree(n->arg1, indent + 2); + printf("%*c", indent, ' '); + printf("RHS\n"); + (void) dump_tree(n->arg2, indent + 2); break; case NODE_UOP: - printf("Unary operator %s\n", decode_UnOp(n->uop)); - printf("Arg expression:\n"); - (void) dump_tree(n->arg1); + printf("%*c", indent, ' '); + printf("Unary operator %s:\n", decode_UnOp(n->uop)); + (void) dump_tree(n->arg1, indent + 2); break; case NODE_CONST: + printf("%*c", indent, ' '); printf("Constant "); dump_parseconst(&n->con); printf("\n"); break; case NODE_GENERATOR: + printf("%*c", indent, ' '); printf("List generator\n"); + printf("%*c", indent, ' '); printf("Start:\n"); - (void) dump_tree(n->arg1); + (void) dump_tree(n->arg1, indent + 2); if (n->arg2) { + printf("%*c", indent, ' '); printf("Next:\n"); - (void) dump_tree(n->arg2); + (void) dump_tree(n->arg2, indent + 2); } if (n->arg3) { + printf("%*c", indent, ' '); printf("End:\n"); - (void) dump_tree(n->arg3); + (void) dump_tree(n->arg3, indent + 2); } break; case NODE_COMPOSE: + printf("%*c", indent, ' '); printf("Function compose\n"); - printf("Left:\n"); - (void) dump_tree(n->arg1); - printf("Right:\n"); - (void) dump_tree(n->arg2); + printf("LHS\n"); + (void) dump_tree(n->arg1, indent + 2); + printf("RHS\n"); + (void) dump_tree(n->arg2, indent + 2); break; case NODE_LISTCONST: case NODE_SUPER: + printf("%*c", indent, ' '); if (n->type == NODE_LISTCONST) printf("List constant\n"); else printf("Superclass construct\n"); - printf("***[\n"); - slist_map_rev(n->elist, (SListMapFn) dump_tree, NULL); - printf("***]\n"); + slist_map(n->elist, (SListMapFn) dump_list_element, &indent); break; default: diff --git a/src/dump.h b/src/dump.h index 64db09d..0a296f1 100644 --- a/src/dump.h +++ b/src/dump.h @@ -48,7 +48,7 @@ void pgraph(PElement *graph); void graph_heap(int nsp, HeapNode *hn); void graph_test(Heap *heap); -void *dump_tree(ParseNode *n); +void *dump_tree(ParseNode *n, int indent); void dump_links(Symbol *sym); void *dump_link(Link *link); diff --git a/src/filemodel.c b/src/filemodel.c index 7bf5606..638edbb 100644 --- a/src/filemodel.c +++ b/src/filemodel.c @@ -120,6 +120,13 @@ filemodel_unregister(Filemodel *filemodel) } } +Filemodel * +filemodel_new_from_filename(FilemodelClass *class, + Model *parent, const char *filename) +{ + return class->new_from_filename(NULL, parent, filename); +} + /* Trigger the top_load method for a filemodel. */ void * @@ -128,18 +135,8 @@ filemodel_top_load(Filemodel *filemodel, { FilemodelClass *filemodel_class = FILEMODEL_GET_CLASS(filemodel); - if (filemodel_class->top_load) { - if (!filemodel_class->top_load(filemodel, state, parent, xnode)) - return filemodel; - } - else { - error_top(_("Not implemented")); - error_sub(_("_%s() not implemented for class \"%s\""), - "top_load", - G_OBJECT_CLASS_NAME(filemodel_class)); - + if (!filemodel_class->top_load(filemodel, state, parent, xnode)) return filemodel; - } return NULL; } @@ -325,6 +322,18 @@ filemodel_real_load(Model *model, return TRUE; } +static Filemodel * +filemodel_real_new_from_filename(Filemodel *filemodel, + Model *parent, const char *filename) +{ + // derived class makes the object we load into + filemodel_set_filename(filemodel, filename); + gboolean res = filemodel_load_all(filemodel, parent, filename, NULL); + filemodel_set_modified(filemodel, FALSE); + + return res ? filemodel : NULL; +} + static gboolean filemodel_real_top_load(Filemodel *filemodel, ModelLoadState *state, Model *parent, xmlNode *xnode) @@ -471,6 +480,7 @@ filemodel_class_init(FilemodelClass *class) model_class->save = filemodel_real_save; model_class->load = filemodel_real_load; + class->new_from_filename = filemodel_real_new_from_filename; class->top_load = filemodel_real_top_load; class->set_modified = filemodel_real_set_modified; class->top_save = filemodel_real_top_save; @@ -915,7 +925,7 @@ filemodel_save(GtkWindow *window, Filemodel *filemodel, } static void -filemodel_open_sub(GObject *source_object, +filemodel_merge_sub(GObject *source_object, GAsyncResult *res, gpointer user_data) { GtkFileDialog *dialog = GTK_FILE_DIALOG(source_object); @@ -948,8 +958,7 @@ filemodel_open_sub(GObject *source_object, } void -filemodel_open(GtkWindow *window, Filemodel *filemodel, - const char *verb, +filemodel_merge(GtkWindow *window, Filemodel *filemodel, const char *verb, FilemodelSaveasResult next, FilemodelSaveasResult error, void *a, void *b) { @@ -985,7 +994,83 @@ filemodel_open(GtkWindow *window, Filemodel *filemodel, g_object_unref(filters); } - gtk_file_dialog_open(dialog, window, NULL, &filemodel_open_sub, NULL); + gtk_file_dialog_open(dialog, window, NULL, &filemodel_merge_sub, NULL); +} + +static void +filemodel_new_from_file_sub(GObject *source_object, + GAsyncResult *res, gpointer user_data) +{ + GtkFileDialog *dialog = GTK_FILE_DIALOG(source_object); + + GtkWindow *window = g_object_get_data(G_OBJECT(dialog), "nip4-window"); + FilemodelClass *class = + g_object_get_data(G_OBJECT(dialog), "nip4-filemodelclass"); + Model *parent = + g_object_get_data(G_OBJECT(dialog), "nip4-parent"); + FilemodelSaveasResult next = + g_object_get_data(G_OBJECT(dialog), "nip4-next"); + FilemodelSaveasResult error = + g_object_get_data(G_OBJECT(dialog), "nip4-error"); + void *a = g_object_get_data(G_OBJECT(dialog), "nip4-a"); + void *b = g_object_get_data(G_OBJECT(dialog), "nip4-b"); + + g_autoptr(GFile) file = gtk_file_dialog_open_finish(dialog, res, NULL); + if (file) { + g_autofree char *filename = g_file_get_path(file); + + Filemodel *filemodel = + filemodel_new_from_filename(class, parent, filename); + if (filemodel) { + filemodel_set_load_folder(filemodel, file); + if (next) + next(window, filemodel, a, b); + } + else { + if (error) + error(window, filemodel, a, b); + } + } +} + +void +filemodel_new_from_file(GtkWindow *window, + FilemodelClass *class, Model *parent, const char *verb, + FilemodelSaveasResult next, + FilemodelSaveasResult error, void *a, void *b) +{ + const char *user_name = IOBJECT_CLASS(class)->user_name; + g_autofree char *title = g_strdup_printf("%s %s", verb, user_name); + + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_set_title(dialog, title); + gtk_file_dialog_set_modal(dialog, TRUE); + gtk_file_dialog_set_accept_label(dialog, verb); + gtk_file_dialog_set_initial_folder(dialog, class->load_folder); + + g_object_set_data(G_OBJECT(dialog), "nip4-window", window); + g_object_set_data(G_OBJECT(dialog), "nip4-filemodelclass", class); + g_object_set_data(G_OBJECT(dialog), "nip4-parent", parent); + g_object_set_data(G_OBJECT(dialog), "nip4-next", next); + g_object_set_data(G_OBJECT(dialog), "nip4-error", error); + g_object_set_data(G_OBJECT(dialog), "nip4-a", a); + g_object_set_data(G_OBJECT(dialog), "nip4-b", b); + + GtkFileFilter *filter = class->filter_new(NULL); + GListStore *filters = g_list_store_new(GTK_TYPE_FILE_FILTER); + + g_list_store_append(filters, G_OBJECT(filter)); + g_object_unref(filter); + + filter = mainwindow_filter_all_new(); + g_list_store_append(filters, G_OBJECT(filter)); + g_object_unref(filter); + + gtk_file_dialog_set_filters(dialog, G_LIST_MODEL(filters)); + g_object_unref(filters); + + gtk_file_dialog_open(dialog, + window, NULL, &filemodel_new_from_file_sub, NULL); } typedef struct _Suspension { @@ -1006,7 +1091,7 @@ filemodel_replace_next(GtkWindow *window, model_empty(MODEL(filemodel)); - filemodel_open(sus->window, sus->filemodel, sus->verb, + filemodel_merge(sus->window, sus->filemodel, sus->verb, sus->next, sus->error, sus->a, sus->b); g_free(sus); @@ -1024,15 +1109,13 @@ filemodel_replace_error(GtkWindow *window, } void -filemodel_replace(GtkWindow *window, Filemodel *filemodel, - const char *verb, +filemodel_replace(GtkWindow *window, Filemodel *filemodel, const char *verb, FilemodelSaveasResult next, FilemodelSaveasResult error, void *a, void *b) { Suspension *sus = g_new(Suspension, 1); sus->window = window; sus->filemodel = filemodel; - sus->verb = verb; sus->next = next; sus->error = error; sus->a = a; diff --git a/src/filemodel.h b/src/filemodel.h index e50b238..1954685 100644 --- a/src/filemodel.h +++ b/src/filemodel.h @@ -67,6 +67,8 @@ typedef struct _FilemodelClass { /* + new_from_filename make a filemodel from a filename + top_load top level load function ... controls how the rest of the load happens ... eg. merge, rename, etc. @@ -82,6 +84,8 @@ typedef struct _FilemodelClass { */ + Filemodel *(*new_from_filename)(Filemodel *filemodel, + Model *parent, const char *filename); gboolean (*top_load)(Filemodel *filemodel, ModelLoadState *state, Model *parent, xmlNode *xnode); void (*set_modified)(Filemodel *filemodel, gboolean modified); @@ -107,6 +111,8 @@ void filemodel_set_modified(Filemodel *filemodel, gboolean state); GType filemodel_get_type(void); +Filemodel *filemodel_new_from_filename(FilemodelClass *class, + Model *parent, const char *filename); gboolean filemodel_top_save(Filemodel *filemodel, const char *filename); gboolean filemodel_load_all(Filemodel *filemodel, Model *parent, const char *filename, const char *filename_user); @@ -130,12 +136,16 @@ void filemodel_save(GtkWindow *window, Filemodel *filemodel, FilemodelSaveasResult next, FilemodelSaveasResult error, void *a, void *b); -void filemodel_open(GtkWindow *window, Filemodel *filemodel, - const char *verb, +void filemodel_merge(GtkWindow *window, + Filemodel *filemodel, const char *verb, + FilemodelSaveasResult next, + FilemodelSaveasResult error, void *a, void *b); +void filemodel_new_from_file(GtkWindow *window, + FilemodelClass *class, Model *parent, const char *verb, FilemodelSaveasResult next, FilemodelSaveasResult error, void *a, void *b); -void filemodel_replace(GtkWindow *window, Filemodel *filemodel, - const char *verb, +void filemodel_replace(GtkWindow *window, + Filemodel *filemodel, const char *verb, FilemodelSaveasResult next, FilemodelSaveasResult error, void *a, void *b); diff --git a/src/mainwindow.c b/src/mainwindow.c index 048efa6..ee54b93 100644 --- a/src/mainwindow.c +++ b/src/mainwindow.c @@ -396,7 +396,7 @@ mainwindow_merge_action(GSimpleAction *action, if (main->wsg) { workspacegroup_set_load_type(main->wsg, WORKSPACEGROUP_LOAD_NEW); - filemodel_open(GTK_WINDOW(main), FILEMODEL(main->wsg), _("Merge"), + filemodel_merge(GTK_WINDOW(main), FILEMODEL(main->wsg), _("Merge"), mainwindow_merge_next, mainwindow_save_error, main, NULL); } diff --git a/src/parse.y b/src/parse.y index acb0b6a..7a01385 100644 --- a/src/parse.y +++ b/src/parse.y @@ -260,98 +260,105 @@ toplevel_definition: } ; -/* Parse a new defining occurence. This can be a local or a top-level. +/* Parse a new defining occurrence. This can be a local or a top-level or a new + * def for an existing sym that we make into a local. */ definition: - simple_pattern { - Symbol *sym; - - /* Two forms: , or . - * Enforce the no-args-to-pattern-assignment rule in the arg - * pattern parser. - */ - if ($1->type == NODE_LEAF) { - const char *name = IOBJECT($1->leaf)->name; - - /* Make a new defining occurence. - */ - sym = symbol_new_defining(current_compile, name); - - (void) symbol_user_init(sym); - (void) compile_new_local(sym->expr); - } - else { - char name[256]; + simple_pattern { + Symbol *sym; - /* We have . Make an anon symbol for this - * value, then the variables in the pattern become - * toplevels which access that. - */ - if (!compile_pattern_has_leaf($1)) - yyerror(_( "left-hand-side pattern contains no identifiers")); - g_snprintf(name, 256, "$$pattern_lhs%d", parse_object_id++); - sym = symbol_new_defining(current_compile, name); - sym->generated = TRUE; - (void) symbol_user_init(sym); - (void) compile_new_local(sym->expr); - } + /* Two forms: , or . + * Enforce the no-args-to-pattern-assignment rule in the arg + * pattern parser. + */ + if ($1->type == NODE_LEAF) { + const char *name = IOBJECT($1->leaf)->name; + + /* Make a new defining occurrence. If there's already a sym of this + * name, this new sym will become a local of that with a name like + * "$$name_defN". + */ + sym = symbol_new_defining(current_compile, name); + (void) symbol_user_init(sym); + (void) compile_new_local(sym->expr); + } + else { + char name[256]; + + /* We have . Make an anon symbol for this + * value, then the variables in the pattern become + * toplevels which access that. + */ + if (!compile_pattern_has_leaf($1)) + yyerror(_( "left-hand-side pattern contains no identifiers")); + g_snprintf(name, 256, "$$value%d", parse_object_id++); + sym = symbol_new_defining(current_compile, name); + sym->generated = TRUE; + (void) symbol_user_init(sym); + (void) compile_new_local(sym->expr); + } + + /* Note on the enclosing last_sym. Things like the program + * window use this to work out what sym to display after a + * parse. symbol_dispose() is careful to NULL this out. + */ + current_compile->last_sym = sym; - /* Note on the enclosing last_sym. Things like the program - * window use this to work out what sym to display after a - * parse. symbol_dispose() is careful to NULL this out. - */ - current_compile->last_sym = sym; + /* Initialise symbol parsing variables. Save old current symbol, + * add new one. + */ + scope_push(); + current_symbol = sym; + current_compile = sym->expr->compile; - /* Initialise symbol parsing variables. Save old current symbol, - * add new one. - */ - scope_push(); - current_symbol = sym; - current_compile = sym->expr->compile; + g_assert(!current_compile->param); + g_assert(current_compile->nparam == 0); - g_assert(!current_compile->param); - g_assert(current_compile->nparam == 0); + /* Junk any old def text. + */ + VIPS_FREE(current_compile->text); + VIPS_FREE(current_compile->prhstext); + VIPS_FREE(current_compile->rhstext); + } + params_plus_rhs { + compile_check(current_compile); - /* Junk any old def text. - */ - VIPS_FREE(current_compile->text); - VIPS_FREE(current_compile->prhstext); - VIPS_FREE(current_compile->rhstext); - } - params_plus_rhs { - compile_check(current_compile); + /* Link unresolved names into the outer scope. + */ + compile_resolve_names(current_compile, + compile_get_parent(current_compile)); - /* Link unresolved names into the outer scope. - */ - compile_resolve_names(current_compile, - compile_get_parent(current_compile)); + /* Is this (pattern = rhs)? current_symbol is $$valueN and we need + * to make a set of peer symbols which access that. + */ + if ($1->type != NODE_LEAF) { + Compile *parent = compile_get_parent(current_compile); + GSList *built_syms; - /* Is this the end of a top-level? Needs extra work to add to - * the enclosing toolkit etc. - */ - if (is_scope(symbol_get_parent(current_symbol))) - parse_toplevel_end(current_symbol); + built_syms = compile_pattern(parent, current_symbol, $1); - /* Is this a pattern definition? Expand the pattern to a - * set of access defs. - */ - if ($1->type != NODE_LEAF) { - Compile *parent = compile_get_parent(current_compile); - GSList *built_syms; + /* We may have made some top levels. + */ + if (is_scope(symbol_get_parent(current_symbol))) + slist_map(built_syms, (SListMapFn) parse_toplevel_end, NULL); - built_syms = compile_pattern_lhs(parent, current_symbol, $1); + /* Note the source code on each of the access funcs. + */ + slist_map(built_syms, + (SListMapFn) parse_access_end, current_symbol); - if (is_scope(symbol_get_parent(current_symbol))) - slist_map(built_syms, (SListMapFn) parse_toplevel_end, NULL); - slist_map(built_syms, - (SListMapFn) parse_access_end, current_symbol); + g_slist_free(built_syms); + } - g_slist_free(built_syms); - } + /* Is this the end of a top-level? Needs extra work to add to + * the enclosing toolkit etc. + */ + if (is_scope(symbol_get_parent(current_symbol))) + parse_toplevel_end(current_symbol); - scope_pop(); - } - ; + scope_pop(); + } + ; /* Parse params/body/locals into current_expr */ @@ -408,46 +415,58 @@ params_plus_rhs: params: /* Empty */ | - params simple_pattern { - Symbol *sym; - - /* If the pattern is just an identifier, make it a direct - * parameter. Otherwise make an anon param and put the pattern - * in as a local with the same id. - * - * fred [a] = 12; - * - * parses to: - * - * fred $$arg42 = 12 { $$patt42 = [a]; } - * - * A later pass creates the "a = $$arg42?0" definition. - */ - if ($2->type == NODE_LEAF) { - const char *name = IOBJECT($2->leaf)->name; - - /* Make defining occurence. - */ - sym = symbol_new_defining(current_compile, name); - (void) symbol_parameter_init(sym); - } - else { - char name[256]; - - g_snprintf(name, 256, "$$arg%d", parse_object_id); - sym = symbol_new_defining(current_compile, name); - sym->generated = TRUE; - (void) symbol_parameter_init(sym); - - g_snprintf(name, 256, "$$patt%d", parse_object_id++); - sym = symbol_new_defining(current_compile, name); - sym->generated = TRUE; - (void) symbol_user_init(sym); - (void) compile_new_local(sym->expr); - sym->expr->compile->tree = $2; - } - } - ; + params simple_pattern { + /* If the pattern is just an identifier, make it a direct + * parameter. Otherwise make an anon param and put the pattern + * in as a local with the same id. + * + * fred [a] = 12; + * + * parses to: + * + * fred $$arg42 = 12 { $$patt42 = [a]; } + * + * A later pass creates the "a = $$arg42?0" definition. + */ + if ($2->type == NODE_LEAF) { + const char *name = IOBJECT($2->leaf)->name; + + /* A single name ... make the zombie into a parameter. + */ + Symbol *sym = symbol_new_defining(current_compile, name); + (void) symbol_parameter_init(sym); + } + else { + char name[256]; + + g_snprintf(name, 256, "$$arg%d", parse_object_id); + Symbol *arg = symbol_new_defining(current_compile, name); + arg->generated = TRUE; + (void) symbol_parameter_init(arg); + + /* Use the pattern to make a match func plus a set of locals + * which access this arg. + */ + GSList *built_syms = compile_pattern(current_compile, arg, $2); + + // note the match func for the codegen pass + if (built_syms) + current_compile->matchers = + g_slist_prepend(current_compile->matchers, + SYMBOL(built_syms->data)); + + g_slist_free(built_syms); + + current_compile->params_include_patterns = TRUE; + current_compile->sym->needs_codegen = TRUE; + + /* Tag the toplevel as needing codegen so we don't have to + * search every symbol. + */ + symbol_get_top(current_compile->sym)->needs_codegen = TRUE; + } + } + ; body : '=' TK_CLASS crhs { @@ -1463,9 +1482,22 @@ parse_input(int ch, Symbol *sym, Toolkit *kit, int pos) } yyparse(); - /* All ok. - */ - return TRUE; + /* We may need to generate some code for eg multiple defs. We must codegen + * after parse and not during compile since codegen can add new + * dependencies and affect eval ordering. + */ + if (kit) { + if (compile_codegen_toolkit(kit)) + return FALSE; + } + else if (sym) { + if (compile_codegen_toplevel(sym)) + return FALSE; + } + + /* All ok. + */ + return TRUE; } /* Parse the input into a set of symbols at a position in a kit. diff --git a/src/path.c b/src/path.c index 682ed18..3ab5c4b 100644 --- a/src/path.c +++ b/src/path.c @@ -499,7 +499,7 @@ path_scan_dir(const char *dir_name, Search *search) * for all filenames which match. * * Remove duplicates: if fred.wombat is in the first and second dirs on the - * path, only apply to the first occurence. + * path, only apply to the first occurrence. FIXME ... speed up with a hash and a (date based) cache at some point diff --git a/src/program.c b/src/program.c index 5c87c85..a57dff9 100644 --- a/src/program.c +++ b/src/program.c @@ -470,11 +470,8 @@ program_open_next(GtkWindow *win, Filemodel *filemodel, void *a, void *b) { Program *program = PROGRAM(a); - char name[VIPS_PATH_MAX]; - name_from_filename(filemodel->filename, name); - toolkit_set_name(TOOLKIT(filemodel), name); - program_select_tool(program, TOOLKIT(filemodel), NULL); + symbol_recalculate_all(); } static void @@ -482,9 +479,10 @@ program_open_action(GSimpleAction *action, GVariant *parameter, gpointer user_data) { Program *program = PROGRAM(user_data); + FilemodelClass *class = FILEMODEL_CLASS(g_type_class_peek(TOOLKIT_TYPE)); + Model *model = MODEL(program->kitg); - Toolkit *kit = toolkit_new_filename(program->kitg, "untitled"); - filemodel_open(GTK_WINDOW(program), FILEMODEL(kit), _("Open"), + filemodel_new_from_file(GTK_WINDOW(program), class, model, _("Open"), program_open_next, program_saveas_error, program, NULL); } diff --git a/src/reduce.c b/src/reduce.c index 67d8c6a..59d6929 100644 --- a/src/reduce.c +++ b/src/reduce.c @@ -1192,9 +1192,14 @@ reduce_spine(Reduce *rc, PElement *out) * links to dirty syms through dynamic dependencies. */ if (sym->dirty) { + char txt[256]; + VipsBuf buf = VIPS_BUF_STATIC(txt); + + symbol_qualified_name(sym, &buf); + error_top(_("No value")); - error_sub(_("symbol \"%s\" has no value"), - symbol_name(sym)); + error_sub(_("symbol \"%s\" has no value"), vips_buf_all(&buf)); + reduce_throw(rc); } diff --git a/src/symbol.c b/src/symbol.c index d3a0a8c..8f3691f 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -152,6 +152,33 @@ symbol_get_scope(Symbol *sym) return i; } +/* Get the enclosing top-level for a sym. + */ +Symbol * +symbol_get_top(Symbol *sym) +{ + Symbol *i; + + for (i = sym; i && !is_top(i); i = symbol_get_parent(i)) + ; + + return i; +} + +/* Return the final definition, if this symbol has multiple RHS. + */ +Symbol * +symbol_get_last(Symbol *sym) +{ + Symbol *last; + + for (last = sym; last; last = last->next_def) + if (!last->next_def) + break; + + return last; +} + /* Make a fully-qualified symbol name .. eg fred.jim, given jim. Don't print * static scopes. */ @@ -292,7 +319,6 @@ symbol_clear(Symbol *sym) sym->leaf = FALSE; sym->generated = FALSE; - sym->placeholder = FALSE; sym->tool = NULL; @@ -371,15 +397,15 @@ void symbol_leaf_set_sanity(void) { slist_map(symbol_leaf_set, (SListMapFn) symbol_sanity, NULL); - icontainer_map(ICONTAINER(symbol_root->expr->compile), - (icontainer_map_fn) symbol_sanity, NULL, NULL); + if (symbol_root->expr->compile) + icontainer_map(ICONTAINER(symbol_root->expr->compile), + (icontainer_map_fn) symbol_sanity, NULL, NULL); /* Commented out to reduce spam - * + */ printf( "Leaf set: " ); slist_map( symbol_leaf_set, (SListMapFn) dump_tiny, NULL ); printf( "\n" ); - */ } #endif /*DEBUG*/ @@ -427,6 +453,8 @@ symbol_strip(Symbol *sym) sym->ws = NULL; } + sym->next_def = NULL; + /* It's a ZOMBIE now. */ sym->type = SYM_ZOMBIE; @@ -525,13 +553,11 @@ static void symbol_dispose(GObject *gobject) { Symbol *sym; - Compile *compile; g_return_if_fail(gobject != NULL); g_return_if_fail(IS_SYMBOL(gobject)); sym = SYMBOL(gobject); - compile = COMPILE(ICONTAINER(sym)->parent); #ifdef DEBUG_MAKE printf("symbol_dispose: "); @@ -539,10 +565,16 @@ symbol_dispose(GObject *gobject) printf("(%p)\n", sym); #endif /*DEBUG_MAKE*/ - /* Make sure we're not leaving last_sym dangling. + /* Does our parent ref us as next_def? Clear it. */ - if (compile && compile->last_sym == sym) - compile->last_sym = NULL; + if (sym->expr && + sym->expr->compile) { + Compile *parent_compile = compile_get_parent(sym->expr->compile); + + if (parent_compile && + parent_compile->sym->next_def == sym) + parent_compile->sym->next_def = NULL; + } /* Clear state. */ @@ -714,35 +746,44 @@ symbol_rename(Symbol *sym, const char *new_name) return TRUE; } -void -symbol_error_redefine(Symbol *sym) +extern Toolkit *current_kit; + +/* Can we add a new def to a sym? + */ +static gboolean +symbol_can_add_def(Symbol *sym) { - static char txt[200]; - static VipsBuf buf = VIPS_BUF_STATIC(txt); + /* The sym is in current_kit? it must be in the same parse unit, so a new + * def is OK + */ + if (sym->type == SYM_VALUE && + sym->tool && + sym->tool->kit == current_kit) + return TRUE; - vips_buf_rewind(&buf); - vips_buf_appendf(&buf, _("Redefinition of \"%s\"."), - IOBJECT(sym)->name); - if (sym->tool && sym->tool->lineno != -1) { - vips_buf_appendf(&buf, "\n"); - vips_buf_appendf(&buf, _("Previously defined at line %d."), - sym->tool->lineno); - } + /* Is this a local def? A second def is also OK, since we must still be + * parsing. + */ + if (symbol_get_parent(sym)->type == SYM_VALUE) + return TRUE; - yyerror(vips_buf_all(&buf)); + return FALSE; } -/* Name in defining occurence. If this is a top-level definition, clean the - * old symbol and get ready to attach a user function to it. If its not a top- - * level definition, we flag an error. Consider repeated parameter names, - * repeated occurence of names in locals, local name clashes with parameter - * name etc. +/* Name in defining occurrence. + * + * If this is a redefinition of an existing def in this kit, add it as a local + * of the existing def and tag for codegen. Consider repeated param names, + * param names clashing with locals. + * * We make a ZOMBIE: our caller should turn it into a blank user definition, a * parameter etc. */ Symbol * symbol_new_defining(Compile *compile, const char *name) { + static int symbol_def_id = 0; + Symbol *sym; /* Block definition of "root" anywhere ... too confusing. @@ -751,35 +792,59 @@ symbol_new_defining(Compile *compile, const char *name) nip2yyerror(_("Attempt to redefine root symbol \"%s\"."), name); - /* Is this a redefinition of an existing symbol? + /* Is this a redefinition of an existing symbol in this scope? */ if ((sym = compile_lookup(compile, name))) { - /* Yes. Check that this redefinition is legal. - */ - switch (sym->type) { - case SYM_VALUE: - /* Redef of existing symbol? Only allowed at top - * level. - */ - if (!is_scope(compile->sym)) - symbol_error_redefine(sym); - break; - - case SYM_ZOMBIE: + if (sym->type == SYM_ZOMBIE) { /* This is the definition for a previously referenced * symbol. Just return the ZOMBIE we made. */ - break; + } + else if (symbol_can_add_def(sym)) { + /* This is a new def for an existing def, either a local or a + * top-level. + * + * This new def should be attached as a local of that first def, + * and the whole thing needs tagging for a codegen pass. + */ + char name_id[256]; + Symbol *new_def; + + g_snprintf(name_id, 256, "$$%s_def%d", name, symbol_def_id++); + new_def = symbol_new(sym->expr->compile, name_id); + new_def->generated = TRUE; + + // append to the set of defs + symbol_get_last(sym)->next_def = new_def; - default: - /* Parameter, workspace, etc. + // sym has multiple defs and will need a codegen pass + sym->needs_codegen = TRUE; + + sym = new_def; + } + else { + /* Eg. redef of parameter, workspace, an existing def in + * another kit. */ - nip2yyerror(_("Can't redefine %s \"%s\"."), + char txt[200]; + VipsBuf buf = VIPS_BUF_STATIC(txt); + + vips_buf_appendf(&buf, _("Can't redefine %s \"%s\""), decode_SymbolType_user(sym->type), name); + + if (sym->tool && + sym->tool->kit) { + vips_buf_appendf(&buf, ", "); + vips_buf_appendf(&buf, _("previous definition was %s:%d"), + FILEMODEL(sym->tool->kit)->filename, sym->tool->lineno); + } + vips_buf_appendf(&buf, "."); + + nip2yyerror("%s", vips_buf_all(&buf)); /*NOTREACHED*/ } - /* This is the defining occurence ... move to the end of the + /* This is a defining occurrence ... move to the end of the * traverse order. */ icontainer_child_move(ICONTAINER(sym), -1); @@ -1021,9 +1086,14 @@ symbol_recalculate_leaf_sub(Symbol *sym) printf("symbol_recalculate_leaf_sub: %s\n", symbol_name_scope(sym)); /* We can symbol_recalculate_leaf_sub() syms which are not dirty. - */ + * + * Both these asserts can fail with regular code, just here for debugging. + * + * Test 2 will fail for mutual top-level recursion. + * g_assert(!sym->dirty || symbol_is_leafable(sym)); g_assert(symbol_ndirty(sym) == 0); + */ #endif /*DEBUG_RECALC*/ error_clear(); diff --git a/src/symbol.h b/src/symbol.h index 2706a7a..f0ce4dc 100644 --- a/src/symbol.h +++ b/src/symbol.h @@ -86,10 +86,18 @@ struct _Symbol { int ndirtychildren; /* Number of dirty top syms we refer to */ gboolean leaf; /* True for in recomp set */ + /* This symbol will need a codegen pass at the end of this parse unit. + */ + gboolean needs_codegen; + /* This is a generated symbol, like $$result, $$fn1, whatever. */ gboolean generated; + /* If this func has multiple defs, chain them on this. + */ + Symbol *next_def; + /* A temporary intermediate symbol generated during parse to hold * stuff until we need it. Don't generate code for these. */ @@ -137,6 +145,8 @@ Symbol *symbol_get_parent(Symbol *sym); Workspace *symbol_get_workspace(Symbol *sym); Tool *symbol_get_tool(Symbol *sym); Symbol *symbol_get_scope(Symbol *sym); +Symbol *symbol_get_top(Symbol *sym); +Symbol *symbol_get_last(Symbol *sym); void symbol_qualified_name(Symbol *sym, VipsBuf *buf); void symbol_qualified_name_relative(Symbol *context, diff --git a/src/toolkit.c b/src/toolkit.c index 0ce4e8f..cce3dfa 100644 --- a/src/toolkit.c +++ b/src/toolkit.c @@ -106,6 +106,47 @@ toolkit_load_text(Model *model, Model *parent, iOpenFile *of) return res; } +void +toolkit_set_name(Toolkit *kit, const char *name) +{ + iobject_set(IOBJECT(kit), name, NULL); + if (name[0] == '_') + MODEL(kit)->display = FALSE; + toolkitgroup_sort(kit->kitg); +} + +static void +toolkit_link(Toolkit *kit, Toolkitgroup *kitg, const char *name) +{ + icontainer_child_add(ICONTAINER(kitg), ICONTAINER(kit), -1); + kit->kitg = kitg; + filemodel_register(FILEMODEL(kit)); + toolkit_set_name(kit, name); +} + +static Filemodel * +toolkit_real_new_from_filename(Filemodel *filemodel, + Model *parent, const char *filename) +{ + Toolkitgroup *kitg = TOOLKITGROUP(parent); + + Toolkit *kit; + +#ifdef DEBUG + printf("toolkit_new: %s\n", name); +#endif /*DEBUG*/ + + /* New kit, destroying any old kit. + */ + char name[VIPS_PATH_MAX]; + name_from_filename(filename, name); + kit = toolkit_new(kitg, name); + + // superclass loads into this filemodel + return FILEMODEL_CLASS(toolkit_parent_class)-> + new_from_filename(FILEMODEL(kit), parent, filename); +} + static GtkFileFilter * toolkit_filter_new(Filemodel *filemodel) { @@ -137,6 +178,7 @@ toolkit_class_init(ToolkitClass *class) model_class->save_text = toolkit_save_text; model_class->load_text = toolkit_load_text; + filemodel_class->new_from_filename = toolkit_real_new_from_filename; filemodel_class->filter_new = toolkit_filter_new; filemodel_class->suffix = ".def"; } @@ -148,24 +190,6 @@ toolkit_init(Toolkit *kit) kit->pseudo = FALSE; } -void -toolkit_set_name(Toolkit *kit, const char *name) -{ - iobject_set(IOBJECT(kit), name, NULL); - if (name[0] == '_') - MODEL(kit)->display = FALSE; - toolkitgroup_sort(kit->kitg); -} - -static void -toolkit_link(Toolkit *kit, Toolkitgroup *kitg, const char *name) -{ - icontainer_child_add(ICONTAINER(kitg), ICONTAINER(kit), -1); - kit->kitg = kitg; - filemodel_register(FILEMODEL(kit)); - toolkit_set_name(kit, name); -} - /* Find a kit by kit name. */ Toolkit * @@ -197,54 +221,14 @@ toolkit_new(Toolkitgroup *kitg, const char *name) return kit; } -Toolkit * -toolkit_new_filename(Toolkitgroup *kitg, const char *filename) -{ - char name[VIPS_PATH_MAX]; - Toolkit *kit; - - name_from_filename(filename, name); - kit = toolkit_new(kitg, name); - filemodel_set_filename(FILEMODEL(kit), filename); - - return kit; -} - /* Load a file as a toolkit. */ Toolkit * toolkit_new_from_file(Toolkitgroup *kitg, const char *filename) { - Toolkit *kit = toolkit_new_filename(kitg, filename); - gboolean res; - - res = filemodel_load_all(FILEMODEL(kit), MODEL(kitg), filename, NULL); - filemodel_set_modified(FILEMODEL(kit), FALSE); + FilemodelClass *class = FILEMODEL_CLASS(g_type_class_peek(TOOLKIT_TYPE)); - /* Don't remove the kit if load failed, we want to leave it so the - * user can try to fix the problem. - */ - - return res ? kit : NULL; -} - -/* Load from an iOpenFile. - */ -Toolkit * -toolkit_new_from_openfile(Toolkitgroup *kitg, iOpenFile *of) -{ - Toolkit *kit = toolkit_new_filename(kitg, of->fname); - gboolean res; - - res = filemodel_load_all_openfile(FILEMODEL(kit), - MODEL(kitg), of); - filemodel_set_modified(FILEMODEL(kit), FALSE); - - /* Don't remove the kit if load failed, we want to leave it so the - * user can try to fix the problem. - */ - - return res ? kit : NULL; + return TOOLKIT(filemodel_new_from_filename(class, MODEL(kitg), filename)); } /* Look up a toolkit, make an empty one if not there. @@ -255,13 +239,15 @@ toolkit_by_name(Toolkitgroup *kitg, const char *name) Toolkit *kit; if (!(kit = toolkit_find(kitg, name))) { - char file[VIPS_PATH_MAX]; + char filename[VIPS_PATH_MAX]; - g_snprintf(file, VIPS_PATH_MAX, + g_snprintf(filename, VIPS_PATH_MAX, "$SAVEDIR" G_DIR_SEPARATOR_S "start" G_DIR_SEPARATOR_S "%s.def", name); - kit = toolkit_new_filename(kitg, file); + + kit = toolkit_new(kitg, name); + filemodel_set_filename(FILEMODEL(kit), filename); } return kit; diff --git a/src/util.c b/src/util.c index 88774bd..bd51209 100644 --- a/src/util.c +++ b/src/util.c @@ -653,7 +653,7 @@ slist_fold2(GSList *list, void *start, SListFold2Fn fn, void *a, void *b) return c; } -/* Remove all occurences of an item from a list. +/* Remove all occurrences of an item from a list. */ GSList * slist_remove_all(GSList *list, gpointer data) @@ -1052,7 +1052,7 @@ my_strrcspn(const char *p, const char *spn) return p1; } -/* Find the rightmost occurence of string a in string b. +/* Find the rightmost occurrence of string a in string b. */ const char * findrightmost(const char *a, const char *b) @@ -1364,7 +1364,7 @@ nativeize_path(char *buf) g_snprintf(buf, VIPS_PATH_MAX, "%s", filename); } -/* Change all occurences of "from" into "to". This will loop if "to" contains +/* Change all occurrences of "from" into "to". This will loop if "to" contains * "from", beware. */ static void diff --git a/src/workspacegroup.c b/src/workspacegroup.c index 5d89633..82379f4 100644 --- a/src/workspacegroup.c +++ b/src/workspacegroup.c @@ -600,7 +600,8 @@ workspacegroup_top_load(Filemodel *filemodel, g_assert(FALSE); } - return FILEMODEL_CLASS(workspacegroup_parent_class)->top_load(filemodel, state, parent, xnode); + return FILEMODEL_CLASS(workspacegroup_parent_class)-> + top_load(filemodel, state, parent, xnode); } /* Save the workspace to one of our temp files. diff --git a/src/workspaceview.c b/src/workspaceview.c index 729d585..182c49f 100644 --- a/src/workspaceview.c +++ b/src/workspaceview.c @@ -800,7 +800,7 @@ workspaceview_merge(Workspaceview *wview) */ column_set_offset(2 * VIPS_RECT_RIGHT(&ws->area)); - filemodel_open(GTK_WINDOW(main), FILEMODEL(wsg), _("Merge"), + filemodel_merge(GTK_WINDOW(main), FILEMODEL(wsg), _("Merge"), workspaceview_merge_next, workspaceview_saveas_error, wview, NULL); } diff --git a/test/workspaces/test_snip.def b/test/workspaces/test_snip.def index 1c40c0c..c5314af 100644 --- a/test/workspaces/test_snip.def +++ b/test/workspaces/test_snip.def @@ -15,7 +15,7 @@ fmts = [ ]; // we need a to_real that does images as well -to_real x +to_real2 x = abs x, is_complex x = mean x, is_Image x = x; @@ -33,9 +33,9 @@ test_unary op_name fn message = join_sep " " (map print [ "unary", fname, op_name, x, "==", image, number, matrix ]); - image = (to_real @ fn @ ifmt @ to_image) x; - number = (to_real @ fn @ nfmt) x; - //matrix = (to_real @ fn @ ifmt @ to_matrix) x; + image = (to_real2 @ fn @ ifmt @ to_image) x; + number = (to_real2 @ fn @ nfmt) x; + //matrix = (to_real2 @ fn @ ifmt @ to_matrix) x; matrix = image; } } @@ -52,13 +52,58 @@ test_binary op_name fn message = join_sep " " (map print [ "binary", fname, x, op_name, y, "==", image, number, matrix ]); - image = to_real (fn ((ifmt @ to_image) x) ((ifmt @ to_image) y)); - number = to_real (fn (nfmt x) (nfmt y)); - //matrix = to_real (fn ((ifmt @ to_matrix) x) ((ifmt @ to_matrix) y)); + image = to_real2 (fn ((ifmt @ to_image) x) ((ifmt @ to_image) y)); + number = to_real2 (fn (nfmt x) (nfmt y)); + //matrix = to_real2 (fn ((ifmt @ to_matrix) x) ((ifmt @ to_matrix) y)); matrix = image; } } +test_deconstruct = [ + [test_deconstruct, "deconstruct"], + [test_struct_deconstruct, "structure deconstruct"], + [test_arg_deconstruct, "argument deconstruct"], + [test_list_deconstruct, "list deconstruct"], + [test_arg_list_deconstruct, "arg list deconstruct"] +] +{ + [a] = [12]; + test_deconstruct = a == 12; + + [12, (13, b)] = [12, (13, 14)]; + test_struct_deconstruct = b == 14; + + fred [a] = 12; + test_arg_deconstruct = fred [99] == 12; + + c:d = [1, 2]; + test_list_deconstruct = c == 1 && d == [2]; + + jim (a:x) = a:[1, 2]; + test_arg_list_deconstruct = jim [3] == [3, 1, 2]; +} + +global_factorial 1 = 1; +global_factorial n = n * global_factorial (n - 1); + +test_multidef = [ + [test_local_multidef, "local multiple definitions"], + [test_global_multidef, "global multiple definitions"], + [test_multidef_list_pattern, "list pattern multiple definitions"] +] +{ + factorial 1 = 1; + factorial n = n * factorial (n - 1); + test_local_multidef = factorial 3 == 6; + + test_global_multidef = global_factorial 3 == 6; + + foldr fn st [] = st; + foldr fn st (x:xs) = fn x (foldr fn st xs); + sum = foldr add 0; + test_multidef_list_pattern = sum [1..10] == 55; +} + tests = concat [ test_binary "add" add, test_binary "subtract" subtract, @@ -72,6 +117,8 @@ tests = concat [ test_unary "constant multiplied by" (converse multiply 7), test_unary "constant subtracted from" (subtract 4), test_unary "subtract constant" (converse subtract 4), + test_deconstruct, + test_multidef, [ ["" ++ "a" == "a", "concat"], [hd [1, error "nope"] == 1, "lazy hd"]