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"]