/* Lots of little arg checks. Global for convenience. */ check_any = [(const true), _ "any"]; check_bool = [is_bool, _ "boolean"]; check_real = [is_real, _ "real"]; check_ureal = [is_ureal, _ "unsigned real"]; check_preal = [is_preal, _ "positive real"]; check_list = [is_list, _ "list"]; check_real_list = [is_real_list, _ "list of real"]; check_string = [is_string, _ "string"]; check_string_list = [is_string_list, _ "list of string"]; check_int = [is_int, _ "integer"]; check_uint = [is_uint, _ "unsigned integer"]; check_pint = [is_pint, _ "positive integer"]; check_matrix = [is_matrix, _ "rectangular array of real"]; check_matrix_display = [Matrix_display.is_display, _ "0, 1, 2 or 3"]; check_image = [is_image, _ "image"]; check_xy_list = [is_xy_list, _ "list of form [[1, 2], [3, 4], [5, 6], ...]"]; check_instance name = [is_instanceof name, name]; check_Image = check_instance "Image"; check_Matrix = [is_Matrix, _ "Matrix"]; check_Matrix_width w = [is_Matrix_width, _ "Matrix, " ++ print w ++ _ " columns"] { is_Matrix_width x = is_Matrix x && x.width == w; } check_colour_space = [is_colour_space, "colour_space"]; check_rectangular = [is_rectangular, _ "rectangular [[*]]"]; check_Guide = [is_Guide, _ "HGuide or VGuide"]; check_Colour = check_instance (_ "Colour"); check_Mark = check_instance (_ "Mark"); /* Check a set of args to a class. Two members to look at: _check_args and * _check_all. * * - each line in _check_args is [arg, "arg name", [test_fn, "arg type"]] * same number of lines as there are args * * stuff like "arg 2 must be real" * * - each line in _check_all is [test, "description"] * any number of lines * * stuff like "to must be greater than from" * * generate an error dialog with a helpful message on failure. * * Have as a separate function to try to keep the size of _Object down a bit. */ check_args x = error message, badargs != [] || badalls != [] = check_args x.super, x.super != [] = x { argcheck = x._check_args; allcheck = x._check_all; // join two strings up with a separator string join_sep j a b = a ++ j ++ b; // indent string indent = " "; // test for a condition in a check line fails test_fail x = ! x?0; // set of failed argcheck indexes badargs = map (extract 1) (filter test_fail (zip2 (map testarg argcheck) [0..])) { testarg x = x?2?0 x?0; } // set of failed allcheck indexes badalls = map (extract 1) (filter test_fail (zip2 (map hd allcheck) [0..])); // the error message message = _ "bad arguments to " ++ "\"" ++ x.name ++ "\"\n" ++ argmsg ++ allmsg ++ "\n" ++ _ "usage" ++ "\n" ++ indent ++ usage ++ "\n" ++ _ "where" ++ "\n" ++ arg_types ++ extra; // make the failed argcheck messages ... eg. ""value" should be // real, you passed " etc. argmsg = concat (map fmt badargs) { fmt n = indent ++ "\"" ++ argcheck?n?1 ++ "\"" ++ _ " should be of type " ++ argcheck?n?2?1 ++ ", " ++ _ "you passed" ++ ":\n" ++ indent ++ indent ++ print argcheck?n?0 ++ "\n"; } // make the failed allcheck messages ... eg "condition failed: // x < y" ... don't make a message if any typechecks have // failed, as we'll probably error horribly allmsg = [], badargs != [] = concat (map fmt badalls) ++ _ "you passed" ++ "\n" ++ concat (map fmt_arg argcheck) { fmt n = _ "condition failed" ++ ": " ++ allcheck?n?1 ++ "\n"; fmt_arg l = indent ++ l?1 ++ " = " ++ print l?0 ++ "\n"; } // make usage note usage = x.name ++ " " ++ foldr (join_sep " ") [] (map (extract 1) argcheck); // make arg type notes arg_types = foldr (join_sep "\n") [] (map fmt argcheck) { fmt l = indent ++ l?1 ++ " is of type " ++ l?2?1; } // extra bit at the bottom, if we have any conditions extra = [], allcheck == [] = _ "and" ++ "\n" ++ all_desc; // make a list of all the allcheck descriptions, with a few // spaces in front all_desc_list = map (join indent @ extract 1) allcheck; // join em up to make a set of condition notes all_desc = foldr (join_sep "\n") [] all_desc_list; } /* Operator overloading stuff. */ Operator_type = class { ARITHMETIC = 1; // eg. add RELATIONAL = 2; // eg. less COMPOUND = 3; // eg. max/mean/etc. COMPOUND_REWRAP = 4; // eg. transpose } Operator op_name fn type symmetric = class { } /* Form the converse of an Operator. */ oo_converse op = Operator (converse_name op.op_name) (converse op.fn) op.type op.symmetric { converse_name x = init x, last x == last "'" = x ++ "'"; } /* Given an operator name, look up the definition. */ oo_binary_lookup op_name = matches?0, matches != [] = error (_ "unknown binary operator" ++ ": " ++ print op_name) { operator_table = [ Operator "add" add Operator_type.ARITHMETIC true, Operator "subtract" subtract Operator_type.ARITHMETIC false, Operator "remainder" remainder Operator_type.ARITHMETIC false, Operator "power" power Operator_type.ARITHMETIC false, Operator "subscript" subscript Operator_type.ARITHMETIC false, Operator "left_shift" left_shift Operator_type.ARITHMETIC false, Operator "right_shift" right_shift Operator_type.ARITHMETIC false, Operator "divide" divide Operator_type.ARITHMETIC false, Operator "join" join Operator_type.ARITHMETIC false, Operator "multiply" multiply Operator_type.ARITHMETIC true, Operator "logical_and" logical_and Operator_type.ARITHMETIC true, Operator "logical_or" logical_or Operator_type.ARITHMETIC true, Operator "bitwise_and" bitwise_and Operator_type.ARITHMETIC true, Operator "bitwise_or" bitwise_or Operator_type.ARITHMETIC true, Operator "eor" eor Operator_type.ARITHMETIC true, Operator "comma" comma Operator_type.ARITHMETIC false, Operator "if_then_else" if_then_else Operator_type.ARITHMETIC false, Operator "equal" equal Operator_type.RELATIONAL true, Operator "not_equal" not_equal Operator_type.RELATIONAL true, Operator "less" less Operator_type.RELATIONAL false, Operator "less_equal" less_equal Operator_type.RELATIONAL false ]; matches = filter test_name operator_table; test_name x = x.op_name == op_name; } /* Given an operator name, look up a function that implements that * operator. */ oo_unary_lookup op_name = matches?0, matches != [] = error (_ "unknown unary operator" ++ ": " ++ print op_name) { operator_table = [ /* Operators. */ Operator "cast_signed_char" cast_signed_char Operator_type.ARITHMETIC false, Operator "cast_unsigned_char" cast_unsigned_char Operator_type.ARITHMETIC false, Operator "cast_signed_short" cast_signed_short Operator_type.ARITHMETIC false, Operator "cast_unsigned_short" cast_unsigned_short Operator_type.ARITHMETIC false, Operator "cast_signed_int" cast_signed_int Operator_type.ARITHMETIC false, Operator "cast_unsigned_int" cast_unsigned_int Operator_type.ARITHMETIC false, Operator "cast_float" cast_float Operator_type.ARITHMETIC false, Operator "cast_double" cast_double Operator_type.ARITHMETIC false, Operator "cast_complex" cast_complex Operator_type.ARITHMETIC false, Operator "cast_double_complex" cast_double_complex Operator_type.ARITHMETIC false, Operator "unary_minus" unary_minus Operator_type.ARITHMETIC false, Operator "negate" negate Operator_type.RELATIONAL false, Operator "complement" complement Operator_type.ARITHMETIC false, Operator "unary_plus" unary_plus Operator_type.ARITHMETIC false, /* Built in projections. */ Operator "re" re Operator_type.ARITHMETIC false, Operator "im" im Operator_type.ARITHMETIC false, Operator "hd" hd Operator_type.ARITHMETIC false, Operator "tl" tl Operator_type.ARITHMETIC false, /* Maths builtins. */ Operator "sin" sin Operator_type.ARITHMETIC false, Operator "cos" cos Operator_type.ARITHMETIC false, Operator "tan" tan Operator_type.ARITHMETIC false, Operator "asin" asin Operator_type.ARITHMETIC false, Operator "acos" acos Operator_type.ARITHMETIC false, Operator "atan" atan Operator_type.ARITHMETIC false, Operator "log" log Operator_type.ARITHMETIC false, Operator "log10" log10 Operator_type.ARITHMETIC false, Operator "exp" exp Operator_type.ARITHMETIC false, Operator "exp10" exp10 Operator_type.ARITHMETIC false, Operator "ceil" ceil Operator_type.ARITHMETIC false, Operator "floor" floor Operator_type.ARITHMETIC false ]; matches = filter test_name operator_table; test_name x = x.op_name == op_name; } /* Find the matching methods in a method table. */ oo_method_lookup table = map (extract 0) (filter (extract 1) table); /* A binary op: a is a class, b may be a class ... eg. "add" a b two obvious ways to find a method: - a.oo_binary_search "add" (+) b - b.oo_binary_search "add'" (converse (+)) a, is_class b if these fail but op is a symmetric operator (eg. a + b == b + a), we can also try reversing the args - a.oo_binary_search "add'" (converse (+)) b - b.oo_binary_search "add" (+) a, is_class b */ oo_binary_function op a b = matches1?0, matches1 != [] = matches2?0, is_class b && matches2 != [] = matches3?0, op.symmetric && matches3 != [] = matches4?0, op.symmetric && is_class b && matches4 != [] = error (_ "No method found for binary operator." ++ "\n" ++ _ "left" ++ " = " ++ print a ++ "\n" ++ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++ _ "right" ++ " = " ++ print b) { matches1 = oo_method_lookup (a.oo_binary_table op b); matches2 = oo_method_lookup (b.oo_binary_table (oo_converse op) a); matches3 = oo_method_lookup (a.oo_binary_table (oo_converse op) b); matches4 = oo_method_lookup (b.oo_binary_table op a); } /* A binary op: a is not a class, b is a class ... eg. "subtract" a b only one way to find a method: - b.oo_binary_search "subtract'" (converse (-)) a if this fails but op is a symmetric operator (eg. a + b == b + a), we can try reversing the args - b.oo_binary_search "add" (+) a, is_class b */ oo_binary'_function op a b = matches1?0, matches1 != [] = matches2?0, op.symmetric && matches2 != [] = error (_ "No method found for binary operator." ++ "\n" ++ _ "left" ++ " = " ++ print a ++ "\n" ++ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++ _ "right" ++ " = " ++ print b) { matches1 = oo_method_lookup (b.oo_binary_table (oo_converse op) a); matches2 = oo_method_lookup (b.oo_binary_table op a); } oo_unary_function op x = matches?0, matches != [] = error (_ "No method found for unary operator." ++ "\n" ++ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++ _ "argument" ++ " = " ++ print x) { matches = oo_method_lookup (x.oo_unary_table op); } /* Base class for nip's built-in classes ... base check function, base * operator overload functions. */ _Object = class { check = check_args this; _check_args = []; _check_all = []; /* Operator overloading stuff. */ oo_binary op x = oo_binary_function (oo_binary_lookup op) this x; oo_binary' op x = oo_binary'_function (oo_binary_lookup op) x this; oo_unary op = oo_unary_function (oo_unary_lookup op) this; /* Provide a fallback for class == thing ... just use pointer * equality. */ oo_binary_table op x = [ [pointer_equal this x, op.op_name == "equal" || op.op_name == "equal'"], [not_pointer_equal this x, op.op_name == "not_equal" || op.op_name == "not_equal'"] ]; oo_unary_table op = []; } /* A list of things. Do automatic iteration of unary and binary operators on us. */ Group value = class _Object { _check_args = [ [value, "value", check_list] ]; // methods oo_binary_table op x = [ [this.Group (map2 (apply2 op) value x.value), is_Group x], [this.Group (map (apply2 op' x) value), true] ] ++ super.oo_binary_table op x { op' = oo_converse op; apply2 op x1 x2 = oo_binary_function op x1 x2, is_class x1 = oo_binary'_function op x1 x2, is_class x2 = op.fn x1 x2; }; oo_unary_table op = [ [this.Group (map apply value), true] ] ++ super.oo_unary_table op { apply x = oo_unary_function op x, is_class x = op.fn x; } } /* Single real number ... eg slider. */ Real value = class _Object { _check_args = [ [value, "value", check_real] ]; // methods oo_binary_table op x = [ [this.Real (op.fn this.value x.value), is_Real x && op.type == Operator_type.ARITHMETIC], [this.Real (op.fn this.value x), is_real x && op.type == Operator_type.ARITHMETIC], [op.fn this.value x.value, is_Real x && op.type == Operator_type.RELATIONAL], [op.fn this.value x, !is_class x] ] ++ super.oo_binary_table op x; oo_unary_table op = [ [this.Real (op.fn this.value), op.type == Operator_type.ARITHMETIC], [op.fn this.value, true] ] ++ super.oo_unary_table op; } /* Single bool ... eg Toggle. */ Bool value = class _Object { _check_args = [ [value, "value", check_bool] ]; // methods oo_binary_table op x = [ [if value then x?0 else x?1, op.op_name == "if_then_else"], [this.Bool (op.fn this.value x.value), is_Bool x], [this.Bool (op.fn this.value x), is_bool x] ] ++ super.oo_binary_table op x; oo_unary_table op = [ [this.Bool (op.fn this.value), op.type == Operator_type.ARITHMETIC || op.type == Operator_type.RELATIONAL] ] ++ super.oo_unary_table op; } /* An editable string. */ String caption value = class _Object { _check_args = [ [caption, "caption", check_string], [value, "value", check_string] ]; } /* An editable real number. */ Number caption value = class scope.Real value { _check_args = [ [caption, "caption", check_string] ]; Real x = this.Number caption x; } /* An editable expression. */ Expression caption expr = class (if is_class expr then expr else _Object) { _check_args = [ [caption, "caption", check_string], [expr, "expr", check_any] ]; } /* An editable filename. */ Pathname caption value = class _Object { _check_args = [ [caption, "caption", check_string], [value, "value", check_string] ]; } /* An editable fontname. */ Fontname caption value = class _Object { _check_args = [ [caption, "caption", check_string], [value, "value", check_string] ]; } /* Vector type ... just a finite list of real ... handy for wrapping an * argument to eg. im_lintra_vec. Make it behave like a single pixel image. */ Vector value = class _Object { _check_args = [ [value, "value", check_real_list] ]; bands = len value; // methods oo_binary_table op x = [ // Vector ++ Vector means bandwise join [this.Vector (op.fn this.value x.value), is_Vector x && (op.op_name == "join" || op.op_name == "join'")], [this.Vector (op.fn this.value [get_number x]), has_number x && (op.op_name == "join" || op.op_name == "join'")], // Vector ? number means extract element [op.fn this.value (get_real x), has_real x && (op.op_name == "subscript" || op.op_name == "subscript'")], // extra check for lengths equal [this.Vector (map_binary op.fn this.value x.value), is_Vector x && len value == len x.value && op.type == Operator_type.ARITHMETIC], [this.Vector (map_binary op.fn this.value (get_real x)), has_real x && op.type == Operator_type.ARITHMETIC], // need extra length check [this.Vector (map bool_to_real (map_binary op.fn this.value x.value)), is_Vector x && len value == len x.value && op.type == Operator_type.RELATIONAL], [this.Vector (map bool_to_real (map_binary op.fn this.value (get_real x))), has_real x && op.type == Operator_type.RELATIONAL], [this.Vector (op.fn this.value x.value), 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], [vec op'.op_name x value, is_image x], [op.fn this.value x, is_real x] ] ++ super.oo_binary_table op x { op' = oo_converse op; }; oo_unary_table op = [ [this.Vector (map_unary op.fn this.value), op.type == Operator_type.ARITHMETIC], [this.Vector (map bool_to_real (map_unary op.fn this.value)), op.type == Operator_type.RELATIONAL], [this.Vector (op.fn this.value), op.type == Operator_type.COMPOUND_REWRAP], [op.fn this.value, true] ] ++ super.oo_unary_table op; // turn an ip bool (or a number, for Vector) into VIPSs 255/0 bool_to_real x = 255, is_bool x && x = 255, is_number x && x != 0 = 0; } /* A rectangular array of real. */ Matrix_base value = class _Object { _check_args = [ [value, "value", check_matrix] ]; // calculate these from value width = len value?0; height = len value; // methods oo_binary_table op x = [ // mat multiply is special [this.Matrix_base mul.value, is_Matrix x && op.op_name == "multiply"], [this.Matrix_base mul'.value, is_Matrix x && op.op_name == "multiply'"], // mat divide is also special [this.Matrix_base div.value, is_Matrix x && op.op_name == "divide"], [this.Matrix_base div'.value, is_Matrix x && op.op_name == "divide'"], // power -1 means invert [this.Matrix_base inv.value, is_real x && x == -1 && op.op_name == "power"], [this.Matrix_base sq.value, is_real x && x == 2 && op.op_name == "power"], [error "matrix **-1 and **2 only", op.op_name == "power" || op.op_name == "power'"], // matrix op vector ... treat a vector as a 1 row matrix [this.Matrix_base (map (map_binary op'.fn x.value) this.value), is_Vector x && op.type == Operator_type.ARITHMETIC], [this.Matrix_base (map_binary op.fn this.value x.value), (is_Matrix x || is_Real x) && op.type == Operator_type.ARITHMETIC], [this.Matrix_base (map_binary op.fn this.value x), is_real x && op.type == Operator_type.ARITHMETIC], // compound ... don't do iteration [this.Matrix_base (op.fn this.value x.value), (is_Matrix x || is_Real x || is_Vector x) && op.type == Operator_type.COMPOUND_REWRAP] ] ++ super.oo_binary_table op x { mul = im_matmul this x; mul' = im_matmul x this; div = im_matmul this (im_matinv x); div' = im_matmul x (im_matinv this); inv = im_matinv this; sq = im_matmul this this; op' = oo_converse op; } oo_unary_table op = [ [this.Matrix_base (map_unary op.fn this.value), op.type == Operator_type.ARITHMETIC], [this.Matrix_base (op.fn this.value), op.type == Operator_type.COMPOUND_REWRAP], [op.fn this.value, true] ] ++ super.oo_unary_table op; } /* How to display a matrix: text, sliders, toggles, or text plus scale/offset. */ Matrix_display = class { text = 0; slider = 1; toggle = 2; text_scale_offset = 3; is_display = member [text, slider, toggle, text_scale_offset]; } /* A matrix as VIPS sees them ... add scale, offset and filename. For nip, add * a display type as well to control how the widget renders. */ Matrix_vips value scale offset filename display = class scope.Matrix_base value { _check_args = [ [scale, "scale", check_real], [offset, "offset", check_real], [filename, "filename", check_string], [display, "display", check_matrix_display] ]; Matrix_base x = this.Matrix_vips x scale offset filename display; } /* A plain 'ol matrix which can be passed to VIPS. */ Matrix value = class Matrix_vips value 1 0 "" Matrix_display.text {} /* Specialised constructors ... for convolutions, recombinations and * morphologies. */ Matrix_con scale offset value = class Matrix_vips value scale offset "" Matrix_display.text_scale_offset {}; Matrix_rec value = class Matrix_vips value 1 0 "" Matrix_display.slider {}; Matrix_mor value = class Matrix_vips value 1 0 "" Matrix_display.toggle {}; Matrix_file filename = (im_read_dmask @ expand @ search) filename; /* A CIE colour ... a triple, plus a format (eg XYZ, Lab etc) */ Colour colour_space value = class scope.Vector value { _check_args = [ [colour_space, "colour_space", check_colour_space] ]; _check_all = [ [len value == 3, "len value == 3"] ]; Vector x = this.Colour colour_space x; // make a colour-ish thing from an image // back to Colour if we have another 3 band image // to a vector if bands > 1 // to a number otherwise itoc im = this.Colour nip_type (to_matrix im).value?0, bands == 3 = scope.Vector (map mean (bandsplit im)), bands > 1 = mean im { type = im_header_int "Type" im; bands = im_header_int "Bands" im; nip_type = Image_type.colour_spaces.lookup 1 0 type; } // methods oo_binary_table op x = [ [itoc (op.fn ((float) (to_image this).value) ((float) (to_image x).value)), // here REWRAP means go via image op.type == Operator_type.COMPOUND_REWRAP] ] ++ super.oo_binary_table op x; oo_unary_table op = [ [itoc (op.fn ((float) (to_image this).value)), op.type == Operator_type.COMPOUND_REWRAP] ] ++ super.oo_unary_table op; } // a subclass with widgets for picking a space and value Colour_picker default_colour default_value = class Colour space.value_name colour.expr { _vislevel = 3; space = Option_enum Image_type.colour_spaces "Colour space" default_colour; colour = Expression "Colour value" default_value; Colour_edit colour_space value = Colour_picker colour_space value; } /* Base scale type. */ Scale caption from to value = class scope.Real value { _check_args = [ [caption, "caption", check_string], [from, "from", check_real], [to, "to", check_real] ]; _check_all = [ [from < to, "from < to"] ]; Real x = this.Scale caption from to x; // methods oo_binary_table op x = [ [this.Scale caption (op.fn this.from x.from) (op.fn this.to x.to) (op.fn this.value x.value), is_Scale x && op.type == Operator_type.ARITHMETIC], [this.Scale caption (op.fn this.from x) (op.fn this.to x) (op.fn this.value x), is_real x && op.type == Operator_type.ARITHMETIC] ] ++ super.oo_binary_table op x; } /* Compat. slider type. */ Slider = Scale ""; /* Base toggle type. */ Toggle caption value = class scope.Bool value { _check_args = [ [caption, "caption", check_string], [value, "value", check_bool] ]; Bool x = this.Toggle caption x; } /* Base option type. */ Option caption labels value = class scope.Real value { _check_args = [ [caption, "caption", check_string], [labels, "labels", check_string_list], [value, "value", check_uint] ]; } Option_enum enum caption value_name = class Option caption enum.names (index (equal value_name) enum.names) { // corresponding thing value_thing = enum.get_thing value_name; Option_edit caption labels value = this.Option_enum enum caption (enum.names ? value); } /* A rectangle. width and height can be -ve. */ Rect left top width height = class _Object { _check_args = [ [left, "left", check_real], [top, "top", check_real], [width, "width", check_real], [height, "height", check_real] ]; // derived right = left + width; bottom = top + height; oo_binary_table op x = [ [equal x, is_Rect x && (op.op_name == "equal" || op.op_name == "equal'")], [!equal x, is_Rect x && (op.op_name == "not_equal" || op.op_name == "not_equal'")], // binops with a complex are the same as (comp op comp) [oo_binary_function op this (Rect (re x) (im x) 0 0), is_complex x], // all others are just pairwise [this.Rect left' top' width' height', is_Rect x && op.type == Operator_type.ARITHMETIC], [this.Rect left'' top'' width'' height'', has_number x && op.type == Operator_type.ARITHMETIC] ] ++ super.oo_binary_table op x { left' = op.fn left x.left; top' = op.fn top x.top; width' = op.fn width x.width; height' = op.fn height x.height; left'' = op.fn left x'; top'' = op.fn top x'; width'' = op.fn width x'; height'' = op.fn height x'; x' = get_number x; } oo_unary_table op = [ // arithmetic uops just map [this.Rect left' top' width' height', op.type == Operator_type.ARITHMETIC], // compound uops are just like ops on complex // do (width, height) so thing like abs(Arrow) work as you'd expect [op.fn (width, height), op.type == Operator_type.COMPOUND] ] ++ super.oo_unary_table op { left' = op.fn left; top' = op.fn top; width' = op.fn width; height' = op.fn height; } // empty? ie. contains no pixels is_empty = width == 0 || height == 0; // normalised version, ie. make width/height +ve and flip the origin nleft = left + width, width < 0 = left; ntop = top + height, height < 0 = top; nwidth = abs width; nheight = abs height; nright = nleft + nwidth; nbottom = ntop + nheight; equal x = left == x.left && top == x.top && width == x.width && height == x.height; // contains a point? includes_point x y = nleft <= x && x <= nright && ntop <= y && y <= nbottom; // contains a rect? just test top left and bottom right points includes_rect r = includes_point r.nleft r.ntop && includes_point r.nright r.nbottom; // bounding box of two rects // if either is empty, can just return the other union r = r, is_empty = this, r.is_empty = Rect left' top' width' height' { left' = min_pair nleft r.nleft; top' = min_pair ntop r.ntop; width' = max_pair nright r.nright - left'; height' = max_pair nbottom r.nbottom - top'; } // intersection of two rects ... empty rect if no intersection intersect r = Rect left' top' width'' height'' { left' = max_pair nleft r.nleft; top' = max_pair ntop r.ntop; width' = min_pair nright r.nright - left'; height' = min_pair nbottom r.nbottom - top'; width'' = width', width > 0 = 0; height'' = height', height > 0 = 0; } // expand/collapse by n pixels margin_adjust n = Rect (left - n) (top - n) (width + 2 * n) (height + 2 * n); } /* Values for Compression field in image. */ Image_compression = class { NO_COMPRESSION = 0; TCSF_COMPRESSION = 1; JPEG_COMPRESSION = 2; LABPACK_COMPRESSED = 3; RGB_COMPRESSED = 4; LUM_COMPRESSED = 5; } /* Values for Coding field in image. */ Image_coding = class { NOCODING = 0; COLQUANT = 1; LABPACK = 2; } /* Values for BandFmt field in image. */ Image_format = class { DPCOMPLEX = 9; DOUBLE = 8; COMPLEX = 7; FLOAT = 6; INT = 5; UINT = 4; SHORT = 3; USHORT = 2; CHAR = 1; UCHAR = 0; NOTSET = -1; maxval fmt = [ 255, // UCHAR 127, // CHAR 65535, // USHORT 32767, // SHORT 4294967295, // UINT 2147483647, // INT 255, // FLOAT 255, // COMPLEX 255, // DOUBLE 255 // DPCOMPLEX ] ? fmt, fmt >= 0 && fmt <= DPCOMPLEX = error (_ "bad value for BandFmt"); } /* A lookup table. */ Table value = class _Object { _check_args = [ [value, "value", check_rectangular] ]; /* present col x: is there an x in column col */ present col x = member (map (extract col) value) x; /* Look on column from, return matching item in column to. */ lookup from to x = value?n?to, n >= 0 = error (_ "item" ++ " " ++ print x ++ " " ++ _ "not in table") { n = index (equal x) (map (extract from) value); } } /* A two column lookup table with the first column a string and the second a * thing. Used for representing various enums. Option_enum makes a selector * from one of these. */ Enum value = class Table value { _check_args = [ [value, "value", check_enum] ] { check_enum = [is_enum, _ "is [[char, *]]"]; is_enum x = is_rectangular x && is_listof is_string (map (extract 0) x); } // handy ... all the names and things as lists names = map (extract 0) value; things = map (extract 1) value; // is a legal name or thing has_name x = this.present 1 x; has_thing x = this.present 0 x; // map things to strings and back get_name x = this.lookup 1 0 x; get_thing x = this.lookup 0 1 x; } /* Type field. */ Image_type = class { FOURIER = 24; YXY = 23; sRGB = 22; LABS = 21; LCH = 19; UCS = 18; RGB = 17; LABQ = 16; CMYK = 15; CMC = 14; LAB = 13; XYZ = 12; LUT = 11; HISTOGRAM = 10; POWER_SPECTRUM = 9; BLUE_ONLY = 8; GREEN_ONLY = 7; RED_ONLY = 6; YUV = 5; IR = 4; XRAY = 3; LUMINACE = 2; B_W = 1; MULTIBAND = 0; /* Table to get names <-> numbers. */ type_names = Enum [ ["FOURIER", FOURIER], ["YXY", YXY], ["sRGB", sRGB], ["LABS", LABS], ["LCH", LCH], ["UCS", UCS], ["RGB", RGB], ["LABQ", LABQ], ["CMYK", CMYK], ["CMC", CMC], ["LAB", LAB], ["XYZ", XYZ], ["LUT", LUT], ["HISTOGRAM", HISTOGRAM], ["POWER_SPECTRUM", POWER_SPECTRUM], ["BLUE_ONLY", BLUE_ONLY], ["GREEN_ONLY", GREEN_ONLY], ["RED_ONLY", RED_ONLY], ["YUV", YUV], ["IR", IR], ["XRAY", XRAY], ["LUMINACE", LUMINACE], ["B_W", B_W], ["MULTIBAND", MULTIBAND] ]; /* Table relating nip's colour space names and VIPS's Type numbers. * Options generated from this, so match the order to the order in the * Colour menu. */ colour_spaces = Enum [ ["sRGB", sRGB], ["Lab", LAB], ["LCh", LCH], ["XYZ", XYZ], ["Yxy", YXY], ["UCS", UCS] ]; /* A slightly larger table ... the types of colorimetric image we can * have. Add mono, and the S and Q forms of LAB. */ image_colour_spaces = Enum [ ["Mono", B_W], ["sRGB", sRGB], ["Lab", LAB], ["LabQ", LABQ], ["LabS", LABS], ["LCh", LCH], ["XYZ", XYZ], ["Yxy", YXY], ["UCS", UCS] ]; } /* Base image type. Simple layer over vips_image. */ Image value = class _Object { _check_args = [ [value, "value", check_image] ]; // fields from VIPS header width = get_width value; height = get_height value; bands = get_bands value; format = get_format value; bits = get_bits value; coding = get_coding value; type = get_type value; xres = im_header_double "Xres" value; yres = im_header_double "Yres" value; xoffset = im_header_int "Xoffset" value; yoffset = im_header_int "Yoffset" value; filename = im_header_string "filename" value; // convenience ... the area our pixels occupy, as a rect rect = Rect 0 0 width height; // operator overloading // (op Image Vector) done in Vector class oo_binary_table op x = [ // handle image ++ constant here [wrap join_result_image, (has_real x || is_Vector x) && (op.op_name == "join" || op.op_name == "join'")], // image ++ image is slightly different ... we want to // sizealike, but we must not bandalike [wrap (op.fn (get_image resized?0) (get_image resized?1)), has_image x && (op.op_name == "join" || op.op_name == "join'")], [wrap ite_result_image, op.op_name == "if_then_else"], // arithmetic and reational binops between image resize // and band_alike images to match [wrap (op.fn (get_image rebanded?0) (get_image rebanded?1)), has_image x && (op.type == Operator_type.ARITHMETIC || op.type == Operator_type.RELATIONAL)], // other op types don't resize [wrap (op.fn this.value (get_image x)), has_image x], [wrap (op.fn this.value (get_number x)), has_number x], [wrap (op.fn this.value x), true] ] ++ super.oo_binary_table op x { // wrap the result with this ... only skip rewrap for COMPOUND wrap = id, op.type == Operator_type.COMPOUND = this.Image; join_result_image = value ++ new_stuff, op.op_name == "join" = new_stuff ++ value { new_stuff = image_new width height new_bands format coding Image_type.B_W x xoffset yoffset; new_bands = get_bands x, has_bands x = 1; } then_part = x?0; else_part = x?1; // get things about our output from inputs in this order objects = [then_part, else_part, this]; // properties of our output image target_bands = get_member_list has_bands get_bands objects; target_format = get_member_list has_format get_format objects; target_type = get_member_list has_type get_type objects; to_image x = x, is_image x = x.value, is_Image x = black + x { black = im_black width height target_bands; } then_image = to_image then_part; else_image = to_image else_part; then_image' = clip2fmt target_format then_image; else_image' = clip2fmt target_format else_image; ite_resized = size_alike [value, then_image', else_image']; ite_result_image = image_set_type target_type (if ite_resized?0 then ite_resized?1 else ite_resized?2); resized = size_alike [this, x]; rebanded = bands_alike resized; } // FIXME ... yuk ... don't use operator hints, just always rewrap if // we have an image result // forced on us by things like abs: // abs Vector -> real // abs Image -> Image // does not fit well with COMPOUND/whatever scheme oo_unary_table op = [ [this.Image result, is_image result], [result, true] ] ++ super.oo_unary_table op { result = op.fn this.value; } } /* Construct an image from a file. */ Image_file filename = class Image value { _check_args = [ [filename, "filename", check_string] ]; value = vips_image filename; } Region image left top width height = class Image value { _check_args = [ [image, "Image", check_Image], [left, "left", check_real], [top, "top", check_real], [width, "width", check_preal], [height, "height", check_preal] ]; // a rect for our coordinates // region.rect gets the rect for the extracted image region_rect = Rect left top width height; // we need to always succeed ... value is our enclosing image if we're // out of bounds value = extract_area left top width height image.value, image.rect.includes_rect region_rect = image.value; } Area image left top width height = class scope.Region image left top width height { Region image left top width height = this.Area image left top width height; } Arrow image left top width height = class scope.Rect left top width height { _check_args = [ [image, "Image", check_Image], [left, "left", check_real], [top, "top", check_real], [width, "width", check_real], [height, "height", check_real] ]; Rect l t w h = this.Arrow image l t w h; } HGuide image top = class scope.Arrow image image.rect.left top image.width 0 { Arrow image left top width height = this.HGuide image top; } VGuide image left = class scope.Arrow image left image.rect.top 0 image.height { Arrow image left top width height = this.VGuide image left; } Mark image left top = class scope.Arrow image left top 0 0 { Arrow image left top width height = this.Mark image left top; } // convenience functions: ... specify position as [0 .. 1) Region_relative image u v w h = Region image (image.width * u) (image.height * v) (image.width * w) (image.height * h); Area_relative image u v w h = Area image (image.width * u) (image.height * v) (image.width * w) (image.height * h); Arrow_relative image u v w h = Arrow image (image.width * u) (image.height * v) (image.width * w) (image.height * h); VGuide_relative image v = VGuide image (image.height * v); HGuide_relative image u = HGuide image (image.width * u); Mark_relative image u v = Mark image (image.width * u) (image.height * v); Interpolate = class { NEAREST_NEIGHBOUR = 0; BILINEAR = 1; BICUBIC = 2; /* Table to map interpol numbers to descriptive strings */ names = Enum [ [_ "Nearest neighbour", NEAREST_NEIGHBOUR], [_ "Bilinear", BILINEAR], [_ "Bicubic", BICUBIC] ]; } Render_intent = class { PERCEPTUAL = 0; RELATIVE = 1; SATURATION = 2; ABSOLUTE = 3; /* Table to get names <-> numbers. */ names = Enum [ [_ "Perceptual", PERCEPTUAL], [_ "Relative", RELATIVE], [_ "Saturation", SATURATION], [_ "Absolute", ABSOLUTE] ]; } // abstract base class for toolkit menus Menu = class {} // a "----" line in a menu Menuseparator = class Menu {} // abstract base class for items in menus Menuitem label tooltip = class Menu {} Menupullright label tooltip = class Menuitem label tooltip {} Menuaction label tooltip = class Menuitem label tooltip {}