#@gmic # # File : gmic_stdlib.gmic # ( G'MIC command file ) # # Description : GREYC's Magic for Image Computing - Standard library # ( https://gmic.eu ) # # Copyright : David Tschumperlé # ( https://tschumperle.users.greyc.fr/ ) # # Licenses : This file is 'dual-licensed', you have to choose one # of the two licenses below to apply. # # CeCILL-C # The CeCILL-C license is close to the GNU LGPL. # ( http://cecill.info/licences/Licence_CeCILL-C_V1-en.html ) # # or CeCILL v2.1 # The CeCILL license is compatible with the GNU GPL. # ( http://cecill.info/licences/Licence_CeCILL_V2.1-en.html ) # # This software is governed either by the CeCILL or the CeCILL-C license # under French law and abiding by the rules of distribution of free software. # You can use, modify and or redistribute the software under the terms of # the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA # at the following URL: "http://cecill.info". # # As a counterpart to the access to the source code and rights to copy, # modify and redistribute granted by the license, users are provided only # with a limited warranty and the software's author, the holder of the # economic rights, and the successive licensors have only limited # liability. # # In this respect, the user's attention is drawn to the risks associated # with loading, using, modifying and/or developing or reproducing the # software by the user in light of its specific status of free software, # that may mean that it is complicated to manipulate, and that also # therefore means that it is reserved for developers and experienced # professionals having in-depth computer knowledge. Users are therefore # encouraged to load and test the software's suitability as regards their # requirements in conditions enabling the security of their systems and/or # data to be ensured and, more generally, to use and operate it in the # same conditions as regards security. # # The fact that you are presently reading this means that you have had # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. # #------ Syntax rules for a G'MIC command file : #*** General syntax : # # - Each line starting with 'command_name :' starts a new definition of the G'MIC custom command 'command_name'. # - Each line starting with '#' is a comment line. # - Any other line is considered as the continuation of a previously started G'MIC custom command. # #*** Specific rules for the command-line interface 'gmic': # # - A comment line starting with '#@cli' will be parsed by 'gmic' to print help for # G'MIC custom commands (when invoked with option 'h'). More precisely : # # _ '#@cli :: subsection' defines a new command subsection in the displayed help. # _ '#@cli command_name : arguments_format1 : arguments_format2 : ... : (qualifier)' # starts a new command description. # _ '#@cli : description' add a new description line to the current command description. # _ '#@cli : $ command_line' defines a new example of use of the current command. # _ '#@cli : $$ _pagename' tells the command has a dedicated page in the web tutorial. # #*** Specific rules for the universal plug-in 'gmic-qt': # # - A comment line starting with '#@gui' will be parsed by the plug-in to define the filters tree. # - A comment line starting with '#@gui_xx' will define a filter only for a specific language 'xx' # (e.g. 'en','fr'...). # - A comment line starting with '#@gui_xx hide(/Filter or folder name)' will hide the existing # filter of folder for the locale 'xx'. # - More precisely, the syntax of a '#@gui' comment line is : # # '#@gui Folder name' # # or # # '#@gui Command name : command, preview_command (zoom_factor)[*|+] [: default_input_mode] # '#@gui : parameter1 = typedef(arguments1...), parameter2 = typedef(arguments2...)' # '#@gui : parameter3 = typedef(arguments3...), # # where : # # 'command' is the G'MIC command name called to process the image. # # 'preview_command' is the G'MIC command name called to process the preview. # # Note that you can optionally specify a float-valued factor>=0 between parentheses at the end of # the 'preview_command' to force the default zoom factor used by the preview for this filter. # Use (0) for a 1:1 preview, (1) for previewing the whole image, (2) for 1/2 image and so on... # You can put an additional '+' sign after the parenthesis to specify the rendered preview # is still accurate for different zoom factors. Use '*' instead to tell the plug-in that the preview filter # must get the entire image rather than a thumbnail. # # # 'default_input_mode' set the default input mode for that filter. It can be # { x:none | .:active (default) | *:all | +:active & below | -:active & above | v:all visible | i:all invisible } # # 'parameter = typedef' tells about the names, types and default values of the filter parameters. # # 'typedef' can be : # # _ 'bool(default_value={ 0 | 1 | false | true })': # Add a boolean parameter (0 or 1) (as a checkbutton). # # _ 'button(_alignment)': # Add a boolean parameter (0 or 1) (as a button). # # _ 'choice(_default_index,Choice0,..,ChoiceN)': # Add a integer parameter (as a combobox). # # _ 'color(R,_G,_B,_A)': # Add R,G,B[,A] parameters (as a colorchooser). # # _ 'point(_X,_Y,_removable={ -1 | 0 | 1 },_burst={ 0 | 1 },_R,_G,_B,_[-]A,_radius[%])': # Add X,Y parameters (as a moveable point over the preview). # # _ 'file(_default_filename)', 'file_in(_default_filename)' or 'file_out(_default_filename)': # Add a filename parameter (as a filechooser). # # _ 'float(default_value,min_value,max_value)': # Add a float-valued parameter (as a float slider). # # _ 'folder(_default_foldername)': # Add a foldername parameter (as a folderchooser). # # _ 'int(default_value,min_value,max_value)': # Add a integer parameter (as an integer slider). # # _ 'link(_alignment,_label,URL)': # Display a URL (do not add a parameter). # # _ 'note(_label)': # Display a label (do not add a parameter). # # _ 'text(_is_multiline={ 0 | 1 },_default text)': # Add a single or multi-line text parameter (as a text entry). # # _ 'separator()': # Display an horizontal separator (do not add a parameter). # # _ 'value(value)': # Add a pre-defined value parameter (not displayed). # # Type separators '()' can be replaced by '[]' or '{}' if necessary (for instance if parentheses are required in # an argument of the typedef, e.g in a text). You can also replace 'typedef' by '_typedef' to tell the plug-in not # being responsive, i.e not to update the image preview when the corresponding parameter is modified. # Prepend a '~' character to the argument type (like '~typedef') to make it randomizable. # After the closing separator, you may specify a 'visibility state' character for the parameter, which can be # { _0:Hidden | _1:Grayed-out | _2:Visible (default) }, opt. followed by a propagation character that tells # if this visibility state must be propagated to neighboring non-valued interface widgets # (s.a. separator(), link() or note()). # This propagation character can be: # { +:propagate forward | -:propagate backward | *:propagate in both directions }. # # Use '_none_' as a special command or preview_command to tell the plug-in that the entry requires no G'MIC call. # # A G'MIC command can set new values for each filter parameter, through the status (see command ''status''). # To do so, the returned status must follow the syntax : # '{params1}{params2}{..}{paramsN}' where N must be exactly equal to the number of parameters # for the current filter. Optionally, you can append to each {param} its visibility state suffix ( e.g: {param}_1 ). # # A G'MIC command can also specify the output blending mode, the opacity and the position of each of the output image # (i.e. layer in the plug-in). To do so, set the image name to something like: # 'mode(grainmerge),opacity(50),pos(30,50),name(name)'. # # - Blending mode name should be the same as the argument of the 'blend' command. # - Opacity is a float number in [0,100]. # - X and Y positions are integers. # - 'name' is the layer name. # # Pre-defined plug-in variables: # # - _persistent : Variable that is shared between successive calls to the G'MIC interpreter, for a single filter. # - _preview_enabled: Boolean that tells if the preview is enabled or not. # - _preview_width: Width of the preview image. # - _preview_height: Height of the preview image. # - _preview_area_width: Width of the preview widget area. # - _preview_area_height: Height of the preview widget area. # - _preview_x0: X-coordinate of the upper-left corner of the preview (expressed in the image space). # - _preview_y0: Y-coordinate of the upper-left corner of the preview (exressed in the image space). # - _preview_x1: X-coordinate of the lower-right corner of the preview (expressed in the image space). # - _preview_y1: Y-coordinate of the lower-right corner of the preview (expressed in the image space). # - _randomized: Boolean that tells if the filter parameters have been randomized with the "Randomize" button. # # Values of variables $_preview[x/y][0/1] vary only when preview mode is set to * (full preview). # #----------------------------------------------------------------------------------------------------------------------- #--------------------------------- # #@cli :: Global Options # #--------------------------------- # This command is run when the cli tool 'gmic' is invoked without arguments on the command line. cli_noarg : v 0 use_vt100 g=$_vt100_g c=$_vt100_c n=$_vt100_n r=$_vt100_r _cli_noarg=1 version +e "\n[gmic] No commands, options or data provided." gmic_help="gmic help" if !${-is_macos}" && "!${-is_windows} gmic_help.=" | less" if $_vt100 gmic_help.=" -r" fi fi if {*,u}>0 +e "[gmic] (type "${c}"'"$gmic_help"'"$n" to print help, "${c}"'gmic demos'"$n" to launch demos)." else +e "[gmic] (type "${c}"'"$gmic_help"'"$n" to print help)." fi file_update=${_path_rc}update$_version.gmic if "isfile(['"{/$file_update}"'])" update_old:="Y = date(0); M = date(1); D = date(2); date_current = Y*365 + M*31 + D; Y = date(0,'"{/$file_update}"'); M = date(1,'"{/$file_update}"'); D = date(2,'"{/$file_update}"'); date_file = Y*365 + M*31 + D; date_current - date_file" if $update_old>=14 +e "[gmic] Command update file is "$update_old" days old, type "$g"'gmic up'"$n" to update it." fi else +e "[gmic] Fetch update and documentation from the G'MIC server." l[] { up state=${g}Success!$n onfail state=${r}Failed!$n } +e "\r[gmic] Fetch update and documentation from the G'MIC server. "$state fi +e "\n" # cli_start # This command is called each time the cli interface 'gmic' starts. # Overload it in your local user command file if necessary. cli_start : #@cli debug : (+) #@cli : Activate debug mode. #@cli : When activated, the G'MIC interpreter becomes very verbose and outputs additional log #@cli : messages about its internal state on the standard output (stdout). #@cli : This option is useful for developers or to report possible bugs of the interpreter. #@cli h : eq. to 'help'. h : help $"*" #@cli help : command : (no arg) #@cli : Display help (optionally for specified command only) and exit. #@cli : (eq. to 'h'). help : skip ${1=""} use_vt100 foreach { if "w!=1 || d!=1 || s!=1" rm fi } if ['$1']==0 # Display global help. _no_examples,_no_default_values,_no_tutorial_link=1 reference ascii else # Display help for a single command. is_embedded_stdlib:=$!==1 if narg("$1")" && "isfile(['{/$_path_user}']) l[] { it[] $_path_user onfail } fi if narg("$1")" && "narg($_path_commands) l[] { $_path_commands foreach { l { it[] {n} onfail 0 } k. } onfail } fi if isfile(['{/$_path_rc/update$_version.gmic}']) l[] { i cimgz:$_path_rc/update$_version.gmic onfail l[] { it[] $_path_rc/update$_version.gmic onfail } } fi if $is_embedded_stdlib mv[0] $! fi if !$! return fi a y # Check that requested command exist. command={`" s = ['$1']; len = size(s); z = 0; (p = find(s,_'['))>0?(s[p] = 0; len = p); isin(s[0],_'-',_'+') && len>1 && !(s[z + 1]==_'3' && s[z + 2]==_'d')?(copy(s,s[z + 1],len - 1); s[len - 1] = 0); s"`} s +,{'"#@cli "$command" :"'} s +,{'"#@cli "$command":"'} s +,{'"#@cli "$command"\n"'} if $!==1 l[] { m "foo : "$command # Detect command misspelling. repeat 16 { um $command } # Be sure the specified command does not exist anymore ! foo um foo onfail ('${}') s -,{'"; did you mean "'} if $!>1 s[1] -,39 k[1] misspelling={t} fi rm } if narg($misspelling) misspelling="; did you mean '"$_vt100_g$misspelling$_vt100_n"' ?" fi +e "\n[gmic] No help available for command '"$_vt100_r$command$_vt100_n"'"$misspelling". "\ "\n Try '"${_vt100_c}"gmic help"$_vt100_n"' for global help." else a y _display_categories=0 +parse_cli ascii,$command if narg(${}) parse_cli ascii,${} fi # In case of shortcut, display also help for shortcut command. fi fi +e "\n" rm q # Command to write reference documentation with various output modes. # $1 = output mode, can be { ascii | html | man | pdf }. # $2 = name of the folder containing additional .gmd pages (optional). # Reference and command documentation is written using the G'MIC-markdown syntax. reference : skip "${2=}" m "_section : reference_section_$1 \"$""*\"" m "_text : reference_text_$1 \"$""*\"" l { reference_begin_$1 reference_header_$1 onfail } _section "Usage" _text \ "~~~\ngmic [command1 [arg1_1,arg1_2,..]] .. [commandN [argN_1,argN_2,..]]\n~~~"\n\n\ "`gmic` is the open-source interpreter of the G'MIC language, a scripting programming "\ "language dedicated to the design of possibly complex image processing pipelines and operators."\n\ "It can be used to convert, manipulate, filter and visualize image datasets made of one or "\ "several 1D/2D or 3D multi-spectral images."\n\ \n\ "This reference documentation describes all the technical aspects of the G'MIC framework, "\ "in its current version ___"${-strver}"___."\n\ \n\ "As a starting point, you may want to visit our detailed tutorial pages, at: " _section "Overall Context" _text \ "* At any time, G'MIC manages one list of numbered (and optionally named) pixel-based images, "\ "entirely stored in computer memory (uncompressed)."\n\ "* The first image of the list has index '0' and is denoted by '[0]'. The second image of the "\ "list is denoted by '[1]', the third by '[2]' and so on."\n\ "* Negative indices are treated in a periodic way: '[-1]' refers to the last image of the list, '[-2]' to the "\ "penultimate one, etc. Thus, if the list has 4 images, '[1]' and '[-3]' both designate the second image of the list."\n\ "* A named image may be also indicated by '[name]', if 'name' uses the character set `[a-zA-Z0-9_]` and does not "\ "start with a number. Image names can be set or reassigned at any moment during the processing pipeline "\ "(see command ''name'' for this purpose)."\n\ "* G'MIC defines a set of various commands and substitution mechanisms to allow the design of complex "\ "pipelines and operators managing this list of images, in a very flexible way: You can insert or remove images "\ "in the list, rearrange image order, process images (individually or grouped), merge image data together, "\ "display and output image files, etc."\n\ "* Such a pipeline can define a new custom G'MIC command (stored in a user command file), and re-used "\ "afterwards as a regular command, in a larger pipeline if necessary." _section "Image Definition and Terminology" _text \ "* In G'MIC, each image is modeled as a 1D, 2D, 3D or 4D array of scalar values, uniformly "\ "discretized on a rectangular/parallelepipedic domain."\n\ "* The four dimensions of this array are respectively denoted by:"\n\ " - `width`, the number of image columns (size along the `x-axis`)."\n\ " - `height`, the number of image rows (size along the `y-axis`)."\n\ " - `depth`, the number of image slices (size along the `z-axis`). "\ "The depth is equal to '1' for usual color or grayscale 2D images."\n\ " - `spectrum`, the number of image channels (size along the `c-axis`). "\ "The spectrum is respectively equal to '3' and '4' for usual `RGB` and `RGBA` color images."\n\ \n\ "* There are no hard limitations on the size of the image along each dimension. For instance, the number of image "\ "slices or channels can be of arbitrary size within the limits of the available memory."\n\ "* The `width`, `height` and `depth` of an image are considered as _spatial_ dimensions, while the `spectrum` has a "\ "_multi-spectral_ meaning. Thus, a 4D image in G'MIC should be most often regarded as a 3D dataset of multi-spectral "\ "voxels. Most of the G'MIC commands will stick with this idea (e.g. command ''blur'' blurs images only along the "\ "spatial `xyz`-axes)."\n\ "* G'MIC stores all the image data as buffers of `float` values (32 bits, value range '[-3.4E38,+3.4E38]'. "\ "It performs all its image processing operations with floating point numbers. Each image pixel takes "\ "then 32bits/channel (except if double-precision buffers have been enabled during the compilation of the software, "\ "in which case 64bits/channel can be the default)."\n\ "* Considering `float`-valued pixels ensure to keep numerical precision when executing image processing "\ "pipelines. For image input/output operations, you may want to prescribe the image datatype to be different than "\ "`float` (like `bool`, `char`, `int`, etc.). This is possible by specifying it as a file option when using "\ "I/O commands (see section ''Input/Output Properties'' to learn more about file options)." _section "Items of a Processing Pipeline" _text \ "* In G'MIC, an image processing pipeline is described as a sequence of items separated by the "\ "__space__ character. Such items are interpreted and executed from the left to the right. For instance, "\ "the expression:"\n\ "~~~\nfilename.jpg blur 3,0 sharpen 10 resize 200%,200% output file_out.jpg\n~~~\n"\ "defines a valid pipeline composed of nine G'MIC items."\n\n\ "* Each G'MIC item is either a __command__, a list of __command arguments__, "\ "a __filename__ or a special __input string__."\n\ "* Escape characters '\\' and double quotes '\"' can be used to define items containing spaces or "\ "other special characters. For instance, the two strings `single\\ item` and `\"single item\"` "\ "both define the same single item, with a space in it." _section "Input Data" _text \ "* If a specified G'MIC item appears to be an existing filename, the corresponding image data "\ "are loaded and inserted at the end of the image list (which is equivalent to the use of `input filename`). "\n\ "* Special filenames `-` and `-.ext` stand for the standard input/output streams, optionally "\ "forced to be in a specific 'ext' file format (e.g. `-.jpg` or `-.png`). "\n\ "* The following special input strings may be used as G'MIC items to create and insert new "\ "images with prescribed values, at the end of the image list:"\n\ " - '[selection]' or '[selection]xN': Insert 1 or N copies of already existing images. "\ "'selection' may represent one or several images (see section ''Command Items and Selections'' to learn more "\ "about selections)."\n\ " - 'width[%],_height[%],_depth[%],_spectrum[%],_values[xN]': Insert one or N images with specified "\ "size and values (adding '%' to a dimension means __\"percentage of the size along the same axis\"__, "\ "taken from the last image '[-1]'). Any specified dimension can be also written as "\ "'[image]', and is then set to the size (along the same axis) of the existing specified image "\ "'[image]'. 'values' can be either a sequence of numbers separated by commas ',', "\ "or a mathematical expression, as e.g. in input item '256,256,1,3,[x,y,128]' which "\ "creates a `256x256` RGB color image with a spatial shading on the red and green channels. "\ "(see section ''Mathematical Expressions'' to learn more about mathematical expressions). "\n\ " - '(v1,v2,..[:delimiter | axis_order])[xN]': Insert one or `N` new images from specified prescribed values. "\ "Value separator inside parentheses can be ',' (column separator), ';' (row separator), '/' (slice separator) or "\ "'^' (channel separator). For instance, expression '(1,2,3;4,5,6;7,8,9)' creates a 3x3 matrix (scalar image), "\ "with values running from 1 to 9. "\n\ " - '(\\'string\\'[:delimiter])[xN]': Insert one or N new images from specified string, by filling "\ "the images with the character codes composing the string. When specified, 'delimiter' tells about "\ "the main orientation of the image. Delimiter can be 'x' (eq. to ',' which is the default), "\ "'y' (eq. to ';'), 'z' (eq. to '/') or 'c' (eq. to '^'). "\ "When specified delimiter is ',', ';', '/' or '^', the expression is actually equivalent to "\ "'({\\'string\\'[:delimiter]})[xN]' (see section ''Substitution Rules'' for more information on the syntax)."\n\ " - '0[xN]': Insert one or N new `empty` images, containing no pixel data. "\ "Empty images are used only in rare occasions."\n\ \n\ "* Input item 'name=value' declares a new variable 'name', or assign a new string value to an existing variable. "\ "Variable names must use the character set `[a-zA-Z0-9_]` and cannot start with a number. "\n\ "* A variable definition is always local to the current command except :"\n\ " - When it starts by the underscore character '_'. In that case, it becomes also accessible by any command invoked "\ "outside the current command scope (global variable)."\n\ " - When defined in a _subcommand_ of the current command, a variable becomes also accessible in the parent command. "\ "A _subcommand_ of a command `foo` is a command whose name starts with `__foo_` (e.g. `__foo_sub`) and that is called "\ "from `foo`."\n\ "* If a variable name starts with two underscores `__`, the global variable is also shared among different threads "\ "and can be read/set by commands running in parallel (see command ''parallel'' for this purpose). "\ "Otherwise, it remains local to the thread that defined it."\n\ "* Numerical variables can be updated with the use of these special operators: "\ "'+=' (addition), '-=' (subtraction), '*=' (multiplication), '/=' (division), '%=' (modulo), '&=' (bitwise and), "\ "'|=' (bitwise or), '^=' (power), '<<=' and '>>' (bitwise left and right shifts). For instance, 'foo=1' 'foo+=3'."\n\ "* Input item 'name.=string' appends specified `string` at the end of variable 'name'."\n\ "* Input item 'name..=string' prepends specified `string` at the beginning of variable 'name'."\n\ "* Multiple variable assignments and updates are allowed, with expressions: 'name1,name2,...,nameN=value' or "\ "'name1,name2,...,nameN=value1,value2,...,valueN' where assignment operator '=' can be replaced by one of the "\ "allowed operators (e.g. '+=')."\n\ "* Variables usually store numbers or strings. Use command ''store'' to assign variables from image data "\ "(and syntax `input $variable` to bring them back on the image list afterwards)." _section "Command Items and Selections" _text \ "* A G'MIC item that is not a filename nor a special input string designates a 'command' "\ "most of the time. Generally, commands perform image processing operations on one or several available images "\ "of the list."\n\ "* Reccurent commands have two equivalent names ('regular' and 'short'). For instance, command names "\ "'resize' and 'r' refer to the same image resizing action."\n\ "* A G'MIC command may have mandatory or optional __arguments__. Command arguments must be specified "\ "in the next item on the command line. Commas ',' are used to separate multiple arguments of a single command, "\ "when required."\n\ "* The execution of a G'MIC command may be restricted only to a __subset__ of the image list, by "\ "appending '[selection]' to the command name. Examples of valid syntaxes for 'selection' are: "\n\ " - 'command[-2]': Apply command only on the penultimate image '[-2]' of the list."\n\ " - 'command[0,1,3]': Apply command only on images '[0]', '[1]' and '[3]'."\n\ " - 'command[3-6]': Apply command only on images '[3]' to '[6]' (i.e, '[3]', '[4]', '[5]' and '[6]')."\n\ " - 'command[50%-100%]': Apply command only on the second half of the image list."\n\ " - 'command[0,-4--1]': Apply command only on the first image and the last four images."\n\ " - 'command[0-9:3]': Apply command only on images '[0]' to '[9]', with a step of 3 "\ "(i.e. on images '[0]', '[3]', '[6]' and '[9]')."\n\ " - 'command[0--1:2]': Apply command only on images of the list with even indices. "\n\ " - 'command[0,2-4,50%--1]': Apply command on images '[0]', '[2]', '[3]', '[4]' and on the second half of "\ "the image list."\n\ " - 'command[^0,1]': Apply command on all images except the first two."\n\ " - 'command[name1,name2]': Apply command on named images 'name1' and 'name2'."\n\ \n\ "* Indices in selections are always sorted in increasing order, and duplicate indices are "\ "discarded. For instance, selections '[3-1,1-3]' and '[1,1,1,3,2]' are both equivalent to "\ "'[1-3]'. If you want to repeat a single command multiple times on an image, use a "\ "'repeat..done' loop instead. Inverting the order of images for a command is achieved by "\ "explicitly inverting the order of the images in the list, with command 'reverse[selection]'."\n\ "* Command selections '[-1]', '[-2]' and '[-3]' are so often used they have their own shortcuts, respectively "\ "'.', '..' and '...'. For instance, command 'blur..' is equivalent to 'blur[-2]'. "\ "These shortcuts work also when specifying command arguments."\n\ "* G'MIC commands invoked without '[selection]' are applied on all images of the list, i.e. the "\ "default selection is '[0--1]' (except for command ''input'' whose default selection is '[-1]'')."\n\ "* Prepending a single hyphen '-' to a G'MIC command is allowed. This may be useful to recognize "\ "command items more easily in a one-liner pipeline (typically invoked from a shell). "\n\ "* A G'MIC command prepended with a plus sign '+' does not act __in-place__ but inserts its result as one or "\ "several new images at the end of the image list."\n\ "* There are two different types of commands that can be run by the G'MIC interpreter:"\n\ " - __Built-in commands__ are the hard-coded functionalities in the interpreter core. They are thus compiled as "\ "binary code and run fast, most of the time. Omitting an argument when invoking a built-in command is not permitted, "\ "except if all following arguments are also omitted. "\ "For instance, invoking 'blur 1,,1' is invalid but 'blur 1' is correct. "\n\ " - __Custom commands__, are defined as G'MIC pipelines of built-in or other custom commands. "\ "They are parsed by the G'MIC interpreter, and thus run a bit slower than built-in commands. "\ "Omitting arguments when invoking a custom command is permitted. For instance, expressions "\ "`flower ,,,100,,2` or `flower ,` are correct. "\n\ \n\ "* Most of the existing commands in G'MIC are actually defined as __custom commands__. "\n\ "* A user can easily add its own custom commands to the G'MIC interpreter (see section "\ " ''Adding Custom Commands'' for more details). New built-in commands cannot be added (unless you modify the "\ "G'MIC interpreter source code and recompile it)." _section "Input/Output Properties" _text \ "* G'MIC is able to read/write most of the classical image file formats, including:"\n\ " - 2D grayscale/color files: `.png`, `.jpeg`, `.gif`, `.pnm`, `.tif`, `.bmp`, ..."\n\ " - 3D volumetric files: `.dcm`, `.hdr`, `.nii`, `.cube`, `.pan`, `.inr`, `.pnk`, ..."\n\ " - Video files: `.mpeg`, `.avi`, `.mp4`, `.mov`, `.ogg`, `.flv`, ..."\n\ " - Generic text or binary data files: `.gmz`, `.cimg`, `.cimgz`, `flo`, `ggr`, `gpl`, `.dlm`, `.asc`, "\ "`.pfm`, `.raw`, `.txt`, `.h`."\n\ " - 3D mesh files: `.off`, `.obj`."\n\ \n\ "* When dealing with color images, G'MIC generally reads, writes and displays data using the usual "\ "sRGB color space."\n\ "* When loading a `.png` and `.tiff` file, the bit-depth of the input image(s) is returned to the status."\n\ "* G'MIC is able to manage __3D mesh objects__ that may be read from files or generated by G'MIC commands. "\ "A 3D object is stored as a one-column scalar image containing the object data, in the "\ "following order: { magic_number; sizes; vertices; primitives; colors; opacities }. "\ "These 3D representations can be then processed as regular images (see command ''split3d'' for accessing "\ "each of these 3D object data separately)."\n\ "* Be aware that usual file formats may be sometimes not adapted to store all the available image "\ "data, since G'MIC uses float-valued image buffers. For instance, saving an image that was "\ "initially loaded as a 16bits/channel image, as a `.jpg` file will result in a loss of "\ "information. Use the G'MIC-specific file extension `.gmz` to ensure that all data "\ "precision is preserved when saving images."\n\ "* Sometimes, file options may/must be set for file formats:"\n\ " - __Video files:__ Only sub-frames of an image sequence may be loaded, using the input expression "\ "'filename.ext,[first_frame[,last_frame[,step]]]'. Set 'last_frame==-1' to tell it must be "\ "the last frame of the video. Set 'step' to '0' to force an opened video file to be "\ "opened/closed. Output framerate and codec can be also set by using the output expression "\ "'filename.avi,_fps,_codec,_keep_open' where 'keep_open' can be { 0 | 1 }. 'codec' is a 4-char string "\ "(see ) or '0' for the default codec. 'keep_open' "\ "tells if the output video file must be kept open for appending new frames afterwards."\n\ " - `.cimg[z]` __files:__ Only crops and sub-images of .cimg files can be loaded, using the input "\ "expressions 'filename.cimg,N0,N1', 'filename.cimg,N0,N1,x0,x1', "\ "'filename.cimg,N0,N1,x0,y0,x1,y1', 'filename.cimg,N0,N1,x0,y0,z0,x1,y1,z1' or "\ "'filename.cimg,N0,N1,x0,y0,z0,c0,x1,y1,z1,c1'. "\ "Specifying '-1' for one coordinates stands for the maximum possible value. Output expression "\ "'filename.cimg[z][,datatype]' can be used to force the output pixel type. 'datatype' can be "\ "{ auto | bool | uint8 | int8 | uint16 | int16 | uint32 | int32 | uint64 | int64 | float32 | float64 }. "\n\ " - `.raw` __binary files:__ Image dimensions and input pixel type may be specified when loading `.raw` "\ "files with input expression 'filename.raw[,datatype][,width][,height[,depth[,dim[,offset]]]]]'. If no dimensions are "\ "specified, the resulting image is a one-column vector with maximum possible height. Pixel "\ "type can also be specified with the output expression 'filename.raw[,datatype]'. "\ "'datatype' can be the same as for `.cimg[z]` files. "\n\ " - `.yuv` __files:__ Image dimensions must be specified when loading, and only sub-frames of an image "\ "sequence may be loaded, using the input expression "\ "'filename.yuv,width,height[,chroma_subsampling[,first_frame[,last_frame[,step]]]'. "\ "'chroma_subsampling' can be { 420 | 422 | 444 }. "\ "When saving, chroma subsampling mode can be specified with output expression "\ "'filename.yuv[,chroma_subsampling]'. "\n\ " - `.tiff` __files:__ Only sub-images of multi-pages tiff files can be loaded, using the input "\ "expression 'filename.tif,_first_frame,_last_frame,_step'. "\ "Output expression 'filename.tiff,_datatype,_compression,_force_multipage,_use_bigtiff' can be used "\ "to specify the output pixel type, as well as the compression method. "\ "'datatype' can be the same as for `.cimg[z]` files. 'compression' can be "\ " { none (default) | lzw | jpeg }. 'force_multipage' can be { 0:no (default) | 1:yes }. "\ "'use_bigtiff' can be { 0:no | 1:yes (default) }."\n\ " - `.pdf` __files:__ When loading a file, the rendering resolution can be specified using the input expression "\ "'filename.pdf,resolution', where 'resolution' is an unsigned integer value."\n\ " - `.gif` __files:__ Animated gif files can be saved, using the input expression "\ "'filename.gif,fps>0,nb_loops'. Specify 'nb_loops=0' to get an infinite number of animation "\ "loops (this is the default behavior)."\n\ " - `.jpeg` and `.webp` __files:__ The output quality may be specified (in %), using the output expression "\ "'filename.jpg,30' (here, to get a 30% quality output). '100' is the default."\n\ " - `.mnc` __files:__ The output header can set from another file, using the output expression "\ "'filename.mnc,header_template.mnc'. "\n\ " - `.pan`, `.cpp`, `.hpp`, `.c` and `.h` __files:__ The output datatype can be selected with output expression "\ "'filename[,datatype]'. 'datatype' can be the same as for `.cimg[z]` files."\n\ " - `.gmic` __files:__ These filenames are assumed to be G'MIC custom commands files. Loading such a "\ "file will add the commands it defines to the interpreter. Debug information can be "\ "enabled/disabled by the input expression 'filename.gmic[,add_debug_info]' where 'debug_info' can be "\ "{ 0:false | 1:true }. "\n\ " - Inserting 'ext:' on the beginning of a filename (e.g. 'jpg:filename') forces G'MIC to "\ "read/write the file as it would have been done if it had the specified extension `.ext`."\n\ \n\ "* Some input/output formats and options may not be supported, depending on the configuration "\ "flags that have been set during the build of the G'MIC software." _section "Substitution Rules" _text \ "* G'MIC items containing '$' or '{}' are substituted before being interpreted. Use these "\ "substituting expressions to access various data from the interpreter environment."\n\ "* '$name' and '${name}' are both substituted by the value of the specified named variable "\ "(set previously by the item 'name=value'). If this variable has not been already set, the "\ "expression is substituted by the highest positive index of the named image '[name]'. If no "\ "image has this name, the expression is substituted by the value of the OS environment variable "\ "with same name (it may be thus an empty string if it is not defined). "\n\ "* The following reserved variables are predefined by the G'MIC interpreter: "\n\ " - '$!': The current number of images in the list."\n\ " - '$>' and '$<': The increasing/decreasing index of the latest (currently running) "\ "'repeat...done' loop. '$>' goes from `0` (first loop iteration) to `nb_iterations - 1` (last iteration). "\ "'$<' does the opposite. "\n\ " - '$/': The current call stack. Stack items are separated by slashes '/'."\n\ " - '$|': The current value (expressed in seconds) of a millisecond precision timer."\n\ " - '$^': The current verbosity level."\n\ " - '$_cpus': The number of computation cores available on your machine."\n\ " - '$_flags': The list of enabled flags when G'MIC interpreter has been compiled."\n\ " - '$_host': A string telling about the host running the G'MIC interpreter (e.g. `cli` or `gimp`)."\n\ " - '$_os': A string describing the running operating system."\n\ " - '$_path_rc': The path to the G'MIC folder used to store configuration files (its value is OS-dependent)."\n\ " - '$_path_user': The path to the G'MIC user file `.gmic` or `user.gmic` (its value is OS-dependent)."\n\ " - '$_path_commands': A list of all imported command files (stored as an image list)."\n\ " - '$_pid': The current process identifier, as an integer."\n\ " - '$_pixeltype': The type of image pixels (default: 'float32')."\n\ " - '$_prerelease': For pre-releases, the date of the pre-release as `yymmdd`. "\ "For stable releases, this variable is set to `0`."\n\ " - '$_version': A 3-digits number telling about the current version of the G'MIC interpreter "\ " (e.g. '"$_version"'). "\n\ " - '$_vt100': Set to `1` if colored text output is allowed on the console. Otherwise, set to `0`."\n\ \n\ "* '$$name' and '$${name}' are both substituted by the G'MIC script code of the specified named "\ "`custom command`, or by an empty string if no custom command with specified name exists. "\n\ "* '${\"-pipeline\"}' is substituted by the __status value__ after the execution of the specified "\ "G'MIC pipeline (see command ''status''). Expression '${}' thus stands for the current status value."\n\ "* '{``string}' (starting with two backquotes) is substituted by a double-quoted version of the specified string."\n\ "* '{/string}' is substituted by the escaped version of the specified string."\n\ "* '{\\'string\\'[:delimiter]}' (between single quotes) is substituted by the sequence of character codes "\ "that composes the specified string, separated by specified delimiter. Possible delimiters are "\ "',' (default), ';', '/', '^' or ' '. For instance, item '{'foo'}' is substituted by '102,111,111' and "\ "'{'foo':;}' by '102;111;111'."\n\ "* '{image,feature[:delimiter]}' is substituted by a specific feature of the image '[image]'. "\ "'image' can be either an image number or an image name. It can be also eluded, in which case, "\ "the last image '[-1]' of the list is considered for the requested feature. "\ "Specified 'feature' can be one of:"\n\ " - 'b': The image basename (i.e. filename without the folder path nor extension)."\n\ " - 'f': The image folder name."\n\ " - 'n': The image name or filename (if the image has been read from a file)."\n\ " - 't': The text string from the image values regarded as character codes."\n\ " - 'x': The image extension (i.e the characters after the last `.` in the image name)."\n\ " - '^': The sequence of all image values, separated by commas `,`."\n\ " - '@subset': The sequence of image values corresponding to the specified subset, and separated by commas `,`. "\n\ " - Any other 'feature' is considered as a __mathematical expression__ associated to the image '[image]' and is "\ "substituted by the result of its evaluation (float value). For instance, expression '{0,w+h}' is substituted by "\ "the sum of the width and height of the first image (see section ''Mathematical Expressions'' for more details). "\ "If a mathematical expression starts with an underscore `_`, the resulting value is truncated to a readable format. "\ "For instance, item '{_pi}' is substituted by '3.14159' (while '{pi}' is substituted by '3.141592653589793')."\n\ " - A 'feature' delimited by backquotes is replaced by a string whose character codes correspond to the list of "\ "values resulting from the evaluation of the specified mathematical expression. For instance, item "\ "'{`[102,111,111]`}' is substituted by 'foo' and item '{`vector8(65)`}' by 'AAAAAAAA'."\n\ \n\ "* '{*}' is substituted by the visibility state of the instant display window '#0' "\ "(can be { 0:closed | 1:visible }."\n\ "* '{*[index],feature1,...,featureN[:delimiter]}' is substituted by a specific set of features of the instant display "\ "window '#0' (or '#index', if specified). Requested 'features' can be:"\n\ " - 'u': screen width (actually independent on the window size)."\n\ " - 'v': screen height (actually independent on the window size)."\n\ " - 'uv': screen width*screen height."\n\ " - 'd': window width (i.e. width of the window widget)."\n\ " - 'e': window height (i.e. height of the window widget)."\n\ " - 'de': window width*window height."\n\ " - 'w': display width (i.e. width of the display area managed by the window)."\n\ " - 'h': display height (i.e. height of the display area managed by the window)."\n\ " - 'wh': display width*display height."\n\ " - 'i': X-coordinate of the display window."\n\ " - 'j': Y-coordinate of the display window."\n\ " - 'f': current fullscreen state of the instant display."\n\ " - 'n': current normalization type of the instant display."\n\ " - 't': window title of the instant display."\n\ " - 'x': X-coordinate of the mouse position (or -1, if outside the display area)."\n\ " - 'y': Y-coordinate of the mouse position (or -1, if outside the display area)."\n\ " - 'b': state of the mouse buttons { 1:left-but. | 2:right-but. | 4:middle-but. }."\n\ " - 'o': state of the mouse wheel."\n\ " - 'k': decimal code of the pressed key if any, 0 otherwise."\n\ " - 'c': boolean (0 or 1) telling if the instant display has been closed recently."\n\ " - 'r': boolean telling if the instant display has been resized recently."\n\ " - 'm': boolean telling if the instant display has been moved recently."\n\ " - Any other 'feature' stands for a keycode name (in capital letters), and is substituted "\ "by a boolean describing the current key state { 0:pressed | 1:released }."\n\ " - You can also prepend a hyphen '-' to a 'feature' (that supports it) to flush the "\ "corresponding event immediately after reading its state (works for keys, mouse and window events)."\n\ \n\ "* Item substitution is __never__ performed in items between double quotes. One must break the quotes "\ "to enable substitution if needed, as in '\"3+8 kg = \"{3+8}\" kg\"'. Using double quotes is then "\ "a convenient way to disable the substitutions mechanism in items, when necessary."\n\ "* One can also disable the substitution mechanism on items outside double quotes, by escaping the "\ "`{`, `}` or `$` characters, as in `\\{3+4\\}\\ doesn\47t\\ evaluate`." _section "Mathematical Expressions" _text \ "* G'MIC has an embedded __mathematical parser__, used to evaluate (possibly complex) math expressions "\ "specified inside braces '{}', or formulas in commands that may take one as an argument (e.g. ''fill'' or ''eval'')."\n\ "* When the context allows it, a formula is evaluated __for each pixel__ of the selected images "\ "(e.g. ''fill'' or ''eval'')."\n\ "* A math expression may return or take as an argument a __scalar__ or a __vector-valued__ result "\ "(with a fixed number of components)."\n\ "The mathematical parser understands the following set of functions, operators and variables:"\n\ "## Usual math operators:"\n\ "'||' (logical or), '&&' (logical and), '|' (bitwise or), '&' (bitwise and), "\ "'!=', '==', '<=', '>=', '<', '>', '<<' (left bitwise shift), '>>' (right bitwise shift), '-', '+', '*', '/', "\ "'%' (modulo), '^' (power), '!' (logical not), '~' (bitwise not), '++', '--', '+=', '-=', '*=', '/=', '%=', "\ "'&=', '|=', '^=', '>>', '<<=' (in-place operators)."\n\ "## Usual math functions:"\n\ "'abs()', 'acos()', 'acosh()', 'arg()', 'arg0()', 'argkth()', 'argmax()', 'argmaxabs()', "\ "'argmin()', 'argminabs()', 'asin()', 'asinh()', 'atan()', 'atan2()', 'atanh()', 'avg()', 'bool()', 'cbrt()', "\ "'ceil()', 'cos()', 'cosh()', 'cut()', 'deg2rad()', 'erf()', 'erfinv()', 'exp()', 'fact()', 'fibo()', 'floor()', "\ "'gamma()', 'gauss()', 'gcd()', 'hypot()', 'int()', 'isconst()', 'isnan()', 'isnum()', 'isinf()', 'isint()', "\ "'isbool()', 'isexpr()', 'isfile()', 'isdir()', 'isin()', 'kth()', 'lcm()', 'log()', 'log2()', 'log10()', 'max()', "\ "'maxabs()', 'med()', 'min()', 'minabs()', 'narg()', 'prod()', 'rad2deg()', 'rol()' (left bit rotation), 'ror()' "\ "(right bit rotation), 'round()', 'sign()', 'sin()', 'sinc()', 'sinh()', 'sqrt()', 'std()', 'srand(_seed)', 'sum()', "\ "'tan()', 'tanh()', 'var()', 'xor()'."\n\ \n\ "* 'cov(A,B,_avgA,_avgB)' estimates the covariance between vectors 'A' and 'B' (estimated averages of these vectors "\ "may be specified as arguments)."\n\ "* 'mse(A,B)' returns the mean-squared error between vectors 'A' and 'B'."\n\ "* 'atan2(y,x)' is the version of 'atan()' with two arguments 'y' and 'x' (as in C/C++)."\n\ "* 'perm(k,n,_with_order)' computes the number of permutations of 'k' objects from a set of 'n' objects."\n\ "* 'gauss(x,_sigma,_is_normalized)' returns `exp(-x^2/(2*s^2))/(is_normalized?sqrt(2*pi*sigma^2):1)`."\n\ "* 'cut(value,min,max)' returns 'value' if it is in range '[min,max]', or 'min' or 'max' otherwise."\n\ "* 'narg(a_1,...,a_N)' returns the number of specified arguments (here, 'N')."\n\ "* 'arg(i,a_1,..,a_N)' returns the `i`-th argument 'a_i'."\n\ "* 'isnum()', 'isnan()', 'isinf()', 'isint()', 'isbool()' test the type of the given number or expression, "\ "and return '0' (false) or '1' (true)."\n\ "* 'isfile(\\'path\\')' (resp. 'isdir('path')') returns '0' (false) or '1' (true) whether its string argument is a "\ "path to an existing file (resp. to a directory) or not."\n\ "* 'ispercentage(arg)' returns `1` (true) or `0` (false) whether 'arg' ends with a '%' or not."\n\ "* 'isvarname(\\'str\\')' returns '0' (false) or '1' (true) whether its string argument would be a valid to name "\ "a variable or not."\n\ "* 'isin(v,a_1,...,a_n)' returns '0' (false) or '1' (true) whether the first argument 'v' appears in the set of other "\ "argument 'a_i'."\n\ "* 'isint(x,_xmin,_xmax)' returns '1' (true), if 'x' is an integer in range '[xmin,xmax]', otherwise '0' (false)."\n\ "* 'inrange(value,m,M,include_m,include_M)' returns '0' (false) or '1' (true) whether the specified value lies in "\ "range '[m,M]' or not ('include_m' and 'includeM' tells how boundaries 'm' and 'M' are considered)."\n\ "* 'argkth()', 'argmin()', 'argmax()', 'argminabs()', 'argmaxabs()'', 'avg()', 'kth()', 'min()', 'max()', 'minabs()', "\ "'maxabs()', 'med()', 'prod()', 'std()', 'sum()' and 'var()' can be called with an arbitrary number of scalar/vector "\ "arguments."\n\ "* 'vargkth()', 'vargmin()', 'vargmax()', 'vargminabs()', 'vargmaxabs()', 'vavg()', 'vkth()', 'vmin()', "\ "'vmax()', 'vminabs()', 'vmaxabs()', 'vmed()', 'vprod()', 'vstd()', 'vsum()' and 'vvar()' are the versions of the "\ "previous function with vector-valued arguments."\n\ "* 'round(value,rounding_value,direction)' returns a rounded value. 'direction' can be "\ "{ -1:to-lowest | 0:to-nearest | 1:to-highest }."\n\ "* 'softmax(V,_temperature)' returns the softmax of specified vector 'V'. Default value for 'temperature' is 1."\n\ "* 'lerp(a,b,t)' returns 'a*(1-t)+b*t'."\n\ "* 'swap(a,b)' swaps the values of the given arguments."\n\ "## Predefined variable names:"\n\ "Variable names below are pre-defined. They can be overridden though."\n\ "* 'l': length of the associated list of images."\n\ "* 'k': index of the associated image, in '[0,l-1]'."\n\ "* 'w': width of the associated image, if any ('0' otherwise)."\n\ "* 'h': height of the associated image, if any ('0' otherwise)."\n\ "* 'd': depth of the associated image, if any ('0' otherwise)."\n\ "* 's': spectrum of the associated image, if any ('0' otherwise)."\n\ "* 'r': shared state of the associated image, if any ('0' otherwise)."\n\ "* 'wh': shortcut for 'width*height'."\n\ "* 'whd': shortcut for 'width*height*depth'."\n\ "* 'whds': shortcut for 'width*height*depth*spectrum' (i.e. number of image values)."\n\ "* 'im', 'iM', 'ia', 'iv', 'id', 'is', 'ip', 'ic', 'in': Respectively the minimum, maximum, average, variance, "\ "standard deviation, sum, product, median value and L2-norm of the associated image, if any ('0' otherwise)."\n\ "* 'xm', 'ym', 'zm', 'cm': The pixel coordinates of the minimum value in the associated image, "\ "if any ('0' otherwise)."\n\ "* 'xM', 'yM', 'zM', 'cM': The pixel coordinates of the maximum value in the associated image, "\ "if any ('0' otherwise)."\n\ "* All these variables are considered as __constant values__ by the math parser (for optimization purposes) "\ "which is indeed the case most of the time. Anyway, this might not be the case, if function 'resize(#ind,..)' "\ "is used in the math expression. If so, it is safer to invoke functions 'l()', 'w(_#ind)', 'h(_#ind)', ... 's(_#ind)' "\ "and 'in(_#ind)' instead of the corresponding named variables."\n\ "* 'i': current processed pixel value (i.e. value located at `(x,y,z,c)`) in the associated image, "\ "if any ('0' otherwise)."\n\ "* 'iN': N-th channel value of current processed pixel (i.e. value located at `(x,y,z,N)` in the associated image, "\ "if any ('0' otherwise). 'N' must be an integer in range '[0,9]'."\n\ "* 'R', 'G', 'B' and 'A' are equivalent to 'i0', 'i1', 'i2' and 'i3' respectively."\n\ "* 'I': current vector-valued processed pixel in the associated image, if any ('0' otherwise). "\ "The number of vector components is equal to the number of image channels (e.g. 'I' = `[ R,G,B ]` for a "\ "`RGB` image)."\n\ "* You may add '#ind' to any of the variable name above to retrieve the information for any "\ "numbered image '[ind]' of the list (when this makes sense). For instance 'ia#0' denotes the average value of the "\ "first image of the list)."\n\ "* 'x': current processed column of the associated image, if any ('0' otherwise)."\n\ "* 'y': current processed row of the associated image, if any ('0' otherwise)."\n\ "* 'z': current processed slice of the associated image, if any ('0' otherwise)."\n\ "* 'c': current processed channel of the associated image, if any ('0' otherwise)."\n\ "* 't': thread id when an expression is evaluated with multiple threads ('0' means __master thread__)."\n\ "* 'n': maximum number of threads when expression is evaluated in parallel (so that 't' goes from '0' to 'n-1')."\n\ "* 'e': value of e, i.e. `2.71828...`."\n\ "* 'pi': value of pi, i.e. `3.1415926...`."\n\ "* 'eps': value of machine epsilon, that is the difference between 1.0 and the next value representable by a double."\n\ "* 'u': a random value between '[0,1]', following a uniform distribution."\n\ "* 'v': a random integer that is either '0' or '1', following a uniform distribution."\n\ "* 'g': a random value, following a gaussian distribution of variance 1 (roughly in '[-6,6]')."\n\ "* 'interpolation': value of the default interpolation mode used when reading pixel values with the pixel access "\ "operators (i.e. when the interpolation argument is not explicitly specified, see below for more details on pixel "\ "access operators). Its initial default value is '0'."\n\ "* 'boundary': value of the default boundary conditions used when reading pixel values with the pixel access "\ "operators (i.e. when the boundary condition argument is not explicitly specified, see below for more details "\ "on pixel access operators). Its initial default value is '0'."\n\ "* The last image of the list is always associated to the evaluations of 'expressions', e.g. G'MIC sequence "\ "\n~~~\n256,128 fill {w}\n~~~\n will create a 256x128 image filled with value 256."\n\ "## Vector-valued functions and operators:"\n\ "The math evaluator is able to work with vector-valued elements. A math function applied on a vector-valued "\ "argument usually returns a vector with same dimension, where each element of the input vector "\ "has been passed to the specified function (e.g. 'abs([-1,2,-3])' returns '[1,2,3]')."\n\n\ "There are specific functions and operators to define or compute vector-valued elements though :"\n\ "* '[a0,a1,...,aN-1]' defines a 'N'-dimensional vector with scalar coefficients 'ak'."\n\ "* 'vectorN(a0,a1,,...,aN-1)' does the same, with the 'ak' being repeated periodically if only a few are specified."\n\ "* 'vector(#N,a0,a1,,...,aN-1)' does the same, and can be used for any constant expression 'N'."\n\ "* In previous expressions, the 'ak' can be vectors themselves, to be concatenated into a single vector."\n\ "* The scalar element 'ak' of a vector 'X' is retrieved by 'X[k]'."\n\ "* The sub-vector '[X[p],X[p+s]...X[p+s*(q-1)]]' (of size 'q') of a vector 'X' is retrieved by 'X[p,q,s]'."\n\ "* Equality/inequality comparisons between two vectors is done with operators '==' and '!='."\n\ "* Some vector-specific functions can be used on vector values: "\ "'cross(X,Y)' (cross product), 'dot(X,Y)' (dot product), 'size(X)' (vector dimension), "\ "'sort(X,_is_increasing,_nb_elts,_size_elt,_sort_index)' (sorted values), 'reverse(A)' (reverse order of components), "\ "'map(X,P,_nb_channelsX,_nb_channelsP,_boundary_conditions)', "\ "'shift(A,_length,_boundary_conditions)' and 'same(A,B,_nb_vals,_is_case_sensitive)' (vector equality test)."\n\ "* Function 'normP(u1,...,un)' computes the LP-norm of the specified vector "\ "('P' being a constant or 'inf', as in e.g. 'norm1()')."\n\ "* Function 'normp(V,_p)' computes the Lp-norm of the specified vector 'V'. Here, 'p' can be variable. "\ "Default value for 'p' is 2."\n\ "* Function 'unitnorm(V,_p)' returns a normalized version 'V/normp(V)' of specified vector 'V'. "\ "Default value for 'p' is 2."\n\ "* Function 'resize(A,size,_interpolation,_boundary_conditions)' returns a resized version of a vector 'A' with "\ "specified interpolation mode. 'interpolation' can be "\ "{ -1:none (memory content) | 0:none | 1:nearest | 2:average | 3:linear | 4:grid | 5:bicubic | 6:lanczos }, and "\ "'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }."\n\ "* Function 'find(A,B,_starting_index,_search_step)' returns the index where sub-vector 'B' appears in vector 'A', "\ "(or '-1' if 'B' is not contained in 'A'). Argument 'A' can be also replaced by an image index '#ind'."\n\ "* Specifying a vector-valued math expression as an argument of a command that operates on image values "\ "(e.g. 'fill') modifies the whole spectrum range of the processed image(s), for each spatial coordinates `(x,y,z)`. "\ "The command does not loop over the `c`-axis in this case."\n\ "## Complex-valued functions:"\n\ "A `2`-dimensional vector may be seen as a complex number and used in those particular functions/operators: "\ "'**' (complex multiplication), '//' (complex division), '^^' (complex exponentiation), "\ "'**=' (complex self-multiplication), '//=' (complex self-division), '^^=' (complex self-exponentiation), "\ "'cabs()' (complex modulus), 'carg()' (complex argument), 'cconj()' (complex conjugate), "\ "'cexp()' (complex exponential), 'clog()' (complex logarithm), 'ccos()' (complex cosine), "\ "'csin()' (complex sine), 'csqr()' (complex square), 'csqrt()' (complex square root), 'ctan()' (complex tangent), "\ "'ccosh()' (complex hyperpolic cosine), 'csinh()' (complex hyperbolic sine) and "\ "'ctanh()' (complex hyperbolic tangent)."\n\ "## Matrix-valued functions:"\n\ "A `MN`-dimensional vector may be seen as a `M` x `N` matrix and used in those particular functions/operators: "\ "'*' (matrix-vector multiplication), 'det(A)' (determinant), 'diag(V)' (diagonal matrix from a vector), "\ "'eig(A)' (eigenvalues/eigenvectors), 'eye(n)' (n x n identity matrix), 'invert(A,_nb_colsA,_use_LU,_lambda)' "\ "(matrix inverse), 'mul(A,B,_nb_colsB)' (matrix-matrix multiplication), "\ "'rot(u,v,w,angle)' (3D rotation matrix), 'rot(angle)' (2D rotation matrix), "\ "'solve(A,B,_nb_colsB,_use_LU)' (solver of linear system A.X = B), 'svd(A,_nb_colsA)' (singular value decomposition), "\ "'trace(A)' (matrix trace) and 'transpose(A,nb_colsA)' (matrix transpose). Argument 'nb_colsB' may be omitted if "\ "it is equal to `1`".\n\ "## Image-valued functions:"\n\ "Some functions takes vector-valued arguments that represent image data :"\n\ "* Function 'expr(formula,_w,_h,_d,_s)' outputs a vector of size 'w*h*d*s' with values generated from "\ "the specified formula, as if one were filling an image with dimensions '(w,h,d,s)'."\n\ "* Function 'resize(A,wA,hA,dA,sA,nwA,_nhA,_ndA,_nsA,_interpolation,_boundary_conditions,_ax,_ay,_az,_ac)' is an "\ "extended version of the 'resize()' function. It allows to resize the vector 'A', seen as an image of size "\ "'(ow,oh,od,os)' as a new image of size '(nw,nh,nd,ns)', with specified resizing options."\n\ "* Function 'warp(A,wA,hA,dA,sA,B,wB,hB,dB,sB,_mode,_interpolation,_boundary_conditions)' returns the warped version "\ "of the image 'A' (of size '(wA,hA,dA,sA)', viewed as a vector of size 'wA*hA*dA*sA') by the warping field 'B' "\ "(of size '(wB,hB,dB,sB)'). The resulting image has size '(wB,hB,dB,sA)'. "\ "This is the math evaluator analog to command ''warp''."\n\ "* Function 'index(A,P,nb_channelsP,_dithering,_map_colors)' returns the indexed version "\ "of the image 'A' by the colormap 'P'. "\ "This is the math evaluator analog to command ''index''."\n\ "* Function 'permute(A,wA,hA,dA,sA,permutation_string)' returns a permuted version of the image 'A' "\ "(of size '(wA,hA,dA,sA)', viewed as a vector of size 'wA*hA*dA*sA'). "\ "This is the math evaluator analog to command ''permute''."\n\ "* Function 'mirror(A,wA,hA,dA,sA,axes_string)' returns a mirrored version of the image 'A' "\ "(of size '(wA,hA,dA,sA)', viewed as a vector of size 'wA*hA*dA*sA'). "\ "This is the math evaluator analog to command ''mirror''."\n\ "* Function 'cumulate(A,wA,hA,dA,sA,_axes_string)' returns a cumulated version of the image 'A' "\ "(of size '(wA,hA,dA,sA)', viewed as a vector of size 'wA*hA*dA*sA'). "\ "This is the math evaluator analog to command ''cumulate''."\n\ "* Function 'histogram(A,nb_levels,_min_value,_max_value)' returns the histogram of the vector 'A'. "\ "This is the math evaluator analog to command ''histogram''."\n\ "* Function 'equalize(A,nb_levels,_min_value,_max_value)' returns the equalized version of the vector 'A'. "\ "This is the math evaluator analog to command ''equalize''."\n\ "* Function 'normalize(A,_min_value,_max_value)' returns the normalized version of the vector 'A'. "\ "This is the math evaluator analog to command ''normalize''."\n\ "* 'mproj(S,nb_colsS,D,nb_colsD,method,max_iter,max_residual)' projects a matrix 'S' onto a dictionary (matrix) 'D'. "\ "This is the math evaluator analog to command ''mproj''."\n\ "* Function 'noise(A,amplitude,_noise_type)' returns the noisy version of the vector 'A'. "\ "This is the math evaluator analog to command ''noise''."\n\ "* Function 'rand(#size,_min_value,_max_value,_pdf,_precision)' returns the a vector of 'size' random values. "\ "This is the math evaluator analog to command ''rand''."\n\ "## String manipulation:"\n\ "Character strings are defined as vectors objects and can be then managed as is. "\ "Dedicated functions and initializers to manage strings exist:"\n\ "* `['string']` and `'string'` define a vector whose values are the character codes of the "\ "specified `character string` (e.g. `'foo'` is equal to `[ 102,111,111 ]`)."\n\ "* `_'character'` returns the (scalar) byte code of the specified character (e.g. `_'A'` is equal to '65')."\n\ "* A special case happens for __empty__ strings: Values of both expressions `['']` and `''` are '0'."\n\ "* Functions 'lowercase()' and 'uppercase()' return string with all string characters lowercased or uppercased."\n\ "* Function 's2v(str,_starting_index,_is_strict)' parses specified string 'str' and returns the value contained "\ "in it."\n\ "* Function 'v2s(expr,_nb_digits,_siz)' returns a vector of size 'siz' which contains the character representation "\ "of values described by expression 'expr'. "\ "'nb_digits' can be { <-1:0-padding of integers | -1:auto-reduced | 0:all | >0:max number of digits }."\n\ "* Function 'echo(str1,str2,...,strN)' prints the concatenation of given string arguments on the console."\n\ "* Function 'string(_#siz,str1,str2,...,strN)' generates a vector corresponding to the concatenation of given "\ "string/number arguments."\n\ "## Dynamic arrays:"\n\ "A dynamic array is defined as a one-column (or empty) image '[ind]' in the image list. "\ "It allows elements to be added or removed, each element having the same dimension "\ "(which is actually the number of channels of image '[ind]'). "\ "Dynamic arrays adapt their size to the number of elements they contain."\n\n\ "A dynamic array can be manipulated in a math expression, with the following functions:"\n\ "* 'da_size(_#ind)': Return the number of elements in dynamic array '[ind]'."\n\ "* 'da_back(_#ind)': Return the last element of the dynamic array '[ind]'."\n\ "* 'da_insert(_#ind,pos,elt_1,_elt_2,...,_elt_N)': Insert 'N' new elements 'elt_k' starting from index 'pos' "\ "in dynamic array '[ind]'."\n\ "* 'da_push(_#ind,elt1,_elt2,...,_eltN)': Insert 'N' new elements 'elt_k' at the end of dynamic array '[ind]'."\n\ "* 'da_pop(_#ind)': Same as 'da_back()' but also remove last element from the dynamic array '[ind]'."\n\ "* 'da_push_heap(_#ind,elt1,_elt2,...,_eltN)' and 'da_pop_heap(_#ind)' does the same but for a dynamic array viewed "\ "as a min-heap structure."\n\ "* 'da_remove(_#ind,_start,_end)': Remove elements located between indices 'start' and 'end' (included) "\ "in dynamic array '[ind]'."\n\ "* 'da_freeze(_#ind)': Convert a dynamic array into a 1-column image with height 'da_size(#ind)'."\n\ "* The value of the k-th element of dynamic array '[ind]' is retrieved with 'i[_#ind,k]' (if the element is a "\ "scalar value), or 'I[_#ind,k]' (if the element is a vector)."\n\n\ "In the functions above, argument '#ind' may be omitted in which case it is assumed to be '#-1'."\n\ "## Special operators:"\n\ "* ';': expression separator. The returned value is always the last encountered expression. "\ "For instance expression '1;2;pi' is evaluated as 'pi'."\n\ "* '=': variable assignment. Variables in mathematical parser can only refer to numerical "\ "values (vectors or scalars). Variable names are case-sensitive. Use this operator in conjunction with ';' to define "\ "more complex evaluable expressions, such as \n~~~\nt = cos(x); 3*t^2 + 2*t + 1\n~~~\n"\ "These variables remain __local__ to the mathematical parser and cannot be accessed outside the evaluated "\ "expression."\n\ "* Variables defined in math parser may have a __constant__ property, by specifying keyword 'const' before the "\ "variable name (e.g. 'const foo = pi/4;'). The value set to such a variable must be indeed a __constant scalar__. "\ "Constant variables allows certain types of optimizations in the math JIT compiler."\n\ \ "## Specific functions:"\n\ "* 'addr(expr)': return the pointer address to the specified expression 'expr'. "\n\ "* 'o2c(_#ind,offset)' and 'c2o(_#ind,x,_y,_z,_c)': Convert image offset to image coordinates and vice-versa. "\n\ "* 'fill(target,expr)' or 'fill(target,index_name,expr)' fill the content of the specified target "\ "(often vector-valued) using a given expression, e.g. `V = vector16(); fill(V,k,k^2 + k + 1);`. "\ "For a vector-valued target, it is basically equivalent to: "\ "`for (index_name = 0, index_name=0' (e.g., '46368' for 'N=24'). 'do(expression,condition)' always evaluates the specified "\ "expression at least once, then check for the loop condition. When done, it returns the last value of 'expression'."\n\ "* 'for(init,condition,_procedure,body)' first evaluates the expression 'init', then iteratively evaluates 'body' "\ "(followed by 'procedure' if specified) while 'condition' holds (i.e. not zero). It may happen that no iterations are "\ "done, in which case the function returns 'nan'. Otherwise, it returns the last value of 'body'. "\ "For instance, the expression: \n~~~\nif(N<2,N,for(n=N;F0=0;F1=1,n=n-1,F2=F0+F1;F0=F1;F1=F2))\n~~~\n "\ "returns the 'N'-th value of the Fibonacci sequence, for 'N>=0' (e.g., '46368' for 'N=24')."\n\ "* 'while(condition,expression)' is exactly the same as 'for(init,condition,expression)' without the specification of "\ "an initializing expression."\n\ "* 'repeat(nb_iters,expr)' or 'fill(nb_iters,iter_name,expr)' run 'nb_iters' iterations of the specified expression "\ "'expr', e.g. `V = vector16(); repeat(16,k,V[k] = k^2 + k + 1);`. "\ "It is basically equivalent to: "\ "`for (iter_name = 0, iter_namebegin(foo = 0); ++foo\"')."\n\ "* 'copy(dest,src,_nb_elts,_inc_d,_inc_s,_opacity)' copies an entire memory block of 'nb_elts' elements starting "\ "from a source value 'src' to a specified destination 'dest', with increments defined by 'inc_d' and 'inc_s' "\ "respectively for the destination and source pointers."\n\ "* 'stats(_#ind)' returns the statistics vector of the running image '[ind]', i.e the vector "\ "`[ im,iM,ia,iv,xm,ym,zm,cm,xM,yM,zM,cM,is,ip ]` (14 values)."\n\ "* 'ref(expr,a)' references specified expression 'expr' as variable name 'a'."\n\ "* 'unref(a,b,...)' destroys references to the named variable given as arguments."\n\ "* 'breakpoint()' inserts a possible computation breakpoint (useless with the cli interface)."\n\ "* '_(comment) expr' just returns expression 'expr' (useful for inserting inline comments in math expressions)."\n\ "* 'run('pipeline')' executes the specified G'MIC pipeline as if it was called outside the currently evaluated "\ "expression."\n\ "* 'set(\\'variable_name\\',A)' set the G'MIC variable '$variable_name' with the value of expression 'A'. If 'A' is"\ " a vector-valued variable, it is assumed to encode a string."\n\ "* 'store(\\'variable_name\\',A,_w,_h,_d,_s,_is_compressed)' transfers the data of vector 'A' as a "\ "`(w,h,d,s)` image to the G'MIC variable '$variable_name'. Thus, the data becomes available outside the math "\ "expression (that is equivalent to using the regular command ''store'', but directly in the math expression)."\n\ "* 'get(\\'variable_name\\',_size,_return_as_string)' returns the value of the specified variable, as a vector of "\ "'size' values, or as a scalar (if 'size' is zero or not specified)."\n\ "* 'name(_#ind,size)' returns a vector of size 'size', whose values are the characters codes of the name of image "\ "'[ind]' (or default image selected if 'ind' is not specified)."\n\ "* 'correlate(I,wI,hI,dI,sI,K,wK,hK,dK,sK,_boundary_conditions,_is_normalized,_channel_mode,_xcenter,_ycenter,"\ "_zcenter,_xstart,_ystart,_zstart,_xend,_yend,_zend,_xstride,_ystride,_zstride,_xdilation,_ydilation,_zdilation,"\ "_interpolation_type)' returns the correlation, unrolled as a vector, of the `(wI,hI,dI,sI)`-sized image 'I' "\ "with the `(wK,hK,dK,sK)`-sized kernel 'K' (the meaning of the other arguments are the same as in command "\ "'correlate'). Similar function 'convolve(...)' is also defined for computing the convolution between 'I' and 'K'."\n\ \ "## User-defined macros:"\n\ "* Custom macro functions can be defined in a math expression, using the assignment operator "\ "'=', e.g. \n~~~\nfoo(x,y) = cos(x + y); result = foo(1,2) + foo(2,3)\n~~~\n"\n\ "* Trying to override a built-in function (e.g. 'abs()') has no effect."\n\ "* Overloading macros with different number of arguments is possible. Re-defining a previously defined macro with "\ "the same number of arguments discards its previous definition."\n\ "* Macro functions are indeed processed as __macros__ by the mathematical evaluator. You should avoid invoking them "\ "with arguments that are themselves results of assignments or self-operations. "\ "For instance, \n~~~\nfoo(x) = x + x; z = 0; foo(++z)\n~~~\n returns '4' rather than expected value '2'."\n\ "* When substituted, macro arguments are placed inside parentheses, except if a number sign "\ "'#' is located just before or after the argument name. For instance, expression \n"\ "~~~\nfoo(x,y) = x*y; foo(1+2,3)\n~~~\n "\ "returns '9' (being substituted as '(1+2)*(3)'), while expression \n~~~\nfoo(x,y) = x#*y#; foo(1+2,3)\n~~~\n "\ "returns '7' (being substituted as '1+2*3')."\n\ "* Number signs appearing between macro arguments function actually count for __empty__ separators. They may be used "\ "to force the substitution of macro arguments in unusual places, e.g. as in \n~~~\nstr(N) = ['I like N#'];\n~~~\n"\ "* Macros with variadic arguments can be defined, by specifying a single argument name followed by `...`. "\ "For instance,\n~~~\nfoo(args...) = sum([ args ]^2);\n~~~\n "\ "defines a macro that returns the sum of its squared arguments, so `foo(1,2,3)` returns `14` and "\ "`foo(4,5)` returns `41`.\n"\ \ "## Multi-threaded and in-place evaluation:"\n\ "* If your image data are large enough and you have several CPUs available, it is likely that the math expression "\ "passed to a 'fill', 'eval' or 'input' commands is evaluated in parallel, using multiple computation threads."\n\ "* Starting an expression with ':' or '*' forces the evaluations required for an image to be run in parallel, "\ "even if the amount of data to process is small (beware, it may be slower to evaluate in this case!). "\ "Specify ':' (rather than '*') to avoid possible image copy done before evaluating the expression "\ "(this saves memory, but do this only if you are sure this step is not required!)"\n\ "* Expression starting with '+' are evaluated in a single-threaded way, with possible image copy."\n\ "* If the specified expression starts with '>' or '<', the pixel access operators 'i()', 'i[]', 'j()' and 'j[]' "\ "return values of the image being currently modified, in forward ('>') or backward ('<') order. "\ "The multi-threading evaluation of the expression is disabled in this case."\n\ "* Function 'critical(expr)' forces the execution of the given expression in a single thread at a time."\n\ "* 'begin_t(expr)' and 'end_t(expr)' evaluates the specified expression once for each running thread "\ "(so possibly several times) at the beginning and the end of the evaluation procedure."\n\ "* 'merge(variable,operator)' tells to merge the local variable value computed by threads, with the specified "\ "operator, when all threads have finished computing."\n\ "* Expressions 'i(_#ind,x,_y,_z,_c)=value', 'j(_#ind,x,_y,_z,_c)=value', 'i[_#ind,offset]=value' and "\ "'j[_#ind,offset]=value' set a pixel value at a different location than the running one in the image '[ind]' "\ "(or in the associated image if argument '#ind' is omitted), either with global coordinates/offsets "\ "(with 'i(...)' and 'i[...]'), or relatively to the current position `(x,y,z,c)` (with 'j(...)' and 'j[...]'). "\ "These expressions always return 'value'." _section "Adding Custom Commands" _text \ "* New custom commands can be added by the user, through the use of G'MIC __custom commands files__."\n\ "* A command file is a simple text file, where each line starts either by "\ "\n~~~\ncommand_name: command_definition\n~~~\n or \n~~~\ncommand_definition (continuation)\n~~~\n"\n\ "* At startup, G'MIC automatically includes user's command file '$HOME/.gmic' (on __Unix__) or "\ "'%USERPROFILE%\\user.gmic' (on __Windows__). The CLI tool 'gmic' automatically runs the command "\ "'cli_start' if defined."\n\ "* Custom command names must use character set `[a-zA-Z0-9_]` and cannot start with a number."\n\ "* Any `# comment` expression found in a custom commands file is discarded by the G'MIC parser, "\ "wherever it is located in a line."\n\ "* In a custom command, the following '$-expressions' are recognized and substituted:"\n\ " - '$""\*' is substituted by a verbatim copy of the specified string of arguments "\ "(do not include arguments set to default values)."\n\ " - '$\"*\"' is substituted by the sequence of specified arguments, separated by commas ',', "\ "each being double-quoted (include arguments set to default values)."\n\ " - '$""#' is substituted by the maximum index of known arguments (either specified by the user or set to a default "\ "value in the custom command)."\n\ " - '$""[]' is substituted by the list of selected image indices that have been specified in the command "\ "invocation."\n\ " - '$""?' is substituted by a printable version of '$""[]' to be used in command descriptions."\n\ " - '$i' and '${i}' are both substituted by the `i`-th specified argument. Negative indices such as '${-j}' are "\ "allowed and refer to the `j`-th latest argument. '$""0' is substituted by the custom command name."\n\ " - '${i=default}' is substituted by the value of '$i' (if defined) or by its new value set to 'default' otherwise "\ "('default' may be a `$-expression` as well)."\n\ " - '${subset}' is substituted by the argument values (separated by commas ',') of a specified argument subset. "\ "For instance expression '$""{2--2}' is substituted by all specified command arguments except the first and the last "\ "one. Expression '$""{^0}' is then substituted by all arguments of the invoked command (eq. to '$""*' if all "\ "arguments have been indeed specified)."\n\ " - '$""=var' is substituted by the set of instructions that will assign each argument '$i' to the named variable "\ "'var$i' (for i in '[0...$""#]'. This is particularly useful when a custom command want to manage variable numbers "\ "of arguments. Variables names must use character set `[a-zA-Z0-9_]` and cannot start with a number."\n\ \n\ "* These particular `$-expressions` for custom commands are __always substituted__, even in "\ "double-quoted items or when the dollar sign '$' is escaped with a backslash '\\$'. To avoid substitution, place an "\ "empty double quoted string just after the '$' (as in '$\"\"1')."\n\ "* Specifying arguments may be skipped when invoking a custom command, by replacing them by commas ',' as in "\ "expression \n~~~\nflower ,,3\n~~~\n Omitted arguments are set to their default values, which must be thus explicitly "\ "defined in the code of the corresponding custom command (using default argument expressions as '$""{1=default}')."\n\ "* If one numbered argument required by a custom command misses a value, an error is thrown by the G'MIC "\ "interpreter."\n\ "* It is possible to specialize the invocation of a '+command' by defining it as "\ "\n~~~\n+command_name: command_definition\n~~~\n"\ "* A +-specialization takes priority over the regular command definition when the command is invoked with a "\ "prepended '+'."\n\ "* When only a +-specialization of a command is defined, invoking 'command' is actually equivalent to '+command'." _section "List of Commands" _text \ "All available G'MIC commands are listed below, by categories. An argument specified between '[]' "\ "or starting by '_' is optional except when standing for an existing image '[image]', where 'image' "\ "can be either an index number or an image name. In this case, the '[]' characters are mandatory when writing the "\ "item. Note that all images that serve as illustrations in this reference documentation are normalized in "\ "range `[0,255]` before being displayed. You may need to do this explicitly (command `normalize 0,255`) if you want "\ "to save and view images with the same aspect than those illustrated in the example codes."\n\ "The examples accompanying this 'List of Commands' illustrate the use of the G'MIC language and are written as they "\ "would appear in a custom command. While some examples may work if entered directly at a shell prompt, there is no "\ "guarantee. No attempt has been made to escape special characters in these examples, which many shells reserve." # Insert list of commands. l { reference_list_of_commands_$1 onfail } # Insert additional sections if specified. xfolder="$2" if "['$1']=='html' && ['$2']==0" xfolder=$HOME/work/src/gmic-community/reference fi if ['$xfolder']!=0 files $xfolder/*.gmd files=${} repeat narg({/$files}) { arg0 $>,$files file=${} l[] { it $file s={b} t={t} rm } _section {/$s} _text {/$t} } fi _section "Examples of Use" _text \ "`gmic` is a generic image processing tool which can be used in a wide variety of situations. "\ "The few examples below illustrate possible uses of this tool:"\n\ "### View a list of images: "\n\ "\n~~~\n$ gmic file1.bmp file2.jpeg\n~~~"\n\n\ "### Convert an image file: "\n\ "\n~~~\n$ gmic input.bmp output output.jpg\n~~~"\n\n\ "### Create a volumetric image from a movie sequence: "\n\ "\n~~~\n$ gmic input.mpg append z output output.hdr\n~~~"\n\n\ "### Compute image gradient norm: "\n\ "\n~~~\n$ gmic input.bmp gradient_norm\n~~~"\n\n\ "### Denoise a color image: "\n\ "\n~~~\n$ gmic image.jpg denoise 30,10 output denoised.jpg\n~~~"\n\n\ "### Compose two images using overlay layer blending: "\n\ "\n~~~\n$ gmic image1.jpg image2.jpg blend overlay output blended.jpg\n~~~"\n\n\ "### Evaluate a mathematical expression: "\n\ "\n~~~\n$ gmic echo \"cos(pi/4)^2+sin(pi/4)^2={cos(pi/4)^2+sin(pi/4)^2}\"\n~~~"\n\n\ "### Plot a 2D function: "\n\ "\n~~~\n$ gmic 1000,1,1,2 fill \"X=3*(x-500)/500;X^2*sin(3*X^2)+(!c?u(0,-1):cos(X*10))\" plot\n~~~"\n\ "===\n![2D Plot](../img/example_plot.png)\n==="\n\n\ "### Plot a 3D elevated function in random colors: "\n\ "\n~~~\n$ gmic 128,128,1,3,\"u(0,255)\" plasma 10,3 blur 4 sharpen 10000 n 0,255 "\ "elevation3d[-1] \"'X=(x-64)/6;Y=(y-64)/6;100*exp(-(X^2+Y^2)/30)*abs(cos(X)*sin(Y))'\"\n~~~"\n\n\ "===\n![3D Elevation](../img/example_elevation3d.png)\n==="\n\n\ "### Plot the isosurface of a 3D volume: "\n\ "\n~~~\n$ gmic mode3d 5 moded3d 5 double3d 0 isosurface3d \"'x^2+y^2+abs(z)^abs(4*cos(x*y*z*3))'\",3\n~~~"\n\ "===\n![3D Isosurface](../img/example_isosurface3d.png)\n==="\n\n\ "### Render a G'MIC 3D logo: "\n\ "\n~~~\n$ gmic 0 text G\\\47MIC,0,0,53,1,1,1,1 expand xy,10 blur 1 normalize 0,100 +plasma 0.4 add blur 1 "\ "elevation3d -0.1 moded3d 4\n~~~"\n\ "===\n![3D G'MIC Logo](../img/example_logo.png)\n==="\n\n\ "### Generate a 3D ring of torii: "\n\ "\n~~~\n$ gmic repeat 20 torus3d 15,2 color3d[-1] \"{u(60,255)},{u(60,255)},{u(60,255)}\" *3d[-1] 0.5,1 if \"{$>%2}\" "\ "rotate3d[-1] 0,1,0,90 fi add3d[-1] 70 add3d rotate3d 0,0,1,18 done moded3d 3 mode3d 5 double3d 0\n~~~"\n\ "===\n![3D Ring](../img/example_torii.png)\n==="\n\n\ "### Create a vase from a 3D isosurface: "\n\ "\n~~~\n$ gmic moded3d 4 isosurface3d \"'x^2+2*abs(y/2)*sin(2*y)^2+z^2-3',0\" sphere3d 1.5 sub3d[-1] 0,5 "\ "plane3d 15,15 rotate3d[-1] 1,0,0,90 center3d[-1] add3d[-1] 0,3.2 color3d[-1] 180,150,255 color3d[-2] 128,255,0 "\ "color3d[-3] 255,128,0 add3d\n~~~"\n\ "===\n![3D Vase](../img/example_vase.png)\n==="\n\n\ "### Launch a set of interactive demos: "\n\ "\n~~~\n$ gmic demos\n~~~\n" l { reference_footer_$1 reference_end_$1 onfail } um _section,_text rm # # Implement output mode 'ascii' for command 'reference'. # reference_begin_ascii : use_vt100 if !narg($_shell_cols) _shell_cols:=${-shell_cols}-5 fi _section=0 +e "" reference_header_ascii : str=\n\ " "${_vt100_b}"gmic: GREYC\'s Magic for Image Computing:"$_vt100_n" Command-line interface"\n\ " "${_vt100_c}${_vt100_b}"Version "${strver" "$_version,$_prerelease}$_vt100_n\n\ " "$_vt100_g$_vt100_u"(https://gmic.eu)"$_vt100_n\n\ \n\ " Copyright (c) Since 2008, David Tschumperlé / GREYC / CNRS."\n\ " "$_vt100_g$_vt100_u"(https://www.greyc.fr)"$_vt100_n +e $str reference_section_ascii : _section+=1 +e "" ('$_section." "') ('"$*"') +f.. {'" "'} +f.. {'-'} a[-4,-3] x a[-2,-1] x +e " "$_vt100_r$_vt100_b{-2,t}$_vt100_n +e " "$_vt100_r{t}$_vt100_n\n rm[-2,-1] reference_text_ascii : l[] { ('"$*"') gmd2ascii $_shell_cols,1 # Ensure output text contains no more than two consecutive newlines. # Also add a 2-chars left margin on each line. s +,{'\n'} eval "repeat (l,p, i(#p)==_'\n'?(h#p==1?resize(#p,0,0,0,0):(resize(#p,1,1,1,1);i(#p) = 0)): (resize(#p,1,h#p + 2,1,1,0,0,0,1); copy(i[#p,0],_' ',2,1,0)))" foreach { if w +e {/{t}} fi } rm } reference_list_of_commands_ascii : l { if !$! it $_path_rc/update$_version.gmic fi _display_categories=1 parse_cli ascii onfail rm +e \n" "$_vt100_r${_vt100_b}"No command descriptions available!"$_vt100_n +e " "${_vt100_r}"Try updating your command files, with command "$_vt100_b"'update'."$_vt100_n } reference_footer_ascii : +e \n" "$_vt100_r$_vt100_b"** G\47MIC comes with ABSOLUTELY NO WARRANTY; "\ "for details visit: https://gmic.eu **"$_vt100_n # # Implement output mode 'html' for command 'reference'. # reference_section_html : name="$*" reference_end_section_html ('""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing "\ "- "$name""\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\n\ "
"\n\ " "\n\ ""\n\n\ ""\n\n\ "

"$name"

"\n':y) => $name reference_end_section_html : if $! # End previous section ('"
"\n\n\ ""\n\n\ ""\n\ "
"\n\ " "\n\ " "':y) a[-2,-1] y fi reference_text_html : ('"$*"':y) gmd2html. 0 if $!>1 a[-2,-1] y fi reference_footer_html : reference_end_section_html reference_finalize_html : # Generate table of contents. html=""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing "\ "- Table of Contents"\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\n\ "
"\n\n\ " "\n\ ""\n\n\ ""\n\n\ "

Preamble

"\n\ " "\n\ "

Version

"\n\ "
"\n\ " G'MIC: "\ "GREYC's Magic for Image Computing
"\n\ " https://gmic.eu
"\n\ " Version "${strver" "$_version,$_prerelease}"

"\n\ " Copyright © Since 2008, "\ "David Tschumperlé / "\ "GREYC / "\ "CNRS
"\n\ " https://www.greyc.fr
"\n\ "
"\n\ "

Table of Contents

"\n\ "
    "\n ind_loc=${"-nmd 1,\"List of Commands\""} if narg($ind_loc) foreach { name={n} strvar $name url=${}.html if $>==$ind_loc html.="
  • "$name"
  • " else html.="
  • "$name"
  • " fi } html.="\n
"\n\n\ ""\n\n\ ""\n\ "
"\n\ " "\n\ " " i[0] ({'$html'}:y) =>[0] "Table of Contents" ind_loc+=1 if narg($ind_loc)" && "isfile('list_of_commands.html') # Merge with existing 'List of commands' page if it exists it list_of_commands.html if find(crop(),'""')<0 l[$ind_loc,-1] { s[0] -,{'""'} s. -,{'""'} i[1] ('""':y) k[0,1,-1] a y } else => "List of Commands" rv[$ind_loc,-1] rm. fi fi fi # Insert top and bottom navigation bar into pages. if !0$_pdf_output repeat $! { current={$>,n} strvar[] $current url_current=${}.html if $>>1 previous={{$>-1},n} strvar[] $previous url_previous=${}.html else previous= fi if $< next={{$>+1},n} strvar[] $next url_next=${}.html else next= fi html_top="
"\ "Table of Contents" if $> html_top.="  ▸  "$current"" fi html_top.="" if ['$previous']!=0 html_top.="◀  "$previous"" fi if ['$previous']!=0" && "['$next']!=0 html_top.="    |    " fi if ['$next']!=0 html_top.=""$next"  ▶" fi html_top.="
" html_bottom="
" if ['$previous']!=0 html_bottom.="◀  "$previous"" fi if ['$previous']!=0" && "['$next']!=0 html_bottom.="    |    " fi if ['$next']!=0 html_bottom.=""$next"  ▶" fi html_bottom.="
" replace_str[$>] "",$html_top replace_str[$>] "",$html_bottom } fi reference_end_html : reference_finalize_html # Save html pages. foreach { strvar {n} ot ${}.html } if isfile('table_of_contents.html') x "ln -fs table_of_contents.html index.html" fi rm # # Implement output mode 'man' for command 'reference'. # (It is based on the 'ascii' output with some tricks to generate a roff file). # reference_begin_man : _section=0 +e ".TH G\47MIC 1\n\ .SH NAME\n\ gmic \\- Perform image processing operations using the G\47MIC framework.\n\ \n\ .SH HELP\n" reference_header_man : _vt100_b="\\fB" _vt100_c="\\fB" _vt100_g="\\fB" _vt100_m= _vt100_n="\\fR" _vt100_r="\\fB" _vt100_u="\\fI" _prerelease= reference_header_ascii reference_section_man : reference_section_ascii "$*" reference_text_man : reference_text_ascii "$*" reference_list_of_commands_man : _vt100_c= reference_list_of_commands_ascii _vt100_m="\\fB" _vt100_b= reference_footer_man : reference_footer_ascii "$*" # # Implement output mode 'pdf' for command 'reference'. # (It is based on the 'html' output with some tricks to generate a pdf file with 'wkhtmltopdf'). # reference_begin_pdf : _pdf_output=1 reference_section_pdf : reference_section_html "$*" reference_text_pdf : reference_text_html "$*" reference_footer_pdf : reference_end_section_html reference_end_pdf : 1024,4,1,3 o. reference_pdf.png rm. reference_finalize_html # Load existing html page for each command. l[] { it ../../src/gmic_stdlib.gmic +parse_cli. list loc=${} parse_cli html repeat narg($loc) { command=${"arg "1+$>","$loc} l[] { if ['$command'][0]!=_'_' it $command.html fi onfail } } sort_list +,n } s +,{'""'} s +,{'""'} k[2--1:5] # Add specific header and footer. i[0] ('""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " reference_pdf"\n\ " "\n\ " "\n\n\ " "\n\ "
"\n\ " "\n\ "

The Handbook

"\n\ "

Version "${strver" "$_version,$_prerelease}"

"\n\ "

© David Tschumperlé / GREYC / CNRS

"\n\ "

"${-date}"

"\n\ "
"':y) ('""\n\ "

□ End of document

"\n\ ""':y) a y ot reference_pdf.html rm # Copy css to current folder. it ../style.css ot. style.css rm # Use 'wkhtmltopdf' to convert html to a pdf file. # (There is a bug in wkhtmltopdf that prevents all images to appear in the generated document! # I have to print the .pdf manually 'in a file' first). delete reference_pdf.pdf v 0 e " > Waiting for file 'reference_pdf.pdf'." for !isfile('reference_pdf.pdf') { wait 5000 } e " > Removing links in file 'reference_pdf.pdf'." x "pdfjam reference_pdf.pdf" e " > Compressing file 'reference_pdf.pdf' to 'gmic_reference.pdf'." x "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=gmic_reference.pdf reference_pdf-pdfjam.pdf" e " > Clean temporary files." delete style.css,reference_pdf.html,reference_pdf.png,reference_pdf.pdf,reference_pdf-pdfjam.pdf e " > Upload file 'gmic_reference.pdf' to G'MIC server." x "lftp sftp://"$GMIC_LOGIN":@ovh -e \"put -O /home/"$GMIC_LOGIN"/www/gmic/reference gmic_reference.pdf; "\ "quit\" >/dev/null" # use_vt100 # This command defines some global variables used to output colored text on VT100 terminals. use_vt100 : if !0$_vt100" || "['$_vt100_n']!=0 return fi _vt100_b="\33[1m" # Bold _vt100_c="\33[0;36;59m" # Cyan _vt100_g="\33[0;32;59m" # Green _vt100_i="\33[3m" # Italic _vt100_m="\33[0;35;59m" # Magenta _vt100_n="\33[0;0;0m" # Normal _vt100_r="\33[0;31;59m" # Red _vt100_s="\33[9m" # Strikethrough _vt100_u="\33[4m" # Underline #@cli version #@cli : Display current version number on stdout. version : use_vt100 reference_header_ascii[] if !0$_cli_noarg +e "\n" fi #------------------------------- # #@cli :: Input / Output # #------------------------------- #@cli camera : _camera_index>=0,_nb_frames>0,_skip_frames>=0,_capture_width>=0,_capture_height>=0 : (+) #@cli : Insert one or several frames from specified camera. #@cli : When 'nb_frames==0', the camera stream is released instead of capturing new images. #@cli : This command requires features from the OpenCV library (not enabled in G'MIC by default). #@cli : Default values: 'camera_index=0' (default camera), 'nb_frames=1', 'skip_frames=0' and \ # 'capture_width=capture_height=0' (default size). # [Internal] Use this command to 'clean' a .gmz file that represents CLUT keypoints. # What it does is: # # - Standardize CLUT name. # - Convert RGB CLUTs to Grayscale when possible. # - Remove duplicates and sort by lexicographic order. # - Display list of CLUTs to ease documentation update of command 'clut'. # clean_cluts : e[^-1] "Clean CLUT dataset.\n" round c 0,255 # Standardize names. foreach { nm={n} # Standardize names for 'Moviz'. if "str = lowercase(['"$nm"*']); !find(str,'tpf_-_cinematica_')" l[] { if !narg($tpf) tpf=1 fi ('moviz_$tpf') tpf+=1 nm={t} rm } fi if "str = lowercase(['"$nm"*']); find(str,'_-_standard-vk')>=0" l[] { if !narg($tpf) tpf=1 fi ('moviz_$tpf') tpf+=1 nm={t} rm } fi # Standardize names for 'SmallHD MovieLook'. if "str = lowercase(['"$nm"*']); find(str,'_-_rec_709*')>=0" l[] { ('$nm') z. 0,{w-11} nm={t} rm } fi # Standardize names for 'SmallHD MovieLook'. if "str = lowercase(['"$nm"']); !find(str,'smallhd_movielook_')" l[] { ('$nm') z. 18,100% replace_str "apocalypsethisverymoment","apocalypse_this_very_moment" replace_str "bobford","bob_ford" replace_str "lifegivingtree","life_giving_tree" replace_str "savingprivatedamon","saving_private_damon" replace_str "thematrices","the_matrices" nm={t} rm } fi # Standardize names for 'Fuji XTrans III'. if "str = lowercase(['"$nm"']); !find(str,'fuji_xtrans_iii')" l[] { ('$nm') replace_str "_-_","_" nm={t} rm } fi # Standardize names for 'RawTherapee'. if "str = lowercase(['"$nm"']); !find(str,'kodak') || !find(str,'polaroid') || !find(str,'fuji') || !find(str,'ilford')" l[] { ({'$nm'},{'*'}) replace_str " ","_" replace_str "xp_2","xp2" replace_str "hp_5","hp5" repeat 8 { n:=1+$> replace_str "_"${n}"_+","_+" replace_str "_"${n}"_-","_-" replace_str "_"${n}"_alt","_alt" replace_str "_"${n}"_Alt","_alt" replace_str "_"${n}"*","*" } = 0,0,100% nm={t} rm } fi strclut $nm nm=${} # Standardize names for 'PictureFX'. if "str = lowercase(['"$nm"']); !find(str,'technicalfx') || !find(str,'picturefx') || !find(str,'analogfx') || !find(str,'goldfx') || !find(str,'zilverfx')" l[] { ('$nm') replace_str "-","_" nm={t} rm } fi # Other name changes. l[] { ({'$nm'},{'*'}) replace_str "_v_2*","*" replace_str "_v_1*","*" replace_str "_*","*" replace_str "_b_w","_bw" replace_str "&","" replace_str "rec_709_-_","rec709_" replace_str "s-log","slog" replace_str "__","_" replace_str "action_-_","action_" replace_str "-version-","" replace_str "picturefx_","" = 0,0,100% nm={t} rm } =>[^] $nm } # Convert RGB CLUTs to Grayscale when possible. foreach { if "R = crop(#0,0,0,0,3,1,h,1,1); G = crop(#0,0,0,0,4,1,h,1,1); B = crop(#0,0,0,0,5,1,h,1,1); R==G && G==B" channels 0,3 fi } # Search for duplicates and sort. p=0 for $p<$! { nm0={$p,n} e "\r- Search duplicates for ["$p"] = '"$nm0"' " q:=$p+1 for $q<$! { nm={$q,n} if ['$nm0']==['$nm'] e " > Found duplicate ["$q"] -> Original 1x"{$p,h}", new 1x"{$q,h}"\n" rv[$p,$q] rm[$q] else q+=1 fi } p+=1 } sort_list +,n # Display all clut names. doc="#@cli clut : \"clut_name\",_resolution>0,_cut_and_round={ 0:no | 1:yes }\n"\ "#@cli : Insert one of the "$!" pre-defined CLUTs at the end of the image list.\\n\n"\ "#@cli : 'clut_name' can be {" sep="|" nbc=28 foreach { if !$< sep="}" fi str=" "{n}" "$sep s_str:=size(['$str']) nbc+=$s_str if $nbc<118 doc=${doc}${str} else doc=${doc}" \\\n#"${str} nbc:=1+$s_str fi } doc=${doc}"\n"\ "#@cli : Default values: 'resolution=33' and 'cut_and_round=1'.\n"\ "#@cli : $ clut summer clut alien_green,17 clut orange_dark4,48\n" e "\n"$doc #@cli m : eq. to 'command'. : (+) #@cli command : _add_debug_info={ 0 | 1 },{ filename | http[s]://URL | "string" } : (+) #@cli : Import G'MIC custom commands from specified file, URL or string. #@cli : (eq. to 'm').\n #@cli : Imported commands are available directly after the 'command' invocation. #@cli : Specified filename is not allowed to contain colons ':'. #@cli : Default value: 'add_debug_info=1' (except for a "string" argument, in which case 'add_debug_info=0'). #@cli : $ image.jpg command "foo : mirror y deform $""1" +foo[0] 5 +foo[0] 15 #@cli compress_to_keypoints : _method,_max_keypoints>=0,_err_avg[%]>=0,_err_max[%]>=0,_"err_command" #@cli : Compress each of the selected images into a set of keypoints that can be further decompressed \ # using command ''decompress_from_keypoints''. #@cli : **Beware**: This type of compression is effective only for images with very smooth content. #@cli : 'method' can be { 0:PDE | 1:RBF }. Add '2' to 'method' to skip the point removal step. #@cli : - 'max_keypoints' is the maximal number of keypoints generated by the compression method. \ # If 'max_keypoints<0', the removal step is not done when number of maximal keypoints has been reached. \ # 'max_keypoints=0' means 'no limits'. #@cli : - 'err_avg' is the desired average compression error. #@cli : - 'err_max' is the desired pointwise max compression error. #@cli : - 'err_command' is the code of a command that inputs the two images '[reference]' and '[compressed]' \ # and compute a single error map as a last image. #@cli : Defaults values: 'method=3', 'max_keypoints=0', 'err_avg=1%', 'err_max=5%' and 'err_command=-. [0] norm.' compress_to_keypoints : check "inrange(${1=3},0,3) && isint(${2=0}) && ${3=1%}>=0 && ${4=5%}>=0" \ skip "${5=-. [0] norm.}" use_rbf,is_removal,nb_max_keypoints,skip_removal_if_max_keypoints:=$1%2,$1>1,abs($2),$2<0 m0,m1=PDE,RBF M0,M1=$nb_max_keypoints" max keypoints, ", e[^-1] "Compress image$? as sets of keypoints, using "${m{$1%2}}" method, "${M{!$2}}"average error $3 and "\ "pointwise max error $4." pass[$[]] -1 sel=${} nb_img=$! foreach { nm={n} d_ind=[{arg0($>,$sel)}]='{b}'" "({$>+1}/$nb_img) e "" # Initialize keypoints with pixel values at image corners. dim,im,iM:=d>1?3:h>1?2:1,im,iM if $dim==3 1,1,1,{3+s} eval "const w1 = w#0 - 1; const h1 = h#0 - 1; const d1 = d#0 - 1; dims = vals = vectors(); copy(dims,[ w#0,h#0,d#0 ]); copy(vals,[ $im,$iM,$use_rbf ]); da_push(dims,vals, [0,0,0,I(#0,0,0,0)], [w1,0,0,I(#0,w1,0,0)], [w1,h1,0,I(#0,w1,h1,0)], [0,h1,0,I(#0,0,h1,0)], [0,0,d1,I(#0,0,0,d1)],[w1,0,d1,I(#0,w1,0,d1)],[w1,h1,d1,I(#0,w1,h1,d1)],[0,h1,d1,I(#0,0,h1,d1)])" elif $dim==2 1,1,1,{2+s} eval "const w1 = w#0 - 1; const h1 = h#0 - 1; const m = min(s,3); dims = vals = vectors(); copy(dims,[ w#0,h#0,1 ],m); copy(vals,[ $im,$iM,$use_rbf ],m); da_push(dims,vals, [0,0,I(#0,0,0)],[w1,0,I(#0,w1,0)],[w1,h1,I(#0,w1,h1)],[0,h1,I(#0,0,h1)])" else 1,1,1,{1+s} eval "const w1 = w#0 - 1; const m = min(s,3); dims = vals = vectors(); copy(dims,[ w#0,1,1 ],m); copy(vals,[ $im,$iM,$use_rbf ],m); da_push(dims,vals, [0,I(#0,0,0)],[w1,I(#0,w1,0)])" fi target_err_avg,target_err_max:=ispercentage($3)?($iM-$im)*$3:$3,ispercentage($4)?($iM-$im)*$4:$4 d_target_err_avg,d_target_err_max={_[$target_err_avg,$target_err_max]} # Step #1: Greedily add improving keypoints. err_avg,err_max=inf,inf nb_keypoints:="da_size(#1) - 2" do +da_freeze[1] if $dim==3 [0],[0],[0] eval.. "begin(ret = I); i(#-1,i0,i1,i2) = 1; ret" elif $dim==2 [0],[0] eval.. "begin(ret = I); i(#-1,i0,i1) = 1; ret" else [0] eval.. "begin(ret = I); i[#-1,i0] = 1; ret" fi distance. 1 n. 0.1,1 decompress_from_keypoints.. , rv[-2,-1] if {*} w. fi l[0,-2,-1] { $5 err_avg,err_max:=ia,iM *. .. xM,yM,zM:=xM,yM,zM k[0] } eval "$dim==3?da_push(#1,[$xM,$yM,$zM,I(#0,$xM,$yM,$zM)]): $dim==2?da_push(#1,[$xM,$yM,I(#0,$xM,$yM)]): da_push(#1,[$xM,I(#0,$xM)])" stats="avg = "{_$err_avg}" / "$d_target_err_avg", max = "{_$err_max}" / "$d_target_err_max nb_keypoints+=1 e "\r "$d_ind" > Add \#"$nb_keypoints": "$stats" " while (!$nb_max_keypoints" || "$nb_keypoints<$nb_max_keypoints)" && "\ ($err_avg>$target_err_avg" || "$err_max>$target_err_max) da_freeze[1] # Step #2: Remove keypoints. if $is_removal" && "($nb_keypoints<$nb_max_keypoints" || "!$skip_removal_if_max_keypoints) s:=s if $err_avg>$target_err_avg # Constraints not reached : relax them to allow keypoint removal anyway target_err_avg:=$err_avg*1.05 d_target_err_avg={_$target_err_avg} fi if $err_max>$target_err_max target_err_max:=$err_max*1.05 d_target_err_max={_$target_err_max} fi stats="avg = "{_$err_avg}" / "$d_target_err_avg", max = "{_$err_max}" / "$d_target_err_max ind=0 repeat h-2 { +point. 0,{$ind+2},0,1,-1 discard. -1 r. 1,{h/$s},1,$s,-1 +decompress_from_keypoints. l[0,-1] { $5 err_avg,err_max,xM,yM,zM:=ia,iM,xM,yM,zM k[0] } is_deleted=0 if $err_avg<=$target_err_avg" && "$err_max<=$target_err_max rv[1,-1] nb_keypoints-=1 is_deleted=1 stats="avg = "{_$err_avg}" / "$d_target_err_avg", max = "{_$err_max}" / "$d_target_err_max fi e "\r "$d_ind" > Rem \#"{1+$ind}/$nb_keypoints": "$stats" " if !$is_deleted ind+=1 fi rm. } fi k. => $nm } # compress_gmic # Compress .gmic custom command files for compressing update files a little bit, # by removing empty lines, and useless comments. compress_gmic : merge_multiline_comments merge_multiline eval " # Remove useless comments p = 0; while (p look for comment at line end l = find(#-1,_'#',p); l>=0 && l1 ('""\n\n\ ""\n\ ""\n\ ""\n\ " FilterTextTranslator"\n\n':y) repeat h#0 { str={0,`I(0,$>)`} tr={0,`I(1,$>)`} if s=['$tr'];s!=0" && norm(s-=_' ')" ({'$str'}) autocrop. {'" "'} _gmd_ascii2html. str={t} rm. ({'$tr'}) autocrop. {'" "'} _gmd_ascii2html. tr={t} rm. if {0,!i(2,$>)} # Global translation ('" "\n\ " "$str""\n\ " "$tr""\n\ " "\n\n':y) else # Filter specialization i=2 for {0,i($i,$>)} { path={0,`I($i,$>)`} ({'$path'}) autocrop. {'" "'} _gmd_ascii2html. path={t} rm. ('" "\n\ " "$str""\n\ " "$path""\n\ " "$tr""\n\ " "\n':y) i+=1 } r. 1,{h+1},1,1,0,1 # Last item has two done fi fi } ('"\n"\n':y) a[^0] y k. fi } #@cli delete : filename1[,filename2,...] : (+) #@cli : Delete specified filenames on disk. Multiple filenames must be separated by commas. #@cli d : eq. to 'display'. d : _gmic_s="$?" v + _display "",1,$[] +d : _gmic_s="$?" v + _display +,{$^>=0},$[] #@cli display #@cli : Display selected images in an interactive window. #@cli : (eq. to 'd'). #@cli : When invoked with a '+' prefix (i.e. '+display'), the command outputs its log messages on 'stdout' \ # rather than on 'stderr'. #@cli : Display window \#0 is used as the default window for the display, if already opened. #@cli : \nAvailable controls are shown below \ # (where 'LMB' = Left mouse button, 'RMB' = Right mouse button, 'MMB' = Middle mouse button and 'MW' = Mouse wheel). #@cli : \n- **Thumbnail navigation bar:** #@cli : 'TAB': Show/hide thumbnails - 'LMB': Select thumbnail or shift thumbnail bar - \ # '0'-'9','ARROWS' (opt. '+SHIFT'),'B','BACKSPACE','C','E','END','H','HOME','SPACE': Navigate and select thumbnails \ # (add 'CTRL' if mouse pointer is outside thumbnail bar). #@cli : \n- **Image view:** #@cli : 'LMB' or 'MMB': Image pan - 'RMB' or 'MW': Image zoom - 'ARROWS' (opt. '+SHIFT'),'HOME','END': Shift view - \ # 'A': Switch alpha rendering - 'C': Center view - 'E': Go to lower-right corner - 'ENTER': Reset view - \ # 'G': Toggle grid - 'H': Go to upper-left corner - 'K': Switch background - 'M': Toggle 3D view - \ # 'N': Switch normalization - 'P': Print info about current image pixel on 'stdout' - \ # 'PAGEUP' or 'PAGEDOWN': Raise/lower base channel - 'R': Rotate image - 'T': Plot as a 1D curve - 'V': Crop image - \ # 'Z': Switch zoom factor - '0'-'9': Set zoom factor. #@cli : \n- **3D mesh view:** #@cli : 'LMB': Mesh rotation - 'CTRL+LMB' or 'MMB': Mesh pan - 'RMB': Mesh zoom - \ # 'A': Toggle axes - 'D': Switch face side mode - 'F': Change focale - 'J': Start/stop animation - \ # 'K': Switch background - 'O': Switch outline mode - 'P': Print 3D pose matrix on 'stdout' - \ # 'R': Switch rendering mode - 'T': Switch motion rendering mode - 'X': Show/hide bounding-box - \ # 'U': Switch animation mode - 'Z': Toggle z-buffer. #@cli : \n- **2D images specific:** #@cli : 'CTRL+LMB': Rectangular selection. #@cli : \n- **3D volumetric images specific:** #@cli : 'CTRL+MW': Pan along orthogonal axis - 'X': Reset area layout. #@cli : \n- **Window size, decoration and data I/O:** #@cli : 'CTRL+C': Decrease window size - 'CTRL+D': Increase window size - 'CTRL+F': Toggle fullscreen - \ # 'CTRL+I': Toggle info label - 'CTRL+O': Save copy of image as a `.gmz` file - \ # 'CTRL+L': Save copy of image list as `.gmz` file - 'CTRL+S': Save screenshot as a `.png` file - \ # 'CTRL+W': Start/stop window recording - 'CTRL+X': Toggle cursor. #@cli : \n- **Configuration variables:** #@cli : The viewer configuration can be tuned by assigning the following variables: #@cli : - '_display_selected' is an integer or an image name that tells which image is selected by default. #@cli : - '_display_alpha' can be { 0:off | 1:on | 2:over black | 3:over gray | 4:over white } (default value: '0'). #@cli : - '_display_background', an integer in range [ 0,9 ] (default value: '3'). #@cli : - '_display_cursor' can be { 0:off | 1:on (2D only) | 2:on (+3D volumetric images) } (default value: '1'). #@cli : - '_display_is_grid' can be { 0:off | 1:on } (default value: '1'). #@cli : - '_display_is_info' can be { 0:off | 1:on } (default value: '1'). #@cli : - '_display_normalization' can be { -1:auto | 0:off | 1:cut | 2:stretch channelwise | 3:stretch global | \ # 4: stretch (global-once) } (default value: '-1'). #@cli : - '_display_print_images' can be { 0:off | N>0 } (default value: '5'). It sets the max number 'N' of images \ # whose information is initially printed on `stderr` or `stdout`. #@cli : - '_display_3d_is_rendered' can be { 0:off | 1:on } (default value: '1'). #@cli : - '_display_3d_rendering_mode' can be { 0:dots | 1:wireframe | 2:flat | 3:flat-shaded | 4:gouraud-shaded | \ # 5=phong-shaded } (default value: '4'). #@cli : - '_display_3d_outline_mode' can be { 0:no-outline | 1:black-outline | 2:gray-outline | 3:red-outline | \ # 4:green-outline | 5:blue-outline | 6:white-outline } (default value: '0'). #@cli : - '_display_3d_motion_rendering_mode' can be { -1:bounding-box | 0:dots | 1:wireframe | 2:flat | \ # 3:flat-shaded | 4:gouraud-shaded | 5=phong-shaded } (default value: '3'). #@cli : - '_display_3d_motion_time_limit' is specified in ms. Above this time, motion rendering toggle to \ # 'bounding-box' mode (default value: '300'). #@cli : - '_display_3d_side_mode' can be { 0:single-sided | 1:double-sided | 2:single-sided (flipped) } \ # (default value: '0'). #@cli : - '_display_3d_is_zbuffer' can be { 0:off | 1:on } (default value: '1'). #@cli : - '_display_3d_focale' can be { <0: perspective projection w/o sprite zooming, 0: parallel projection | \ # >0: perspective projection } (default value: 1.5). #@cli : - '_display_3d_is_axes' can be { 0:off | 1:on } (default value: '1'). #@cli : - '_display_3d_is_bounding_box' can be { 0:off | 1:on } (default value: '0'). #@cli : - '_display_3d_background' is an unsigned integer in range [0,11] (default value: '11'). #@cli : - '_display_3d_pose' is a sequence of 12 values that defines the current 3D pose matrix (read/write). #@cli : - '_display_3d_animation' can be { 0:off | 1:forward | 2:backward } (default value: '0'). #@cli : - '_display_3d_animation_mode' can be { 0-3:X-axis | 4-7:Y-axis | 8-11:Z-axis | 12-15:XYZ-axes } \ # (default value: '4'). display : _gmic_s="$?" v + _$0 "",1,$[] +display : _gmic_s="$?" v + _$0 +,{$^>=0},$[] # $1: '+' or '' to specify output on stdout or stderr. # $2: display_header={ 0 | 1 } # $3: list of selected images indices. _display : skip ${1=},${3=} if !$! if $2 e[0--5] "Display image []." fi is_change 0 return fi check_display "display" m "$0_rmn : nmd 2,$""* rm[${}]" nb_images=$! # Initialize setting variables. if ['$_display_selected']!=0 setting_selected=$_display_selected else setting_selected=0 fi setting_alpha:=narg($_display_alpha)?cut(int(0$_display_alpha),0,4):0 setting_background:=narg($_display_background)?cut(int(0$_display_background),0,9):3 setting_cursor:=narg($_display_cursor)?cut(int(0$_display_cursor),0,2):1 setting_is_grid:=narg($_display_is_grid)?cut(int(0$_display_is_grid),0,1):1 setting_is_info:=narg($_display_is_info)?cut(int(0$_display_is_info),0,1):1 setting_normalization:=narg($_display_normalization)?cut(int(0$_display_normalization),-1,4):-1 setting_print_images:=narg($_display_print_images)?max(0,$_display_print_images):5 setting_3d_is_rendered:=narg($_display_3d_is_rendered)?cut(int(0$_display_3d_is_rendered),0,1):1 setting_3d_rendering_mode:=narg($_display_3d_rendering_mode)?\ cut(int(0$_display_3d_rendering_mode),0,5):\ narg($_mode3d)?cut(int(0$_mode3d),0,5):4 setting_3d_outline_mode:=narg($_display_3d_outline_mode)?cut(int(0$_display_3d_outline_mode),0,6):0 setting_3d_motion_rendering_mode:=narg($_display_3d_motion_rendering_mode)?\ cut(int(0$_display_3d_motion_rendering_mode),-1,5):\ narg($_moded3d)?cut(int(0$_moded3d),-1,5):3 setting_3d_motion_time_limit:=narg($_display_3d_motion_time_limit)?max(0$_display_3d_motion_time_limit,0):300 setting_3d_side_mode:=narg($_display_3d_side_mode)?cut(int(0$_display_3d_side_mode),0,2):1 setting_3d_is_zbuffer:=narg($_display_3d_is_zbuffer)?cut(int(0$_display_3d_is_zbuffer),0,1):1 setting_3d_focale:=narg($_display_3d_focale)?0$_display_3d_focale:1.5 setting_3d_is_axes:=narg($_display_3d_is_axes)?cut(int(0$_display_3d_is_axes),0,1):1 setting_3d_is_bounding_box:=narg($_display_3d_is_bounding_box)?cut(int(0$_display_3d_is_bounding_box),0,1):0 setting_3d_background:=narg($_display_3d_background)?cut(int(0$_display_3d_background),0,12):11 setting_3d_animation:=narg($_display_3d_animation)?cut(int(0$_display_3d_animation),0,1):0 setting_3d_animation_mode:=narg($_display_3d_animation_mode)?cut(int(0$_display_3d_animation_mode),0,15):4 if !narg($_display_3d_pose) _display_3d_pose=1,0,0,0,0,1,0,0,0,0,1,0 fi if !narg($_display_3d_zoom) _display_3d_zoom=0.65 fi thumb_height_factor=0.15 thumb_min_ratio=0.45 thumb_is_visible:=$nb_images>1 is_opencv:="find([' "$_flags"'],'opencv')>0" l { # Set default selected image index if isint($setting_selected) selected:=$setting_selected%$nb_images else nmd 1,$setting_selected selected=${} if !narg($selected) selected=0 fi fi onfail selected=0 } # Save original image names to prevent name conflicts with those used in this command. foreach { image_name$>={n} => _display$> } # Generate window and log titles and print log message. (${3--1}:y) => image_indices if $nb_images<=6 $nb_images,1,1,1,x sel={^} rm. is_ellipsized=0 else sel=0,1,2,{$nb_images-[3,2,1]} is_ellipsized=1 fi log_title,window_title,sep= repeat narg($sel) { l_ind:=arg0($>,$sel) g_ind={image_indices,i[$l_ind]} basename {``${image_name$l_ind}} if ['{/${}}']!=0 ({'{/${}}'}) else ('{``${image_name$l_ind}}') fi basename={`"b = crop(); size(b)>32?copy(b[32-3],[['...'],0]); b"`} rm. log_title.=$sep$basename window_title.=$sep[$g_ind]" "$basename if $is_ellipsized" && "$>==2 sep=", (...) " else sep=", " fi } if $nb_images>1 window_title.=" (#"$nb_images")" else window_title.=" ("{0,[w,h,d,s]}")" fi if $2 e[0--4] "Display image"$_gmic_s" = '"$log_title"'." fi # Initialize display window. window_previous_normalization,windows_previous_fullscreen= if {*} window_width,window_height={*,d,e} window_previous_normalization,windows_previous_fullscreen={*,n,f} if $setting_normalization<0 setting_normalization:={*,n}?4:0 fi w[] $window_width,$window_height,0,$window_title else window_width,window_height=\ ${"$0_default_window_size[0-"{$nb_images-1}"] "$thumb_height_factor,$thumb_min_ratio} if $setting_normalization<0 setting_normalization=4 fi w[] $window_width,$window_height,0,$window_title fi window_nonfullscreen_width,window_nonfullscreen_height={*,w,h} is_fullscreen,mouse_x,mouse_y={*,f,x,y} mouse_area,is_area1,is_area2,is_area3,is_area4,is_area5,is_area234=0 previous_mouse_x,previous_mouse_y,previous_mouse_area=-1,-1,0 event_type0,is_clicked0,is_clicked1,is_clicked2,is_released0,is_released1,is_released2,\ is_drag0,is_drag1,is_drag2=0 window_no_wait=0 # Initialize other viewer variables. thumb_mouse_over=-1 thumb_view_update=0 thumb_x= view_center_x,view_center_y,view_center_z,view_zoom=0.5,0.5,0.5,1 view_zoom_mode,view_is_rotated,view_base_channel=3,0,0 canvas_rect_x0,canvas_rect_y0,canvas_rect_x1,canvas_rect_y1,canvas_cross_x,canvas_cross_y= canvas_mouse_x,canvas_mouse_y,window_record_frame=-1 canvas_label_alignment=0 area_width,area_height=0 area_mouse_x,area_mouse_y=-1 zoom_1_1,view_3d_draw_on_canvas,area_3d_is_motion=0 if $nb_images>1 (255,0) r. 33,1,1,1,3 channels. -3,0 +mirror. x => thumb_shade_left,thumb_shade_right fi 1,${nb_images}x5 => is_printed,is_infnan,is_rotated,is_mesh3d,thumb_is_cached f[is_infnan,is_mesh3d] -1 # Print information of first images. repeat min($nb_images,$setting_print_images) { =>[$>] ${image_name$>} v + _print[$>] $1,0,{$image_indices,i[$>]} v - # '$image_indices' prevents issue if image name is 'image_indices' =>[$>] _display$> =[is_printed] 1,0,$> } # Enter user interaction loop. for {*}" && "!{*,ESC} { window_width,window_height={*,w,h} is_volumetric={$selected,d>1} if {is_mesh3d,i[$selected]<0} sh[$selected] if {is_rotated,i[$selected]} y. fi if h>6" && "int(crop(0,0,1,6))=='CImg3d' =[is_mesh3d] ${is_mesh3d.},0,$selected else =[is_mesh3d] 0,0,$selected fi rm. fi is_mesh3d={is_mesh3d,i[$selected]} is_view3d:=$setting_3d_is_rendered" && "($is_volumetric" || "$is_mesh3d) # Rotate image if necessary. if {is_rotated,i[$selected]!=$view_is_rotated} rotate[$selected] {is_rotated,90*($view_is_rotated-i[$selected])} =[is_rotated] $view_is_rotated,0,$selected fi # Render thumbnail navigation bar. if !$thumb_is_visible thumb_height,thumb_height2=0 else # Generate thumbnail coordinates and sizes for current window resolution. thumb_height:=max(48,round($window_height*$thumb_height_factor)) if !0$thumb_coords 1,$nb_images,1,2,"begin(CImg3d = 'CImg3d'); const ratio = $thumb_min_ratio; w = w#y; h = h#y; d = d#y; s = s#y; w==1 && h>6 && d==1 && s==1 && int(crop(#y,0,0,0,0,1,6,1,1))==CImg3d?[ 1,1 ]:( # is 3D mesh? d>1?(w+=d + 2; h+=d + 2); Mwh = max(w,h); wt = Mwh?max(ratio,w/Mwh):0.5; ht = Mwh?max(ratio,h/Mwh):0.5; i[#$is_rotated,y]%2?swap(wt,ht); [ wt,ht ])" sh. 100% /.. {iM} rm. *. $thumb_height round. +f. ">begin(Sw = 0); res = [ Sw, round(($thumb_height - i1)/2) ]; Sw+=i0 + 2; res" rv[-2,-1] a[-2,-1] c => thumb_coords thumb_sumw={thumb_coords,max(i[h-1]+i(0,h-1,0,2),$window_width)} thumb_x_px_max={thumb_coords,max(-16,i[h-1]+i(0,h-1,0,2)-$window_width+17)} if !narg($thumb_x) x0,w={thumb_coords,[i(0,$selected,0,0),i(0,$selected,0,2)]} thumb_x:=($x0-48)/$thumb_sumw fi $0_flush_thumb_cache fi thumb_height2:=$thumb_height+2 thumb_x_px:=cut($thumb_x*$thumb_sumw,-16,$thumb_x_px_max) # Generate thumnail view. if $thumb_view_update $0_rmn thumb_view thumb_view_update=0 fi if !0$thumb_view $0_rmn thumb_index_map $window_width,$thumb_height,1,3 100%,1,1,1,-1 => thumb_view,thumb_index_map # Retrieve index range of visible thumbnails. ind0,ind1={thumb_coords," find_ind(val) = ( ref(val,v); i0 = 0; i1 = h - 1; v<=i[i0]?i0:v>=i[i1]?i1:(while (i1 - i0>1, ic = int((i0 + i1)/2); i[ic]<=v?(i0 = ic):(i1 = ic)); i0) ); ind0 = find_ind($thumb_x_px); ind1 = find_ind($thumb_x_px + $window_width); [ ind0, ind1 ]"} # Render visible thumbnails. ind=$ind0 repeat $ind1-$ind0+1 { x,y,w,h={thumb_coords,I[$ind]} if {thumb_is_cached,i[$ind]} ${thumb_cache_$ind} r. $w,$h,1,100%,2 if {thumb_is_cached,i[$ind]==2} sh[thumb_3d_icon] 100% j.. [thumb_3d_icon],0.05~,0.95~,0,0,1,.,255 rm. fi else if {$ind,!w} # Empty image font_size:=cut($thumb_height/8,16,32) $w,$h,1,3,100 to. \330,0.42~,0.5~,$font_size,1,1,255 else # Generate 3D icon if necessary. if {is_mesh3d,i[$ind]<0} sh[$ind] if {is_rotated,i[$ind]} y. fi =[is_mesh3d] ${is_mesh3d.},0,$ind rm. fi if {is_mesh3d,i[$ind]} if !0$thumb_3d_icon box3d 35 col3d. 0,200,255 c3d. r3d. 1,1,1,45 64,64,1,3,-1 j3d. ..,50%,50%,0,1,3,0 rm.. +channels. 0 neq. -1 dilate. 3 *. 200 f.. "I!=J(-1) || I!=J(1) || I!=J(0,-1) || I!=J(0,1)?[0,0,0]:I+1" to.. 3D,0.5~,0.5~,24,1,1,255 a[-2,-1] c autocrop. r. 42,42,1,4,2 => thumb_3d_icon fi fi is_alpha={$ind,$setting_alpha" && "(s==2||s==4)} sh[$ind] 0,{$ind,$is_alpha?s-1:min(2,s-1)} if d>1 +rs3d. {ceil(min($w/(w+d+2),$h/(h+d+2))*[w,h,d])},1 volumetric2d. {round([w-(w==1),h-(h==1),d-(d==1)]/2)},2 rm.. fi if w>$w" || "h>$h +r. $w,$h else . fi rm.. if {is_rotated,i[$ind]} rotate. {is_rotated,-90*i[$ind]} fi if !$setting_normalization # Normalize thumb: none and. 255 elif $setting_normalization==1 # Normalize thumb: cut c. 0,255 else if {is_infnan,i[$ind]<0} =[is_infnan] ${_display_is_infnan[$ind]},0,$ind fi if $setting_normalization==2 # Normalize thumb: stretch (channelwise) if {is_infnan,i[$ind]} l. { s c $0_normalize_infnan a c } else l. { s c n 0,255 a c } fi else # Normalization thumb: stretch (global and global-once) if {is_infnan,i[$ind]} $0_normalize_infnan. else n. 0,255 fi fi fi r. {[$w,$h]-2} # -2: Keep space for frame if $is_alpha to_rgba. if $setting_alpha==1 (64,96;96,64) r. 16,16,1,1 r. $w,$h,1,3,0,2 else $w,$h,1,3,{arg($setting_alpha-1,0,128,255)} fi sh.. 100% j.. ...,0,0,0,0,1,.,255 rm[-3,-1] elif s==1 r. 100%,100%,1,3 elif s==2 r. 100%,100%,1,3,0 fi if {is_mesh3d,i[$ind]} sh[thumb_3d_icon] 100% j.. [thumb_3d_icon],0.05~,0.95~,0,0,1,.,255 rm. fi r. {[w,h]+2},1,100%,0,0,0.5,0.5 rectangle. 0,0,100%,100%,1,0x55555555,96 fi +store. thumb_cache_$ind =[thumb_is_cached] 1,0,$ind fi offx:=$x-$thumb_x_px line[thumb_index_map] {[$offx,0,$offx+w-1,0]},1,$ind j[thumb_view] .,$offx,$y rm. ind+=1 } # Decorate selected/mouse over thumbnail. x0,y0,w,h={thumb_coords,I[$selected]} x0-=$thumb_x_px x1,y1:=[$x0,$y0]+[$w,$h]-1 rectangle[thumb_view] $x0,$y0,$x1,$y1,1,0xFFFFFFFF,255 rectangle[thumb_view] {[[$x0,$y0]+1,[$x1,$y1]-1]},1,0xFFFFFFFF,255 rectangle[thumb_view] {[[$x0,$y0]+2,[$x1,$y1]-2]},1,0xFFFFFFFF,0 font_size:=cut($thumb_height/6,16,32) to[thumb_view] "#"{image_indices,i[$selected]},{[$x0,$y0]+1},$font_size,2 if $thumb_mouse_over>=0 x0,y0,w,h={thumb_coords,I[$thumb_mouse_over]} x0-=$thumb_x_px x1,y1:=[$x0,$y0]+[$w,$h]-1 rectangle[thumb_view] $x0,$y0,$x1,$y1,0.25,200 if $thumb_mouse_over!=$selected rectangle[thumb_view] $x0,$y0,$x1,$y1,0.75,0x33333333,0 rectangle[thumb_view] $x0,$y0,$x1,$y1,0.75,0xCCCCCCCC,255 to[thumb_view] "#"{image_indices,i[$thumb_mouse_over]},{[$x0,$y0]+1},$font_size,1,0.75 fi fi # Add left/right shading in thumb view. if $thumb_x_px>0 r[thumb_shade_left] 100%,$thumb_height sh[thumb_shade_left] 100% j[thumb_view] [thumb_shade_left],0,0,0,0,{min(1,$thumb_x_px/32)},.,255 rm. fi if $thumb_x_px+$window_width<$thumb_sumw r[thumb_shade_right] 100%,$thumb_height sh[thumb_shade_right] 100% j[thumb_view] [thumb_shade_right],1~,0,0,0,{min(1,($thumb_sumw-$window_width-$thumb_x_px)/32)},.,255 rm. fi $0_rmn window_view fi fi canvas_width,canvas_height=$window_width,{$window_height-$thumb_height2} # Render canvas background. if !0$canvas_background" || "{0$canvas_background,[w,h]!=[$canvas_width,$canvas_height]} $0_rmn canvas_background $canvas_width,$canvas_height,1,3,{arg0($setting_background>>1,0,64,128,192,255)} if $setting_background&1 amp:=$setting_background==1?32:12 16,16,1,1,-$amp +f. $amp a[-2,-1] x +mirror. x a[-2,-1] y r. $canvas_width,$canvas_height,1,3,0,2 +[-2,-1] c. 0,255 fi => canvas_background $0_rmn canvas_base,canvas_volumetric_background fi # Generate canvas base. if !0$canvas_base # Print information about image on stderr/stdout (if not already done). if {is_printed,$setting_print_images" && "!i[$selected]} =>[$selected] ${image_name$selected} v + _print[$selected] $1,0,{image_indices,i[$selected]} v - =>[$selected] _display$selected =[is_printed] 1,0,$selected fi # Determine size of the different XY,XZ,ZY,3D areas. if $is_volumetric if !narg($canvas_cross_x) w,h,d={$selected,i[#$is_rotated,$selected]%2?[h,w,d]:[w,h,d]} whd={$selected,`string($w,'_',$h,'_',$d)`} if narg(${view_coords_$whd}) canvas_cross_x,canvas_cross_y:=f=[${view_coords_$whd}];f[size(f)-2,2] else canvas_cross_x,canvas_cross_y={$selected,[min(0.95,w/(w+d)),min(0.95,h/(h+d))]} fi fi canvas_cross_x,canvas_cross_y:="[ cut("$canvas_cross_x",16/$canvas_width,($canvas_width-17)/$canvas_width), cut("$canvas_cross_y",16/$canvas_height,($canvas_height-17)/$canvas_height) ]" area_xy_width,area_xy_height,area_xz_width,area_xz_height,area_zy_width,area_zy_height,\ area_3d_width,area_3d_height:=" const w_ratio = "$canvas_cross_x"; const h_ratio = "$canvas_cross_y"; w_xy = round(w_ratio*$canvas_width) - 1; h_xy = round(h_ratio*$canvas_height) - 1; w_zy = $canvas_width - w_xy - 1; h_xz = $canvas_height - h_xy - 1; [ w_xy,h_xy,w_xy,h_xz,w_zy,h_xy,w_zy,h_xz ]" elif $is_mesh3d" && "$is_view3d area_xy_width,area_xy_height,area_xz_width,area_xz_height,area_zy_width,area_zy_height=0 area_3d_width,area_3d_height=$canvas_width,$canvas_height else area_xy_width,area_xy_height=$canvas_width,$canvas_height area_xz_width,area_xz_height,area_zy_width,area_zy_height,area_3d_width,area_3d_height=0 fi # Render canvas base image. if {$selected,!w} [canvas_background] => canvas_base elif $is_mesh3d" && "$is_view3d view_3d_draw_on_canvas=1 else is_alpha={$selected,$setting_alpha" && "(s==2||s==4)} view_base_channel={$selected,cut($view_base_channel,0,max(0,s-3))} cmin,cmax={$selected,$is_alpha?[0,s-1]:[$view_base_channel,min($view_base_channel+2,s-1)]} area_xy_x0,area_xy_y0,area_xy_x1,area_xy_y1,area_xy_dx,area_xy_dy,\ area_xz_x0,area_xz_z0,area_xz_x1,area_xz_z1,area_xz_dx,area_xz_dz,\ area_zy_z0,area_zy_y0,area_zy_z1,area_zy_y1,area_zy_dz,area_zy_dy={$selected," const x = $view_center_x*w; const y = $view_center_y*h; const z = $view_center_z*d; const w_xy = $area_xy_width; const h_xy = $area_xy_height; const w_xz = $area_xz_width; const h_xz = $area_xz_height; const w_zy = $area_zy_width; const h_zy = $area_zy_height; const mw4 = max(w,4); const mh4 = max(h,4); const md4 = max(d,4); const f_xy = max(mw4/w_xy,mh4/h_xy); $is_volumetric?( const f_xz = max(mw4/w_xz,md4/h_xz); const f_zy = max(md4/w_zy,mh4/h_zy); const f = 0.5*min(f_xy,f_xz,f_zy)/$view_zoom; ):(const f = 0.5*f_xy/$view_zoom); [ x - f*w_xy, y - f*h_xy, x + f*w_xy, y + f*h_xy, 2*f*w_xy, 2*f*h_xy, x - f*w_xz, z - f*h_xz, x + f*w_xz, z + f*h_xz, 2*f*w_xz, 2*f*h_xz, z - f*w_zy, y - f*h_zy, z + f*w_zy, y + f*h_zy, 2*f*w_zy, 2*f*h_zy ]"} # Generate and draw XY, XZ and YZ views. if $is_volumetric if !0$canvas_volumetric_background" || "\ {0$canvas_volumetric_background,[w,h]!=[$canvas_width,$canvas_height]} $0_rmn canvas_volumetric_background +j[canvas_background] [canvas_background],{$area_xy_width+2},0 j. [canvas_background],0,{$area_xy_height+2} rectangle. 0,$area_xy_height,100%,{$area_xy_height+1},1,0 rectangle. $area_xy_width,0,{$area_xy_width+1},100%,1,0 rectangle. {[$area_xy_width,$area_xy_height]+2},100%,100%,0.25,0 => canvas_volumetric_background fi [canvas_volumetric_background] => canvas_base else [canvas_background] => canvas_base fi repeat $is_volumetric?3:1 { if !$> # XY-view can_x0,can_y0,can_x1,can_y1,can_dx,can_dy,can_width,can_height=\ $area_xy_x0,$area_xy_y0,$area_xy_x1,$area_xy_y1,\ $area_xy_dx,$area_xy_dy,$area_xy_width,$area_xy_height w_sel,h_sel={$selected,[w,h]} plane={$selected,cut(round($view_center_z*d,1,-1),0,d-1)} joffx,joffy=0 elif $>==1 # XZ-view can_x0,can_y0,can_x1,can_y1,can_dx,can_dy,can_width,can_height=\ $area_xz_x0,$area_xz_z0,$area_xz_x1,$area_xz_z1,\ $area_xz_dx,$area_xz_dz,$area_xz_width,$area_xz_height w_sel,h_sel={$selected,[w,d]} plane={$selected,cut(round($view_center_y*h,1,-1),0,h-1)} joffx,joffy=0,{$area_xy_height+2} else # ZY-view can_x0,can_y0,can_x1,can_y1,can_dx,can_dy,can_width,can_height=\ $area_zy_z0,$area_zy_y0,$area_zy_z1,$area_zy_y1,\ $area_zy_dz,$area_zy_dy,$area_zy_width,$area_zy_height w_sel,h_sel={$selected,[d,h]} plane={$selected,cut(round($view_center_x*w,1,-1),0,w-1)} joffx,joffy={$area_xy_width+2},0 fi ix0,iy0,ix1,iy1:=floor([$can_x0,$can_y0,$can_x1,$can_y1]) # Crop coordinates ix0,ix1:=cut([$ix0,$ix1],0,$w_sel-1) iy0,iy1:=cut([$iy0,$iy1],0,$h_sel-1) dix,diy:=$ix1-$ix0+1,$iy1-$iy0+1 # Dimension of cropped image p0:=ceil(($ix0-$can_x0)/$can_dx*$can_width) q0:=ceil(($iy0-$can_y0)/$can_dy*$can_height) p1:=ceil(($ix1+1-$can_x0)/$can_dx*$can_width) q1:=ceil(($iy1+1-$can_y0)/$can_dy*$can_height) dp,dq:=max(1,$p1-$p0),max(1,$q1-$q0) # Dimension of resized image if {$selected,(w==1" || "h==1)" && "($view_zoom>4*$zoom_1_1" || "$zoom_1_1>1)} if $dp<8 joffx-=round((8-$dp)/2) dp=8 fi if $dq<8 joffy-=round((8-$dq)/2) dq=8 fi fi fact:=max($can_width/$can_dx,$can_height/$can_dy)*100 if $fact<100 # When image is larger than portion displayed on canvas: downsize before crop. sh[$selected] $cmin,$cmax if !$> if $is_volumetric +z. 0,0,$plane,100%,100%,$plane fi # XY elif $>==1 +z. 0,$plane,0,100%,$plane,100% permute. xzyc # XZ else +z. $plane,0,0,$plane,100%,100% permute. zyxc # ZY fi if $is_volumetric r. $fact%,$fact% else +r. $fact%,$fact% fi rm.. z. {floor([$ix0*w/$w_sel,$iy0*h/$h_sel,$ix1*w/$w_sel,$iy1*h/$h_sel])} else if !$> # XY-view +z[$selected] $ix0,$iy0,$plane,$cmin,$ix1,$iy1,$plane,$cmax elif $>==1 # XZ-view +z[$selected] $ix0,$plane,$iy0,$cmin,$ix1,$plane,$iy1,$cmax permute. xzyc else # ZY-view +z[$selected] $plane,$iy0,$ix0,$cmin,$plane,$iy1,$ix1,$cmax permute. zyxc fi fi if !$setting_normalization # Normalize: none and. 255 elif $setting_normalization==1 # Normalize: cut c. 0,255 else if {is_infnan,i[$selected]<0} =[is_infnan] ${$0_is_infnan[$selected]},0,$selected fi if $setting_normalization==2 # Normalize: stretch (channelwise) if {is_infnan,i[$selected]} l. { s c $0_normalize_infnan a c } else l. { s c n 0,255 a c } fi elif $setting_normalization==3 # Normalize: stretch (global) if {is_infnan,i[$selected]} $0_normalize_infnan. else n. 0,255 fi else # Normalize: stretch (global-once) if !narg(${display_range_global$selected}) if {is_infnan,i[$selected]} display_range_global$selected=${-_display_minmax_infnan} else display_range_global$selected={$selected,[im,iM]} fi fi im,iM:=${display_range_global$selected} if $im==$iM f. 0 else if {is_infnan,i[$selected]} $0_normalize_infnan. $im,$iM else -. $im *. {255/($iM-$im)} c. 0,255 fi fi fi fi r. $dp,$dq dx,dy,dl:=dx=$can_width/$can_dx;dy=$can_height/$can_dy;[dx,dy,max(dx,dy)] if $setting_is_grid" && "max($dx,$dy)>8 # Draw grid opacity,color:=lerp(0,0.5,min(($dl-8)/10,1)^2),ia<64?128:0 $dix,1,1,1,1 r. $dp,1,1,1,4 eval. "begin(col = $color); i || x==w - 1?polygon(#-2,2,x,0,x,$dq-1,$opacity,col,col,col,255)" rm. $diy,1,1,1,1 r. $dq,1,1,1,4 eval. "begin(col = $color); i || x==w - 1?polygon(#-2,2,0,x,$dp-1,x,$opacity,col,col,col,255)" rm. fi if $is_volumetric # Slightly crop to remove extra borders if $p0<0 cx0,p0={-$p0},0 else cx0=0 fi if $q0<0 cy0,q0={-$q0},0 else cy0=0 fi if $p1>$can_width cx1:=$can_width+w-$p1-1 else cx1=100% fi if $q1>$can_height cy1:=$can_height+h-$q1-1 else cy1=100% fi z. $cx0,$cy0,$cx1,$cy1 fi np0,nq0:=[$p0,$q0]+[$joffx,$joffy] if $is_alpha to_rgba. if $setting_alpha==1 coords:=$np0-1,$nq0-1,$np0+w,$nq0+h rectangle[canvas_base] $coords,0.5,0x0F0F0F0F,0 # Draw image boundaries rectangle[canvas_base] $coords,0.5,0xF0F0F0F0,255 sh. 100% j[canvas_base] ..,$np0,$nq0,0,0,1,.,255 rm. else 100%,100%,1,3,{arg($setting_alpha-1,0,128,255)} sh.. 100% j.. ...,0,0,0,0,1,.,255 rm[-3,-1] j[canvas_base] .,$np0,$nq0 fi else if s==1 r. 100%,100%,1,3 elif s==2 r. 100%,100%,1,3,0 fi j[canvas_base] .,$np0,$nq0 fi rm. } # Draw central cursor for volumetric images. if $is_volumetric w2,h2:=int([$area_xy_width,$area_xy_height]/2) line[canvas_base] 0,$h2,100%,$h2,0.5,0x55555555,0 line[canvas_base] 0,$h2,100%,$h2,0.5,0xAAAAAAAA,255 line[canvas_base] $w2,0,$w2,100%,0.5,0x55555555,0 line[canvas_base] $w2,0,$w2,100%,0.5,0xAAAAAAAA,255 w2,h2:=[$area_xy_width,$area_xy_height]+2+int([$area_zy_width,$area_xz_height]/2) line[canvas_base] 0,$h2,$area_xy_width,$h2,0.5,0x55555555,0 line[canvas_base] 0,$h2,$area_xy_width,$h2,0.5,0xAAAAAAAA,255 line[canvas_base] $w2,0,$w2,$area_xy_height,0.5,0x55555555,0 line[canvas_base] $w2,0,$w2,$area_xy_height,0.5,0xAAAAAAAA,255 if $is_view3d view_3d_draw_on_canvas=1 fi fi fi $0_rmn canvas_label_coords fi # Generate 3D mesh objects. if $is_view3d if $setting_3d_is_axes" && "!0$area_3d_axes" && "!$is_volumetric axes3d 40,40,40,20,X,Y,Z,0 col3d. 0,255,0 l. { s3d a[0-3] y area_3d_axes_offset={0,h} a y } => area_3d_axes fi if !0$area_3d_mesh if $is_volumetric +projections3d[$selected] {$selected,floor([$view_center_x*w,$view_center_y*h,$view_center_z*d])},1,10 asiz={$selected,max(w,h,d)/3} axes3d $asiz,$asiz,$asiz,16 o3d. 0.5 col3d. 255,128,0 +3d[-2,-1] else [$selected] if {is_rotated,i[$selected]} rotate. {is_rotated,-90*i[$selected]} fi fi c3d. n3d. => area_3d_mesh $0_rmn area_3d_mesh_notexture,area_3d_bounding_box,area_3d_view fi if !0$area_3d_bounding_box" && "(\ $setting_3d_is_bounding_box" || "\ ($area_3d_is_motion" && "$setting_3d_motion_rendering_mode<0" && "!$is_volumetric)) +boundingbox3d[area_3d_mesh] => area_3d_bounding_box if $setting_3d_is_bounding_box +o3d[area_3d_bounding_box] 0.35 +3d[area_3d_mesh,-1] fi fi if $setting_3d_outline_mode" && "!0$area_3d_mesh_notexture +l[area_3d_mesh] { p3d 2 nbp:=f2ui(i[7]) if $nbp 1,$nbp,1,1,y j.. .,0,{-2,h-4*$nbp} j.. .,0,{-2,h-3*$nbp} j.. .,0,{-2,h-2*$nbp} rm. fi } => area_3d_mesh_notexture fi # Render and draw 3D mesh object. if !0$area_3d_view if !0$canvas_3d_background" || "{0$canvas_3d_background,[w,h]!=[$area_3d_width,$area_3d_height]} $0_rmn canvas_3d_background background,is_checkerboard:=int([$setting_3d_background>>1,$setting_3d_background&1]) if $background<3 {*,w,h},1,3,{arg(1+$background,0,128,255)} elif $background==3 3,2,1,1,"16,16,48,44,96,76" permute. cyzx elif $background==4 3,2,1,1,"0,0,0,0,64,96" permute. cyzx else 3,3,1,1,"0,0,0,0,0,100,100,0,50" permute. cyzx fi r. $area_3d_width,$area_3d_height,1,3,3 if $is_checkerboard 16,16,1,1,48 +f. 64 a[-2,-1] x +mirror. x a[-2,-1] y r. {-2,[w,h]},1,3,0,2 +[-2,-1] /. 2 fi round. => canvas_3d_background fi [canvas_3d_background] => area_3d_view if !0$view_3d_pose ($_display_3d_pose) r. 4,3,1,1,-1 => view_3d_pose view_3d_zoom=$_display_3d_zoom fi is_bounding_box_only:=$area_3d_is_motion" && "$setting_3d_motion_rendering_mode<0" && "!$is_volumetric if $is_bounding_box_only [area_3d_bounding_box] else [area_3d_mesh] fi if $setting_3d_side_mode==2 rv3d. fi # Apply pose to 3D mesh. nbv:=f2ui(i[6]) if $nbv +z. 0,8,0,{7+3*$nbv} r. 3,$nbv,1,1,-1 permute. yzcx fact:=min($area_3d_width,$area_3d_height) eval. "begin( R = crop(#$view_3d_pose,0,0,3,3); S = crop(#$view_3d_pose,3,0,1,3); R*=$view_3d_zoom*$fact; S*=$fact; ); copy(i[#-2,8+3*x],(R*I)+=S)" rm. else fact=1 fi if $is_bounding_box_only j3d[area_3d_view] .,50%,50%,0,0.5,1,0,0,{1.5*$fact} else time0=$| j3d[area_3d_view] .,50%,50%,0,1,\ {$is_volumetric?[2,1,1,1.5*$fact]:[$area_3d_is_motion?min($setting_3d_motion_rendering_mode,\ $setting_3d_rendering_mode):\ $setting_3d_rendering_mode,\ $setting_3d_side_mode==1,\ $setting_3d_is_zbuffer,\ $setting_3d_focale*$fact]} if $setting_3d_outline_mode" && "!$is_volumetric" && "$setting_3d_rendering_mode>1 # Draw primitive outlines +z. 0,8,0,{7+3*$nbv} j[area_3d_mesh_notexture] .,0,8 rm. [area_3d_view],[area_3d_view],1,1,-1 j3d. [area_3d_mesh_notexture],50%,50%,0,1,2,{$setting_3d_side_mode==1},$setting_3d_is_zbuffer,\ {$setting_3d_focale*$fact} g. xy,1 !=[-2,-1] 0 or[-2,-1] 100%,100%,1,3 fc. ${"arg "$setting_3d_outline_mode,\ "\"0,0,0\",\"128,128,128\",\"255,0,0\",\"0,128,0\",\"0,0,255\",\"255,255,255\""} j[area_3d_view] .,0,0,0,0,0.5,.. rm[-2,-1] fi dtime:=$|-$time0 if $area_3d_is_motion" && "1000*$dtime>$setting_3d_motion_time_limit setting_3d_motion_rendering_mode=-1 fi fi rm. if $setting_3d_is_axes" && "!$is_volumetric [area_3d_axes] nbv={@6} +z. 0,8,0,{7+3*$nbv} r. 3,$nbv,1,1,-1 permute. yzcx # Apply pose to 3D axes eval. "begin(R = crop(#$view_3d_pose,0,0,3,3)); copy(i[#-2,8+3*x],R*I)" rm. eval " # Colorize axes depending on their Z-sign const off = "$area_3d_axes_offset"; col = [ 255,0,0 ]; i[13]>0?copy(i[off],col,3); i[19]>0?copy(i[off + 3],col,3); i[25]>0?copy(i[off + 6],col,3)" j3d[area_3d_view] .,50,{area_3d_view,h-50},0,0.75,1,0,0,150 rm. fi if {thumb_is_cached,$thumb_is_visible" && "i[$selected]<2" && "!$is_volumetric} w,h,Mwh={$thumb_coords,wh=I[$selected][2,2];[wh,max(wh,256)]} +rs[area_3d_view] $Mwh,$Mwh,2,3 store. 1,thumb_cache_$selected =[thumb_is_cached] 2,0,$selected thumb_view_update,window_no_wait=1 fi view_3d_draw_on_canvas=1 fi if $view_3d_draw_on_canvas" && "0$area_3d_view if $is_mesh3d # Small hack to avoid image copy $0_rmn canvas_base =>[area_3d_view] canvas_base 0 => area_3d_view else j[canvas_base] [area_3d_view],{[$area_xy_width,$area_xy_height]+($is_volumetric?2:0)} fi view_3d_draw_on_canvas=0 $0_rmn canvas_view fi fi # Generate label for image title. if !0$canvas_label_info 0 basename {``${image_name$selected}} if ['{/${}}']!=0 ({'{/${}}'}) else ('{``${image_name$selected}}') fi basename={`"b = crop(); size(b)>32?copy(b[32-3],[['...'],0]); b"`} rm. if $nb_images==1" && "!$is_fullscreen str="["{image_indices,i[$l_ind]}"] "$basename" " if $is_mesh3d" && "$is_view3d str.="("{$selected,f2ui(i[6])}" vertices, "{$selected,f2ui(i[7])}" primitives)" else str.="("{$selected,[w,h,d,s]}")" fi w[] -1,-1,$str else str= if $thumb_mouse_over>=0" && "$thumb_mouse_over!=$selected basename {``${image_name$thumb_mouse_over}} ('${}') codes_nm={^} rm. basename_mo={`"b = ["$codes_nm"]; size(b)>32?copy(b[32-3],[['...'],0]); b"`} str.="#"{image_indices,i[$thumb_mouse_over]}": " if {is_mesh3d,$is_view3d" && "i[$thumb_mouse_over]} str.=$basename_mo" ("\ {$thumb_mouse_over,f2ui(i[6])}" vertices, "{$thumb_mouse_over,f2ui(i[7])}" primitives)\n" else whds={$thumb_mouse_over,i[#$is_rotated,$thumb_mouse_over]%2?[h,w,d,s]:[w,h,d,s]} str.=$basename_mo" ("{``$whds}")\n" fi fi if $nb_images>1 str.="#"{image_indices,i[$selected]}": " fi if $is_mesh3d" && "$is_view3d str.=$basename" ("\ {$selected,f2ui(i[6])}" vertices, "{$selected,f2ui(i[7])}" primitives)" else whds={$selected,i[#$is_rotated,$selected]%2?[h,w,d,s]:[w,h,d,s]} str.=$basename" ("{``$whds}")" fi font_size:=cut(round($canvas_height/40),16,32) t. {``$str},0,0,$font_size,1,255 fi => canvas_label_info $0_rmn canvas_label fi # Generate label for image coordinates. if !0$canvas_label_coords" && "{$selected,w} 0 view_coords= str= x,y,z={$selected,"floor( $is_area2?[ lerp($area_xy_x0,$area_xy_x1,$area_mouse_x/$area_xy_width), lerp($area_xy_y0,$area_xy_y1,$area_mouse_y/$area_xy_height), cut($view_center_z*d,0,d - 1) ]: $is_area3?[ lerp($area_xz_x0,$area_xz_x1,$area_mouse_x/$area_xz_width), cut($view_center_y*h,0,h - 1), lerp($area_xz_z0,$area_xz_z1,$area_mouse_y/$area_xz_height) ]: $is_area4?[ cut($view_center_x*w,0,w - 1), lerp($area_zy_y0,$area_zy_y1,$area_mouse_y/$area_zy_height), lerp($area_zy_z0,$area_zy_z1,$area_mouse_x/$area_zy_width) ]: [ -1,-1,-1 ])"} view_xyz=$x,$y,$z view_xy_disp={is_rotated,"const w1 = w#$selected - 1; const h1 = h#$selected - 1; ang = i[$selected]; !ang?[ $x, $y ]:ang==1?[ $y, w1 - $x ]:ang==2?[ w1 - $x, h1 - $y ]:[ h1 - $y, $x ]"} if narg($canvas_crop_x0)" || "narg($canvas_rect_x0) if narg($canvas_crop_x0) x0,y0,z0,x1,y1,z1=$canvas_crop_x0,$canvas_crop_y0,$canvas_crop_z0,$view_xyz else x0,y0,z0,x1,y1,z1=$canvas_rect_x0,$canvas_rect_y0,0,$canvas_rect_x1,$canvas_rect_y1,0 fi if $x0>$x1 x0,x1=$x1,$x0 fi if $y0>$y1 y0,y1=$y1,$y0 fi if $z0>$z1 z0,z1=$z1,$z0 fi dx,dy,dz:=$x1-$x0,$y1-$y0,$z1-$z0 view_xy0_disp={is_rotated,"const w1 = w#$selected - 1; const h1 = h#$selected - 1; ang = i[$selected]; !ang?[ $x0, $y0 ]:ang==1?[ $y0, w1 - $x0 ]:ang==2?[ w1 - $x0, h1 - $y0 ]:[ h1 - $y0, $x0 ]"} view_xy1_disp={is_rotated,"const w1 = w#$selected - 1; const h1 = h#$selected - 1; ang = i[$selected]; !ang?[ $x1, $y1 ]:ang==1?[ $y1, w1 - $x1 ]:ang==2?[ w1 - $x1, h1 - $y1 ]:[ h1 - $y1, $x1 ]"} view_dxy_disp={is_rotated,(i[$selected]%2?[$dy,$dx]:[$dx,$dy])+1} if $is_volumetric str="Box ( "$view_xy0_disp,$z0" ) - ( "$view_xy1_disp,$z1" ), Size = ( "$view_dxy_disp,{$dz+1}" )"\n\ "Length = "{_norm($dx,$dy,$dz)} else str="Box ( "$view_xy0_disp" ) - ( "$view_xy1_disp" ), Size = ( "$view_dxy_disp" )"\n\ "Length = "{_norm($dx,$dy)}", Angle = "{_rad2deg(atan2($y1-$y0,$x1-$x0))%360}"\260" fi else if $is_volumetric view_xyz_disp=$view_xy_disp,$z else view_xyz_disp=$view_xy_disp fi if {$selected,inrange($x,0,w,1,0)" && "inrange($y,0,h,1,0)" && "inrange($z,0,d,1,0)} s_value={`"val = I(#$selected,$x,$y,$z); size(val)<=6?v2s(val,-1):( vals = vale = vector3(); copy(vals,val,3); copy(vale,val[size(val)-3],3); string(#256,v2s(vals,-1),', ... ,',v2s(vale,-1)); )"`} s_hex= if {$selected,"s>4?0:(val = I($x,$y,$z); min(isint(val,0,255)))"} s_hex=" = \#"{$selected,`" to_hex(x) = (ref(x,_x); _x<10?_'0'+_x:_'A'+_x-10); res = vector(#2*s); off = 0; repeat (s,k, val = i($x,$y,$z,k); res[off++] = to_hex(val>>4); res[off++] = to_hex(val&15)); res"`} fi if $is_volumetric str="Point ( "$view_xyz_disp" ) = [ "$s_value" ]"$s_hex else str="Point ( "$view_xyz_disp" ) = [ "$s_value" ]"$s_hex fi fi fi font_size:=cut(round($canvas_height/40),16,32) t. {``$str},0,0,$font_size,1,255 => canvas_label_coords $0_rmn canvas_label fi # Generate label for notification. if !0$canvas_label_notification" && "['$notification']!=0 font_size:=cut(round($window_height/40),16,32) 0 t. {``$notification},0,0,{round(1.25*$font_size)},1,255 r. {[w,h]+20},1,1,0,0,0.5,0.5 frame. xy,2,255 r. {[w,h]+6},1,1,0,0,0.5,0.5 +n. 200,255 r.. 100%,100%,1,3 a[-2,-1] c => canvas_label_notification notification_opacity,notification=3, $0_rmn window_view fi # Render canvas label. if !0$canvas_label 0 if $setting_is_info if $canvas_label_info a. [canvas_label_info],y fi if $canvas_label_coords a. [canvas_label_coords],y fi fi if w r. {[w,h]+10},1,1,0,0,0.5,0.5 +n. 150,255 r.. 100%,100%,1,3 a[-2,-1] c fi => canvas_label $0_rmn canvas_view fi if $window_record_frame>=0 $0_rmn canvas_view fi if !0$canvas_view [canvas_base] => canvas_view if {$selected,!w}" || "$is_area1 cursor[0] 1 else # Draw cursor. ix,iy,iz={$selected," $is_area2?[ lerp($area_xy_x0,$area_xy_x1,$area_mouse_x/$area_width), # XY lerp($area_xy_y0,$area_xy_y1,$area_mouse_y/$area_height), cut($view_center_z*d,0,d - 1) ]: $is_area3?[ lerp($area_xz_x0,$area_xz_x1,$area_mouse_x/$area_width), # XZ cut($view_center_y*h,0,h - 1), lerp($area_xz_z0,$area_xz_z1,$area_mouse_y/$area_height) ]: $is_area4?[ cut($view_center_x*w,0,w - 1), lerp($area_zy_y0,$area_zy_y1,$area_mouse_y/$area_height), # ZY lerp($area_zy_z0,$area_zy_z1,$area_mouse_x/$area_width) ]: [ -1,-1,-1 ]"} rix,riy,riz:=floor([$ix,$iy,$iz]); is_cursor_over_image={$selected,(!$is_view3d" || "$is_volumetric)" && "\ (inrange($rix,0,w-1)" && "inrange($riy,0,h-1)" && "inrange($riz,0,d-1))} if {$selected,!$is_cursor_over_image" || "!$setting_cursor" || "($setting_cursor==1" && "$is_volumetric)} cursor[0] 1 else cursor[0] 0 wxy2,hxy2:=[$area_xy_width,$area_xy_height]+2 repeat $is_volumetric?3:1 { cx,cy:=!$>?[($ix-$area_xy_x0)/$area_xy_dx*$area_xy_width,\ ($iy-$area_xy_y0)/$area_xy_dy*$area_xy_height]:\ $>==1?[($ix-$area_xz_x0)/$area_xz_dx*$area_xz_width,\ ($iz-$area_xz_z0)/$area_xz_dz*$area_xz_height]:\ [($iz-$area_zy_z0)/$area_zy_dz*$area_zy_width,\ ($iy-$area_zy_y0)/$area_zy_dy*$area_zy_height] if !$> w1,h1:=[$area_xy_width,$area_xy_height]-1 line[canvas_view] 0,$cy,$w1,$cy,0.65,0x00FF00FF,0 line[canvas_view] 0,$cy,$w1,$cy,0.65,0xFF00FF00,255 line[canvas_view] $cx,0,$cx,$h1,0.65,0x00FF00FF,0 line[canvas_view] $cx,0,$cx,$h1,0.65,0xFF00FF00,255 elif $>==1 w1,h1:=[$area_xz_width,$area_xz_height]-1 cy+=$hxy2 if $cy>=$hxy2 line[canvas_view] 0,$cy,$w1,$cy,0.65,0x00FF00FF,0 line[canvas_view] 0,$cy,$w1,$cy,0.65,0xFF00FF00,255 fi line[canvas_view] $cx,$hxy2,$cx,100%,0.65,0x00FF00FF,0 line[canvas_view] $cx,$hxy2,$cx,100%,0.65,0xFF00FF00,255 elif $>==2 w1,h1:=[$area_zy_width,$area_zy_height]-1 cx+=$wxy2 line[canvas_view] $wxy2,$cy,100%,$cy,0.65,0x00FF00FF,0 line[canvas_view] $wxy2,$cy,100%,$cy,0.65,0xFF00FF00,255 if $cx>=$wxy2 line[canvas_view] $cx,0,$cx,$h1,0.65,0x00FF00FF,0 line[canvas_view] $cx,0,$cx,$h1,0.65,0xFF00FF00,255 fi fi } fi if $is_area234" && "$is_cursor_over_image repeat $is_volumetric?3:1 { dx,dy:=!$>?[$area_xy_width/$area_xy_dx,$area_xy_height/$area_xy_dy]:\ $>==1?[$area_xz_width/$area_xz_dx,$area_xz_height/$area_xz_dz]:\ [$area_zy_width/$area_zy_dz,$area_zy_height/$area_zy_dy] dl:=max($dx,$dy) if $dl>8 # Draw outline of pixel/voxel under cursor if zoomed enough opacity:=lerp(0,1,min(($dl-8)/10,1)) cx,cy:=!$>?[($rix-$area_xy_x0)/$area_xy_dx*$area_xy_width,\ ($riy-$area_xy_y0)/$area_xy_dy*$area_xy_height]:\ $>==1?[($rix-$area_xz_x0)/$area_xz_dx*$area_xz_width,\ ($riz-$area_xz_z0)/$area_xz_dz*$area_xz_height]:\ [($riz-$area_zy_z0)/$area_zy_dz*$area_zy_width,\ ($riy-$area_zy_y0)/$area_zy_dy*$area_zy_height] ncx,ncy:=$cx+$dx,$cy+$dy cx,cy,ncx,ncy:=ceil([$cx,$cy,$ncx-1,$ncy-1]) if $setting_is_grid cx+=1 cy+=1 fi if $>==1 cy,ncy+=$area_xy_height+2 elif $>==2 cx,ncx+=$area_xy_width+2 fi if $is_volumetric # Crop cursor properly if needed, for volumetric images +z[canvas_view] $cx,$cy,$ncx,$ncy rectangle. 0,0,100%,100%,$opacity,0x33333333,0 rectangle. 0,0,100%,100%,$opacity,0xCCCCCCCC,255 if isin($>,0,1)" && "$ncx>=$area_xy_width x:=w-2-$ncx+$area_xy_width z. 0,0,$x,100% fi if isin($>,0,2)" && "$ncy>=$area_xy_height y:=h-2-$ncy+$area_xy_height z. 0,0,100%,$y fi if $>==1" && "$cy<$area_xy_height+2 y,cy:=a=$area_xy_height+2;[a-$cy,a] if $y==2" && "$cx<$area_xy_width+2 x,cx:=a=$area_xy_width+2;[a-$cx,a] if $x?[($canvas_crop_x0-$area_xy_x0)/$area_xy_dx*$area_xy_width,\ ($canvas_crop_y0-$area_xy_y0)/$area_xy_dy*$area_xy_height]:\ $>==1?[($canvas_crop_x0-$area_xz_x0)/$area_xz_dx*$area_xz_width,\ ($canvas_crop_z0-$area_xz_z0)/$area_xz_dz*$area_xz_height]:\ [($canvas_crop_z0-$area_zy_z0)/$area_zy_dz*$area_zy_width,\ ($canvas_crop_y0-$area_zy_y0)/$area_zy_dy*$area_zy_height] cx1,cy1:=!$>?[($rix-$area_xy_x0)/$area_xy_dx*$area_xy_width,\ ($riy-$area_xy_y0)/$area_xy_dy*$area_xy_height]:\ $>==1?[($rix-$area_xz_x0)/$area_xz_dx*$area_xz_width,\ ($riz-$area_xz_z0)/$area_xz_dz*$area_xz_height]:\ [($riz-$area_zy_z0)/$area_zy_dz*$area_zy_width,\ ($riy-$area_zy_y0)/$area_zy_dy*$area_zy_height] cx1,cy1:=$cx1+$dx,$cy1+$dy cx0,cy0,cx1,cy1:=ceil([$cx0,$cy0,$cx1-1,$cy1-1]) if $>==1 cy0,cy1+=$area_xy_height+2 elif $>==2 cx0,cx1+=$area_xy_width+2 fi rectangle[canvas_view] $cx0,$cy0,$cx1,$cy1,0.5,128 rectangle[canvas_view] $cx0,$cy0,$cx1,$cy1,0.75,0x33333333,0 rectangle[canvas_view] $cx0,$cy0,$cx1,$cy1,0.75,0xCCCCCCCC,255 line[canvas_view] $cx0,$cy0,$cx1,$cy1,0.75,0xF0F0F0F0,0 line[canvas_view] $cx0,$cy0,$cx1,$cy1,0.75,0x0F0F0F0F,255 fi } fi # Draw rectangular selection. if $is_area2" && "narg($canvas_rect_x0)" && "!narg($canvas_crop_x0) x0:=($canvas_rect_x0-$area_xy_x0)/$area_xy_dx*$canvas_width y0:=($canvas_rect_y0-$area_xy_y0)/$area_xy_dy*$canvas_height x1:=($canvas_rect_x1-$area_xy_x0)/$area_xy_dx*$canvas_width y1:=($canvas_rect_y1-$area_xy_y0)/$area_xy_dy*$canvas_height if $x0>$x1 x0,x1=$x1,$x0 fi if $y0>$y1 y0,y1=$y1,$y0 fi x0,y0,x1,y1:=ceil([$x0,$y0,$x1,$y1]) x1+=$canvas_width/$area_xy_dx-1 y1+=$canvas_height/$area_xy_dy-1 if $is_setting_is_grid x0+=1 x1+=1 fi rectangle[canvas_view] $x0,$y0,$x1,$y1,0.5,128 rectangle[canvas_view] $x0,$y0,$x1,$y1,0.75,0x33333333,0 rectangle[canvas_view] $x0,$y0,$x1,$y1,0.75,0xCCCCCCCC,255 dx,dy:=0.5*[$canvas_width,$canvas_height]/[$area_xy_dx,$area_xy_dy] x0,y0,x1,y1+=$dx,$dy,-$dx+1,-$dy+1 line[canvas_view] $x0,$y0,$x1,$y1,0.75,0xF0F0F0F0,0 line[canvas_view] $x0,$y0,$x1,$y1,0.75,0x0F0F0F0F,255 fi fi # Draw area cross button (volumetric images). if $is_volumetric if !narg($canvas_cross_button) 21,21,1,4 circle. 50%,50%,10,1,0,0,0,255 circle. 50%,50%,8,1,200,200,200,255 => canvas_cross_button fi sh[canvas_cross_button] 100% j[canvas_view] [canvas_cross_button],\ {canvas_cross_button,round([$canvas_cross_x*$canvas_width-w/2,$canvas_cross_y*$canvas_height-h/2])},\ 0,0,0.5,.,255 rm. fi # Draw labels. if {canvas_label,w} sh[canvas_label] 100% j[canvas_view] [canvas_label],0,$canvas_label_alignment~,0,0,1,.,255 rm. fi $0_rmn window_view fi # Render global view. if !0$window_view $window_width,$window_height,1,3 => window_view if $thumb_is_visible j[window_view] [thumb_view] fi j[window_view] [canvas_view],0,$thumb_height2 # Record window frames. if $window_record_frame>=0 if !$window_record_frame # First frame: determine filename if $is_opencv repeat 256 { filename_animation=${"filename gmic.mp4,"$>} if !isfile('$filename_animation') break fi } else repeat 256 { filename_animation=${"filename gmic.jpg,"$>,0} if !isfile('$filename_animation') filename_animation=${"filename gmic.jpg",$>} break fi } fi notification="Start recording: "$filename_animation $0_rmn canvas_label_notification fi font_size:=cut(round($canvas_height/40),16,32) if $is_opencv if {window_view,max([w,h]%2)} # One dimension is odd -> resize frame +r[window_view] {[w,h]+([w,h]%2)},1,100%,0 o. $filename_animation,20,0,1 rm. else o[window_view] $filename_animation,20,0,1 fi else o[window_view] ${"filename "$filename_animation,$window_record_frame},75 fi 0 t. "Frame \#"$window_record_frame,0,0,$font_size,1,255 +dilate. 3 !=. 0 r.. 100%,100%,1,3 j[window_view] ..,{window_view,[w,h]-[w#-1,h#-1]-5},0,0,0.75,.,1 rm[-2,-1] window_record_frame+=1 window_no_wait=1 fi if $canvas_label_notification sh[canvas_label_notification] 100% j[window_view] [canvas_label_notification],1~,0,0,0,{min(1,$notification_opacity)},.,255 rm. window_no_wait=1 fi w[window_view] fi if $window_no_wait wait 40 else wait fi # Convert low-levels events to higher-level (buttons / drag). is_CTRL,is_SHIFT:={*,CTRLLEFT}" || "{*,CTRLRIGHT},{*,SHIFTLEFT}" || "{*,SHIFTRIGHT} previous_mouse_x,previous_mouse_y,previous_mouse_area=$mouse_x,$mouse_y,$mouse_area mouse_x,mouse_y,mouse_button={*,x,y,b} mouse_area,canvas_mouse_x,canvas_mouse_y:=" const x = $mouse_x; const y = $mouse_y; const ht = $thumb_height; const ht2 = $thumb_height2; x<0?[0,-1,-1]: # Mouse area = 0: cursor outside window y}" && "$mouse_area" && "!${is_clicked$>} # Possible start of a drag is_clicked$>,is_released$>,is_drag$>=$mouse_area,0,0 # At this point, cannot ensure drag has started drag_start_x$>,drag_start_y$>,drag_end_x$>,drag_end_y$>=$mouse_x,$mouse_y,$mouse_x,$mouse_y elif ${is_button$>}" && "$mouse_area" && "${is_clicked$>} # Possible update of a drag drag_end_x$>,drag_end_y$>=$mouse_x,$mouse_y if !${is_drag$>} # Determine if that is really a drag is_drag$>:=norm(${drag_end_x$>}-${drag_start_x$>},${drag_end_y$>}-${drag_start_y$>})>5?$mouse_area:0 fi elif !${is_button$>}" && "$mouse_area" && "${is_clicked$>} # Possible end of a drag is_clicked$>,is_released$>=0,$mouse_area drag_end_x$>,drag_end_y$>=$mouse_x,$mouse_y # 'is_drag$>' is not reset to 0 to ease drag end detection (must be then zero-ed manually afterwards). elif $mouse_area" && "!${is_clicked$>} is_drag$>=0 fi } # Manage events for thumbnail navigation. previous_selected=$selected if $thumb_is_visible if $is_drag0==1" && "$is_clicked0==1 n_thumb_x:=cut($thumb_x-($drag_end_x0-$drag_start_x0)/$thumb_sumw,-16/$thumb_sumw,$thumb_x_px_max/$thumb_sumw) if $n_thumb_x!=$thumb_x thumb_x=$n_thumb_x drag_start_x0=$drag_end_x0 $0_rmn thumb_view fi elif $is_drag0==1" && "($is_released0" || "!$mouse_area) is_drag0,is_released0=0 fi if $is_area1 canvas_label_alignment=0 ind={thumb_index_map,i[$mouse_x]} if $ind>=0 if $ind!=$thumb_mouse_over thumb_mouse_over=$ind $0_rmn thumb_view,canvas_label_info fi if !$is_drag0" && "$is_released0 if $selected!=$ind selected=$ind $0_rmn thumb_view,canvas_label_info fi fi fi else if $thumb_mouse_over>=0 $0_rmn thumb_view,canvas_label_info fi thumb_mouse_over=-1 fi fi if $nb_images>1 if (($is_area1" || "$is_CTRL)" && "(\ {*,ARROWLEFT}" || "{*,ARROWRIGHT}" || "{*,ARROWUP}" || "{*,ARROWDOWN}" || "\ {*,PAGEUP}" || "{*,PAGEDOWN}" || "{*,HOME}" || "{*,END}" || "{*,H}" || "{*,E}\ ))" || "{*,SPACE}" || "{*,BACKSPACE}" || "{*,B} step:={*,-ARROWLEFT}||{*,-ARROWUP}||{*,BACKSPACE}||{*,B}?($is_SHIFT?-4:-1):\ {*,-ARROWRIGHT}||{*,-ARROWDOWN}||{*,SPACE}?($is_SHIFT?4:1):\ {*,-PAGEUP}?-max(2,round($nb_images/16))*($is_SHIFT?4:1):\ {*,-PAGEDOWN}?max(2,round($nb_images/16))*($is_SHIFT?4:1):\ {*,-HOME}||{*,-H}?-$selected:\ {*,-END}||{*,-E}?$nb_images-1-$selected:0 if {*,-BACKSPACE}" || "{*,-SPACE}" || "{*,-B} selected:=($selected+$step)%$nb_images else selected:=cut($selected+$step,0,$nb_images-1) fi $0_rmn thumb_view,canvas_label_info elif ($is_area1" || "$is_CTRL)" && "\ ({*,0}" || "{*,1}" || "{*,2}" || "{*,3}" || "{*,4}" || "\ {*,5}" || "{*,6}" || "{*,7}" || "{*,8}" || "{*,9}" || "\ {*,PAD0}" || "{*,PAD1}" || "{*,PAD2}" || "{*,PAD3}" || "{*,PAD4}" || "\ {*,PAD5}" || "{*,PAD6}" || "{*,PAD7}" || "{*,PAD8}" || "{*,PAD9}) ind=-1 repeat 10 { if {*,$>}" || "{*,PAD$>} ind=$> break fi } if inrange($ind,0,$nb_images,1,0)" && "$selected!=$ind selected=$ind $0_rmn thumb_view,canvas_label_info skip {*,-PAD$ind},{*,-$ind} fi fi if $is_area1 if {*,o} step:={*,-o}*max(1,round($nb_images/32)) selected:=cut($selected+$step,0,$nb_images-1) $0_rmn thumb_view,canvas_label_info elif !$is_CTRL" && "{*,C} # C: Center thumbnail view selected:=floor($nb_images/2) $0_rmn thumb_view,canvas_label_info fi fi # Do stuffs when new thumbnail has been selected. if $selected!=$previous_selected canvas_crop_x0,canvas_crop_y0,canvas_crop_z0,canvas_rect_x0,canvas_rect_y0,canvas_rect_x1,canvas_rect_y1= drag_start_x0,drag_start_y0,drag_end_x0,drag_end_y0= is_clicked0,is_clicked1,is_clicked2,is_release0,is_release1,is_release2,is_drag0,is_drag1,is_drag2=0 # Ensure that selected thumbnail becomes visible. if $thumb_is_visible x0,w={thumb_coords,[i(0,$selected,0,0),i(0,$selected,0,2)]} if $x0<$thumb_x_px thumb_x:=($x0-48)/$thumb_sumw elif $x0+$w>=$thumb_x_px+$window_width thumb_x:=($x0+$w-$window_width+48)/$thumb_sumw fi fi # Remember view properties for each different image resolution. pw,ph,pd={$previous_selected,i[#$is_rotated,$previous_selected]%2?[h,w,d]:[w,h,d]} w,h,d={$selected,i[#$is_rotated,$selected]%2?[h,w,d]:[w,h,d]} if [$w,$h,$d]!=[$pw,$ph,$pd] whd={$previous_selected,`string($pw,'_',$ph,'_',$pd)`} view_coords_$whd=$view_center_x,$view_center_y,$view_center_z,$view_zoom,$view_base_channel,\ $view_is_rotated,$canvas_cross_x,$canvas_cross_y whd={$selected,`string($w,'_',$h,'_',$d)`} if narg(${view_coords_$whd}) view_center_x,view_center_y,view_center_z,view_zoom,view_base_channel,\ view_is_rotated,canvas_cross_x,canvas_cross_y=${view_coords_$whd} else view_center_x,view_center_y,view_center_z,view_zoom,view_base_channel,\ view_is_rotated,canvas_cross_x,canvas_cross_y=0.5,0.5,0.5,1,0,0,, fi fi $0_rmn canvas_volumetric_background,canvas_base,area_3d_mesh fi fi # Manage events for image exploration and visualization. if {$selected,w" && "($is_volumetric" || "!$is_view3d)} zoom_1_1,zoom_min,zoom_max={$selected," const mw4 = max(w,4); const mh4 = max(h,4); const md4 = max(d,4); const M = max(mw4/$area_xy_width,mh4/$area_xy_height); [ M,min(M,0.25),M*max($area_xy_width,$area_xy_height,$area_xz_height,$area_zy_width)/min(max(w,h,d),12) ]"} pan_x_min,pan_x_max,pan_y_min,pan_y_max,pan_z_min,pan_z_max={$selected," const mw4 = max(w,4); const mh4 = max(h,4); const md4 = max(d,4); const delta = 0.5/$view_zoom*min(mw4/$area_xy_width,mh4/$area_xy_height,md4/$area_xz_height,md4/$area_zy_width); const delta_x = ceil(delta*$area_xy_width/w); const delta_y = ceil(delta*$area_xy_height/h); const delta_z = ceil(delta*min($area_xz_height,$area_zy_width)/d); [ w==1?[ 0.5,0.5 ]:[ min(0,1 - delta_x), max(1,delta_x) ], h==1?[ 0.5,0.5 ]:[ min(0,1 - delta_y), max(1,delta_y) ], d==1?[ 0.5,0.5 ]:[ min(0,1 - delta_z), max(1,delta_z) ] ]"} # Image pan, using LMB or MMB (drag). repeat 2 { but:=2*$> if !$but" && "$is_CTRL continue fi if isin($event_type0,0,1)" && "inrange(${is_drag$but},2,4)" && "inrange(${is_clicked$but},2,4) # Pan starts drag_dx,drag_dy:=[${drag_end_x$but},${drag_end_y$but}]-[${drag_start_x$but},${drag_start_y$but}] ar=${is_drag$but} if $ar==2 view_center_x-=$drag_dx/$area_width*$area_dx/w#$selected view_center_y-=$drag_dy/$area_height*$area_dy/h#$selected elif $ar==3 view_center_x-=$drag_dx/$area_width*$area_dx/w#$selected view_center_z-=$drag_dy/$area_height*$area_dy/d#$selected elif $ar==4 view_center_z-=$drag_dx/$area_width*$area_dx/d#$selected view_center_y-=$drag_dy/$area_height*$area_dy/h#$selected fi view_center_x,view_center_y,view_center_z:=\ [cut($view_center_x,$pan_x_min,$pan_x_max),\ cut($view_center_y,$pan_y_min,$pan_y_max),\ cut($view_center_z,$pan_z_min,$pan_z_max)] drag_start_x$but,drag_start_y$but=${drag_end_x$but},${drag_end_y$but} event_type0=1 $0_rmn canvas_base,area_3d_mesh elif $event_type0==1" && "${is_drag$but}==2" && "${is_released$but} # Pan ends event_type0,is_drag0,is_released0,is_drag2,is_released2=0 fi } # Rectangular selection, using CTRL+LMB (drag). if !$is_volumetric" && "$is_CTRL" && "$mouse_x>=0 if isin($event_type0,0,2)" && "$is_drag0==2" && "$is_clicked0==2" && "!narg($canvas_crop_x0) # Selection starts if !narg($canvas_rect_x0) x,y=$drag_start_x0,$drag_start_y0 else x,y=$drag_end_x0,$drag_end_y0 fi cx,cy={$selected,[cut(floor(lerp($area_xy_x0,$area_xy_x1,$x/$canvas_width)),0,w-1),\ cut(floor(lerp($area_xy_y0,$area_xy_y1,($y-$thumb_height2)/$canvas_height)),0,h-1)]} if !narg($canvas_rect_x0) canvas_rect_x0,canvas_rect_y0,canvas_rect_x1,canvas_rect_y1=$cx,$cy,$cx,$cy $0_rmn canvas_label_coords elif [$cx,$cy]!=[$canvas_rect_x1,$canvas_rect_y1] canvas_rect_x1,canvas_rect_y1=$cx,$cy $0_rmn canvas_label_coords fi n_view_center_x,n_view_center_y=$view_center_x,$view_center_y if $canvas_mouse_x<64 n_view_center_x-={$selected,lerp(128,8,$canvas_mouse_x/64)/$view_zoom/w} elif $canvas_mouse_x>=$canvas_width-64 n_view_center_x+={$selected,lerp(128,8,($canvas_width-$canvas_mouse_x)/64)/$view_zoom/w} fi if $canvas_mouse_y<64 n_view_center_y-={$selected,lerp(128,8,$canvas_mouse_y/64)/$view_zoom/h} elif $canvas_mouse_y>=$canvas_height-64 n_view_center_y+={$selected,lerp(128,8,($canvas_height-$canvas_mouse_y)/64)/$view_zoom/h} fi n_view_center_x,n_view_center_y:=\ [cut($n_view_center_x,$pan_x_min,$pan_x_max),\ cut($n_view_center_y,$pan_y_min,$pan_y_max)] if [$n_view_center_x,$n_view_center_y]!=[$view_center_x,$view_center_y] view_center_x,view_center_y=$n_view_center_x,$n_view_center_y is_rect_scroll=1 $0_rmn canvas_base else is_rect_scroll=0 fi event_type0=2 fi fi if $event_type0==2" && "$is_drag0==2" && "$is_released0" && "!narg($canvas_crop_x0) # Selection ends if $canvas_rect_x0>$canvas_rect_x1 canvas_rect_x0,canvas_rect_x1=$canvas_rect_x1,$canvas_rect_x0 fi if $canvas_rect_y0>$canvas_rect_y1 canvas_rect_y0,canvas_rect_y1=$canvas_rect_y1,$canvas_rect_y0 fi if max($canvas_rect_x1-$canvas_rect_x0,$canvas_rect_y1-$canvas_rect_y0)>8 view_center_x,view_center_y,view_zoom={$selected," const wc = $canvas_width; const hc = $canvas_height; const f = 0.5*max(w/wc,h/hc); [ cut(0.5*($canvas_rect_x0 + $canvas_rect_x1)/w,0,1), cut(0.5*($canvas_rect_y0 + $canvas_rect_y1)/h,0,1), 2*f*min(wc/max(8,$canvas_rect_x1 - $canvas_rect_x0), hc/max(8,$canvas_rect_y1 - $canvas_rect_y0)) ]"} $0_rmn canvas_base fi canvas_rect_x0,canvas_rect_y0,canvas_rect_x1,canvas_rect_y1= event_type0,is_drag0,is_released0=0 $0_rmn canvas_label_coords fi # Image zoom, using RMB (drag). if inrange($is_drag1,2,4)" && "inrange($is_clicked1,2,4) # Zoom starts amount:=" dx = $drag_end_x1 - $drag_start_x1; dy = $drag_end_y1 - $drag_start_y1; sign(dx)*norm(dx,dy)/200" if !narg($zoom_focus_x) zoom_focus_x,zoom_focus_y=$area_mouse_x,$area_mouse_y fi n_view_zoom:=cut($view_zoom*(1+$amount),$zoom_min,$zoom_max) ratio:=$view_zoom/$n_view_zoom if $n_view_zoom!=$view_zoom if !$is_volumetric if $is_drag1==2 xc:=lerp($area_xy_x0,$area_xy_x1,$zoom_focus_x/$area_width) yc:=lerp($area_xy_y0,$area_xy_y1,$zoom_focus_y/$area_height) view_center_x:=cut(lerp($xc,$view_center_x*w#$selected,$ratio)/w#$selected,$pan_x_min,$pan_x_max) view_center_y:=cut(lerp($yc,$view_center_y*h#$selected,$ratio)/h#$selected,$pan_y_min,$pan_y_max) elif $is_drag1==3 xc:=lerp($area_xz_x0,$area_xz_x1,$zoom_focus_x/$area_width) zc:=lerp($area_xz_z0,$area_xz_z1,$zoom_focus_y/$area_height) view_center_x:=cut(lerp($xc,$view_center_x*w#$selected,$ratio)/w#$selected,$pan_x_min,$pan_x_max) view_center_z:=cut(lerp($zc,$view_center_z*d#$selected,$ratio)/d#$selected,$pan_z_min,$pan_z_max) elif $is_drag1==4 zc:=lerp($area_zy_z0,$area_zy_z1,$zoom_focus_x/$area_width) yc:=lerp($area_zy_y0,$area_zy_y1,$zoom_focus_y/$area_height) view_center_z:=cut(lerp($zc,$view_center_z*d#$selected,$ratio)/d#$selected,$pan_z_min,$pan_z_max) view_center_y:=cut(lerp($yc,$view_center_y*h#$selected,$ratio)/h#$selected,$pan_y_min,$pan_y_max) fi fi view_zoom=$n_view_zoom $0_rmn canvas_base fi drag_start_x1,drag_start_y1=$drag_end_x1,$drag_end_y1 elif inrange($is_drag1,2,5)" && "$is_released1 # Zoom ends zoom_focus_x,zoom_focus_y= is_drag1,is_released1=0 fi # Volumetric images: Area cross drag, with LMB. if $is_volumetric if $is_clicked0>=2" && "$canvas_mouse_x>=0" && "(\ $event_type0==3" || "(!$event_type0" && "narg($canvas_cross_x)" && "\ norm($canvas_mouse_x-$canvas_cross_x*$canvas_width,\ $canvas_mouse_y-$canvas_cross_y*$canvas_height)<16)) is_drag0=$mouse_area # Button clicked -> activate drag (avoid drag latency) canvas_cross_x,canvas_cross_y:=cut($canvas_mouse_x/$canvas_width,0,1),\ cut($canvas_mouse_y/$canvas_height,0,1) drag_start_x0,drag_start_y0=$drag_end_x0,$drag_end_y0 event_type0=3 $0_rmn canvas_volumetric_background,canvas_base,area_3d_view elif $event_type0==3" && "$is_drag0>=2" && "$is_released1 # Drag ends event_type0,is_drag0,is_released0=0 fi fi if $event_type0" && "!$is_clicked0" && "!$is_released0" && "!$is_drag0 event_type0=0 $0_rmn canvas_view fi if $is_area234 if {*,o}" || "({*,PADSUB}" || "{*,PADADD}) if $is_CTRL" && "$is_volumetric # Pan volumetric image along orthogonal axis amount:={*,PADADD}?1:{*,PADSUB}?-1:{*,-o} view_center_x,view_center_y,view_center_z={$selected," const cx = $view_center_x; const cy = $view_center_y; const cz = $view_center_z; const amount = $amount; P = $is_area2?[ cx,cy,cz + amount/d ]: $is_area3?[ cx,cy + amount/d,cz ]: [ cx + amount/w,cy,cz ]; [ cut(P[0],$pan_x_min,$pan_x_max), cut(P[1],$pan_y_min,$pan_y_max), cut(P[2],$pan_z_min,$pan_z_max) ]"} $0_rmn canvas_base,area_3d_mesh else # Zoom image amount:={*,PADADD}?0.25:{*,PADSUB}?-0.25:{*,-o}/5 n_view_zoom:=cut($view_zoom*(1+$amount),$zoom_min,$zoom_max) ratio:=$view_zoom/$n_view_zoom if $n_view_zoom!=$view_zoom xc,yc,zc={$selected,"$is_volumetric?( $is_area2?[ ($area_xy_x0 + $area_xy_x1)/2, ($area_xy_y0 + $area_xy_y1)/2, $view_center_z*d ]: $is_area3?[ ($area_xz_x0 + $area_xz_x1)/2, $view_center_y*h, ($area_xz_z0 + $area_xz_z1)/2 ]: [ $view_center_x*w, ($area_zy_y0 + $area_zy_y1)/2, ($area_zy_z0 + $area_zy_z1)/2 ]): [ lerp($area_xy_x0,$area_xy_x1,$area_mouse_x/$area_width), lerp($area_xy_y0,$area_xy_y1,$area_mouse_y/$area_height), 0 ]"} view_center_x:=cut(lerp($xc,$view_center_x*w#$selected,$ratio)/w#$selected,$pan_x_min,$pan_x_max) view_center_y:=cut(lerp($yc,$view_center_y*h#$selected,$ratio)/h#$selected,$pan_y_min,$pan_y_max) view_center_z:=cut(lerp($zc,$view_center_z*d#$selected,$ratio)/d#$selected,$pan_z_min,$pan_z_max) view_zoom=$n_view_zoom $0_rmn canvas_base fi fi fi if !$is_CTRL if ({*,0}" || "{*,1}" || "{*,2}" || "{*,3}" || "{*,4}" || "\ {*,5}" || "{*,6}" || "{*,7}" || "{*,8}" || "{*,9}" || "\ {*,PAD0}" || "{*,PAD1}" || "{*,PAD2}" || "{*,PAD3}" || "{*,PAD4}" || "\ {*,PAD5}" || "{*,PAD6}" || "{*,PAD7}" || "{*,PAD8}" || "{*,PAD9}) ind=-1 repeat 10 { if {*,$>}" || "{*,PAD$>} ind=$> break fi } if $ind>=0 view_zoom_mode:=($ind-1)%10 n_view_zoom:=cut(arg0($view_zoom_mode,$zoom_1_1,2*$zoom_1_1,4*$zoom_1_1,\ 1,2,4,$zoom_max/8,$zoom_max/4,$zoom_max/2,$zoom_max),$zoom_min,$zoom_max) if $n_view_zoom!=$view_zoom view_zoom=$n_view_zoom $0_rmn canvas_base fi notification="Zoom: "${"arg0 "$view_zoom_mode,"1:1,2:1,4:1,x1,x2,x4,Max/8,Max/4,Max/2,Max"} $0_rmn canvas_label_notification skip {*,-PAD$ind},{*,-$ind} fi elif {*,-END} view_center_x,view_center_y,view_center_z={$selected,1-0.5/[w,h,d]} $0_rmn canvas_base,area_3d_mesh elif {*,-HOME} view_center_x,view_center_y,view_center_z={$selected,0.5/[w,h,d]} $0_rmn canvas_base,area_3d_mesh elif {*,-K} # K: Switch background setting_background:={$setting_background+1}%10 str=${"arg0 "$setting_background>>1,"Black,\"Dark gray\",Gray,\"Light gray\",White"} if $setting_background&1 str.=" checkerboard" fi notification="Background: "$str $0_rmn canvas_background,canvas_label_notification elif {*,-P}" && "['$view_xyz']!=0 # Print current pixel value on stdout +e "Command 'display': I(#"$selected,$view_xyz_disp") = [ "{$selected,I($view_xyz)}" ]" elif {*,-T} # Plot image as a 1D curve plot[$selected] 1,4,0,0,0,0,1 $0_rmn canvas_background elif ({*,PAGEUP}" || "{*,PAGEDOWN})" && "{$selected,s>3}" && "!$setting_alpha n_view_base_channel={$selected,cut($view_base_channel+({*,-PAGEUP}?1:{*,-PAGEDOWN}?-1:0),0,s-3)} if $n_view_base_channel!=$view_base_channel view_base_channel=$n_view_base_channel notification="Base channel: "$view_base_channel $0_rmn canvas_base,canvas_label_notification fi elif $is_area234" && "({*,ARROWLEFT}" || "{*,ARROWRIGHT}" || "{*,ARROWUP}" || "{*,ARROWDOWN}) step:=$is_volumetric" || "$view_zoom>=$zoom_max/4?1:($area_xy_dx+$area_xy_dy)/40 dx,dy:={*,ARROWLEFT}?-$step:{*,ARROWRIGHT}?$step:0,\ {*,ARROWUP}?-$step:{*,ARROWDOWN}?$step:0 if $is_SHIFT dx,dy*=4 fi if $is_area2 dx,dy,dz={$selected,[$dx/w,$dy/h,0]} elif $is_area3 dx,dy,dz={$selected,[$dx/w,0,$dy/d]} else dx,dy,dz={$selected,[0,$dy/h,$dx/d]} fi view_center_x:=cut($view_center_x+$dx,$pan_x_min,$pan_x_max) view_center_y:=cut($view_center_y+$dy,$pan_y_min,$pan_y_max) view_center_z:=cut($view_center_z+$dz,$pan_z_min,$pan_z_max) $0_rmn canvas_base,area_3d_mesh fi fi if [$mouse_x,$mouse_y]!=[$previous_mouse_x,$previous_mouse_y]" && "$is_area234 $0_rmn canvas_label_coords fi elif inrange($previous_mouse_area,2,4) $0_rmn canvas_label_coords fi if {*,-A} # A: Switch alpha mode setting_alpha:=($setting_alpha+1)%5 notification="Alpha: "${"arg0 "$setting_alpha,"Off,On,\"Over black\",\"Over gray\",\"Over white\""} $0_flush_thumb_cache $0_rmn canvas_base,canvas_label_notification elif {*,-G} # G: Toggle grid setting_is_grid:=!$setting_is_grid notification="Grid: "${"arg0 "$setting_is_grid,"Off,On"} $0_rmn canvas_base,canvas_label_notification elif {*,-H} # H: Go upper-left view_center_x,view_center_y,view_center_z={$selected,0.5/[w,h,d]} $0_rmn canvas_base,area_3d_mesh elif {*,-E} # E: Go lower-right view_center_x,view_center_y,view_center_z={$selected,1-0.5/[w,h,d]} $0_rmn canvas_base,area_3d_mesh elif {*,-N} # N: Switch normalization mode setting_normalization:=($setting_normalization+1)%5 notification="Normalization: "${"arg0 "$setting_normalization,\ "Off,Cut,\"Stretch (channelwise)\",\ \"Stretch (global)\",\"Stretch (global-once)\""} $0_flush_thumb_cache $0_rmn canvas_base,canvas_label_notification elif {*,-R} # Rotate view view_is_rotated:=($view_is_rotated+1)%4 notification="Angle: "{$view_is_rotated*90}\260 $0_rmn canvas_base,canvas_label_info,canvas_label_notification,area_3d_mesh elif {*,-V} # Crop image x,y,z=$view_xyz if !narg($canvas_crop_x0) canvas_crop_x0,canvas_crop_y0,canvas_crop_z0=$x,$y,$z notification="Start crop" else x0,y0,z0,x1,y1,z1:=" x0 = $canvas_crop_x0; y0 = $canvas_crop_y0; z0 = $canvas_crop_z0; x1 = $x; y1 = $y; z1 = $z; x0>x1?swap(x0,x1); y0>y1?swap(y0,y1); z0>z1?swap(z0,z1); [ x0,y0,z0,x1,y1,z1 ]" notification="End crop" z[$selected] $x0,$y0,$z0,$x1,$y1,$z1 canvas_crop_x0,canvas_crop_y0,canvas_crop_z0= view_center_x,view_center_y,view_center_z,view_zoom,view_3d_zoom=0.5,0.5,0.5,1,1 if $is_volumetric $0_rmn area_3d_mesh fi =[thumb_is_cached] 0,0,$selected $0_rmn thumb_view,canvas_base,canvas_label_info fi $0_rmn canvas_label_notification elif !$is_CTRL" && "{*,X}" && "$is_volumetric # Reset area cross position canvas_cross_x,canvas_cross_y= $0_rmn canvas_volumetric_background,canvas_base,area_3d_view skip {*,-X} elif {*,-Z} # Switch zoom factor (1:1,2:1,4:1,x1,x2,x4,max/8,max/4,max/2,max) view_zoom_mode:=($view_zoom_mode+1)%10 n_view_zoom:=cut(arg0($view_zoom_mode,$zoom_1_1,2*$zoom_1_1,4*$zoom_1_1,\ 1,2,4,$zoom_max/8,$zoom_max/4,$zoom_max/2,$zoom_max),$zoom_min,$zoom_max) if $n_view_zoom!=$view_zoom view_zoom=$n_view_zoom $0_rmn canvas_base fi notification="Zoom: "${"arg0 "$view_zoom_mode,"1:1,2:1,4:1,x1,x2,x4,Max/8,Max/4,Max/2,Max"} $0_rmn canvas_label_notification fi fi if {canvas_label,($canvas_mouse_x5*h)} canvas_label_alignment:=!$canvas_label_alignment $0_rmn canvas_view fi # Manage events for 3D mesh visualization. if $is_view3d # 3D mesh rotation, using LMB (drag). if !$is_CTRL" && "isin($event_type0,0,4)" && "$is_drag0==5" && "$is_clicked0==5 # 3D rotation starts eval " const w2 = $area_3d_width/2; const h2 = $area_3d_height/2; const radius = 0.75*min(w2,h2); const offx = $area_xy_width + ($is_volumetric?2:0); const offy = $thumb_height2 + $area_xy_height + ($is_volumetric?2:0); const u0 = $drag_start_x0 - offx - w2; const v0 = $drag_start_y0 - offy - h2; const u1 = $drag_end_x0 - offx - w2; const v1 = $drag_end_y0 - offy - h2; n0 = norm(u0,v0); nu0 = n0>radius?u0*radius/n0:u0; nv0 = n0>radius?v0*radius/n0:v0; nw0 = sqrt(max(0,radius^2 - nu0^2 - nv0^2)); n1 = norm(u1,v1); nu1 = n1>radius?u1*radius/n1:u1; nv1 = n1>radius?v1*radius/n1:v1; nw1 = sqrt(max(0,radius^2 - nu1^2 - nv1^2)); u = nv0*nw1 - nw0*nv1; v = nw0*nu1 - nu0*nw1; w = nv0*nu1 - nu0*nv1; n = norm(u,v,w); R = rot(u,v,w,-asin(n/radius^2)); R = mul(R,crop(#$view_3d_pose,0,0,3,3),3); draw(#$view_3d_pose,R,0,0,3,3)" drag_start_x0,drag_start_y0=$drag_end_x0,$drag_end_y0 $0_rmn area_3d_view area_3d_is_motion=1 event_type0=4 elif $event_type0==4" && "$is_drag0==5" && "$is_released0 # 3D rotation ends event_type0,is_drag0,is_released0=0 fi # 3D mesh pan, using CTRL-LMB or MMB (drag). pan_3d_x_min,pan_3d_x_max,pan_3d_y_min,pan_3d_y_max:=" const w = $area_3d_width; const h = $area_3d_height; const mwh = min(w,h); [ -0.47*$view_3d_zoom, 0.47*$view_3d_zoom, -0.47*$view_3d_zoom, 0.47*$view_3d_zoom ]" repeat 2 { but:=2*$> if !$but" && "!$is_CTRL continue fi if isin($event_type0,0,5)" && "${is_drag$but}==5" && "${is_clicked$but}==5 # Pan starts drag_dx,drag_dy:=[${drag_end_x$but},${drag_end_y$but}]-[${drag_start_x$but},${drag_start_y$but}] eval " const mwh = min($area_3d_width,$area_3d_height); i[#$view_3d_pose,3] = cut(i[#$view_3d_pose,3] + $drag_dx/mwh,$pan_3d_x_min,$pan_3d_x_max); i[#$view_3d_pose,7] = cut(i[#$view_3d_pose,7] + $drag_dy/mwh,$pan_3d_y_min,$pan_3d_y_max)" drag_start_x$but,drag_start_y$but=${drag_end_x$but},${drag_end_y$but} area_3d_is_motion=1 event_type0=5 $0_rmn area_3d_view elif $event_type0==5" && "${is_drag$but}==5" && "${is_released$but} # Pan ends event_type0,is_drag0,is_released0,is_drag2,is_released2=0 fi } # 3D mesh zoom, using RMB (drag) or MW. zoom_3d_min,zoom_3d_max=0.1,10 if {*,o}" || "($is_drag1==5" && "$is_clicked1==5)" || "{*,PADADD}" || "{*,PADSUB} # 3D zoom starts amount:="const mw = "{*,-o}"; mw?mw: "{*,-PADADD}"?3: "{*,-PADSUB}"?-3:( const dx = $drag_end_x1 - $drag_start_x1; const dy = $drag_end_y1 - $drag_start_y1; sign(dx)*norm(dx,dy)/10)" n_view_3d_zoom:=cut($view_3d_zoom*1.05^$amount,$zoom_3d_min,$zoom_3d_max) if $n_view_3d_zoom!=$view_3d_zoom eval " const mwh = min($area_3d_width,$area_3d_height); const mx = $is_volumetric?0:$area_mouse_x - $area_width/2; const my = $is_volumetric?0:$area_mouse_y - $area_height/2; tx = i[#$view_3d_pose,3]; ty = i[#$view_3d_pose,7]; x = (mx/mwh - tx)/$view_3d_zoom; y = (my/mwh - ty)/$view_3d_zoom; const dalpha = $view_3d_zoom - $n_view_3d_zoom; i[#$view_3d_pose,3] = cut(dalpha*x + tx,$pan_3d_x_min,$pan_3d_x_max); i[#$view_3d_pose,7] = cut(dalpha*y + ty,$pan_3d_x_min,$pan_3d_x_max)" drag_start_x1,drag_start_y1=$drag_end_x1,$drag_end_y1 view_3d_zoom=$n_view_3d_zoom area_3d_is_motion=1 $0_rmn area_3d_view fi elif $is_drag1==5" && "$is_released0 # 3D zoom ends event_type0,is_drag0,is_released0,is_drag1,is_released2 fi # 3D mesh pan with ARROW keys. if $is_area5" && "({*,ARROWLEFT}" || "{*,ARROWRIGHT}" || "{*,ARROWUP}" || "{*,ARROWDOWN}) eval " const stepx = ($pan_3d_x_max - $pan_3d_x_min)/30; const stepy = ($pan_3d_y_max - $pan_3d_y_min)/30; dx = "{*,-ARROWLEFT}"?-stepx:"{*,-ARROWRIGHT}"?stepx:0; dy = "{*,-ARROWUP}"?-stepy:"{*,-ARROWDOWN}"?stepy:0; $is_SHIFT?(dx*=2; dy*=2); x = cut(i[#$view_3d_pose,3] + dx,$pan_3d_x_min,$pan_3d_x_max); y = cut(i[#$view_3d_pose,7] + dy,$pan_3d_y_min,$pan_3d_y_max); i[#$view_3d_pose,3] = x; i[#$view_3d_pose,7] = y" $0_rmn area_3d_view fi if {*,-K} # K: Switch 3D background setting_3d_background:={$setting_3d_background+1}%12 notification="3D background: \#"$setting_3d_background $0_rmn canvas_3d_background,area_3d_view,canvas_label_notification elif {*,-J} # J: Toggle 3D animation setting_3d_animation:=($setting_3d_animation+1)%3 notification="3D animation: "${"arg0 "$setting_3d_animation,Off,Forward,Backward} $0_rmn canvas_label_notification elif {*,-U} # U: Switch 3D animation mode setting_3d_animation_mode:=($setting_3d_animation_mode+1)%16 if $setting_3d_animation_mode<12 str={`_'X'+int($setting_3d_animation_mode/4)`}-axis else str=XYZ-axes fi str.=" ("${"arg0 "{$setting_3d_animation_mode%4},slow,normal,fast,fast+}")" notification="3D animation mode: "$str $0_rmn canvas_label_notification fi if !$is_volumetric if {*,-A} # A: Toggle 3D axes setting_3d_is_axes:=!$setting_3d_is_axes notification="Axes: "${"arg0 "$setting_3d_is_axes,Off,On} $0_rmn area_3d_view,canvas_label_notification elif !$is_CTRL" && "{*,D} # D: Switch 3D side mode setting_3d_side_mode:={($setting_3d_side_mode+1)%3} notification="3D side mode: "${"arg0 "$setting_3d_side_mode,\ "Single-sided,Double-sided,\"Single-sided (flipped)\""} $0_rmn area_3d_view,canvas_label_notification skip {*,-D} elif !$is_CTRL" && "{*,F} # F: Change 3D focale setting_3d_focale:="_const f = $setting_3d_focale; lof = [ 0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,1,1.5,2,3,4,6,8,10 ]; for (k = 0, k} if !isfile('$filename') break fi } if {*,-L} f[is_rotated] "i?run('rotate[',y,'] ',-90*i); 0" foreach[^$nb_images--1] { => ${image_name$>} } # Set back original images names o[^$nb_images--1] $filename foreach[^$nb_images--1] { => _display$> } notification="Saved list copy: "$filename elif {*,-O} ang={is_rotated,i[$selected]} if $ang rotate[$selected] {-90*$ang} =[is_rotated] 0,0,$selected fi =>[$selected] ${image_name$selected} o[$selected] $filename =>[$selected] _display$selected notification="Saved image copy: "$filename fi $0_rmn canvas_label_notification elif {*,-S}" && "0$canvas_base # CTRL+S: Save screenshot repeat 256 { filename=${"filename gmic.png,"$>} if !isfile('$filename') break fi } notification="Save screenshot: "$filename $0_rmn canvas_label_notification o[canvas_base] $filename elif $is_CTRL" && "{*,-W} # CTRL+W: Start/stop window recording if $window_record_frame<0 # Start recording window_record_frame=0 else # Stop recording if $is_opencv o[] $filename_animation,0,0,0 fi # Close video stream notification="Stop recording (\#"$window_record_frame" frames)" window_record_frame=-1 $0_rmn canvas_label_notification fi $0_rmn area_3d_view elif {*,-X} # CTRL+X: Toggle cursor mode setting_cursor:=a=($setting_cursor+1)%3;$is_volumetric?(a?2:0):(a==1) notification="Cursor: "${"arg0 "$setting_cursor,"Off,\"On (2D)\",\"On (3D)\""} $0_rmn canvas_view,canvas_label_notification fi fi if $nb_images>1" && "{*,-TAB} # Hide/show thumbnails thumb_is_visible:=!$thumb_is_visible area_mouse_x,area_mouse_y=-1 $0_rmn thumb_view,canvas_base,area_3d_view fi if !$is_clicked0 event_type0=0 fi if $is_released0 is_released0=0 fi if $canvas_label_notification notification_opacity-=0.1 if $notification_opacity<=0 rm[canvas_label_notification] notification_opacity=0 fi if $notification_opacity<1 $0_rmn window_view fi fi } # Return current 3D pose and zoom. if 0$view_3d_pose _display_3d_pose={view_3d_pose,^} fi if narg($view_3d_zoom) _display_3d_zoom=$view_3d_zoom fi # Reset image list and display window to their previous states. if $is_opencv" && "$window_record_frame>=0 o[] $filename_animation,0,0,0 fi # Close recording stream f[is_rotated] "i?run('rotate[',y,'] ',-90*i); 0" # Rotate back image to their original angle if narg($window_previous_normalization) w[] -1,-1,$window_previous_normalization,$window_previous_fullscreen else w[] 0 fi rm[$nb_images--1] foreach { => ${image_name$>} } # Set back original images names um "$0_rmn" is_change 0 # Flush thumb cache: discard all images except 3D mesh rendering. _display_flush_thumb_cache : ==[thumb_is_cached] 2 *[thumb_is_cached] 2 _display_rmn thumb_view # Return default window size for selected image list. # $1 : thumb_height_factor # $2 : thumb_min_ratio _display_default_window_size : u {"const fmax = 0.75; const u = min(1280,fmax*"{*,u}"); const v = min(1280,fmax*"{*,v}"); const muv = min(u,v); CImg3d = ['CImg3d']; Mw = Mh = 0; repeat (l,k, w = w#k; h = h#k; is_3d = w==1 && h>6 && d#k==1 && s#k==1 && int(crop(#k,0,0,0,0,1,6,1,1))==CImg3d; !w || is_3d?(nw = 1.25*muv; nh = muv):( d>1?(w+=d; h+=d); fact = min(u/w,v/h); Mwh = max(w,h); Mwh<16?(f = lerp(2,1,Mwh/16); w/=f; h/=f); nw = ceil(fact*w); nh = ceil(fact*h) ); nw>Mw?(Mw = nw); nh>Mh?(Mh = nh); ); Mwh = max(Mw,Mh); Mw = Mwh*max($2,Mw/Mwh); Mh = Mwh*max($2,Mh/Mwh); l>1?(Mh = (Mh + 2)/(1 - $1)): !w#0?(Mw/=2; Mh/=2); ceil([ Mw,Mh ])"} # Return 1 or 0 if one of the 3 first channels of the selected image contains 'inf' or 'NaN' values. _display_is_infnan : if w sh 0,{min(s-1,2)} u {s=is;isinf(s)||isnan(s)} rm. else u 0 fi # Return non-infnan min and max values of the selected image. _display_minmax_infnan : eval. " begin( val_min = inf; val_max = -inf ); val = i; !isinf(val)?( valval_max?(val_max = val) ); end( merge(val_min,min); merge(val_max,max); set('{}',v2s([val_min,val_max])); )" # Normalize selected image without considering inf and NaN values. # Optional arguments: min-max values. _display_normalize_infnan : skip "${1=none},${2=none}" is_minmax:="isnum($1) && isnum($2)" if !$is_minmax noarg fi foreach { if $is_minmax val_min,val_max:=$1,$2 else val_min,val_max=${-_display_minmax_infnan} fi f " const min = $val_min; const max = $val_max; const delta = max - min; const epsilon = delta>0?delta:1; const nmin = min - epsilon; const nmax = max + epsilon; const ndelta = nmax - nmin; const nfact = 255/(ndelta>0?ndelta:1); val = i; isinf(val)?(val<0?0:255):isnan(val)?0:(val - nmin)*nfact" } #@cli d0 : eq. to 'display0'. d0 : _gmic_s="$?" prev_display_normalization=$_display_normalization prev_display_alpha=$_display_alpha _display_normalization=0 _display_alpha=1 v + _display "",1,$[] v - _display_normalization=$prev_display_normalization _display_alpha=$prev_display_alpha +d0 : prev_display_normalization=$_display_normalization prev_display_alpha=$_display_alpha _display_normalization=0 _display_alpha=1 _gmic_s="$?" v + _display +,1,$[] v - _display_normalization=$prev_display_normalization _display_alpha=$prev_display_alpha #@cli display0 #@cli : Display selected images in an interactive window, without normalization and alpha mode activated. display0 : prev_display_normalization=$_display_normalization prev_display_alpha=$_display_alpha _display_normalization=0 _display_alpha=1 _gmic_s="$?" v + _display "",1,$[] v - _display_normalization=$prev_display_normalization _display_alpha=$prev_display_alpha +display0 : prev_display_normalization=$_display_normalization prev_display_alpha=$_display_alpha _display_normalization=0 _display_alpha=1 _gmic_s="$?" v + _display +,1,$[] v - _display_normalization=$prev_display_normalization _display_alpha=$prev_display_alpha #@cli da : eq. to 'display_array'. da : _gmic_s="$?" v + _display_array $* #@cli display_array : _width>0,_height>0 #@cli : Display images in interactive windows where pixel neighborhoods can be explored. #@cli : Default values: 'width=13' and 'height=width'. display_array : _gmic_s="$?" v + _$0 $* _display_array : check ${1=13}>0" && "${2=$1}>0 e[0--3] "Display $1x$2 array of pixel values for image"$_gmic_s"." dxb:=round($1/2,1,1) dxf:=$1-1-$dxb dyb:=round($2/2,1,1) dyf:=$2-1-$dyb foreach { if w<128" && "h<128 r 128,128,100%,100%,0,0,0.5,0.5 fi # Manage cases of small and large images. x0,y0,w,h=0,0,{w},{h} wmax:=0.9*{*,u} hmax:=0.9*{*,v} do if w>=$wmax" || "h>=$hmax n={n} => "Image "'{b}.{x}'" is too large, please select a sub-image." +select. 2 x0,y0,w,h:=i[0],i[1],1+i[3]-i[0],1+i[4]-i[1] rm. => $n fi +z. $x0,$y0,0,{$x0+$w-1},{$y0+$h-1},0 round. 1 n. 0,255 while w>=$wmax" || "h>=$hmax x1=-1 y1=-1 c1=0 ox1=-1 oy1=-1 oc1=-1 x2=-1 y2=-1 c2=0 ox2=-1 oy2=-1 oc2=-1 x3=-1 y3=-1 c3=0 ox3=-1 oy3=-1 oc3=-1 c0=0 oxm=-1 oym=-1 w. -1,-1,0,0,{-2,b}.{-2,x} do # Enter event loop. # Manage user interactions. wait[0-3] oc0=$c0 repeat 4 { if $>" && "!{*$>}" && "${x$>}>=0 w$> 0 x$>=-1 y$>=-1 c$>=0 fi if {*$>,o} c$>:=(${c$>}+sign({*$>,o}))%s wait[$>] -1 fi if {*$>,SPACE}" || "{*$>,ENTER}" || "{*$>,ARROWRIGHT}" || "{*$>,ARROWDOWN} c$>:=(${c$>}+1)%s wait[$>] -1 fi if {*$>,BACKSPACE}" || "{*$>,ARROWLEFT}" || "{*$>,ARROWUP} c$>:=(${c$>}-1)%s wait[$>] -1 fi } if $oc0!=$c0 c1=$c0 c2=$c0 c3=$c0 fi xm,ym={*,x,y} if $xm>=0" && "{*,b}&1 x1=$xm y1=$ym fi if $xm>=0" && "{*,b}&2 x2=$xm y2=$ym fi if $xm>=0" && "{*,b}&4 x3=$xm y3=$ym fi # Generate main image view. if $xm>=0" && "($oxm!=$xm" || "$oym!=$ym) w[] -1,-1,{-2,b}.{-2,x}" - ("$xm,$ym")" fi if $x1!=$ox1" || "$y1!=$oy1" || "$x2!=$ox2" || "$y2!=$oy2" || "$x3!=$ox3" || "$y3!=$oy3 . if $x1>=0 xb:=$x1-$dxb yb:=$y1-$dyb xe:=$x1+$dxf ye:=$y1+$dyf rectangle. $xb,$yb,$xe,$ye,0.2,0,255,255 rectangle. $xb,$yb,$xe,$ye,1,0xFFFFFFFF,0,255,255 fi if $x2>=0 xb:=$x2-$dxb yb:=$y2-$dyb xe:=$x2+$dxf ye:=$y2+$dyf rectangle. $xb,$yb,$xe,$ye,0.2,255,32,255 rectangle. $xb,$yb,$xe,$ye,1,0xFFFFFFFF,255,32,255 fi if $x3>=0 xb:=$x3-$dxb yb:=$y3-$dyb xe:=$x3+$dxf ye:=$y3+$dyf rectangle. $xb,$yb,$xe,$ye,0.2,255,255,0 rectangle. $xb,$yb,$xe,$ye,1,0xFFFFFFFF,255,255,0 fi w. {-2,w},{-2,h} rm. oxm=$xm oym=$ym fi # Generate zoomed views. if $x1>=0" && "($ox1!=$x1" || "$oy1!=$y1" || "$oc1!=$c1) +z.. {$x1-$dxb},{$y1-$dyb},0,$c1,{$x1+$dxf},{$y1+$dyf},0,$c1 +z.. {$x1-$dxb},{$y1-$dyb},0,{$x1+$dxf},{$y1+$dyf},0 __display_array[-2,-1] $1,$2,0,255,255 w1. {w},{h},0,0,{-3,b}" - ("$x1,$y1,c=$c1")" rm. ox1=$x1 oy1=$y1 oc1=$c1 fi if $x2>=0" && "($ox2!=$x2" || "$oy2!=$y2" || "$oc2!=$c2) +z.. {$x2-$dxb},{$y2-$dyb},0,$c2,{$x2+$dxf},{$y2+$dyf},0,$c2 +z.. {$x2-$dxb},{$y2-$dyb},0,{$x2+$dxf},{$y2+$dyf},0 __display_array[-2,-1] $1,$2,255,32,255 w2. {w},{h},0,0,{-3,b}" - ("$x2,$y2,c=$c2")" rm. ox2=$x2 oy2=$y2 oc2=$c2 fi if $x3>=0" && "($ox3!=$x3" || "$oy3!=$y3" || "$oc3!=$c3) +z.. {$x3-$dxb},{$y3-$dyb},0,$c3,{$x3+$dxf},{$y3+$dyf},0,$c3 +z.. {$x3-$dxb},{$y3-$dyb},0,{$x3+$dxf},{$y3+$dyf},0 __display_array[-2,-1] $1,$2,255,255,0 w3. {w},{h},0,0,{-3,b}" - ("$x3,$y3,c=$c3")" rm. ox3=$x3 oy3=$y3 oc3=$c3 fi while {*}" && "\ !{*,ESC}" && "!{*,Q}" && "\ !{*1,ESC}" && "!{*1,Q}" && "\ !{*2,ESC}" && "!{*2,Q}" && "\ !{*3,ESC}" && "!{*3,Q} k[0] w 0 w1 0 w2 0 w3 0 } __display_array : round.. 1 c.. 0,999 r. 100%,100%,1,3,{s==1} +luminance. r.. {$1*24},{$2*24} grid.. {100/$1}%,{100/$2}%,0,0,1,0 xb:=24*int($1/2) yb:=24*int($2/2) xe:=$xb+24 ye:=$yb+24 rectangle.. $xb,$yb,$xe,$ye,1,0xFFFFFFFF,$3,$4,$5 repeat $2 { yg=$> repeat $1 { xg=$> t.. {-3,i($xg,$yg)},{5+$xg*24},{5+$yg*24},13,0.8,{i($xg,$yg)>128?0:255} } } rm[-3,-1] #@cli dc : eq. to 'display_camera'. dc : check_opencv $0 v + _display_camera #@cli display_camera #@cli : Open camera viewer. #@cli : This command requires features from the OpenCV library (not enabled in G'MIC by default). display_camera : check_opencv $0 v + _$0 _display_camera : e[0--3] "Open camera stream viewer." # Initialize camera and get resolution. l[] { camera onfail use_vt100 e[0--4] ${_vt100_r}${_vt100_b}"Command 'display_camera': Unable to read camera stream. Exiting."$_vt100_n return } wc,hc:=w,h # Open interactive window. w ${"fitscreen "$wc,$hc},0,"G'MIC Camera Stream Viewer" wnfs,hnfs={*,w,h} angle,fullscreen,brightness,contrast=0 do # Display frame from camera. ww,wh={*,w,h} camera if $angle rotate. {90*$angle} fi if $brightness +. {10*$brightness} c. 0,255 fi if $contrast /. 255 *. {1.2^$contrast} *. 255 c. 0,255 fi rs. $ww,$wh,1,1 w. rm wait 30 # Manage user events. if {*,r} w[] {*,d,e} elif {*,-R}" || "{*,-SPACE} angle:=($angle+1)%4 elif {*,-ARROWUP} brightness:=min(5,$brightness+1) elif {*,-ARROWDOWN} brightness:=max(-5,$brightness-1) elif {*,-ARROWRIGHT} contrast:=min(5,$contrast+1) elif {*,-ARROWLEFT} contrast:=max(-5,$contrast-1) elif {*,-F}" || "{*,-ENTER}" || "{*,-F5} fullscreen:=1-$fullscreen if $fullscreen wwnfs,whnfs={*,w,h} w[] {*,u,v},0,1 else w[] $wwnfs,$whnfs,0,0 fi fi while {*}" && "!{*,ESC} camera 0,0 w[] 0 #@cli dclut : eq. to 'display_clut'. dclut : check "isint(${1=256},1) && isint(${2=33},1)" v + _display_clut $* #@cli display_clut : _image_resolution>0,_clut_resolution>0 #@cli : Display selected 3D color LUTs. #@cli : Default values: 'image_resolution=320' and 'clut_resolution=33'. #@cli : $ clut tealorange clut summer clut 60s display_clut 400 display_clut : v + _$0 $* _display_clut : check "isint(${1=320},1) && isint(${2=33},1)" e[0--3] "Display selected 3D color LUTs, with image resolution $1x$1 and CLUT resolution $2x$2x$2." foreach { nm={n} rs3d $2 pointcloud3d circles3d 1 o3d 0.1 *3d {255/($2-1)} colorcube3d 1 +3d pose3d 0.815360904,-0.144610703,-0.560602367,0,\ -0.149177685,0.883129835,-0.444778264,0,\ 0.559404552,0.446283728,0.698496521,0 c3d n3d *3d. {2.10*$1} {2*$1},{2*$1},1,3,-1 j3d. ..,47%,46%,0,1,2,0,0,{4*$1} rm.. to_rgba replace_color 0,0.5%,-1,-1,-1,255,96,96,96,0 rs $1 => $nm } #@cli dfft : eq. to 'display_fft'. dfft : v + _display_fft #@cli display_fft #@cli : Display fourier transform of selected images, with centered log-module and argument. #@cli : (eq. to 'dfft'). #@cli : $ image.jpg +display_fft display_fft : v + _$0 _display_fft : e[0--3] "Render fourier transform of image$? with centered log-module and argument." foreach { fftpolar +.. 1 log.. n 0,255 a x } s x,2 #@cli dg : eq. to 'display_graph'. dg : check "${1=0}>=0 && ${2=0}>=0 && isint(${11=32},0)" skip ${3=1},${4=0},${5=0},${6=0},${7=0},${8=0},"${9=x-axis}","${10=y-axis}" _display_graph ${1-8},"$9","$10",$11 #@cli display_graph : _width>=0,_height>=0,_plot_type,_vertex_type,_xmin,_xmax,_ymin,_ymax,_xlabel,_ylabel,\ # _frame_size>=0 #@cli : Render graph plot from selected image data. #@cli : 'plot_type' can be { 0:none | 1:lines | 2:splines | 3:bar }. #@cli : 'vertex_type' can be { 0:none | 1:points | 2,3:crosses | 4,5:circles | 6,7:squares }. #@cli : 'xmin','xmax','ymin','ymax' set the coordinates of the displayed xy-axes. #@cli : if specified 'width' or 'height' is '0', then image size is set to half the screen size. #@cli : Default values: 'width=0', 'height=0', 'plot_type=1', 'vertex_type=1', 'xmin=xmax=ymin=ymax=0 (auto)', \ # 'xlabel="x-axis"', 'ylabel="y-axis"' and 'frame_size=32'. #@cli : $ 128,1,1,1,'cos(x/10+u)' +display_graph 400,300,3 display_graph : check "${1=0}>=0 && ${2=0}>=0 && isint(${11=32},0)" skip ${3=1},${4=0},${5=0},${6=0},${7=0},${8=0},"${9=x-axis}","${10=y-axis}" _display_graph ${1-8},"$9","$10",$11 _display_graph : check "${1=0}>=0 && ${2=0}>=0 && isint(${11=32},0)" skip ${3=1},${4=0},${5=0},${6=0},${7=0},${8=0},"${9=x-axis}","${10=y-axis}" e[0--3] "Render $1x$2 graph plot from data of image$?." foreach { nm={n} # Determine output size. if $1>0" && "$2>0 w,h=$1,$2 else w,h:=[{*,u,v}]/2 fi w,h:=max($w,33),max($h,33) # Determine xmin,xmax/ymin,ymax. siz:=w*h*d if $5==$6 xmin,xmax=0,{$siz-1} else xmin,xmax:=min(${5,6}),max(${5,6}) fi is_infnan=0 if "max(isinf([im,iM])) || max(isnan([im,iM]))" +replace_infnan. 0 is_infnan=1 fi if $7==$8 ymin,ymax:="dy = (iM - im)/20; !dy?(dy = 0.5); [ im - dy,iM + dy ]" else ymin,ymax:=min(${7,8}),max(${7,8}) fi if $is_infnan rm. fi if $3==3 xmax:=lerp($xmin,$xmax,$siz/($siz-1)) fi dx,dy:=$xmax-$xmin,$ymax-$ymin # Determine number of axis tick marks. u=${"_axes[] "$xmin,$xmax",{0.3*"$w"/14}"} offx:=arg(1,$u) deltax:=arg(2,$u) u=${"_axes[] "$ymin,$ymax",{0.3*"$h"/14}"} offy:=arg(1,$u) deltay:=arg(2,$u) # Create plot canvas. one:=$3!=3 gw:=$w-2*$11 gh:=$h-2*$11 gg:=($gw-$one)/($siz-$one) if $gw>0" && "$gh>0 $gw,$gh,1,3,255 if $dx>0" && "$dy>0 grid. {$deltax*$gw/$dx},{$deltay*$gh/$dy},{($offx-$xmin)*$gw/$dx},{$gh-($offy-$ymin)*$gh/$dy},0.25,0xCCCCCCCC,0 fi # Define color palette for curves. if s#-2==1 (100,100,200) elif s#-2<=3 (220,100,100;100,220,100;100,100,220) else (0,255) r. 256,1,1,1,3 map. 2 z. 2,100% permute. cxyz r. 3,{-3,max(3,s)},1,1,0,2 sh. 0,2,0,0 f. 255,0,0,0,255,0,0,0,255 rm. fi # Draw plot for each channel. repeat s#-3 { sh... $> graph... .,$3,$4,$ymax,$ymin,1,{-2,@0-2} rm. shift. 0,-1 } rm[-3,-1] line. 0,0,100%,0,1,110 line. 100%,0,100%,100%,1,110 line. 100%,100%,0,100%,1,255 line. 0,100%,0,0,1,255 100%,100%,1,1,255 axes. $xmin,$xmax,$ymax,$ymin,14,1,0 if $xmin>0 axes. 0,0,$ymax,$ymin,14,1,160 fi if $xmax<0 axes. {w-1},{w-1},$ymax,$ymin,14,1,160 fi if $ymin>0 axes. $xmin,$xmax,{h-1},{h-1},14,1,160 fi if $ymax<0 axes. $xmin,$xmax,0,0,14,1,160 fi +erode. 3 !=. 255 r.. 100%,100%,1,3 j... ..,0,0,0,0,1,.,1 rm[-2,-1] else 0 fi frame. xy,$11,220 fontsize:=min(20,round(0.75*$11)) if $fontsize 0 t. "$9",0,0,$fontsize,1,-220,-220,-220 j.. .,0.5~,{-2,h-1-($fontsize+$11)/2},0,0,-1 rm. 0 t. "$10",0,0,$fontsize,1,-220,-220,-220 rotate. -90 j.. .,{($11-$fontsize)/2},0.5~,0,0,-1 rm. fi => $nm } c 0,255 #@cli dh : eq. to 'display_histogram'. dh : _gmic_s="$?" v + _display_histogram $"*" #@cli display_histogram : _width>=0,_height>=0,_clusters>0,_min_value[%],_max_value[%],_show_axes={ 0 | 1 },_expression. #@cli : Render a channel-by-channel histogram. #@cli : If selected images have several slices, the rendering is performed for all input slices. #@cli : 'expression' is a mathematical expression used to transform the histogram data for visualization purpose. #@cli : (eq. to 'dh'). #@cli : if specified 'width' or 'height' is '0', then image size is set to half the screen size. #@cli : Default values: 'width=0', 'height=0', 'clusters=256', 'min_value=0%', 'max_value=100%', 'show_axes=1' \ # and 'expression=i'. #@cli : $ image.jpg +display_histogram 512,300 display_histogram : _gmic_s="$?" v + _$0 $"*" _display_histogram : check "${1=0}>=0 && ${2=0}>=0 && ${3=256}>0" skip ${4=0%},${5=100%},${6=1},"${7=i}" e[0--3] "Render $1x$2 channel-by-channel histogram of image"$_gmic_s", with $3 clusters, minimum value $4 and maximum value $5." foreach { nm={n} if ispercentage($4) m:=im+(iM-im)*$4 else m=$4 fi if ispercentage($5) M:=im+(iM-im)*$5 else M=$5 fi s:=s s c repeat $s { l[{-1-$>}] { s z histogram $3,$m,$M a z } } a c f "${7--1}" vM:=iM s z foreach { if $1>0" && "$2>0 wh=$1,$2 else wh:=[{*,u,v}]/2 fi $wh,1,{s},-255 repeat s { sh[-2,-1] $> graph. ..,3,0,$vM,0,1,0 rm[-2,-1] } rm.. + 255 if $6 100%,100% axes. $m,$M,$vM,0,14,1,255 if $m>0 axes. 0,0,$vM,0,14,1,200 fi if $M<0 axes. {w-1},{w-1},$vM,0,14,1,200 fi +dilate. 3 ri.. ... j... ..,0,0,0,0,1,.,255 rm[-2,-1] fi } a z => $nm } #@cli display_parametric : _width>0,_height>0,_outline_opacity,_vertex_radius>=0,_is_antialiased={ 0 | 1 },\ # _is_decorated={ 0 | 1 },_xlabel,_ylabel #@cli : Render 2D or 3D parametric curve or point clouds from selected image data. #@cli : Curve points are defined as pixels of a 2 or 3-channel image. #@cli : If the point image contains more than 3 channels, additional channels define the (R,G,B) color for each vertex. #@cli : If 'outline_opacity>1', the outline is colored according to the specified vertex colors and #@cli : 'outline_opacity-1' is used as the actual drawing opacity. #@cli : Default values: 'width=512', 'height=width', 'outline_opacity=3', 'vertex_radius=0', 'is_antialiased=1',\ # 'is_decorated=1', 'xlabel="x-axis"' and 'ylabel="y-axis"'. #@cli : $ 1024,1,1,2,'t=x/40;(!c?sin(t):cos(t))*(exp(cos(t))-2*cos(4*t)-sin(t/12)^5)' display_parametric 512,512 #@cli : $ 1000,1,1,2,u(-100,100) quantize 4,1 noise 12 channels 0,2 +normalize 0,255 append c \ # display_parametric 512,512,0.1,8 display_parametric : check "${1=512}>0 && ${2=$1}>0 && ${4=0}>=0" skip ${3=3},${5=1},${6=1},"${7=x-axis}","${8=y-axis}" s0="no " s1="" o0="" o1="colored " e[^-1] "Render $1x$2 parametric graph plot from data of image$?, with "${o{$3>1}}"outline opacity "\ {$3>1?$3-1:$3}", vertex radius $4, "${s{$5!=0}}"antialiasing and "${s{$6!=0}}"decoration." foreach { nm={n} N:=w*h*d i[0] ('CImg3d') +[0] 0.5 i[1] ($N;$N) # Header + nb of vertices/primitives. # Calibrate colors of vertices. if s==4 +channels. 3,3 r. 100%,100%,1,2 a[-2,-1] c is_grayscale=1 else is_grayscale:=s<4 channels. 0,5 fi # Manage coordinates of vertices. sh. 0 xm,xM:=im,iM rm. sh. 1 ym,yM:=im,iM rm. sh. 2 zm,zM:=im,iM rm. permute. cxyz s. x,2 i.. (1,0;1,{$N-1}) r.. 2,$N,1,1,3 round.. 1,$N,1,1,1 # Primitives, colors and opacities. y a y c3d n3d *3d 1,-1,1 {$6?[max(1,$1-32),max(1,$2-32)]:[$1,$2]},1,{$is_grayscale?1:3},255 *3d[0] {0.96*min(w,h)} if $6 L:=0.1*max($1,$2) grid[1] $L,$L,0,0,0.25,0xCCCCCCCC,0 fi if $5 # Anti-aliased. r[1] 200%,200%,1,100%,1 *3d[0] 2 if $4 +circles3d[0] {2*$4} j3d[1] [2],50%,50%,0,1,3,0,0 rm[2] fi elif $4 # Aliased. +circles3d[0] $4 j3d[1] [2],50%,50%,0,1,3,0,0 rm[2] fi # Convert point cloud to connected segments. if $3 l[0] { s3d f[1] "i-y" rm[3] i[3] (2,0,1;2,{$N-2},{$N-1}) r[3] 3,{$N-1},1,1,3 round[3] r[5] 1,{h-1},1,1,0 if $3>1 r[4] 3,{4,h/3},1,1,-1 r[4] 3,{4,h-1},1,1,2 else rm[4] i[4] 3,{$N-1} fi y a y } j3d[1] [0],50%,50%,0,{$3>1?$3-1:$3},2,0,0 fi rm[0] if $5 r. 50%,50%,1,100%,2 fi if $6 # Add decoration. xc:=0.5*($xm+$xM) yc:=0.5*($ym+$yM) dx:=0.5*($xM-$xm)/0.96 dy:=0.5*($yM-$ym)/0.96 xm,xM,ym,yM:=$xc-$dx,$xc+$dx,$yc-$dy,$yc+$dy 100%,100%,1,1,255 axes. $xm,$xM,$yM,$ym,14,1,0 if $xm>0 axes. 0,0,$yM,$ym,14,1,160 fi if $xM<0 axes. {w-1},{w-1},$yM,$ym,14,1,160 fi if $ym>0 axes. $xm,$xM,{h-1},{h-1},14,1,160 fi if $yM<0 axes. $xm,$xM,0,0,14,1,160 fi +erode. 3 !=. 255 r.. 100%,100%,1,3 j... ..,0,0,0,0,1,.,1 rm[-2,-1] frame xy,1,128 frame xy,15,220 0 t. "$7",0,0,14,1,-220,-220,-220 j.. .,{({-2,w}-w)/2},{{-2,h}-16},0,0,-1 rm. 0 t. "$8",0,0,14,1,-220,-220,-220 rotate. -90 j.. .,2,{({-2,h}-h)/2},0,0,-1 rm. fi => $nm } #@cli display_polar : _width>32,_height>32,_outline_type,_fill_R,_fill_G,_fill_B,_theta_start,_theta_end,_xlabel,_ylabel #@cli : Render polar curve from selected image data. #@cli : 'outline_type' can be { r<0:dots with radius -r | 0:no outline | r>0:lines+dots with radius r }. #@cli : 'fill_color' can be { -1:no fill | R,G,B:fill with specified color }. #@cli : Default values: 'width=500', 'height=width', 'outline_type=1', 'fill_R=fill_G=fill_B=200', 'theta_start=0', \ # 'theta_end=360', 'xlabel="x-axis"' and 'ylabel="y-axis"'. #@cli : $ 300,1,1,1,'0.3+abs(cos(10*pi*x/w))+u(0.4)' display_polar 512,512,4,200,255,200 #@cli : $ 3000,1,1,1,'x^3/1e10' display_polar 400,400,1,-1,,,0,{15*360} display_polar : check "${1=500}>32 && ${2=$1}>32" skip ${3=1},${4=200},${5=$4},${6=$5},${7=0},${8=360},"${9=x-axis}","${10=y-axis}" e[^-1] "Render $1x$2 polar graph plot from data of image"$_gmic_s", with outline $4 and fill color ($4,$5,$6)." foreach { nm={n} # Compute (x,y) coordinates of the polar curve points. M:=max(abs(iM),abs(im)) * {0.48*min($1,$2)/$M} y ({$7*pi/180};{-$8*pi/180}) r. 1,..,1,1,3 +sin. cos.. *. ... *[-3,-2] a[-2,-1] x N:=h => coords # Generate 3D object for curve outline. if $3 ('CImg3d') +. 0.5 ($N,$N) +z[coords] 0,2 1,$N,1,1,2 1,$N,1,1,'y' ++. 1 a[-3--1] x =. 0,2,100% 3,$N,1,1,0 1,$N,1,1,1 y[-6--1] a[-6--1] y => _plot_polar_outline fi # Generate 3D object for filling. if "$4>=0 && $5>=0 && $6>=0" ('CImg3d') +. 0.5 ({$N+1},$N) +z[coords] 0,-1,2,100% z. 0,2 1,$N,1,1,3 1,$N 1,$N,1,1,'1+y' ++. 1 a[-4--1] x =. 1,3,100% 3,$N,1,1,$4,$5,$6 1,$N,1,1,1 y[-6--1] a[-6--1] y => _plot_polar_fill fi rm[coords] # Remove original curve coordinates. # Render graph image. {$1-32},{$2-32},1,3,255 L:=0.1*max($1,$2) grid. $L,$L,0,0,0.25,0xCCCCCCCC,0 # Draw background grid if "$4>=0 && $5>=0 && $6>=0" # Draw curve filling j3d. [_plot_polar_fill],50%,50%,0,1,2,1,0 rm[_plot_polar_fill] fi if $3 if $3>=0 # Draw curve outline j3d. [_plot_polar_outline],50%,50%,0,1,1,0,0 fi if $3!=0 # Draw curve vertices if abs($3)>1 circles3d[_plot_polar_outline] {abs($3)} fi j3d. [_plot_polar_outline],50%,50%,0,0.2,2,0,0 fi rm[_plot_polar_outline] fi # Draw axes and frame. nM:=$M/0.96 100%,100%,1,1,255 axes. {-$nM},$nM,$nM,{-$nM},14,1,0 +erode. 3 !=. 255 r.. 100%,100%,1,3 j... ..,0,0,0,0,1,.,1 rm[-2,-1] frame. xy,1,128 frame. xy,15,220 0 t. "$9",0,0,13,1,-220,-220,-220 j.. .,{({-2,w}-w)/2},{{-2,h}-16},0,0,-1 rm. 0 t. "$10",0,0,13,1,-220,-220,-220 rotate. -90 j.. .,2,{({-2,h}-h)/2},0,0,-1 rm. => $nm } #@cli dq : eq. to 'display_quiver'. dq : _gmic_s="$?" v + _display_quiver $* #@cli display_quiver : _size_factor>0,_arrow_size>=0,_color_mode={ 0:monochrome | 1:grayscale | 2:color } #@cli : Render selected images of 2D vectors as a field of 2D arrows. #@cli : (eq. to 'dq'). #@cli : Default values: 'size_factor=16', 'arrow_size=1.5' and 'color_mode=1'. #@cli : $ image.jpg +luminance gradient[-1] xy rv[-2,-1] *[-2] -1 a[-2,-1] c crop 60,10,90,30 +display_quiver[1] , display_quiver : _gmic_s="$?" v + _$0 $* _display_quiver : check "${1=16}>0 && ${2=1.5}>=0 && isint(${3=2},0,2)" e[0--3] "Render field of 2D arrows from image"$_gmic_s", with size factor $1, arrow size $2 in "\ ${arg0\ $3,monochrome,grayscale,color}" mode." foreach { +norm. /.. {max(1e-6,iM)} rm. # Normalize vector values. {$1*w},{$1*h},1,{"1<[0] {1,n} sh. {s-1} j[0] [1],0,0,0,0,1,[2],255 k[0] fi } to_rgb u $is_rgb #@cli dt : eq. to 'display_tensors'. dt : _gmic_s="$?" v + _display_tensors $* #@cli display_tensors : _size_factor>0,_ellipse_size>=0,_color_mode={ 0:monochrome | 1:grayscale | 2:color },_outline>=0 #@cli : Render selected images of tensors as a field of 2D ellipses. #@cli : (eq. to 'dt'). #@cli : Default values: 'size_factor=16', 'ellipse_size=1.5', 'color_mode=2' and 'outline=2'. #@cli : $ image.jpg +diffusiontensors 0.1,0.9 rescale2d. 64 +display_tensors. 16,2 #@cli : $$ https://gmic.eu/oldtutorial/_display_tensors display_tensors : _gmic_s="$?" v + _$0 $* _display_tensors : check "${1=16}>0 && ${2=1.5}>=0 && isint(${3=2},0,2) && ${4=2}>=0" e[0--3] "Render field of 2x2 tensors from image"$_gmic_s", with size factor $1, ellipse size $2 in "\ ${arg0\ $3,monochrome,grayscale,color}" mode and outline $4." foreach { * {($2*$1/2)/max(abs(im),abs(iM))} # Normalize tensor values. {$1*w},{$1*h},1,{"1<=0, --k, ellipse(#1,X,r1 + k*$4,r2 + k*$4,ang°,1,arg(k + 1,C,Co))); I" rm.. } #@cli dv3d : eq. to 'display_voxels3d'. dv3d : _gmic_s="$?" v + _display_voxels3d #@cli display_voxels3d #@cli : Display selected images as set of 3D voxels. #@cli : (eq. to 'dv3d'). display_voxels3d : _gmic_s="$?" v + _$0 _display_voxels3d : nb_images=$! foreach { nm$>={n} } +foreach { w,h,d={w},{h},{d} nm=${nm$>} bnm={b} l. { if ['{x}']!=0 bnm.=.{x} fi onfail bnm.=.{x} } asiz:=max(w,h,d)/5 +norm1. !=. 0 nbv:=is rm. # Count voxels e[0--4] "Display image ["{arg(1+$>,$[])}"] = '"$nm"' ("${w}x${h}x${d}", "$nbv" voxels)." args=0.5,1,0.75 if s==1 im,iM:=im,iM eval. "*begin(is_bin = 1); i!=$im && i!=$iM?(is_bin = 0; break()); end(merge(is_bin,&&); set('is_bin',is_bin))" if $is_bin # Colorize binary images according to rough normal estimation >. {lerp($im,$iM,0.5)} +expand. xyz,1 b. {min(w,h,d)*2%},0 g. xyz a[-3--1] c orientation. n. 0,220 shrink. xyz,1 *. .. k. args=0.9,1.1,1 fi fi surfels3d $args +3d. 0.5,0.5,0.5 axes3d $asiz,$asiz,$asiz,16 o3d. 0.5 col3d. 255,128,0 +3d } foreach[$nb_images--1] { => ${nm$>} } _display_3d_rendering_mode=2 _display_3d_is_bounding_box=1 _display[$nb_images--1] "",0,$[] rm[$nb_images--1] is_change 0 #@cli dw : eq. to 'display_warp'. dw : _gmic_s="$?" v + _display_warp $* #@cli display_warp : _cell_size>0 #@cli : Render selected 2D warping fields. #@cli : (eq. to 'dw'). #@cli : Default value: 'cell_size=15'. #@cli : $ 400,400,1,2,'x=x-w/2;y=y-h/2;r=sqrt(x*x+y*y);a=atan2(y,x);5*sin(r/10)*[cos(a),sin(a)]' +display_warp 10 display_warp : _gmic_s="$?" v + _$0 $* _display_warp : check "${1=15}>0" e[0--3] "Render 2D warping field"$_gmic_s", with cell size $1." foreach { if d!=1" || "s!=2 error[0--3] "Command 'display_warp': Invalid image ["{$!-$>-1}"]: Dimensions "{w}","{h}","{d}","{s}" does not represent a 2D field of 2D vectors." fi i[0] 100%,100%,1,1,1 grid[0] $1,$1 =>[0] {1,n} warp[0] [1],1,1,0 rm[1] } * 255 #@cli e : eq. to 'echo'. : (+) #@cli echo : message : (+) #@cli : Output specified message on the error output. #@cli : (eq. to 'e').\n #@cli : Command selection (if any) stands for displayed call stack subset instead of image indices. #@cli : When invoked with a '+' prefix (i.e. '+echo'), the command output its message on stdout rather than stderr. #@cli echo_file : filename,message #@cli : Output specified message, appending it to specified output file. #@cli : (similar to 'echo' for specified output file stream). echo_file : skip "${2='\n'}" l[] { it "$1" onfail 0 } ('"${2--1}\n"':y) a[-2,-1] y ot. $1 rm. #@cli font : { 'Font_name' | font_number | font.gmz },_font_height[%]>0,_is_bold={ 0 | 1 } #@cli : Return font identifier (variable name) that can be further used in command `text` as a custom font. #@cli : 'Font name' can be { Acme | Arial | ArialBlack | BlackOpsOne | BlackChancery | CabinSketch | Caprasimo | \ # CarnevaleeFreakshow | CheeseBurger | Cheque | ChequeBlack | Chlorinar | ComicSansMS | CourierNew | Creepster | \ # Georgia | Hidayatullah | Impact | Jaro | Lobster | LuckiestGuy | Macondo | MedievalSharp | OdinRounded | Oswald | \ # PalatinoLinotype | PlayfairDisplay | Roboto | Satisfy | Sofia | SundayMilk | TexGyreAdventor | TimesNewRoman | \ # TitanOne | Typewriter | Verdana }. #@cli : If a filename 'font.gmz' is specified, it must be a file converted with command `font2gmz`. #@cli : Default values: 'font_height=64' and 'is_bold=0'. #@cli : $ 400,300,1,3 text "Hello World!",0.5~,0.5~,${"font \"Cheese Burger\",80"},1,255,255,128 +font : check "isint(${2=24},1) && isbool(${3=0})" fonts="Acme","Arial","Arial Black","Black Ops One","Black Chancery","Cabin Sketch","Caprasimo",\ "Carnevalee Freakshow","Cheese Burger","Cheque","Cheque Black","Chlorinar","Comic Sans MS",\ "Courier New","Creepster","Georgia","Hidayatullah","Impact","Jaro","Lobster","Luckiest Guy",\ "Macondo","Medieval Sharp","Odin Rounded","Oswald","Palatino Linotype","Playfair Display",\ "Roboto","Satisfy","Sofia","Sunday Milk","Tex Gyre Adventor","Times New Roman","Titan One",\ "Typewriter","Verdana" if "s = ['$1']; find(s,'.gmz')==size(s) - 4" font="$1" is_file=1 elif "isint($1)" arg0 {$1%narg($fonts)},$fonts font=${} is_file=0 else font="$1" is_file=0 fi qualifier0,qualifier1="regular","bold" e[^-1] "Return default font '"$font"' with height $2 and "${qualifier$3}" face." height:=ispercentage($2)?h*$2:$2 strvar $font ('${}') discard. {'_'} fontname={t} rm. varname=__font_${fontname}_$qualifier$3$height if narg(${$varname}) u $varname return fi # Font already instancied l[] { if $is_file "$1" else input_cached gmic_fonts.gmz k[$fontname] unserialize fi if $3 # Bold face dsiz:="s = round(h/32); s+=1 - (s%2); max(3,s)" expand xy,{round($dsiz/2)} dilate $dsiz fi if h!=$height if h>$height foreach { r {max(1,round(w*$height/h))},$height,1,1,2 } else sigma:=($height-h)/64 foreach { r {max(1,round(w*$height/h))},$height,1,1,3 } b $sigma,0 function1d[] 1,0,0,128,10,200,255,255,255 c. 0,255 map[^-1] . rm. fi round fi [^] f[0-255] 255 foreach { if $><256 => char$> else => mask{$>-256} fi } store $varname onfail v + error[0--3] "Command 'font': Unknown font '$1'." v - varname=$2 # Fallback rm } u $varname #@cli font2gmz : _font_name,_font_size>0,_font_qualifier #@cli : Convert specified font to G'MIC format, so that it can be used as a custom font for command `text`. #@cli : 'font_name' can be either a filename as 'font.ttf', or a 'Google Font Name'. #@cli : This command requires the command line tool `cutycapt` to be installed on your system. #@cli : Beware, 'font_size' is the size of font used for the rendering, it does **not** correspond to the font height. #@cli : Default values: 'font_name=Sofia', 'font_size=24' and 'font_qualifier=""'. +font2gmz : skip "${1=Sofia},${3=}" check "isint(${2=64},1)" # Extract font name/file information. basename "$1",_dir basename=${} if ['$_dir']==0 _dir=${-path_current} fi path=$_dir 0 => $basename base={b} ext={x} rm. strvar $base varname=${} e[^-1] "Convert font '"$basename"' to G'MIC format, with font size $2 and font qualifier '$3'." # Generate fontchart codes. 256,1,1,1,"x; end( codes0 = [ 32,32,8853,8854,8855,8856,8857,8747,8711,32,32,8800,8730,32,9824,9827, 9829,9830,8745,8746,8743,8744,8712,32,32,32,32,32,32,32,32,8364 ]; draw(codes0,0,size(codes0)); codes1 = [ 32,8734,945,946,8706,948,949,951,947,955,956,969,966,960,968,961, 963,964,952,916,931,915,937,934,928,936,920,8592,8593,8594,8595,8596,8597 ]; draw(codes1,127,size(codes1)); ") codes={^} rm. # Generate HTML page. if "isin(['"$ext"'],'ttf','otf')" # Font specified as a filename str1= str2="@font-face {\n\ font-family: gmicFont;\n\ src: url('"$path$basename"');\n\ }" font_family="gmicFont" else str1="" str2= font_family="\"$1\" $3" fi ('" \n\ \n\ \n\ \n\ "$str1"\n\ \n\ \n"') ({'"\n\n"'}) ind=0 repeat 256 code:=[$codes][$>] bgcolor=${dec2hex\ {16752640+$>}} if !($ind%12) ('"\n"') fi ('"\n"') ind+=1 if !($ind%12)" || "!$< ('"\n"') fi done ({'"\
"') ('"&#"$code;') ('"
\n\ \n\ "'}) y a y file_html=${-path_tmp}gmic_$0_$varname.html ot $file_html # Render as a PNG file, using 'cutycapt'. file_png=${-path_tmp}gmic_$0_$varname.png x 0,"cutycapt --min-width="{32*$2}" --min-height="{32*$2}" --url=file://"$file_html" --out="$file_png rm $file_png to_rgb # Extract letters. autocrop eval[0] ": begin( x0s = y0s = vector256(inf); x1s = y1s = vector256(-inf); void = [ 0,0,0 ]; ); i0==255 && i1==160?( ind = i2; xx1s[ind]?(x1s[ind] = x); y>y1s[ind]?(y1s[ind] = y); ); void; end( merge(x0s,min); merge(y0s,min); merge(x1s,max); merge(y1s,max); set('x0s',v2s(x0s)); set('y0s',v2s(y0s)); set('x1s',v2s(x1s)); set('y1s',v2s(y1s)); )" repeat narg($x0s) { x0,y0,x1,y1:=([$x0s][$>]),([$y0s][$>]),([$x1s][$>]),([$y1s][$>]) if max(isinf([$x0,$x1,$y0,$y1])) 0 warn "Command '$0': Character code "$>" ('"{/{/{`$>`}}}"') could not be found!" else +z[0] $x0,$y0,0,0,$x1,$y1,0,0 fi } rm[0] negate # Autocrop characters along the X-axis. foreach { if iM>0 autocrop_coords 0 x0,y0,z0,x1,y1,z1=${} z $x0,$x1 fi } # Autocrop font along the Y-axis. my0,My1=inf,-inf foreach { if iM autocrop_coords 0 x0,y0,z0,x1,y1,z1=${} my0,My1:=min($my0,$y0),max($My1,$y1) fi } z 0,$my0,100%,$My1 # Set default width for invisible characters. W={{'f'},w} foreach { if !iM r $W,100%,1,1,0 fi } # Transform to G'MIC gmz font format. n 0,255 sharpen 100 round foreach { => mask$> } #@cli function1d : 0<=smoothness<=1,x0>=0,y0,x1>=0,y1,...,xn>=0,yn #@cli : Insert continuous 1D function from specified list of keypoints (xk,yk) #@cli : in range [0,max(xk)] (xk are positive integers). #@cli : $ function1d 1,0,0,10,30,40,20,70,30,80,0 +display_graph 400,300 +function1d : e[^-1] "Input continuous 1D function, with smoothness $1 and keypoints (${2--1})." l[] { # Sort and normalize input keypoints. smoothness:=max(0,min(1,$1)) (${2--1}) r 2,{int(w/2)},1,1,-1 sort +,y s x size={0,iM>=0?1+int(iM):0} if !$size rm 0 break fi a x # Compute slopes for splines. +f "(j(0,1,0,0,0,1) - j(0,-1,0,0,0,1))/2" s. x max.. 0.01 /. .. rm.. a x # Determine spline coefficients for each part of the curve. $size,1,1,1,-1 repeat h#0-1 { x0,y0,x1,y1={0,[i(0,$>),i(1,$>),i(0,$>+1),i(1,$>+1)]} slope:=($y1-$y0)/max(0.01,$x1-$x0) yp0={0,i(2,$>)*$smoothness+(1-$smoothness)*$slope} yp1={0,i(2,$>+1)*$smoothness+(1-$smoothness)*$slope} i:=round($x0,1,1) j:=round($x1,1,0) line[1] $i,0,$j,0,1,$> if $j-$i<=1 # Linear interpolation for very close points. ({$y0-$x0*$slope}^{$slope}^0^0) else # Cubic interpolation otherwise. (1,$x0,{($x0)^2},{($x0)^3};\ 1,$x1,{($x1)^2},{($x1)^3};\ 0,1,{2*$x0},{3*($x0)^2};\ 0,1,{2*$x1},{3*($x1)^2}) ($y0;$y1;$yp0;$yp1) invert.. 1 m*[-2,-1] y. c fi } a[2--1] x map.. . rm. # Render final curve. 100%,1,1,1,1 (0,{w-1}) r. {-2,w},1,1,1,3 round. +sqr. +*[-2,-1] a[-4--1] c *[-2,-1] s. c +[-4--1] rm.. } # [Internal] Generate all custom G'MIC fonts and upload them to the G'MIC server. update_fonts : fonts="Acme",\ "Arial",\ "Arial Black",\ "Black Ops One",\ "Cabin Sketch",\ "Caprasimo",\ "Comic Sans MS",\ "Courier New",\ "Creepster",\ "Georgia",\ "Impact",\ "Jaro",\ "Luckiest Guy",\ "Macondo",\ "MedievalSharp",\ "Oswald",\ "Palatino Linotype",\ "Playfair Display",\ "Roboto",\ "Satisfy",\ "Sofia",\ "Titan One",\ "Times New Roman",\ "Verdana" files $HOME/work/src/gmic/html/fonts/*.otf if narg(${}) fonts.=,${} fi files $HOME/work/src/gmic/html/fonts/*.ttf if narg(${}) fonts.=,${} fi target_height=512 nb_fonts:=narg($fonts) repeat narg($fonts) { arg0 $>,$fonts font=${} basename $font 0 => ${} name={b} rm. strvar $name varname=${} outfile=$_path_rc/font_$varname.gmz e " - "$name if !isfile(['$outfile']) l[] { outgmz=${-path_tmp}gmic_$0_$varname.gmz size:=round($target_height/1.18) best_size=0 best_font= do rm if $size!=$best_size" || "!narg($best_font) x 0,"gmic font2gmz \\\""$font"\\\","$size" o "$outgmz $outgmz if h>$target_height +store best_font best_size=$size fi e " \#"$>" -> "{h} else $best_font e " \#"$>" -> Best guess: "{h} break fi if h!=$target_height nsize:=$size+$target_height-h size:=round(lerp($nsize,$size,0.35)) fi while h!=$target_height delete $outgmz if h>$target_height rs ,$target_height fi ge 75% * 255 serialize auto,1,0 => $varname o $outfile rm } fi } # Generate font collection file. files ${_path_rc}font_*.gmz files=${} repeat narg($files) { arg0 $>,$files ${} } sort_list +,n outfile1=${_path_rc}gmic_fonts.gmz outfile2=$HOME/work/src/gmic/resources/gmic_fonts.gmz o $outfile1 o $outfile2 e " - Upload all fonts to G'MIC server." _upload $outfile1 rm # Generate documentation string for command 'font'. list_fonts,sep= repeat narg($fonts) { arg0 $>,$fonts font=${} basename $font 0 => ${} name={b} rm. strvar $name varname=${} list_fonts.=$sep$name sep=, } str="#@cli : 'Font name' can be { " 0x{narg($list_fonts)} => $list_fonts sort_list +,n lof n list_fonts=${} rm repeat narg($list_fonts) { arg0 $>,$list_fonts str.=${} if $< str.=" | " else str.=" }." fi } e " - Generate documentation string for command 'font':\n"$str #@cli identity : _width>=0,_height>=0,_depth>=0 #@cli : Insert an identity map of given size at the end of the image list. #@cli : Default values: 'height=width' and 'depth=1'. #@cli : $ identity 5,1 identity 8,8 +identity : check "isint($1,0) && isint(${2=$1},0) && isint(${3=1},0)" e[^-1] "Input $1x$2x$3 identity map." if $3>1 $1,$2,$3,3,[x,y,z] # 3D elif $2>1 $1,$2,1,2,[x,y] # 2D elif $1>0 $1,1,1,1,x # 1D else 0 fi #@cli i : eq. to 'input'. : (+) #@cli input : \ # [type:]filename : \ # [type:]http://URL : \ # [selection]x_nb_copies>0 : \ # { width>0[%] | [image_w] },{ _height>0[%] | [image_h] },{ _depth>0[%] | [image_d] },{ _spectrum>0[%] \ # | [image_s] },_{ value1,_value2,... | 'formula' } : \ # (value1{,|;|/|^}value2{,|;|/|^}...[:{x|y|z|c|,|;|/|^}]) : \ # 0 : (+) #@cli : Insert a new image taken from a filename or from a copy of an existing image [index], #@cli : or insert new image with specified dimensions and values. Single quotes may be omitted in #@cli : 'formula'. Specifying argument '0' inserts an 'empty' image. #@cli : (eq. to 'i' | (no arg)). #@cli : Default values: 'nb_copies=1', 'height=depth=spectrum=1' and 'value1=0'. #@cli : $ input image.jpg #@cli : $ input (1,2,3;4,5,6;7,8,9^9,8,7;6,5,4;3,2,1) #@cli : $ image.jpg (1,2,3;4,5,6;7,8,9) (255^128^64) 400,400,1,3,'(x>w/2?x:y)*c' #@cli : $$ #@cli input_565 : filename,width>0,height>0,reverse_endianness={ 0 | 1 } #@cli : Insert image data from a raw RGB-565 file, at the end of the list. #@cli : Default value: 'reverse_endianness=0'. +input_565 : check "isint($2,1) && isint($3,1) && isbool(${4=0})" e[^-1] "Input raw RGB-565 file '"{/"$1"}"', with size $2x$3." l[] { raw:"$1",uint16 if $4 endian uint16 fi r $2,$3,1,1,-1 +>> 5 &. 63 +&.. 31 >>... 11 *[-3,-1] 8 *.. 4 a c } #@cli ib : eq. to 'input_bytes'. +ib : v + _input_bytes "$*" #@cli input_bytes : filename #@cli : Input specified filename as a 1D array of bytes. #@cli : (eq. to 'ib'). +input_bytes : v + _$0 "$*" _input_bytes : e[0--3] "Input file '"{/"$*"}"' as a 1D array of bytes." i raw:"$*",uint8 #@cli input_csv : "filename",_read_data_as={ 0:numbers | 1:strings | _variable_name } #@cli : Insert number of string array from specified .csv file. #@cli : If 'variable_name' is provided, the string of each cell is stored in a numbered variable '_variable_name_x_y', \ # where 'x' and 'y' are the indices of the cell column and row respectively (starting from '0'). #@cli : Otherwise, a 'WxH' image is inserted at the end of the list, with each vector-valued pixel 'I(x,y)' encoding \ # the number or the string of each cell. #@cli : This command returns the 'W,H' dimension of the read array, as the status. #@cli : Default value: 'read_data_as=1'. +input_csv : check "isvarname('${2=1}') || isin($2,0,1,2)" l[] { is_var:="s = ['$2']; s!='0' && s!='1'" if $is_var # Read data as new variables e[0--4] "Input string array from file '"{/"$1"}"', in variable '$2'." it "$1" replace {'" "'},255 s -,{'\n'} W,H=0,$! repeat $! { _input_csv_var[] $2,$>,${u\ {$>,t}} W:=max($W,${}) } rm u $W,$H else # Read data as a new image s0,s1="number","string" e[0--4] "Input "${s$2}" array from file '"{/"$1"}"'." it "$1" s -,{'\n'} replace {'" "'},255 if $2 repeat $! { _input_csv_str[] ${u\ {$>,t}} rv[$>,-1] rm. } a y replace 255,{'" "'} # Read as strings else repeat $! { _input_csv_val[] ${u\ {$>,t}} rv[$>,-1] rm. } a y # Read as values fi => "$1" u {[w,h]} fi } _input_csv_var : $=arg W:=$#-2 repeat $W { arg=${arg{3+$>}} if ['$arg']!=0 ({'${arg{3+$>}}'}) replace. 255,{'" "'} $1_$>_$2={t} rm. else $1_$>_$2= fi } u $W _input_csv_str : $=arg repeat $# { arg=${arg{1+$>}} if ['$arg']!=0 ({'$arg'}:c) else (0) fi } a x _input_csv_val : $=arg repeat $# { arg=${arg{1+$>}} if ['$arg']!=0 ({s2v(['$arg'])}) else (nan) fi } a x #@cli input_cube : "filename",_convert_1d_cluts_to_3d={ 0 | 1 }. #@cli : Insert CLUT data from a .cube filename (Adobe CLUT file format). #@cli : Default value: 'convert_1d_cluts_to_3d=1'. +input_cube : skip ${2=1} e[^-1] "Input CLUT from file '"{/"$1"}"'" l[] { it[] "$1" f "i<_' ' && i!=10?_' ':i" s -,10 i[0] 0 range:=" line = vector128(); dmin = [ 0,0,0 ]; dmax = [ 1,1,1 ]; dim = size = 0; target = 0; for (k = 1, k1 && h==1 && d==1 && $2" size:=w s c y.. y. z r $size,$size,$size a c fi => "$1" } #@cli input_flo : "filename" #@cli : Insert optical flow data from a .flo filename (vision.middlebury.edu file format). +input_flo : e[^-1] "Input optical flow from file '"{/"$1"}"'." l[] { i raw:"$1",float32 if i!=202021.25 endian. fi if i!=202021.25 error[0--3] "Command 'input_flo': Filename '$1' is not a valid .flo file." return fi +rows 1,2 cast. float32,uint32 w,h={^} rm. rows 3,100% r 2,$w,$h,1,-1 permute yzcx } #@cli ig : eq. to 'input_glob'. +ig : v + _input_glob "$*" #@cli input_glob : pattern #@cli : Insert new images from several filenames that match the specified glob pattern. #@cli : (eq. to 'ig'). +input_glob : v + _input_glob "$*" _input_glob : e[0--3] "Input all files that match glob pattern '"{/"$*"}"'." files 3,"$*" N=$! m "_ig : $""=arg repeat $""# { i ${arg{1+$>}} }" _ig ${} um _ig if $N==$! error[0--3] "Command 'input_glob': No matching filenames for pattern '$*'." fi #@cli input_gpl : filename #@cli : Input specified filename as a .gpl palette data file. +input_gpl : e[^-1] "Input palette from file '"{/"$*"}"'." l[] { it[] "$*" discard 13 replace 9,32 s -,10 foreach { l { s -,32 if $!>=3" && "isint({0,t})" && "isint({1,t})" && "isint({2,t}) ({0,t}^{1,t}^{2,t}) k. else rm 0 fi onfail rm 0 } } a x => "$1" } #@cli input_cached : "basename.ext",_try_downloading_from_gmic_server={ 0 | 1 } #@cli : Input specified filename, assumed to be stored in one of the G'MIC resource folder. #@cli : If file not found and 'try_downloading=1', file is downloaded from the G'MIC server and stored #@cli : in the '${-path_cache}' folder. #@cli : Default value: 'try_downloading_from_gmic_server=1'. +input_cached : check "isbool(${2=1})" basename "$1" basename=${} e[^-1] "Input cached file '"$basename"'." if ['$GMIC_SYSTEM_PATH']!=0 g_path_unix=$GMIC_SYSTEM_PATH/ else g_path_unix=/usr/lib/gimp/2.0/plug-ins/ fi path_test0=${-path_cache} path_test1=$_path_rc path_test2=${-path_gimp}plug-ins/ path_test3=${-path_gimp}plug-ins/gmic_gimp_qt/ if !${-is_windows} path_test4=/usr/share/gmic/ path_test5=$g_path_unix else path_test4=$g_path_unix fi file_found=0 repeat inf { if ['{/${path_test$>}}']==0 break fi file=${path_test$>}$basename l[] { i $file file_found=1 onfail } if $file_found break fi } if !$file_found" && $2" # Try downloading from the G'MIC server url=https://gmic.eu/"$1" l[] { i $url o ${-path_cache}$basename file_found=1 is_change 1 onfail } fi if !$file_found error[0--3] "Command 'input_cached': Unknown filename '"{/"$1"}"'." fi #@cli in : eq. to 'input_normalized'. +in : v + _input_normalized "$*" #@cli input_normalized : filename #@cli : Input specified filename and constrain its value range to be in [0,255]. #@cli : (eq. to 'in'). +input_normalized : v + _input_normalized "$*" _input_normalized : e[0--3] "Input file '"{/"$*"}"', in normalized range [0,255]." l[] { u 0 i "$1" bpp=${} autodetect=1 if $!==1" && "isnum($bpp) if $bpp==8 autodetect=0 elif $bpp==16 / 257 autodetect=0 fi fi if $autodetect foreach { im,iM:=im,iM if $im>=0 if $iM<=255 # Do nothing elif $iM<=1 * 255 elif inrange($iM,255,65535,0,1) / 257 else n 0,255 fi else n 0,255 fi } fi } #@cli input_obj : filename #@cli : Input specified 3D mesh from a .obj Wavefront file. +input_obj : e[^-1] "Input 3D mesh from file '"{/"$*"}"'" l[] { it "$1" f "i<=_' ' && i!=_'\n'?_' ':i" # Normalize blank characters. 0 => "$1" folder={f} rm. # Retrieve file folder 1 # [1]: vertices (unrolled) 1 # [2]: primitives (unrolled) 1 # [3]: colors (unrolled) 1 # [4]: opacity 1,1,1,2 # [5]: texture 2D pts 0 # [6]: vertices weights/colors (RGB) input_obj_rgb,input_obj_alpha= eval " next_item() = ( while (line[q] && line[q]!=_' ' && line[q]!=_'/',++q); while (line[q] && line[q]==_' ', ++q); ); push_texture() = ( s_ind_prim = string('ind_prim',p_img); ind_prim = get(s_ind_prim); isnan(ind_prim)?( # Texture never used before da_push(#3,-128,ui2f(p_whs[0]),p_whs[1],p_whs[2]); dsiz = da_size(#3); isiz = prod(p_whs); resize(#3,1,h(#3) + isiz,1,1,0); copy(i[#3,dsiz],i[#p_img,0],isiz); i[#3,h(#3) - 1] = ui2f(dsiz + isiz); set(s_ind_prim,nbp); ):( # Texture used before -> shared version da_push(#3,-128,ui2f(ind_prim),0,0); ); ); id_v = 'v '; id_f = 'f '; id_l = 'l '; id_p = 'p '; id_vt = 'vt '; id_mtllib = 'mtllib '; id_usemtl = 'usemtl '; line = vector1024(0); varname = string(#256,'default'); p_rgb = [ 200,200,200 ]; p_alpha = 1; p_img = -1; p_whs = [ 0,0,0 ]; line_number = 1; nbv = nbp = p = 0; while (p0?( copy(line,line[q],len + 1); run('__input_obj_parse_mtl \""{/$folder}"\",\"',line,'\"'); ); ):line[0,7]==id_usemtl?( # Material: change material q = 0; next_item(); len = find(line,0,q); len>0?( # Convert material name to corresponding variable name. pp = q; qq = 0; inrange(line[pp],_'0',_'9')?varname[qq++]=_'_'; while (pp=0" # Convert vertex weights to colored primitives 1,$nbp,1,1,">begin(N = p = 0); nbv = i[#2,p++]; R = G = B = nR = nG = nB = 0; repeat (nbv, ind = i[#2,p++]; val = i(#6,0,ind,0,0); val>=0?(R+=val; ++nR); val = i(#6,0,ind,0,1); val>=0?(G+=val; ++nG); val = i(#6,0,ind,0,2); val>=0?(B+=val; ++nB); ); R = nR>0?round(255*R/nR):200; G = nG>0?round(255*G/nG):R; B = nB>0?round(255*B/nB):B; off = 3*N; i[#3,off++] = R; i[#3,off++] = G; i[#3,off] = B; ++N" rm. fi rm[0,5--1] i[0] ({'CImg3d'},{ui2f([$nbv,$nbp])}:y) a y => "$1" } __input_obj_parse_mtl : skip "${1=}" img_ind=$! mtl_filename="$1$2" if !isfile(['{/$mtl_filename}']) return fi l[] { it $mtl_filename f "i<=_' ' && i!=_'\n'?_' ':i" # Normalize blank characters. s -,{'\n'} autocrop {'" "'} N:=$! m=default input_obj_rgb_$m=200,200,200 input_obj_alpha_$m=1 input_obj_img_$m=-1 input_obj_whs_$m=0,0,0 1,$!,1,1,"> begin( img_ind = $img_ind; line = filename = vector1024(); varname = string(#256,'default'); id_newmtl = 'newmtl'; id_Kd = 'Kd '; id_d = 'd '; id_map_Kd = 'map_Kd '; id_200_200_200 = '200,200,200'; id_0_0_0 = '0,0,0'; ); i[#y,0]!=_'#'?( siz = h#y; copy(line,i[#y,0],min(siz,size(line))); line[siz] = 0; same(line,id_newmtl,size(id_newmtl))?( # New material # Convert material name to corresponding variable name. p = 7; q = 0; inrange(line[p],_'0',_'9')?varname[q++]=_'_'; while (p0?( filename = 0; copy(filename,line[7],p - 7); run(string('l[] { l[] { $1',filename,' onfail basename \"$1',filename,'\" ${} } rs. 1024,1024', ' input_obj_img_',varname,'=',img_ind, ' input_obj_whs_',varname,':=w,h,s', ' y u 1', ' onfail', ' input_obj_img_',varname,'=-1', ' input_obj_whs_',varname,'=0,0,0', ' u 0 }')); get('{}')?++img_ind; # Else error occured when loading texture ); ); )" rm[0-$N] } #@cli it : eq. to 'input_text'. +it : v + _input_text "$*" #@cli input_text : filename #@cli : Input specified text-data filename as a new image. #@cli : (eq. to 'it'). +input_text : v + _$0 "$*" _input_text : e[0--3] "Input text-data file '"{/"$*"}"'." i raw:"$*",uint8 if i[0]==239" && "i[1]==187" && "i[2]==191 rows. 3,100% fi # Remove BOM discard. {'\r'} # Remove CR #@cli lorem : _width>0,_height>0 #@cli : Input random image of specified size, retrieved from Internet. #@cli : Default values: 'width=height=800'. +lorem : check "isint(${1=800},1) && isint(${2=$1},1)" e[^-1] "Input random image of size $1x$2." i jpg:"https://picsum.photos/$1/$2" => lorem # Merge multi-lines (lines continued by backslash before NL) in a string image. merge_multiline : eval[^] "*i==_'\\' && j[-1]!=_'\\' && j[+1]==_'\n'?( for (p = 2, j[p] && j[p]<=_' ' && j[p]!=_'\n', ++p); copy(i(),-1,p,1,0))" discard -1 # Merge multi-line comments in a string image. merge_multiline_comments : eval[^] "*i==_'\\' && j[-1]!=_'\\' && j[+1]==_'\n' && j[+2]==_'#'?( for (p = 3, j[p] && j[p]<=_' ' && j[p]!=_'\n', ++p); copy(i(),-1,p,1,0))" discard -1 #@cli network : mode={ -1=disabled | 0:enabled w/o timeout | >0:enabled w/ specified timeout in seconds } : (+) #@cli : Enable/disable load-from-network and set corresponding timeout. #@cli : (Default mode is 'enabled w/o timeout'). #@cli o : eq. to 'output'. : (+) #@cli output : [type:]filename,_format_options : (+) #@cli : Output selected images as one or several numbered file(s). #@cli : (eq. to 'o'). #@cli : Default value: 'format_options'=(undefined). #@cli output_565 : "filename",reverse_endianness={ 0:false | 1:true } #@cli : Output selected images as raw RGB-565 files. #@cli : Default value: 'reverse_endianness=0'. output_565 : check "isbool(${2=0})" e[^-1] "Output image$? as raw RGB-565 file '"{/"$1"}"'." N=$! +foreach { s c c 0,255 /[-3,-1] 8 /.. 4 round bsl... 11 bsl.. 5 + if $N>1 fn=${"filename \"$1\",$>"} else fn="$1" fi if $2 endian uint16 fi o raw:$fn,uint16 rm } is_change 0 #@cli output_cube : "filename" #@cli : Output selected CLUTs as a .cube file (Adobe CLUT format). output_cube : e[^-1] "Output CLUT$? as file '"{/"$1"}"'." N=$! +foreach { to_rgb l:=round((w*h*d)^(1/3)) if w*h*d!=$l^3 error "Command '$0': CLUT '"{n}"' has invalid dimensions "({w},{h},{d},{s}). fi r $l,$l,$l,3,-1 permute cxyz / 255 if $N>1 fn=${"filename \"$1\",$>"} else fn="$1" fi o dlm:$fn rm it[] $fn replace {','},32 0 => $fn basename={b} rm. header="\# Created by: G'MIC (https://gmic.eu)\n"\ "TITLE \""$basename"\"\n\n"\ "# LUT size\n"\ "LUT_3D_SIZE "$l"\n\n"\ "# Data domain\n"\ "DOMAIN_MIN 0.0 0.0 0.0\n"\ "DOMAIN_MAX 1.0 1.0 1.0\n\n"\ "# LUT data points\n" i[0] ('$header') y a y ot $fn rm } is_change 0 #@cli output_flo : "filename" #@cli : Output selected optical flow as a .flo file (vision.middlebury.edu file format). output_flo : e[^-1] "Output optical flow$? as file '"{/"$1"}"'." N=$! +foreach { w,h={w},{h} channels 0,1 permute cxyz i[0] (202021.25) i[1] ($w,$h) cast[1] uint32,float32 y a y if $N>1 fn=${"filename \"$1\",$>"} else fn="$1" fi o raw:$fn,float32 rm } is_change 0 #@cli output_ggr : filename,_gradient_name #@cli : Output selected images as .ggr gradient files (GIMP). #@cli : If no gradient name is specified, it is deduced from the filename. output_ggr : skip "${2=}" e[^-1] "Output image$? as .ggr gradient file '"{/"$1"}"'." N=$! +foreach { r 1,{w*h*d},1,100%,-1 to_rgba / 255 if narg("$2") name="$2" else l[] { 1 => "$1" ('{b}') f "!x && i>=_'a' && i<=_'z'?i-_'a'+_'A':i" name={t} rm } fi ('"GIMP Gradient\nName: "$name\n{0,h}\n') repeat h#0 { start={_$>/{0,h}} end={_($>+1)/{0,h}} mid={_0.5*($start+$end)} rgba={0,I(0,$>)} r:=arg(1,$rgba) g:=arg(2,$rgba) b:=arg(3,$rgba) a:=arg(4,$rgba) ('$start" "$mid" "$end" "$r" "$g" "$b" "$a" "$r" "$g" "$b" "$a" 0 0\n"') } rm[0] a x if $N>1 ot ${"filename \"$1\",$>"} else ot "$1" fi rm } is_change 0 #@cli output_gmz : filename,_datatype #@cli : Output selected images as .gmz files (G'MIC native file format). #@cli : 'datatype' can be \ # { bool | uint8 | int8 | uint16 | int16 | uint32 | int32 | uint64 | int64 | float32 | float64 }. output_gmz : skip ${2=auto} e[^-1] "Output image$? as gmz file '"{/"$1"}"', with pixel type '$2'." 1,64 eval " draw([_'G',_'M',_'Z'],0,0,1,3); pos = 4; repeat (l - 1,k, nam = name(#k,1026); len = find(nam,0); len>=0?( pos + len>=h?resize(#-1,1,2*h + len,1,1,0); len>0?draw(nam,0,pos,1,len); pos+=1 + len; ); ); resize(#-1,1,pos,1,1,0)" o cimgz:$1,$2 rm. #@cli output_obj : filename,_save_materials={ 0:no | 1:yes } #@cli : Output selected 3D meshes as Wavefront 3D object files. #@cli : Set 'save_materials' to '1' to produce a corresponding material file (`.mtl`) and eventually texture files. #@cli : Beware, the export to `.obj` files may be quite slow for large 3D objects. #@cli : Default value: 'save_materials=1'. output_obj : check "isbool(${2=1})" check3d s0,s1=out, e[^-1] "Output 3D object$? as Wavefront 3D object file '"{/"$1"}"' (with"${s$2}" materials)." N=$! +foreach { nm={n} nb_materials,nb_primitives,nb_textures,nb_tcoords=0 # Generate basename, file header and eval macros. if $N>1 filename=${"filename \"$1\",$>"} else filename="$1" fi 0 => $filename basename={b} folder={f} ext={x} if narg($ext) ext=.$ext fi rm. fnobj="begin(out = 0; s_rgba = vector8()); tos(x) = v2s(x,6); strhex(v,str) = ( ref(v,_v); hex(x) = (_x = int(x); _x + (_x<10?_'0':_'A' - 10)); fill(str,_k,hex(_k%2?_v[(_k-1)/2]%16:_v[_k/2]/16)); ); write(ind,s) = ( l_s = find(s,0); l_s<=0?(l_s = size(s)); out + l_s>h(#ind)?resize(#ind,1,2*h(#ind) + l_s,1,1,0); copy(i[#ind,out],s,l_s); out+=l_s)" # Decompose into vertices, primitives and colors. s3d nb_vertices,nbp={1,f2ui([i[0],i[1]])} rm[0,1] e " > File '"$folder$basename$ext"' ("$nb_vertices" vertices, "$nbp" primitives)." # Export vertices. l[0] { e " - Export vertices." r 3,{h/3},1,1,-1 permute zycx 0 => out_obj eval.. ">"$fnobj"; !(y%10000)?run('e \"\r - Export vertices: ',round(100*y/h),' % \"'); str = string('v ',tos(i0),' ',tos(i1),' ',tos(i2),'\n'); write(#"$out_obj",str); I; end(resize(#"$out_obj",1,out,1,1,0))" rm[0] i[0] ('"\n# Vertices.\n"':y) a y => out_obj e "\r - Export vertices: Done." } # Export materials (colors/textures + opacities). if $2 l[2,3] { e " - Export materials." 1,$nbp,1,3 => materials 0 => out_mtl mv[0] $! eval $fnobj"; const nbp = "$nbp"; off_p = off_o = nb_materials = nb_textures = 0; rgba = vector4(); s_opac = vector32(); repeat (nbp,p, !(p%10000)?run('e \"\r - Export materials: ',round(100*p/nbp),' % \"'); s_opac[0] = 0; opac = i[#0,off_o]; i[off_p]!=-128?( # RGB[A] color rgba = cut(round(crop(0,off_p,1,4)),0,255); rgba[3] = opac!=-128?round(255*cut(opac,0,1)):255; strhex(rgba,s_rgba); vname = string('mat_',s_rgba); m = get(vname); isnan(m)?( ++nb_materials; opac!=-128 && opac<1?copy(s_opac,string('d ',tos(cut(opac,0,1)),'\n')); ref(string('newmtl m',nb_materials,'\n', 'Kd ',tos(rgba[0]/255),' ',tos(rgba[1]/255),' ',tos(rgba[2]/255),'\n',s_opac),str); write(#"$out_mtl",str); set(vname,v2s(m = nb_materials)); ); i[#"$materials",p] = m; off_p+=3; ):i[off_p + 2]?( # Non-shared texture ++nb_materials; ++nb_textures; wt = i[off_p + 1]; ht = i[off_p + 2]; st = i[off_p + 3]; opac!=-128 && opac<1?copy(s_opac,string('d ',tos(cut(opac,0,1)),'\n')); run('+rows. ',off_p + 4,',',off_p + 3 + wt*ht*st,' r. ',wt,',',ht,',1,',st,',-1 tfn=$folder${basename}_t', nb_textures,'.png o. $tfn rm.'); tfile = get('tfn',1024,1); ref(string('newmtl m',nb_materials,'\n', 'map_Kd ',get('tfn',1024,1),'\n',s_opac),str); write(#"$out_mtl",str); I[#"$materials",p] = [ nb_materials,wt,ht ]; off_p+=4 + wt*ht*st; ):( # Shared texture tind = i[off_p + 1]; I[#"$materials",p] = I[#"$materials",tind]; off_p+=4; ); off_o+=(opac==-128?4 + prod(crop(#0,0,off_o + 1,1,3)):1); ); resize(#"$out_mtl",1,out,1,1,0); run('nb_materials,nb_textures=',nb_materials,',',nb_textures)" e "\r - Export materials: Done." k[materials,out_mtl] } fi # Export primitives. e " - Export primitives." 0 => out_prim mv[1] $! eval $fnobj"; const materials = 0"$materials"; const nbp = "$nbp"; off = p_material = material = nb_primitives = nb_tcoords = 0; repeat (nbp,p, !(p%10000)?run('e \"\r - Export primitives: ',round(100*p/nbp),' % \"'); type = i[off]; $2?material = i[#materials,p]; material!=p_material?ref(string('usemtl m',material,'\n'),str_mtl); type==1?( # Colored point ref(string('p ',f2ui(i[off + 1] + 1),'\n'),str); write(#"$out_prim",str); ++nb_primitives; ):type==2 || type==6?( # Colored or textured segment $2 && material!=p_material?write(#"$out_prim",str_mtl); ref(string('l ',f2ui(i[off + 1]) + 1,' ',f2ui(i[off + 2]) + 1,'\n'),str); write(#"$out_prim",str); ++nb_primitives; ):type==3 || (type==9 && !$2)?( # Colored triangle $2 && material!=p_material?write(#"$out_prim",str_mtl); ref(string('f ',f2ui(i[off + 1]) + 1,' ',f2ui(i[off + 2]) + 1,' ',f2ui(i[off + 3]) + 1,'\n'),str); write(#"$out_prim",str); ++nb_primitives; ):type==4 || (type==12 && !$2)?( # Colored quadrangle $2 && material!=p_material?write(#"$out_prim",str_mtl); ref(string('f ',f2ui(i[off + 1]) + 1,' ',f2ui(i[off + 2]) + 1,' ', f2ui(i[off + 3]) + 1,' ',f2ui(i[off + 4]) + 1,'\n'),str); write(#"$out_prim",str); ++nb_primitives; ):type==9?( # Textured triangle material!=p_material?write(#"$out_prim",str_mtl); wt = i(#materials,0,p,0,1); ht = i(#materials,0,p,0,2); ref(string('vt ',tos(i[off + 4]/wt),' ',tos(1 - i[off + 5]/ht),'\n', 'vt ',tos(i[off + 6]/wt),' ',tos(1 - i[off + 7]/ht),'\n', 'vt ',tos(i[off + 8]/wt),' ',tos(1 - i[off + 9]/ht),'\n', 'f ',f2ui(i[off + 1]) + 1,'/',nb_tcoords + 1,' ',f2ui(i[off + 2]) + 1,'/',nb_tcoords + 2, ' ',f2ui(i[off + 3]) + 1,'/',nb_tcoords + 3,'\n'),str); write(#"$out_prim",str); nb_tcoords+=3; ++nb_primitives; ):type==12?( # Textured quadrangle material!=p_material?write(#"$out_prim",str_mtl); wt = i(#materials,0,p,0,1); ht = i(#materials,0,p,0,2); ref(string('vt ',tos(i[off + 5]/wt),' ',tos(1 - i[off + 6]/ht),'\n', 'vt ',tos(i[off + 7]/wt),' ',tos(1 - i[off + 8]/ht),'\n', 'vt ',tos(i[off + 9]/wt),' ',tos(1 - i[off + 10]/ht),'\n', 'vt ',tos(i[off + 11]/wt),' ',tos(1 - i[off + 12]/ht),'\n', 'f ',f2ui(i[off + 1]) + 1,'/',nb_tcoords + 1,' ',f2ui(i[off + 2]) + 1,'/',nb_tcoords + 2, ' ',f2ui(i[off + 3]) + 1,'/',nb_tcoords + 3,' ',f2ui(i[off + 4]) + 1,'/',nb_tcoords + 4,'\n'),str); write(#"$out_prim",str); nb_tcoords+=4; ++nb_primitives; ):( # Unsupported primitive run(['warn \"Command 'output_obj': Cannot convert primitive \#'],p,'/',nbp - 1, ' (size ',type,'). Ignoring it.\"'); ); off+=type + 1; p_material = material; ); resize(#"$out_prim",1,out,1,1,0); run('nb_primitives=',nb_primitives)" e "\r - Export primitives: Done." l[out_obj,out_prim] { i[1] ('"\n# Primitives.\n"':y) a y } if $2 k[out_mtl,out_obj] else k[out_obj] fi # File header. header="# Object name: "$nm\n\ "# Vertices: "$nb_vertices\n\ "# Primitives: "$nb_primitives\n\ "# Materials: "$nb_materials\n\ "# Textures: "$nb_textures\n\ "# Generated on "${-date}" at "${-time}", by G\47MIC (https://gmic.eu).\n" # Save object geometry (.obj file). l[out_obj] { i[0] ('"# File: "$folder$basename$ext\n$header':y) if $2 i[1] ('"\n# Materials.\n\ mtllib "$folder$basename".mtl\n"':y) fi a y ot $folder$basename$ext } # Save object materials (.mtl file). if $2 l[out_mtl] { i[0] ('"# File: "$folder$basename.mtl\n$header\n':y) a y ot $folder$basename.mtl } fi rm } is_change 0 #@cli ot : eq. to 'output_text'. ot : _gmic_s="$?" v + _output_text "$*" #@cli output_text : filename #@cli : Output selected images as text-data filenames. #@cli : (eq. to 'ot'). output_text : _gmic_s="$?" v + _$0 "$1" _output_text : e[0--3] "Output image"$_gmic_s" as text-data file '"{/"$1"}"'." o raw:"$1",uint8 #@cli on : eq. to 'outputn'. on : _gmic_s="$?" v + _outputn $* #@cli outputn : filename,_index #@cli : Output selected images as automatically numbered filenames in repeat...done loops. #@cli : (eq. to 'on'). outputn : _gmic_s="$?" v + _$0 $* _outputn : skip "${2=}" if $#==1 filename=${filename\ "$1",$>} else filename=${filename\ "$1",$2} fi e[0--3] "Output image"$_gmic_s" as file '"{/$filename}"'." o $filename #@cli op : eq. to 'outputp'. op : _gmic_s="$?" v + _outputp $* #@cli outputp : prefix #@cli : Output selected images as prefixed versions of their original filenames. #@cli : (eq. to 'op'). #@cli : Default value: 'prefix=_'. outputp : _gmic_s="$?" v + _$0 $* _outputp : skip ${1="_"} if $!>1 e[0--4] "Output image"$_gmic_s" as their initial locations, prefixed by '$1'." else e[0--4] "Output image"$_gmic_s" as its initial location, prefixed by '$1'." fi foreach { if ['{x}']!=0 ext=.{x} else ext= fi o {f}$1{b}$ext } #@cli ow : eq. to 'outputw'. ow : _gmic_s="$?" v + _outputw #@cli outputw #@cli : Output selected images by overwriting their original location. #@cli : (eq. to 'ow'). outputw : _gmic_s="$?" v + _$0 $* _outputw : if $!>1 e[0--4] "Output image"$_gmic_s" as their initial location." else e[0--4] "Output image"$_gmic_s" as its initial location." fi repeat $! { o[$>] {$>,n} } #@cli ox : eq. to 'outputx'. ox : _gmic_s="$?" v + _outputx $* #@cli outputx : extension1,_extension2,_...,_extensionN,_output_at_same_location={ 0 | 1 } #@cli : Output selected images with same base filenames but for N different extensions. #@cli : (eq. to 'ox'). #@cli : Default value: 'output_at_same_location=0'. outputx : _gmic_s="$?" v + _$0 $* _outputx : $=arg is_last_arg=0 is_same_location=0 if isnum($-1) is_last_arg:=isbool($-1) is_same_location=$-1 fi N:=$#-$is_last_arg s0= s1=s if !$N e[0--3] "Output image"$_gmic_s" at same location, with same base filename but extension '' (skipped, no extension provided)." return fi if $is_same_location if $is_last_arg e[0--4] "Output image"$_gmic_s" at same location, with same base filename but extension"${s{$N>1}}"' ${^-1}'." else e[0--4] "Output image"$_gmic_s" at same location, with same base filename but extension"${s{$N>1}}" '$*'." fi foreach { repeat $N { ext=${arg{1+$>}} if isin(lowercase(['$ext']),'jpg','jpeg') ext.=,85 fi o {0,f}{0,b}.$ext } } else if $is_last_arg e[0--4] "Output image"$_gmic_s" with same base filename but extension"${s{$N>1}}"' ${^-1}'." else e[0--4] "Output image"$_gmic_s" with same base filename but extension"${s{$N>1}}" '$*'." fi foreach { repeat $N { ext=${arg{1+$>}} if isin(lowercase(['$ext']),'jpg','jpeg') ext.=,85 fi o {0,b}.$ext } } fi #@cli parse_cli : _output_mode,_{ * | command_name } #@cli : Parse definition of '@cli'-documented commands and output info about them in specified output mode. #@cli : 'output_mode' can be { ascii | bashcompletion | html | images | print }. #@cli : Default values: 'output_mode=print' and 'command_name=*'. parse_cli : skip "${1=print},${2=*}" e[^-1] "Parse '#@cli' command(s) '$2' and output in '$1' mode." # Check that specified output mode is actually implemented. l[] { ({'$$parse_cli_$1'}) rm onfail error[0--2] "Command 'parse_cli': Invalid output mode '$1'." } if !$! l[] { it ${_path_rc}update$_version.gmic onfail } fi if !$! return fi i[0] ('\n') y a y merge_multiline_comments # Split commands/section into different blocks. eval " for (p = 0, p=h || i[r]==_'\n'?(copy(i[p],0,r - p,1,0); p = r; continue()); (i[r]==_':' && i[r + 1]==_':')?( # Category r = find(#0,_'\n',r)%h; ):i[r]!=_':'?do( # Command r = find(#0,_'\n',r)%h; crop(0,r,1,6)=='\n#@cli'?( for (s = r + 6, s Display warning r = find(#0,_'\n',r)%h; line = vector1024(); copy(line,i[q + 1],min(size(line),r - q - 1)); run('warn[0--3] \"Unexpected line: \'',line,'\'.\"'); copy(i[p],0,r - p,1,0); p = r; continue() ); copy(i[p],0,q - p + 1,1,0); # Set values to discard p = r)" s -,0 # Keep only command blocks and name them. if $! $!,1,1,1,"> begin( command = vector1024(); category = vector1024(); ); for (p = 5, p=h#x || i[#x,p]==_'\n'?(run('=>[',x,'] __to_discard__'); break()); i[#x,p]==_':' && i[#x,p + 1]==_':'?( # Category for (p+=2, i[#x,p]<=_' ', ++p); for (q = h#x - 1, i[#x,q]<=_' ', --q); copy(category,i[#x,p],l = min(q - p + 1,size(category) - 1)); category[l] = 0; run('=>[',x,'] __to_discard__'); ):( # Command for (q = p, q_' ', ++q); copy(command,i[#x,p],l = min(q - p,size(command) - 1)); command[l] = 0; run('=>[',x,'] \"',string(command,'@',category),'\"'); )" rm. rmn __to_discard__ l { parse_cli_trigger_$1 $2 onfail } fi parse_cli_$1 # Return 1 if last of the selected block describes a shortcut command, 0 otherwise. parse_cli_is_eqto : nbl:="n = 1; for (p = 0, (q = find(#-1,_'\n',p))>=0, ++n, p = ++q); n" u {$nbl"==1 && find(#-1,'eq. to \'')>=0"} # # Implements 'bashcompletion' mode for command 'parse_cli'. # parse_cli_bashcompletion : v 0 use_vt100 sort_list +,n +e "#"\n\ "# Bash completion rules for 'gmic'."\n\ "#"\n\ "# This file has been generated automatically."\n\ "# Do not edit!"\n\ "#"\n\ "# This file should be copied/renamed in '/usr/share/bash-completion/completions/gmic'."\n\ "#"\n\n\ "_gmic()"\n\ "{"\n\ " local cur prev opts coms"\n\ " if type -t _init_completion >/dev/null; then"\n\ " _init_completion -n = || return"\n\ " else"\n\ " COMPREPLY=()"\n\ " cur=\"${COMP_WORDS[COMP_CWORD]}\""\n\ " prev=\"${COMP_WORDS[COMP_CWORD-1]}\""\n\ " fi" coms=" coms=\"" c= # Extract list of available commands. foreach { if s=['{n}'];s[0]==_'_' continue fi ('{n}') l. { s -,{'@'} name={0,t} rm } => $name coms.=$c$name c=" " } +e $coms"\""\n\ " opts=$(echo \"$coms\" | sed \"s: \\([^ ]\\+\\): \\1 -\\1 \\+\\1:g\")"\n # Duplicate list of arguments for command shortcuts. repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi name={$>,n} if ${parse_cli_is_eqto[$>]} +l[$>] { s -,{'"eq. to "'} k. s -,39 eqto={0,t} rm } l[$>] { pass[$eqto] 0 k. => $name onfail warn "Warning: Ignoring shortcut '"$_vt100_c$name$_vt100_m$_vt100_b"', "\ "links to unknown command '"$_vt100_c$eqto$_vt100_m$_vt100_b"'." => __to_discard__ } fi } rmn __to_discard__ # Extract list of arguments. +e " case \"${prev}\" in" repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi l[$>] { name={n} if n=['$name'];"n[0]!=_'_' && find(n,'input')!=0 && find(n,'output')!=0 && "\ "find(n,'load')<0 && find(n,'save')<0 && "\ "n!='i' && n!='o' && n!='m' && n!='it' && n!='ot'" if isin(['$name'],'help','h') +e " \""$name"\" | \"-"$name"\" | \"+"$name"\")" +e " COMPREPLY=( $(compgen -W \"$coms\" -- \"$cur\") ); return 0;;" else s -,{'\n'} k[0] discard {'#@cli'} max {'" "'} autocrop {'" "'} s -,{'": "'} autocrop {'" "'} rm[0] replace {'" "'},{'_'} n_args=0 args,c= repeat $! { if ['{/{$>,t}}']!='(+)' args.=$c{$>,t} c=" " n_args+=1 fi } if $n_args==1 args="> "$args fi if ['$args']!=0 +e " \""$name"\" | \"-"$name"\" | \"+"$name"\")" +e " COMPREPLY=( $(compgen -W \""{/{/$args}}"\") ); return 0;;" fi fi rm 0 fi } } +e " esac"\n\n\ " COMPREPLY=( $(compgen -W \"$opts\" -- \"$cur\") )"\n\ " if type -t _filedir >/dev/null; then"\n\ " _filedir"\n\ " else"\n\ " comptopt -o filenames 2>/dev/null"\n\ " COMPREPLY=( $(compgen -f -- ${cur}) )"\n\ " fi"\n\ "}"\n\ "complete -F _gmic -o filenames gmic" rm parse_cli_trigger_bashcompletion : parse_cli_trigger_print $* # # Implements 'ascii' mode for command 'parse_cli'. # parse_cli_ascii : use_vt100 if !narg($_shell_cols) _shell_cols:=${-shell_cols}-5 fi category= n_category=0 if !narg($_section) _section=1 fi repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi l[$>] { # Detect and display new category. if 0$_display_categories ('{n}') l. { s -,{'@'} if $!>1 cat={t} fi rm } if ['$cat']!=['$category'] n_category+=1 category=$cat ('$_section.$n_category." "') ('$category') +f.. {'" "'} +f.. {'-'} a[-4,-3] x a[-2,-1] x +e \n" "$_vt100_r$_vt100_b{-2,t}$_vt100_n +e " "$_vt100_r{t}$_vt100_n rm[-2,-1] fi fi # Parse command declaration. s -,{'\n'} discard {'#@cli'} max {'" "'} autocrop {'" "'} +l[0] { s -,{'": "'} autocrop {'" "'} name={0,t} rm[0] is_builtin=0 if $! is_builtin:="find(#-1,'(+)')>=0" if $is_builtin rm. fi fi +e "\n "$_vt100_m$_vt100_b$name${"s1,s0=\" (+)\", u $s"$is_builtin}:$_vt100_n foreach { if ${parse_cli_is_eqto.} s -,39 eqto={1,t} k[0] +e " "${_vt100_i}"Shortcut for command '"$_vt100_m$_vt100_b$eqto$_vt100_n"'." else eqto= l. { _gmd2ascii_cut $_shell_cols,8 if $!>1 i[1--2] ('"\\\\\n "':y) a y fi } str=" "{t}${"if "$<" u \" |\" else u \"\" fi"} +e ${_vt100_c}$str$_vt100_n fi } rm } rm[0] # Parse command description. n_example=0 nl="\n" repeat $! { if {$>,i==_':'} l[$>] { if "h==1 || (h==2 && i[-1,2]==_' ')" rm ('": "':y) fi # Empty description lines rows {1+(i[1]==_'" "')},100% if "find(#0,'(eq. to ')>=0" # Found 'Eq. to' description. +l { s -,{'\47'} shortcut={1,t} rm onfail shortcut="(unknown)" rm } +e $nl" ("${_vt100_i}"equivalent to shortcut command '"$_vt100_m$_vt100_b$shortcut$_vt100_n"')." nl="\n" elif "find(#0,'Default value:')>=0 || find(#0,'Default values:')>=0" # Found 'Default value(s):' description if !0$_no_default_values s +,{':'} if $!>2" && "h#1==1" && "i("#1")==_':' i[0] ('$_vt100_b':y) i[2] ('$_vt100_n':y) fi a y gmd2ascii $_shell_cols,5 if $!>1 i[1--2] ('"\n "':y) a y fi autocrop {'\n'} +e "\n "{/{t}} nl= fi elif i==_'$'" && "i[1]!=_'$' # Found example description if !0$_no_examples rows 1,100% autocrop {'" "'} _gmd2ascii_cut {$_shell_cols-8},7 if $!>1 i[1--2] ('"\\\\\n "':y) a y fi autocrop {'\n'} example={/{t}} n_example+=1 if $n_example==1 example_str=${_vt100_b}"Example:"$_vt100_n"\n "[#$n_example] nl="\n" else example_str=" "[#$n_example] fi +e $nl" "$example_str" "$_vt100_c$example$_vt100_n nl= fi elif i==_'$'" && "i[1]==_'$' # Found link to tutorial page if !0$_no_tutorial_link if h==2 url=https://gmic.eu/tutorial/$name else rows 2,100 autocrop {'" "'} url={t} fi +e "\n "${_vt100_b}"Tutorial: "$_vt100_n$_vt100_u$url$_vt100_n nl= fi else # Other kind of description line parse_cli_text_ascii. if w +e $nl{/{t}} nl= fi fi } fi } rm 0 } } rm u $eqto parse_cli_text_ascii : if !narg($_shell_cols) _shell_cols:=${-shell_cols}-5 fi replace_str "\\n","\n" gmd2ascii $_shell_cols,0 # Ensure output text contains no more than two consecutive newlines. # Also add a 4-chars left margin on each line. if w s +,{'\n'} if {i==_'\n'} rm. fi # Remove last newline. eval "repeat (l,p, i(#p)==_'\n'?( h(#p)>2?resize(#p,1,1,1,1,0) ):( resize(#p,1,h#p + 4,1,1,0,0,0,1); copy(i[#p,0],_' ',4,1,0)) )" a y fi parse_cli_trigger_ascii : if "['$1']!='*'" 1,$!,1,1," begin(str = [ lowercase(['$1']),_'@' ]); nm = lowercase(name(#y)); !find(nm,str)?y:-1" discard. -1 if h k[{i}] else rm fi fi # # Implements 'html' mode for command 'parse_cli'. # parse_cli_html : v 0 use_vt100 e "" # Sort by categories. # repeat $! { ('{$>,n}') l. { s +,{'@'} rv a y nm={t} } rm. =>[$>] $nm } # Rename as 'category@name' # sort_list +,n # Sort by categories # repeat $! { ('{$>,n}') l. { s +,{'@'} rv a y nm={t} } rm. =>[$>] $nm } # Rename as 'name@category' # Generate html page 'List of Commands'. html=""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " "\n\ " "\n\n\ " G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing "\ "- Reference Documentation"\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\n\ "
"\n\n\ " "\n\n\ ""\n\n\ ""\n list_categories,c= category,p_category= n,is_tr,is_table,row=0 repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi l[$>] { ('{n}') l. { s -,{'@'} name={0,t} if $!>1 category={1,t} else category= fi rm } if ['$category']!=['$p_category'] strvar $category category_id=${} if $is_tr if $n%5" && "$row>1 html.=" "\n fi html.=" "\n is_tr=0 fi if $is_table html.="
"\n is_table=0 fi html.="\n

"$category":

"\n\ " "\n ('$category') replace_str. ",","," lcategory={t} rm. list_categories.=$c$lcategory c=, is_table=1 n,row=0 fi if !${parse_cli_is_eqto.} is_builtin:="find(#-1,'(+)')>=0" if !($n%5) if $is_tr html.=" "\n fi html.=" "\n is_tr=1 row+=1 fi if ['$name']=='index' url_name=_index.html else url_name=$name.html fi if $is_builtin html.=" "\n else html.=" "\n fi n+=1 fi p_category=$category } } if $is_tr if $n%5" && "$row>1 html.=" "\n fi html.=" "\n fi if $is_table html.="
"$name""$name"
"\n fi # Add 'Shortcuts' category. html.="\n

Command Shortcuts:

"\n\ " "\n\ " "\n repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi l[$>] { ('{n}') l. { s -,{'@'} name={0,t} if $!>1 category={1,t} else category= fi rm } if ${parse_cli_is_eqto.} is_builtin:="find(#-1,'(+)')>=0" +l { s -,{'"eq. to \47"'} k. s -,{'\47'} k[0] autocrop {'" "'} eqto={t} rm } ('$name') replace_str. ">",">" replace_str. "<","<" html_name={t} rm. html.=" " if $is_builtin html.="" else html.="" fi html.=""\n n+=1 fi } } if $is_table html.="
Shortcut name"\ "Equivalent command name
"$html_name""$eqto""$eqto"
"\n fi html.=\n\ ""\n\n\ ""\n\ "
"\n\ " "\n\ " " ({'$html'}:y) # Insert TOC for categories. toc_html="

Categories:

    "\n repeat narg({/$list_categories}) { arg0 $>,{/$list_categories} category=${} ('$category') replace_str. ",","," category={t} rm. strvar $category category_id=${} toc_html.="
  • "$category"
  • "\n } toc_html.="
  • Command Shortcuts
  • "\n toc_html.="
" replace_str. "",$toc_html ot. list_of_commands.html if !isfile('index.html') x "ln -fs list_of_commands.html index.html" fi rm. # Retrieve full list of commands and discard those starting with '_'. repeat $! { if s=['{$<,n}'];s[0]==_'_' rm[$<] else l[$<] { ('{n}') l. { s -,{'@'} name={0,t} if $!>1 category={1,t} else category= fi rm } is_eqto=${parse_cli_is_eqto.} if !$is_eqto _is_$name=1 else rm fi onfail rm } fi } # Generate html page for each command. foreach { ('{n}') l. { s -,{'@'} name={0,t} if $!>1 category={1,t} else category= fi rm } e "\r > "$_vt100_c[#{1+$>}]$_vt100_n" "{`s=vector48(_'" "');copy(s,['$category" / "$name']);s`} # Find previous and next commands. if !$> previous= else pass[{$>-1}] 1 ('{n}') discard. {'_c1'} previous={t} rm[-2,-1] fi if !$< next= else pass[{$>+1}] 1 ('{n}') l. { s -,{'@'} next={0,t} rm } rm. fi if ['$name']=='index' url_name=_index.html else url_name=$name.html fi if ['$previous']=='index' url_previous=_index.html else url_previous=$previous.html fi if ['$next']=='index' url_next=_index.html else url_next=$next.html fi strvar[] $category category_id=${} html=""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " "\n\ " "\n\n\ " G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing "\ "- Reference Documentation - "$name""\n\ " "\n\ " "\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\n\ " "\ "
"\n\n\ " "\n\n\ ""\n\n if !0$_pdf_output html.=" " if narg($previous)" || "narg($next) html.=""\n fi html.="
"\ "Table of Contents  ▸  "\ "List of Commands  ▸  "\ ""$category"  ▸  "\ ""$name"" if narg($previous) html.="◀  "$previous"" fi if narg($previous)" && "narg($next) html.="    |    " fi if narg($next) html.=""$next"  ▶" fi html.="
\n" else html.=""\n fi # Parse command declaration. s -,{'\n'} discard {'#@cli'} max {'" "'} autocrop {'" "'} +l[0] { s -,{'": "'} autocrop {'" "'} rm[0] is_builtin=0 if $!" && find(#-1,'(+)')>=0" is_builtin=1 rm. fi if $is_builtin html.="

"\ $name"

Built-in command
"\n else html.="

"$name"

"\n fi if $! html.="\n

Arguments:

\n
    "\n if $!>1 or="    or" else or= fi foreach { if !$< or= fi html.="
  • "{t}""$or"
  • "\n } html.="
"\n else html.="\n

No arguments

"\n fi rm } rm[0] # Parse command description. html.="\n

Description:

"\n line=0 n_example=0 nb_examples=0 is_tutorial_tag=0 tutorial_html= repeat $! { if {$>,i==_':'} l[$>] { if "h==1 || (h==2 && i[-1,2]==_' ')" rm ('": "':y) fi # Empty description lines rows {1+(i[1]==_'" "')},100% if i==_'$'" && "i[1]!=_'$' nb_examples+=1 fi } fi } foreach { if "find(#0,'(eq. to ')>=0" # Found 'Eq. to' description. +l { s -,{'\47'} replace_str[1] "&","&" replace_str[1] "<","<" replace_str[1] ">",">" shortcut={1,t} rm onfail shortcut="(unknown)" rm } html.="

(equivalent to shortcut command "$shortcut\ ").

"\n elif "find(#0,'See also:')>=0" # Found 'See also:' description. s +,{':'} autocrop {'" "'} if $!>2" && "h#1==1" && "i("#1")==_':' html.="\n

See also:

"\n rm[0,1] fi a y gmd2html 0 html.=" "{t}\n elif "find(#0,'Default value:')>=0 || find(#0,'Default values:')>=0" # Found 'Default value(s):' description s +,{':'} autocrop {'" "'} if $!>2" && "h#1==1" && "i("#1")==_':' html.="\n

Default values:

"\n rm[0,1] fi a y gmd2html 0 html.="

"{t}"

"\n elif i==_'$'" && "i[1]!=_'$' # Found 'Example of use' description. rows 1,100% autocrop {'" "'} if !0$_pdf_output replace_str "image.jpg",""\ "image.jpg" fi example={/{t}} n_example+=1 if $n_example>1 basename=${name}_$n_example.jpg else basename=${name}.jpg fi if $n_example==1 if !$is_tutorial_tag html.="" is_tutorial_tag=1 fi html.="\n

Example"${"if "$nb_examples">1 u s else u \"\" fi"}" of use:

"\n fi if $nb_examples!=1 html.="

• Example \#"$n_example"

"\n fi html.="
"$example"

"\n if 0$_pdf_output html.="
"\n fi if isfile('img/f_$basename') # Single output image if !0$_pdf_output html.=" "\ ""\ "
Command: "$example"
"\n else html.=""\n fi elif isfile('img/f0_$basename') # Multiple output images i=0 html.="
" for isfile('img/f${i}_$basename') { if !0$_pdf_output html.=" "\ ""\ "
Command: "$example"
"\n else html.=""\n fi i+=1 } html.="
" fi if 0$_pdf_output html.="
"\n fi elif i==_'$'" && "i[1]==_'$' # Found link to tutorial page. if !$is_tutorial_tag html.="" is_tutorial_tag=1 fi if h==2 url=https://gmic.eu/tutorial/$name l[] { it $url if "find(#-1,'404 Not Found')>=0" url=https://gmic.eu/oldtutorial/_$name fi rm } else rows 2,100 autocrop {'" "'} url={t} fi tutorial_html.="

This command has a "\ "tutorial page.

" else # Other kind of description line +autocrop {"' '"} is_list:="isin(i,_'*',_'-',_'.')" rm. ('\n') a y replace_str "\\n","\n" gmd2html 0 if $line==1" && "!$is_list html.="
"\n fi html.={t} if i[-1,2]!=_'\n' html.="\n" fi line+=1 fi } # Generate page. html.=\n\ ""\n\n\ ""\n\ "
"\n\ " "\n\ " " rm ({'$html'}:y) replace_str. "",$tutorial_html # Insert tutorial button => $name ot $url_name } rm e "\r > "${_vt100_g}{`s=vector64(_'" "');copy(s,'"Parsing done!"');s`}$_vt100_n parse_cli_trigger_html : parse_cli_trigger_print $* # # Implements 'image' mode for command 'parse_cli'. # parse_cli_images : v 0 use_vt100 e "" old_category,category= n_category,n_example=0 if !isfile('image.jpg') l[] { sp bottles,640 onfail testimage2d 500 } o. image.jpg rm. fi n_global=0 repeat $! { if s=['{$>,n}'];s[0]==_'_' continue fi l[$>] { ('{n}') l. { s -,{'@'} name={0,t} if $!>1 category={1,t} else category= fi rm } if ['$category']!=['$old_category'] n_category+=1 e $_vt100_r"\n ** Section \#"$n_category": "$category"."$_vt100_n"\n" fi s -,{'\n'} discard {'#@cli'} max {'" "'} autocrop {'" "'} n_example=0 foreach { if "i==_':' && ( for (p = 1, p1 basename=${name}_$n_example.jpg else basename=${name}.jpg fi e $_vt100_c[$n_global]$_vt100_n" Command '"$_vt100_g$name$_vt100_n"': $ "$example skip_rendering:="['"$name"'][0]==_'_' || "\ "(isfile(['"{/${_parse_cli_images_path}f_$basename}"']) &&"\ " isfile(['"{/${_parse_cli_images_path}t_$basename}"'])) || "\ "(isfile(['"{/${_parse_cli_images_path}f0_$basename}"']) && "\ "isfile(['"{/${_parse_cli_images_path}t0_$basename}"']))" if !$skip_rendering # Generate picture only if the file doesn't yet exist m "run_example : "$example l[] { reset etime=$| run_example etime="done in "$_vt100_n{_round($|-$etime,0.01)}"s"$_vt100_m _parse_cli_images 1 if $! if $!>1 repeat $! { o[$>] ${_parse_cli_images_path}f$>_$basename,85 } else o ${_parse_cli_images_path}f_$basename,85 fi rs 480,320,2 if $!>1 repeat $! { o[$>] ${_parse_cli_images_path}t$>_$basename,75 } else o ${_parse_cli_images_path}t_$basename,75 fi rm fi # onfail # rm gui_error_preview "Unable to generate preview image" # o ${_parse_cli_images_path}f_$basename,85 # rs 480,320,2 # o ${_parse_cli_images_path}t_$basename,75 # rm # etime="failed" } um run_example else etime="skipped" fi e "\r"$_vt100_c[$n_global]$_vt100_n" Command '"$_vt100_g$name$_vt100_n"': $ "\ $example" "${_vt100_m}"("$etime")."$_vt100_n fi } old_category=$category rm 0 } } rm # Generate a single image from a list of images, for the reference documentation. # $1 : boolean that tells if the image name is inserted at the botton of the generated image. _parse_cli_images : l[] { check "isbool(${1=1})" is_arg=1 arg1=$1 onfail is_arg=0 arg1=1 } if !$is_arg noarg fi if !$! rm return fi W,H=640,480 foreach { nm={n} label=[$>]:" "'$nm' if ${-is_mesh3d} # 3D mesh label2="("{i[6]}" vert., "{i[7]}" prim.)" r3d 1,1,0,-80 r3d 0,1,0,80 snapshot3d {max($W,$H)} else # Regular 1D,2D or 3D image label2="("{w}x{h}x{d}x{s}")" if s>4 channels 0,2 fi r 100%,100%,100%,{max(s,3)},{s==1} if d>1 # Volumetric image -> Render as a 2D image +slices 50% +z[0] 50%,0,0,50%,100%,100% permute. zyxc +z[0] 0,50%,0,100%,50%,100% permute. xzyc rs... $W,$H,2 rs.. ,{-3,h},,1 rs. {-3,w},,1 s:=min(w#-2,h) if $s>64 projections3d[0] 50%,50%,50%,1 mv[0] $! r3d. 1,1,0,-80 r3d. 0,1,0,80 snapshot3d. $s else rm[0] 0 fi eW,eH:=[w#0+w#1,h#0+h#2] if $eW>$W" || "$eH>$H fact:=min($W/$eW,$H/$eH)*100 r $fact%,$fact%,1,100%,2 fi n 0,255 fs={0,max(w,h)*7%} if {0,h>1.5*$fs} to[0] XY,4,2,$fs fi if {1,h>1.5*$fs} to[1] XZ,4,2,$fs fi if {2,h>1.5*$fs} to[2] YZ,4,2,$fs fi if w to[3] 3D,4,2,$fs frame[3] xy,1,200 fi frame[0-2] xy,1,200 a[0,1] x a[1,2] x a y fi fi if w>5*h r $W,{$H/3},1,100%,1 elif h>5*w r {$W/3},$H,1,100%,1 else rs $W,$H,2 fi n 0,255 if $arg1 # Insert label at bottom if s>3 drgba fi r {[w,h]+2},1,100%,0,0,0.5,0.5 - 245 r {[w+10,h+5]},1,100%,0,0,0.5,0.5 r 100%,{h+1},1,100%,0,0,0,1 + 245 0 t. $label" "$label2,5,0,${"font Roboto,22"},1,1 if w>w#0" || "h>h#0 rm. 0 t. $label\n$label2,5,0,${"font Roboto,22"},1,1 if w>w#0" || "h>h#0 rs. {0,[w,h]},2 fi fi *. -1 n. 0,255 to_rgb. - 245 a y,0.5 + 245 fi } c 0,255 parse_cli_trigger_images : parse_cli_trigger_print $* # # Implements 'list' mode for command 'parse_cli'. # (return a list of all commands, separated by commas). # parse_cli_list : res= repeat $! { ('{$>,n}') l. { s -,{'@'} res.=$c{0,t} c=, rm } } rm u $res # # Implements 'print' mode for command 'parse_cli'. # parse_cli_print : v 0 e "" sort_list +,n foreach { is_native:="find(#-1,' : (+)')>0" ('{n}') l. { s -,{'@'} name={0,t} rm } if $is_native +e $name" (+)" else +e $name fi } rm parse_cli_trigger_print : if "['$1']!='*'" 1,$!,1,1," begin(str = lowercase(['$1'])); nm = lowercase(name(#y)); arobace = find(nm,_'@'); inrange(find(nm,str),0,arobace - 1)?y:-1" discard. -1 k[{^}] fi #@cli parse_gmd #@cli : Parse and tokenize selected images, viewed as text strings formatted with the G'MIC markdown syntax. parse_gmd : e[^-1] "Parse and tokenize images$? viewed as text strings formatted with the G\47MIC markdown syntax." y 1 a[^-1] .,y rm. eval[^] ">"${-_gmd_tokens}" begin( section = subsection = subsubsection = subsubsubsection = anchor = bullet = subbullet = subsubbullet = center = right = table = blockquote = detail_block = code_block = shell = bold_italic_a = bold_italic_u = bold_a = bold_u = italic_a = italic_u = strikethrough = underline = monospace = value_set = expr_highlight = url = page_link = text_link1 = text_link2 = img1 = img2 = pipeline = formula = opening_offset = -1; expr_highlight_whitespaces = 0; blank(c) = isin(c,_' ',_'\n',s_whitespace,s_tab,0); semiblank(c) = isin(c,_' ',_'\n',_'.',_',',_';',_':',_'!',_'?', _')',_'(',_'[',_']',_'|',_'-',s_whitespace,s_tab,0); newline(c) = isin(c,_'\n',0); reset(c) = (#c = -1); res = crop(); ); pc = j[-1]; c = i; nc = j[1]; ac = j[2]; ym1 = y - 1; y1 = y + 1; y2 = y + 2; linestart = newline(pc); is_raw = max(blockquote,code_block,shell,monospace,url,page_link,text_link2,img2,pipeline,formula)>=0; non_escaped = is_raw || (pc!=_'\\'); # Manage document layout. #------------------------ # Escaped character. !non_escaped && isin(c,_'%',_'$',_'\\',_'\'',_'`',_'*',_'_',_'{',_'}',_'[',_']',_'<',_'>',_'(',_')',_'#',_'+',_'-',_'.',_'!')?( res[ym1] = i[ym1] = 0; ): # Section: '# Section name'. linestart && c==_'#' && nc==_' '?( # Opening section = y; ): section>=0 && newline(c)?( # Closing opening_offset = section; res[section++] = s_section; while (blank(res[section]), res[section++] = 0); # Remove leading whitespaces res[y] = e_section; for (p = opening_offset - 1, p>=0 && res[p]==_'\n', res[p--] = 0); # Remove newlines preceding for (p = ym1, p>opening_offset && blank(res[p]), res[p--] = 0); # Remove trailing blanks for (p = y1, p=0 && newline(c)?( # Closing opening_offset = subsection; res[subsection++] = s_subsection; res[subsection++] = 0; while (blank(res[subsection]), res[subsection++] = 0); # Remove leading whitespaces res[y] = e_subsection; for (p = ym1, p>opening_offset && blank(res[p]), res[p--] = 0); # Remove trailing blanks for (p = y1, p=0 && newline(c)?( # Closing opening_offset = subsubsection; res[subsubsection++] = s_subsubsection; copy(res[subsubsection],0,2,1,0); subsubsection+=2; while (blank(res[subsubsection]), res[subsubsection++] = 0); # Remove leading whitespaces res[y] = e_subsubsection; for (p = ym1, p>opening_offset && blank(res[p]), res[p--] = 0); # Remove trailing blanks for (p = y1, p=0 && newline(c)?( # Closing opening_offset = subsubsubsection; res[subsubsubsection++] = s_subsubsubsection; copy(res[subsubsubsection],0,3,1,0); subsubsubsection+=3; while (blank(res[subsubsubsection]), res[subsubsubsection++] = 0); # Remove leading whitespaces res[y] = e_subsubsubsection; for (p = ym1, p>opening_offset && blank(res[p]), res[p--] = 0); # Remove trailing blanks for (p = y1, p=0 && newline(c)?( # Closing opening_offset = anchor; res[anchor++] = s_anchor; while (blank(res[anchor]), res[anchor++] = 0); # Remove leading whitespaces res[y] = e_anchor; res[y1]==_'\n'?(res[y1] = 0); for (p = ym1, p>opening_offset && blank(res[p]), res[p--] = 0); # Remove trailing blanks ): # List bullet: '* List item'. linestart && isin(c,_'-',_'*',_'+') && nc==_' ' && bullet<0?( # Opening bullet = y; ): bullet>=0 && newline(c)?( # Closing opening_offset = bullet; res[bullet++] = s_bullet; res[bullet] = 0; res[y] = e_bullet; ): # Sublist bullet: ' * Sub-list item'. linestart && c==_' ' && nc==_' ' && isin(ac,_'-',_'*',_'+') && j[3]==_' ' && subbullet<0?( # Opening subbullet = y; ): subbullet>=0 && newline(c)?( # Closing opening_offset = subbullet; res[subbullet++] = s_subbullet; copy(res[subbullet],0,2,1,0); res[y] = e_subbullet; ): # Subsublist bullet: ' * Subsub-list item'. linestart && c==_' ' && nc==_' ' && ac==_' ' && j[3]==_' ' && isin(j[4],_'-',_'*',_'+') && j[5]==_' ' && subsubbullet<0?( # Opening subsubbullet = y; ): subsubbullet>=0 && newline(c)?( # Closing opening_offset = subsubbullet; res[subsubbullet++] = s_subsubbullet; copy(res[subsubbullet],0,4,1,0); res[y] = e_subsubbullet; ): # Centered block: '\n===\n Centered text\n===\n'. linestart && c==_'=' && nc==c && ac==c && newline(j[3])?( center<0?( # Opening center = y; ): center>=0 && y>center + 3?( # Closing opening_offset = center; res[center++] = s_center; copy(res[center],0,3,1,0); center+=3; newline(res[ym1])?(res[ym1] = 0); res[y] = e_center; copy(res[y + 1],0,3,1,0); ); ): # Right block: '\n>>>\n Right-aligned text\n>>>\n'. linestart && c==_'>' && nc==c && ac==c && newline(j[3])?( right<0?( # Opening right = y; ): right>=0 && y>right + 3?( # Closing opening_offset = right; res[right++] = s_right; copy(res[right],0,3,1,0); right+=3; newline(res[ym1])?(res[ym1] = 0); res[y] = e_right; copy(res[y + 1],0,3,1,0); ); ): # Table : '\n| foo | bar\n' (with first delimiter '||', '|+' or '|-'). table<0 && linestart && c==_'|'?( # Opening table = y; ): table>=0 && newline(c) && nc!=_'|'?( # Closing opening_offset = table; d = i[table + 1]; d==_'|'?(res[table++] = s_htable; res[table++] = 0): d==_'-'?(res[table++] = s_vtable; res[table++] = 0): d==_'+'?(res[table++] = s_hvtable; res[table++] = 0):(res[table++] = s_table); i[table]==_' '?(res[table++] = 0); newline(i[table]) && i[table + 1]==_'|'?(copy(res[table],0,2,1,0); table+=2); for (p = table, p line1\n> line2'. code_block<0 && blockquote<0 && linestart && c==_'>' && blank(nc)?( # Opening blockquote = y; ): blockquote>=0 && newline(c) && (nc!=_'>' || !blank(ac))?( # Closing opening_offset = blockquote; res[blockquote++] = s_blockquote; res[blockquote] = 0; # Remove leading '>' when multi-lines: for (p = blockquote, p'?copy(res[p],0,res[p + 1]==_' '?2:1,1,0)); res[y] = e_blockquote; ): # Detail block: '\n??? Details:\nDetail block\n???\n'. linestart && c==_'?' && nc==c && ac==c && blank(j[3])?( detail_block<0?( # Opening detail_block = y; ): detail_block>=0 && y>detail_block + 3 && newline(j[3])?( # Closing opening_offset = detail_block; res[detail_block++] = s_detail_block; copy(res[detail_block],0,2,1,0); res[ym1] = e_detail_block; copy(res[y],0,4,1,0); ); ): # Code block: '\n~~~\n Code block\n~~~\n'. linestart && isin(c,_'~',_'`') && nc==c && ac==c && newline(j[3])?( code_block<0?( # Opening code_block = y; ): code_block>=0 && y>code_block + 3?( # Closing opening_offset = code_block; res[code_block++] = s_code_block; copy(res[code_block],0,3,1,0); code_block+=3; copy(res[code_block],i[code_block],y - code_block); # Cancel tokens found inside for (p = code_block, p=0 && y>shell + 3?( # Closing opening_offset = shell; res[shell++] = s_shell; copy(res[shell],0,3,1,0); shell+=3; copy(res[shell],i[shell],y - shell); # Cancel tokens found inside res[ym1] = e_shell; copy(res[y],0,4,1,0); ); ): # Horizontal rule: '---', '___' or '***'. linestart && isin(c,_'-','_','*') && nc==c && ac==c && newline(j[3])?( res[y] = s_hrule; copy(res[y1],0,3,1,0); ): # Manage text style. #------------------- # Bold italic (asterisk): '***bold italic text***'. non_escaped && c==_'*' && nc==c && ac==c?( # Opening bold_italic_a<0?( bold_italic_a = y; ): bold_italic_a>=0 && y>bold_italic_a + 3 && j[3]!=_'*'?( # Closing opening_offset = bold_italic_a; res[bold_italic_a++] = s_bold_italic_a; res[bold_italic_a++] = 0; res[bold_italic_a] = 0; res[y] = e_bold_italic_a; res[y1] = res[y2] = 0; ); ): # Bold italic (underscore): '___bold italic text___'. non_escaped && c==_'_' && nc==c && ac==c?( bold_italic_u<0 && semiblank(pc)?( # Opening bold_italic_u = y; ): bold_italic_u>=0 && y>bold_italic_u + 3 && semiblank(j[3])?( # Closing opening_offset = bold_italic_u; res[bold_italic_u++] = s_bold_italic_u; res[bold_italic_u++] = 0; res[bold_italic_u] = 0; res[y] = e_bold_italic_u; res[y1] = res[y2] = 0; ); ): # Bold (asterisk): '**bold text**'. non_escaped && c==_'*' && nc==c?( # Opening bold_a<0?( bold_a = y; ): bold_a>=0 && y>bold_a + 2 && ac!=_'*'?( # Closing opening_offset = bold_a; res[bold_a++] = s_bold_a; res[bold_a] = 0; res[y] = e_bold_a; res[y1] = 0; ); ): # Bold (underscore): '__bold text__'. non_escaped && c==_'_' && nc==c?( bold_u<0 && semiblank(pc)?( # Opening bold_u = y; ): bold_u>=0 && y>bold_u + 2 && semiblank(ac)?( # Closing opening_offset = bold_u; res[bold_u++] = s_bold_u; res[bold_u] = 0; res[y] = e_bold_u; res[y1] = 0; ); ): # Italic (asterisk): '*italic text*'. non_escaped && c==_'*' && pc!=c?( # Opening italic_a<0?( italic_a = y; ): italic_a>=0 && y>italic_a + 1 && ac!=_'*'?( # Closing opening_offset = italic_a; res[italic_a] = s_italic_a; res[y] = e_italic_a; ); ): # Italic (underscore): '_italic text_'. non_escaped && c==_'_' && pc!=c?( italic_u<0 && semiblank(pc)?( # Opening italic_u = y ): italic_u>=0 && y>italic_u + 1 && semiblank(nc)?( # Closing opening_offset = italic_u; res[italic_u] = s_italic_u; res[y] = e_italic_u; ); ): # Strikethrough: '~~strikethrough text~~'. non_escaped && c==_'~' && nc==c && pc!=c?( # Opening strikethrough<0?( strikethrough = y; ): strikethrough>=0 && y>strikethrough + 2?( # Closing opening_offset = strikethrough; res[strikethrough++] = s_strikethrough; res[strikethrough] = 0; res[y] = e_strikethrough; res[y1] = 0; ); ): # Underline: '==strikethrough text=='. non_escaped && c==_'=' && nc==c && pc!=c?( # Opening underline<0?( underline = y; ): underline>=0 && y>underline + 2?( # Closing opening_offset = underline; res[underline++] = s_underline; res[underline] = 0; res[y] = e_underline; res[y1] = 0; ); ): # Monospace: '`monospace text`'. non_escaped && c==_'`'?( monospace<0?( # Opening monospace = y; ): monospace>=0 && y>monospace + 1?( # Closing opening_offset = monospace; res[monospace++] = s_monospace; copy(res[monospace],i[monospace],y - monospace); # Cancel tokens found inside for (p = monospace, p=0 && y>value_set + 1 && semiblank(nc)?( # Closing opening_offset = value_set; res[value_set++] = s_value_set; copy(res[value_set],i[value_set],y - value_set); # Cancel tokens found inside res[y] = e_value_set; ): # Expression highlight: ' 'expr_highlight' '. non_escaped && c==_'\'' && nc!=c?( expr_highlight<0 && semiblank(pc)?( # Opening expr_highlight = y; expr_highlight_whitespaces = 0; ): expr_highlight>=0 && y>expr_highlight + 1 && semiblank(nc)?( # Closing opening_offset = expr_highlight; res[expr_highlight++] = s_expr_highlight; copy(res[expr_highlight],i[expr_highlight],y - expr_highlight); # Cancel tokens found inside res[y] = e_expr_highlight; ); ): # URL: ''. non_escaped && c==_'<' && url<0 && (crop(0,y1,1,8)=='https://' || crop(0,y1,1,7)=='http://' || crop(0,y1,1,6)=='ftp://')?( # Opening url = y; ): non_escaped && c==_'>' && url>=0 && y>url + 1?( # Closing opening_offset = url; res[url++] = s_url; copy(res[url],i[url],y - url); # Cancel tokens found inside res[y] = e_url; ): # Page link (command or reference): ' ''command or page name'' ' (not a URL!). non_escaped && c==_'\'' && nc==c?( page_link<0 && semiblank(pc)?( # Opening page_link = y; ): page_link>=0 && y>page_link + 2 && semiblank(ac)?( # Closing opening_offset = page_link; res[page_link++] = s_page_link; res[page_link++] = 0; copy(res[page_link],i[page_link],y - page_link); # Cancel tokens found inside res[y] = e_page_link; res[y1] = 0; ); ): # Text link: '[link text](https://url.com)'. non_escaped && c==_'[' && text_link1<0 && text_link2<0 && img1<0?( # Opening text_link1 = y; ): non_escaped && c==_']' && nc==_'(' && text_link1>=0 && text_link2<0 && img1<0?( # Middle text_link2 = y; ): non_escaped && c==_')' && text_link2>=0 && img1<0?( # Closing opening_offset = text_link1; res[text_link1] = s_text_link; res[text_link2++] = m_text_link; res[text_link2++] = 0; copy(res[text_link2],i[text_link2],y - text_link2); # Cancel tokens found inside res[y] = e_text_link; ): # Image: '![alt caption](https://url.com/image.png)'. non_escaped && c==_'!' && nc==_'[' && img1<0 && img2<0?( # Opening img1 = y; ): non_escaped && c==_']' && nc==_'(' && img1>=0 && img2<0?( # Middle img2 = y; ): non_escaped && c==_')' && img2>=0?( # Closing opening_offset = img1; res[img1++] = s_img; res[img1++] = 0; res[img2++] = m_img; res[img2++] = 0; copy(res[img1],i[img1],img2 - img1 - 2); copy(res[img2],i[img2],y - img2); # Cancel tokens found inside res[y] = e_img; ): # G'MIC pipeline (image): '%% G'MIC pipeline %%'. non_escaped && c==_'%' && nc==c?( pipeline<0 && semiblank(pc)?( # Opening pipeline = y; ): pipeline>=0 && y>pipeline + 2 && semiblank(ac)?( # Closing opening_offset = pipeline; res[pipeline++] = s_pipeline; res[pipeline++] = 0; copy(res[pipeline],i[pipeline],y - pipeline); # Cancel tokens found inside while (blank(i[pipeline]), res[pipeline++] = 0); # Remove leading blanks for (p = ym1, blank(res[p]), res[p--] = 0); # Remove trailing blanks res[y] = e_pipeline; res[y1] = 0; ); ): # Formula in LaTeX: '$$ x = cos(x + 2) $$`'. non_escaped && c==_'$' && nc==c?( formula<0 && semiblank(pc)?( # Opening formula = y; ): formula>=0 && y>formula + 2 && semiblank(ac)?( # Closing opening_offset = formula; res[formula++] = s_formula; res[formula++] = 0; copy(res[formula],i[formula],y - formula); # Cancel tokens found inside while (blank(i[formula]), res[formula++] = 0); # Remove leading blanks for (p = ym1, blank(res[p]), res[p--] = 0); # Remove trailing blanks res[y] = e_formula; res[y1] = 0; ); ): # G'MIC word: '\G'MIC ' or ' G'MIC '. semiblank(pc) && ( c==_'\\' && nc==_'G' && ac==_'\'' && j[3]==_'M' && j[4]==_'I' && j[5]==_'C' && semiblank(j[6]) ) || ( res[ym1]!=s_gmic && c==_'G' && nc==_'\'' && ac==_'M' && j[3]==_'I' && j[4]==_'C' && semiblank(j[5]) )?( res[y] = s_gmic; c==_'\\'?(copy(res[y1],0,4,1,0); res[y + 5] = e_gmic):(copy(res[y1],0,3,1,0); res[y + 4] = e_gmic); ): # Whitespaces. c==_' '?( # Keep consecutive whitespaces. c==_' ' && (linestart || pc==c)?( res[y] = s_whitespace; ); # Reset tokens that cannot contain whitespace. (++expr_highlight_whitespaces)>2?reset(expr_highlight); reset(url); text_link2>=0?(reset(text_link1); reset(text_link2)); img2>=0?(reset(img1); reset(img2)); ): # Escaped newline. c==_'\\' && nc==_'n'?( res[y] = _'\n'; res[y + 1] = 0; ): # Tab. c==_'\t'?( res[y] = s_tab; ): # Newline. c==_'\n'?( # Reset tokens that cannot contain newlines. reset(bold_italic_a); reset(bold_italic_u); reset(bold_u); reset(bold_a); reset(italic_a); reset(italic_u); reset(strikethrough); reset(underline); reset(monospace); reset(expr_highlight); reset(url); reset(page_link); text_link2>=0?(reset(text_link1); reset(text_link2)); img2>=0?(reset(img1); reset(img2)); ); # Cancel opened tokens starting after the opening offset of the latest closed token. #------------------------------------------------------------------------------------ opening_offset>=0?( section>=opening_offset ? reset(section); subsection>=opening_offset ? reset(subsection); subsubsection>=opening_offset ? reset(subsubsection); subsubsubsection>=opening_offset ? reset(subsubsubsection); anchor>=opening_offset ? reset(anchor); bullet>=opening_offset ? reset(bullet); subbullet>=opening_offset ? reset(subbullet); subsubbullet>=opening_offset ? reset(subsubbullet); center>=opening_offset ? reset(center); right>=opening_offset ? reset(right); table>=opening_offset ? reset(table); blockquote>=opening_offset ? reset(blockquote); detail_block>=opening_offset ? reset(detail_block); code_block>=opening_offset? reset(code_block); shell>=opening_offset? reset(shell); bold_italic_a>=opening_offset ? reset(bold_italic_a); bold_italic_u>=opening_offset ? reset(bold_italic_u); bold_a>=opening_offset ? reset(bold_a); bold_u>=opening_offset ? reset(bold_u); italic_a>=opening_offset ? reset(italic_a); italic_u>=opening_offset ? reset(italic_u); strikethrough>=opening_offset ? reset(strikethrough); underline>=opening_offset ? reset(underline); monospace>=opening_offset ? reset(monospace); value_set>=opening_offset ? reset(value_set); expr_highlight>=opening_offset ? reset(expr_highlight); url>=opening_offset ? reset(url); page_link>=opening_offset ? reset(page_link); text_link1>=opening_offset || text_link2>=opening_offset ? (reset(text_link1); reset(text_link2)); img1>=opening_offset || img2>=opening_offset ? (reset(img1); reset(img2)); pipeline>=opening_offset ? reset(pipeline); formula>=opening_offset ? reset(formula); opening_offset = -1; ); end(copy(i[0],res))" discard 0 #@cli gmd2html : _include_default_header_footer={ 0:none | 1:Reference | 2:Tutorial | 3:News } : (no arg) #@cli : Convert selected gmd-formatted text images to html format. #@cli : Default values: 'include_default_header_footer=1'. gmd2html : skip "${1=}" l[] { is_arg:=isint("$1") onfail is_arg=0 } if $is_arg embed_html=$1 else embed_html=1 noarg fi parse_gmd s_section,e_section,s_gmic,e_gmic={${-_gmd_tokens}"[s_section,e_section,s_gmic,e_gmic]"} foreach { nm$>={b} strvar ${nm$>} fnm=${} if "i=="$s_section" && find(#-1,"$e_section")>0" +rows 1,{"find(#-1,"$e_section")-1"} replace_seq. $s_gmic,{``{'G\47MIC'}} f. "i<0?-1:i" discard. -1 title$>={t} rm. else title$>= fi . # [0] = Output, [1] = Input eval. "> begin("${-_gmd_tokens}${-_gmd_write}"); c = i; c>0?( c==_'\n' ? write('
\n'): c==_'&' ? write('&'): c==_'\47' ? write('''): c==_'>' ? write('>'): c==_'\"' ? write('"'): c==_'<' ? write('<'): write(c); ):( isin(c,e_bold_a,e_bold_u,e_bold_italic_a,e_bold_italic_u,e_italic_a,e_italic_u, e_monospace,e_strikethrough,e_underline,e_expr_highlight) ? write(''): isin(c,e_bullet,e_subbullet,e_subsubbullet) ? write('\n'): c==e_section ? write('\n'): c==e_subsection ? write('\n'): c==e_subsubsection ? write('\n'): c==e_subsubsubsection ? write('\n'): c==s_section ? ( ind_e = find(#1,e_section,y); run('_gmd2html_section. ',y + 1,',',ind_e - 1); str_nam = get('_gmd_name',1024,1); write_nl(); write('

'); ): c==s_subsection ? ( ind_e = find(#1,e_subsection,y); run('_gmd2html_section. ',y + 1,',',ind_e - 1); str_nam = get('_gmd_name',1024,1); write_nl(); write('

'); ): c==s_subsubsection ? ( ind_e = find(#1,e_subsubsection,y); run('_gmd2html_section. ',y + 1,',',ind_e - 1); str_nam = get('_gmd_name',1024,1); write_nl(); write('

'); ): c==s_subsubsubsection ? ( ind_e = find(#1,e_subsubsubsection,y); run('_gmd2html_section. ',y + 1,',',ind_e - 1); str_nam = get('_gmd_name',1024,1); write_nl(); write('

'); ): c==s_anchor ? ( ind_e = find(#1,e_anchor,y); run('_gmd2html_section. ',y + 1,',',ind_e - 1); str_nam = get('_gmd_name',1024,1); write('\n'); copy(i[y],0,ind_e - y + 1,1,0); ): c==s_bullet ? write('
'): c==s_subbullet ? write('
'): c==s_subsubbullet ? write('
'): c==s_center ? write('
\n'): c==e_center ? (write_nl(); write('
')): c==s_right ? write('
\n'): c==e_right ? (write_nl(); write('
')): c==s_table ? (write_nl(); write('\n
')): c==s_htable ? (write_nl(); write('\n
')): c==s_vtable ? (write_nl(); write('\n
')): c==s_hvtable ? (write_nl(); write('\n\n\n
')): c==m_table ? write(''): c==n_table ? write('
'): c==e_table ? write('
\n'): c==s_blockquote ? (write_nl(); write('
\n')): c==e_blockquote ? (write('\n
\n')): c==s_detail_block ? ( write_nl(); write('
\n'); ny = y + 1; nc = j[1]; nc!=e_detail_block?( j[1] = 0; ind_e = find(#1,e_detail_block,ny); nc==_' '?( # Has a title ind_nl = find(#1,_'\n',ny); ind_nl<0 || ind_nl>ind_e?(ind_nl = ind_e); write(#1,y + 2,ind_nl - y - 2); copy(i[ny],0,ind_nl - y - 1,1,0); i[ind_nl]!=e_detail_block?(i[ind_nl] = 0); ):write('Details:'); write('\n'); ) ): c==e_detail_block ? (write_nl(); write('
\n')): c==s_code_block ? write('
'): c==e_code_block ? write('
\n'); c==s_shell ? ( ind_e = find(#1,e_shell,y); run('_gmd2html_shell. ',y + 1,',',ind_e - 1); str_com = get('_gmd_command',1024,1); write('
$ '); len = find(str_com,0); write(str_com,len); write('

\n'); write(#-1,0,h(#-1)); run('rm.'); write('
\n'); copy(i[y],0,ind_e - y + 1,1,0); ): c==s_hrule ? (write_nl(); write('
\n')): c==s_bold_italic_a ? write(''): c==s_bold_italic_u ? write(''): c==s_bold_a ? write(''): c==s_bold_u ? write(''): c==s_italic_a ? write(''): c==s_italic_u ? write(''): c==s_strikethrough? write(''): c==s_underline? write(''): c==s_monospace ? write(''): c==s_value_set ? write('{'): c==e_value_set ? write('}'): c==s_expr_highlight ? write(''): c==s_url ? ( ind_e = find(#1,e_url,y); write(''); ): c==e_url ? write(''): c==s_page_link ? ( ind_e = find(#1,e_page_link,y); run('_gmd2html_page_link. ',y + 1,',',ind_e - 1); str_link = get('_gmd_link',1024,1); str_text = get('_gmd_text',1024,1); write(''); len = find(str_text,0); write(str_text,len); write(''); copy(i[y],0,ind_e - y + 1,1,0); ): c==s_text_link ? ( ind_m = find(#1,m_text_link,y); ind_e = find(#1,e_text_link,ind_m); write(''): # Local link write('\" target=\"_blank\">'); ): c==m_text_link ? ( ind_e = find(#1,e_text_link,y); copy(i[y],0,ind_e - y + 1,1,0); write(''); ): c==s_img ? ( ind_m = find(#1,m_img,y); ind_e = find(#1,e_img,ind_m); ind_d = find(#1,_'.',ind_e - 1,-1); is_video = 0; ind_d>ind_m?( # Check for a video ext3 = crop(#1,0,ind_d + 1,1,3); ext4 = crop(#1,0,ind_d + 1,1,4); is_video = isin(ext3,'mp4','ogg') || ext4=='webm' ); is_video?( write('
" else td_$cat="" fi } html=""\n\ ""\n\ " "\n\ " "\n\ " "\n\ " "\n\ " "\n\n\ " G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image "\ "Processing - Gallery"\n\ " "\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\ " "\n\n\ " "\n\ " "\n\n\ "

Image Gallery

"\n\n\ "

This gallery gives a quick overview of the kind of features and generic filters available in the "\ "G'MIC open-source image processing framework.

"\n\ "

All the images below have been processed by the CLI interface "\ "gmic"\ " of G'MIC, from a set of initial 2D color images."\n\ " Click on an image to enlarge it and display the G'MIC command-line "\ "used for the processing (note: to reproduce this, you may have to escape some characters, "\ "according to the type of shell you use!).

"\n\ "

Remember, G'MIC lets you define your own image pipelines through"\ "custom command files."\n\ " Your custom filters can be easily added afterwards in the plug-in for "\ "GIMP or Krita.

"\n\ "

For more details, visit the tutorial pages as well as the "\ "technical reference to get a full documentation on this "\ "software.

"\n html_menu=" \n "\ $td_arrays"Arrays & Frames"\ $td_artistic"Artistic"\ $td_blackandwhite"B&W"\ $td_colors"Colors"\ $td_deformations"Deformations"\ $td_filtering"Filtering"\ $td_patterns"Patterns"\ $td_3dmeshes"3D Meshes"\ $td_stylization"Stylization"\ $td_codesamples"Code samples"\n\ "

\n\n" html=${html}${html_menu}" \n" repeat $nb_commands { command=${"arg "1+$>,$commands} is_stylization:=['$command']=='_gallery_stylization' e $_vt100_m" - Command '"$_vt100_b$command$_vt100_n"' ["{1+$>}/$nb_commands"]." +l { # Parse command definition and examples s -,{'"#@cli "$command" :"'} if $!<2 s -,{'"#@cli "$command"\n"'} i[1] ('\n') a[-2,-1] y fi if $!<2 warn " ** Command '"$command"' not found! **" else k. eval " str = crop(#-1); ind = 0; while ((ind = find(str,'\n#@cli ',ind))>=0, ++ind; str[ind+6]!=_':' ? break() : str[ind+7]==_' ' && str[ind+8]==_'$' && str[ind+9]==_' '?( beg = ind + 10; end = find(str,_'\n',beg) - 1; com = vector1024(0); run('+rows[0] ',beg,',',end); ); )"; rm[0] nb_examples=$! repeat $nb_examples { # For each example found example$nex={$>,t} e $_vt100_g" $ "${example$nex}$_vt100_n sample=${"arg0 "{($nex+8*$ncat)%narg($pics)},$pics} is_codesample=0 l[] { # Create a html representation of the command ('${example$nex}') y is_input:="find(#-1,'image.jpg')>=0" if !$is_input" && find(#-1,'sample ')>=0" s +,{'"sample "'} if {0,crop()=='"sample "'" && "$!>1} l[1] { s +,{'" "'} if "find(#0,_',')==-1" sample={0,t} is_input=1 fi a y } fi a y fi replace_str. "image.jpg",\ ""$sample".png" replace_str. "_output_mode=1","" # Discard '_fps=?' and '_label=?' l. { s +,{'" "'} repeat $! { if {$<,crop(0,0,1,5)=='_fps='" || "crop(0,0,1,7)=='_label='} rm[$<] fi } a y } l. { s +,{'" "'} repeat $! { if {$<,crop(0,0,1,8)=='https://'} l[$<] { # Add hyperlink if crop(0,0,1,24)=='https://gmic.eu/samples/' is_codesample=1 basename_codesample={`crop(0,24,1,h-24)`} filename_codesample="../../resources/samples"/$basename_codesample url_codesample="https://gmic.eu/samples/"$basename_codesample if $1 x "lftp sftp://"$GMIC_LOGIN":@ovh -e \"put -O /home/"$GMIC_LOGIN"/www/gmic/samples "\ "\\\""$filename_codesample"\\\"; quit\" >/dev/null" fi i[0] ('""') else i[0] ('""') fi ('') y a y } fi } a y } replaced_example$nex={t} rm } ('${example$nex}') replace_str. "https://gmic.eu/samples/","../../resources/samples/" example$nex={t} rm. m "_run : _preview_area_width,_preview_area_height=450,300 "${example$nex} if $is_input sample_=${sample}_ else sample_= fi filename_original=img/${category}_${sample_}original_$nex.jpg filename_thumb_original=img/${category}_${sample_}thumb_original_$nex.jpg if "find(['"${example$nex}"'],' _fps=')>=0" filename_full=img/${category}_${sample_}full_$nex.gif filename_thumb=img/${category}_${sample_}thumb_$nex.gif _is_animated=1 else filename_full=img/${category}_${sample_}full_$nex.jpg filename_thumb=img/${category}_${sample_}thumb_$nex.jpg _is_animated=0 fi etime= _label= if !isfile('{/$filename_thumb}') l[] { if $is_input sp $sample,600 o image.jpg rm fi db3d m3d md3d f3d l3d sl3d ss3d 0.8 srand 512 etime=$| _run etime:=_round($|-$etime,0.01) if $is_stylization +l { _gallery o $filename_full,70 rm } k. _gallery width,height:=[w,h] else _gallery width,height:=[w,h] if $_is_animated o $filename_full,$_fps else o $filename_full,70 fi fi e "\r"$_vt100_g" $ "${example$nex}" (done in "$_vt100_n${etime}"s"$_vt100_g")."$_vt100_n crop 5,5,{w-6},{h-6} # Remove frame added by '_gallery' frame xy,3%,255 rs 440,440,3 drop_shadow 2,2,2 if $_is_animated rs 230,230,3 else rs 300,300,3 fi 100%,100%,1,3,245 blend[^-1] .,alpha,1,1 rm. if $_is_animated o $filename_thumb,$_fps else o $filename_thumb,60 fi rm if $is_input image.jpg _gallery rs $width,$height,5 c 0,255 to "Input",2%,2%,6% - 255 r $width,$height,1,3,0,0,0.5,0.5 + 255 o $filename_original,60 crop 5,5,{w-6},{h-6} # Remove frame added by '_gallery' frame xy,3%,255 rs 440,440,3 drop_shadow 2,2,2 if $_is_animated rs 230,230,3 else rs 300,300,3 fi 100%,100%,1,3,245 blend[^-1] .,alpha,1,1 rm. o $filename_thumb_original,60 rm fi } fi is_samesize=0 l[] { $filename_full width,height:=round([w,h]*(max(w,h)<300?1.75:1)) if isfile(['{/${filename_original}}']) $filename_original is_samesize:=w==w#0" && "h==h#0 fi rm } if !$is_samesize filename_original=$filename_full fi if !($col%$nb_cols) html=${html}" \n" fi if $nb_examples==1 counter= else counter=" "[{$>+1}/$nb_examples]"" fi html_etime= if narg($etime) html_etime="
(generated in "${etime}"s)" fi html_codesample= if $is_codesample html_codesample="\n
"\ "\n
\n"
               it[] $url_codesample html_codesample=${html_codesample}{t} rm.
               html_codesample=${html_codesample}"\n
\n"\ "
\n"\ "[ Source code ]

\n" fi if ['$_label']==0 _label=$command$counter else ('$_label') replace_str. "~"," " _label={t} rm. fi if $is_input html=${html}\ " \n" else html=${html}\ " \n" fi nex+=1 col:=($col+1)%$nb_cols if !$col html=${html}" \n" fi } fi rm } } if $col html=${html}" \n" fi html=${html}"
\n"\ " \""gallery_$command$nex"\"
"\ ${_label}${html_codesample}"
\n"\ "
\n"\ " \""gallery_$command$nex"\"\n"\ "
\n"\ "
Command: "\ "$ gmic "${replaced_example$nex}""\ ${html_etime}"
\n"\ " \""gallery_$command$nex"\""\ "
"\ ${_label}${html_codesample}"
\n"\ "
\n"\ " \""gallery_$command$nex"\"\n"\ "
\n"\ "
Command: "\ "$ gmic "${replaced_example$nex}""\ ${html_etime}"

\n"\ ${html_menu}\ "
"\n\ " "\n\ " " ('$html') ot. $category.html rm. } rm # Transfer on G'MIC server x "ln -fs artistic.html index.html" x "ln -fs "$HOME/work/src/gmic/html/header1.html" ." x "ln -fs "$HOME/work/src/gmic/html/footer.html" ." if $1 e "\n * Transfer gallery on G'MIC server.\n" x "lftp sftp://"$GMIC_LOGIN":@ovh -e \"mirror -eRL . /home/"$GMIC_LOGIN"/www/gmic/gallery ; quit\"" fi e " * All done!\n" # Generate a single image from a list of images, for the gallery. _gallery : foreach { if ${-_is_mesh3d} r3d 1,1,0,-80 r3d 0,1,0,30 animate3d 20,0,5,0,1.5,0 s z +rv[1--2] else if w>8192 z 0,8191 elif h>8192 rows 0,8191 fi n 0,255 fi } if !$_is_animated +__gallery if w>1024 r:=round(1024*100/w,0.1) r[^-1] $r%,$r%,1,100%,2 fi rm. fi foreach { if s==1 r {w},{h},1,3 elif s==4 drgba else r {w},{h},1,3,0 fi if w<=h" && "h<256 rs ,256,2 elif h<=w" && "w<256 rs 256 fi if w<=h" && "h>620 rs ,620,2 elif h<=w" && "w>620 rs 620 fi if h<48 r 100%,48 fi if w<48 r 48,100% fi if $_is_animated" && "(w>480" || "h>480) rs 480,480,2 fi frame xy,1,0 frame xy,4,255 } if $_is_animated - 255 r ${-max_wh},1,3,0,0,0.5,0.5 + 255 else - 255 __gallery + 255 if w<256 - 255 r 256,100%,1,100%,0,0,0.5,0.5 + 255 fi if h<256 - 255 r 100%,256,1,100%,0,0,0.5,0.5 + 255 fi fi __gallery : if $!==2 if w>h a y else a x fi else montage A # append_tiles 2 fi # Generate data files for the G'MIC Online website. update_gmicol : e[^-1] "Generate XML file and thumbnails for the G'MIC Online website." filename=$_path_rc/update$_version.gmic if !isfile('$filename') up fi rm it $filename # Generate XML file. m "parse_gui_trigger_gmicol : _update_gmicol $*" v + +parse_gui. gmicol v - um parse_gui_trigger_gmicol ot. gmicol.xml rm. # Generate thumbnails. m "parse_gui_trigger_thumbnails : _update_gmicol $*" w[] v + parse_gui thumbnails v - um parse_gui_trigger_thumbnails _update_gmicol : # Keep only a subset of existing filters nmd 3,\ "About/♥ Support Us ! ♥",\ "About/About G'MIC",\ "Arrays & Tiles/Annular Steiner Chain Round Tile",\ "Arrays & Tiles/Array [Faded]",\ "Arrays & Tiles/Array [Mirrored]",\ "Arrays & Tiles/Array [Random Colors]",\ "Arrays & Tiles/Array [Random]",\ "Arrays & Tiles/Array [Regular]",\ "Arrays & Tiles/Ascii Art",\ "Arrays & Tiles/Chessboard",\ "Arrays & Tiles/Dices",\ "Arrays & Tiles/Grid [Cartesian]",\ "Arrays & Tiles/Grid [Hexagonal]",\ "Arrays & Tiles/Grid [Triangular]",\ "Arrays & Tiles/Loose Photos",\ "Arrays & Tiles/Make Seamless [Diffusion]",\ "Arrays & Tiles/Make Seamless [Patch-Based]",\ "Arrays & Tiles/Ministeck",\ "Arrays & Tiles/Puzzle",\ "Arrays & Tiles/Shuffle Patches",\ "Arrays & Tiles/Taquin",\ "Arrays & Tiles/Tileable Rotation",\ "Arrays & Tiles/Tiled Isolation",\ "Arrays & Tiles/Tiled Normalization",\ "Arrays & Tiles/Tiled Parameterization",\ "Arrays & Tiles/Tiled Random Shifts",\ "Arrays & Tiles/Tiled Rotation",\ "Artistic/Angoisse Anguish",\ "Artistic/Aurora",\ "Artistic/Barbouillage Paint Daub",\ "Artistic/Black Crayon Graffiti",\ "Artistic/Blockism",\ "Artistic/Bokeh",\ "Artistic/Brushify",\ "Artistic/Cartoon",\ "Artistic/Circle Abstraction",\ "Artistic/Color Abstraction Paint",\ "Artistic/Colored Pencils",\ "Artistic/Comic Book",\ "Artistic/Cubism",\ "Artistic/Cutout",\ "Artistic/Diffusion Tensors",\ "Artistic/Dream Smoothing",\ "Artistic/Ellipsionism",\ "Artistic/Felt Pen",\ "Artistic/Finger Paint",\ "Artistic/Fractalize",\ "Artistic/Ghost",\ "Artistic/Graphic Boost",\ "Artistic/Graphic Novel",\ "Artistic/Hard Sketch",\ "Artistic/Highlight Bloom",\ "Artistic/Hope Poster",\ "Artistic/Hough Sketch",\ "Artistic/Illustration Look",\ "Artistic/Kuwahara",\ "Artistic/Lineart",\ "Artistic/Lylejk's Painting",\ "Artistic/Make Squiggly",\ "Artistic/Morphology Painting",\ "Artistic/Paint With Brush",\ "Artistic/Painting",\ "Artistic/Pen Drawing",\ "Artistic/Photoillustration",\ "Artistic/Polygonize [Delaunay]",\ "Artistic/Polygonize [Energy]",\ "Artistic/Poster Edges",\ "Artistic/Posterize",\ "Artistic/Posterized Dithering",\ "Artistic/Quadtree Variations",\ "Artistic/Rodilius",\ "Artistic/Sharp Abstract",\ "Artistic/Simple Noise Canvas",\ "Artistic/Skeletik",\ "Artistic/Sketch",\ "Artistic/Smooth Abstract",\ "Artistic/Stringify",\ "Artistic/Stylize",\ "Artistic/Vector Painting",\ "Artistic/Warhol",\ "Artistic/Whirl Drawing",\ "Black & White/B&W Stencil",\ "Black & White/Black & White",\ "Black & White/Charcoal",\ "Black & White/Colorize [with Colormap]",\ "Black & White/Colorize Lineart [Auto-Fill]",\ "Black & White/Colorize Lineart [Smart Coloring]",\ "Black & White/Desaturate Norm",\ "Black & White/Dithering",\ "Black & White/Emboss",\ "Black & White/Engrave",\ "Black & White/Filaments",\ "Black & White/Freaky B&W",\ "Black & White/Ink Wash",\ "Black & White/Multi-Layer Etch",\ "Black & White/Pencil",\ "Black & White/Pencil Portrait",\ "Black & White/Stamp",\ "Black & White/Threshold Etch",\ "Colors/Abstraction",\ "Colors/Auto Balance",\ "Colors/Basic Adjustments",\ "Colors/Boost Chromaticity",\ "Colors/Boost-Fade",\ "Colors/Brightness",\ "Colors/Channel Processing",\ "Colors/CMYK Tone",\ "Colors/Color Balance",\ "Colors/Color Blindness",\ "Colors/Color Grading",\ "Colors/Color Temperature",\ "Colors/Colormap",\ "Colors/Contrast",\ "Colors/Customize CLUT",\ "Colors/Dark Sky",\ "Colors/Equalize HSI-HSL-HSV",\ "Colors/Equalize HSV",\ "Colors/HSL Adjustment",\ "Colors/HSV Select",\ "Colors/Hue Lighten-Darken",\ "Colors/LMS Adjustment",\ "Colors/Local Contrast",\ "Colors/Metallic Look",\ "Colors/Mixer [CMYK]",\ "Colors/Mixer [Generic]",\ "Colors/Mixer [HSV]",\ "Colors/Mixer [Lab]",\ "Colors/Mixer [PCA]",\ "Colors/Mixer [RGB]",\ "Colors/Mixer [YCbCr]",\ "Colors/Normalize Brightness",\ "Colors/Retinex",\ "Colors/Retro Fade",\ "Colors/RGB Tone",\ "Colors/Saturation EQ",\ "Colors/Select-Replace Color",\ "Colors/Selective Desaturation",\ "Colors/Sepia",\ "Colors/Softlight",\ "Colors/Specific Saturation",\ "Colors/Temperature Balance",\ "Colors/Tone Presets",\ "Colors/Tune HSV Colors",\ "Colors/User-Defined",\ "Colors/Vintage Style",\ "Colors/Zone System",\ "Contours/Convolve",\ "Contours/Curvature",\ "Contours/Difference of Gaussians",\ "Contours/Distance Transform",\ "Contours/Edge",\ "Contours/Edges",\ "Contours/Edges (Canny)",\ "Contours/Edges Offsets",\ "Contours/Gradient Norm",\ "Contours/Gradient RGB",\ "Contours/Isophotes",\ "Contours/Laplacian",\ "Contours/Local Orientation",\ "Contours/Morphological Filter",\ "Contours/Segmentation",\ "Contours/Skeleton",\ "Contours/Super-Pixels",\ "Contours/Thin Edges",\ "Deformations/Breaks",\ "Deformations/Cartesian Transform",\ "Deformations/Circle Transform",\ "Deformations/Conformal Maps",\ "Deformations/Continuous Droste",\ "Deformations/Crease",\ "Deformations/Distort Lens",\ "Deformations/Drop Water",\ "Deformations/Equirectangular to Nadir-Zenith",\ "Deformations/Euclidean - Polar",\ "Deformations/Fish-Eye",\ "Deformations/Flower",\ "Deformations/Kaleidoscope [Blended]",\ "Deformations/Kaleidoscope [Polar]",\ "Deformations/Kaleidoscope [Reptorian-Polar]",\ "Deformations/Kaleidoscope [Symmetry]",\ "Deformations/Logarithmic Distortion",\ "Deformations/Moon2panorama",\ "Deformations/Perspective",\ "Deformations/Pixel Push",\ "Deformations/Pointcaré Disk",\ "Deformations/Point Warp",\ "Deformations/Polar Transform",\ "Deformations/Quadrangle",\ "Deformations/Raindrops",\ "Deformations/Random",\ "Deformations/Reflection",\ "Deformations/Ripple",\ "Deformations/Seamcarve",\ "Deformations/Sinusoidal Water Distortion",\ "Deformations/Sphere",\ "Deformations/Spherize",\ "Deformations/Square to Circle",\ "Deformations/Stereographic Projection",\ "Deformations/Symmetrize",\ "Deformations/Textured Glass",\ "Deformations/Twirl",\ "Deformations/Water",\ "Deformations/Wave",\ "Deformations/Wind",\ "Deformations/Zoom",\ "Degradations/Add Grain",\ "Degradations/Blur [Angular]",\ "Degradations/Blur [Bloom]",\ "Degradations/Blur [Depth-Of-Field]",\ "Degradations/Blur [Gaussian]",\ "Degradations/Blur [Glow]",\ "Degradations/Blur [Linear]",\ "Degradations/Blur [Multidirectional]",\ "Degradations/Blur [Radial]",\ "Degradations/Blur [Splinter]",\ "Degradations/Chromatic Aberrations",\ "Degradations/CRT Phosphors",\ "Degradations/CRT Scanlines",\ "Degradations/CRT Sub-pixels",\ "Degradations/Dirty",\ "Degradations/Flip & Rotate Blocks",\ "Degradations/Fragment Blur",\ "Degradations/JPEG Artefacts",\ "Degradations/Lomo",\ "Degradations/Mess with Bits",\ "Degradations/Noise [Additive]",\ "Degradations/Noise [Gradient]",\ "Degradations/Noise [Perlin]",\ "Degradations/Noise [Spread]",\ "Degradations/Old-Movie Stripes",\ "Degradations/Oldschool 8bits",\ "Degradations/Pixel Sort",\ "Degradations/Rain & Snow",\ "Degradations/Random Shade Stripes",\ "Degradations/Rebuild From Similar Blocks",\ "Degradations/Scanlines",\ "Degradations/Self Glitching",\ "Degradations/Streak",\ "Degradations/UltraWarp++++",\ "Degradations/Visible Watermark",\ "Degradations/Warp by Intensity",\ "Details/Constrained Sharpen",\ "Details/DCP Dehaze",\ "Details/Details Equalizer",\ "Details/Dynamic Range Increase",\ "Details/Easy Skin Retouch",\ "Details/Emboss-Relief",\ "Details/Equalize Local Histograms",\ "Details/Freaky Details",\ "Details/High Pass",\ "Details/Local Contrast Enhancement",\ "Details/Local Normalization",\ "Details/Local Processing",\ "Details/Local Variance Normalization",\ "Details/Magic Details",\ "Details/Make Up",\ "Details/Mighty Details",\ "Details/Portrait Retouching",\ "Details/Pyramid Processing",\ "Details/Quick Tonemap",\ "Details/Sharpen [Alpha]",\ "Details/Sharpen [Deblur]",\ "Details/Sharpen [Gold-Meinel]",\ "Details/Sharpen [Gradient]",\ "Details/Sharpen [Hessian]",\ "Details/Sharpen [Inverse Diffusion]",\ "Details/Sharpen [Multiscale]",\ "Details/Sharpen [Octave Sharpening]",\ "Details/Sharpen [Richardson-Lucy]",\ "Details/Sharpen [Shock Filters]",\ "Details/Sharpen [Texture]",\ "Details/Sharpen [Tones]",\ "Details/Sharpen [Unsharp Mask]",\ "Details/Sharpen [Whiten]",\ "Details/Simple Local Contrast",\ "Details/Spotify",\ "Details/Texture",\ "Details/Texture Enhance",\ "Details/Tone Enhance",\ "Details/Tone Mapping",\ "Details/Tone Mapping [Fast]",\ "Details/YAG Effect",\ "Frames/Droste",\ "Frames/Frame [Blur]",\ "Frames/Frame [Cube]",\ "Frames/Frame [Fuzzy]",\ "Frames/Frame [Mirror]",\ "Frames/Frame [Painting]",\ "Frames/Frame [Pattern]",\ "Frames/Frame [Regular]",\ "Frames/Frame [Relief]",\ "Frames/Frame [Round]",\ "Frames/Frame [Smooth]",\ "Frames/Old Photograph",\ "Frames/Polaroid",\ "Frames/Tunnel",\ "Frames/Vignette",\ "Frequencies/Bandpass",\ "Frequencies/Fourier Analysis",\ "Frequencies/Fourier Transform",\ "Frequencies/Fourier Watermark",\ "Lights & Shadows/Burn",\ "Lights & Shadows/Dodge and Burn",\ "Lights & Shadows/Drop Shadow",\ "Lights & Shadows/Drop Shadow 3D",\ "Lights & Shadows/Equalize Light",\ "Lights & Shadows/Equalize Shadow",\ "Lights & Shadows/Guided Light Rays",\ "Lights & Shadows/Illuminate 2D Shape",\ "Lights & Shadows/Light Glow",\ "Lights & Shadows/Light Leaks",\ "Lights & Shadows/Light Patch",\ "Lights & Shadows/Light Rays",\ "Lights & Shadows/Pop Shadows",\ "Lights & Shadows/Relief Light",\ "Lights & Shadows/Shadow Patch",\ "Map Projection/Albers Equal Area Conic",\ "Map Projection/Azimuthal Equidistant",\ "Map Projection/Conic Equidistant",\ "Map Projection/Cylindrical Equal-Area Projection",\ "Map Projection/Eckert IV",\ "Map Projection/Eckert VI",\ "Map Projection/Lambert Azimuthal Equal-Area",\ "Map Projection/Lambert Conformal Conic",\ "Map Projection/Mercator Projection",\ "Map Projection/Mollweide",\ "Map Projection/Orthographic",\ "Map Projection/Rotate Equirectangular Map",\ "Map Projection/Sinusoidal Map",\ "Map Projection/Triangular Projection",\ "Patterns/Bayer Filter",\ "Patterns/Box Fitting",\ "Patterns/Camouflage",\ "Patterns/Canvas",\ "Patterns/Canvas Texture",\ "Patterns/Clouds",\ "Patterns/Cracks",\ "Patterns/Crystal",\ "Patterns/Crystal Background",\ "Patterns/Denim Texture",\ "Patterns/Fibers",\ "Patterns/Freqy Pattern",\ "Patterns/Halftone",\ "Patterns/Halftone [Generic]",\ "Patterns/Halftone Shapes",\ "Patterns/Hearts",\ "Patterns/Hedcut",\ "Patterns/Lava",\ "Patterns/Marble",\ "Patterns/Maze",\ "Patterns/Mineral Mosaic",\ "Patterns/Mosaic",\ "Patterns/Op Art",\ "Patterns/Pack Ellipses",\ "Patterns/Paper Texture",\ "Patterns/Periodic Dots",\ "Patterns/Pills",\ "Patterns/Plaid",\ "Patterns/Polka Dots",\ "Patterns/Random Color Ellipses",\ "Patterns/Random Pattern",\ "Patterns/Random Rectangles",\ "Patterns/Rays",\ "Patterns/Reaction-Diffusion",\ "Patterns/Reptile",\ "Patterns/Resynthetize Texture [FFT]",\ "Patterns/Resynthetize Texture [Patch-Based]",\ "Patterns/Rorschach",\ "Patterns/Satin",\ "Patterns/Seamless Deco",\ "Patterns/Seamless Turbulence",\ "Patterns/Shock Waves",\ "Patterns/Soft Random Shades",\ "Patterns/Sponge",\ "Patterns/Stained Glass",\ "Patterns/Stars",\ "Patterns/Stencil",\ "Patterns/Strip",\ "Patterns/Tetris",\ "Patterns/Triangular Pattern",\ "Patterns/Truchet",\ "Patterns/Turbulent Halftone",\ "Patterns/Voronoi",\ "Patterns/Weave",\ "Patterns/Whirls",\ "Rendering/3D Blocks",\ "Rendering/3D Colored Object",\ "Rendering/3D Elevation",\ "Rendering/3D Extrusion",\ "Rendering/3D Image Object",\ "Rendering/3D Lathing",\ "Rendering/3D Random Objects",\ "Rendering/Algorithm A",\ "Rendering/Ball",\ "Rendering/Circle Art",\ "Rendering/Construction Material Texture",\ "Rendering/Disco",\ "Rendering/Equation Plot [Parametric]",\ "Rendering/Equation Plot [Y=f(X)]",\ "Rendering/Gradient [Corners]",\ "Rendering/Gradient [Custom Shape]",\ "Rendering/Gradient [from Line]",\ "Rendering/Gradient [Linear]",\ "Rendering/Gradient [Radial]",\ "Rendering/Gradient [Random]",\ "Rendering/Hair Locks",\ "Rendering/Hypotrochoid",\ "Rendering/Kitaoka Spin Illusion",\ "Rendering/Lightning",\ "Rendering/Lissajous",\ "Rendering/Mandelbrot - Julia Sets",\ "Rendering/Nebulous",\ "Rendering/Neon Lightning",\ "Rendering/Newton Fractal",\ "Rendering/Plasma",\ "Rendering/Popcorn Fractal",\ "Rendering/Pseudorandom Noise",\ "Rendering/Pythagoras Tree",\ "Rendering/Quick Copyright",\ "Rendering/Rainbow",\ "Rendering/Random Signature",\ "Rendering/Rectangular Tiling",\ "Rendering/Shade Bobs",\ "Rendering/Sine Curve",\ "Rendering/Snowflake 2",\ "Rendering/Spiral",\ "Rendering/Spiral RGB",\ "Rendering/Spline Spirograph",\ "Rendering/Superformula",\ "Rendering/Symmetric 2D Shape",\ "Rendering/Thorn Fractal - Secant Sea",\ "Rendering/Tree",\ "Rendering/Turbulence",\ "Rendering/Twisted Rays",\ "Rendering/Twisted Rays [Samj]",\ "Rendering/Underwoods",\ "Rendering/Wiremap",\ "Repair/Anti Alias",\ "Repair/Banding Denoise",\ "Repair/Bayer Reconstruction",\ "Repair/Clean Text",\ "Repair/Compression Blur",\ "Repair/Deinterlace",\ "Repair/Deinterlace2x",\ "Repair/Denoise",\ "Repair/Denoise Smooth",\ "Repair/Denoise Smooth Alt",\ "Repair/Descreen",\ "Repair/Despeckle",\ "Repair/Iain Noise Reduction 2019",\ "Repair/Iain's Fast Denoise",\ "Repair/Inpaint [Holes]",\ "Repair/Inpaint [Morphological]",\ "Repair/Inpaint [Multi-Scale]",\ "Repair/Inpaint [Patch-Based]",\ "Repair/Inpaint [Transport-Diffusion]",\ "Repair/JPEG Smooth",\ "Repair/Local Similarity Mask",\ "Repair/Moire Removal",\ "Repair/Pixel Denoise",\ "Repair/Recursive Median",\ "Repair/Red-Eye Attenuation",\ "Repair/Remove Hot Pixels",\ "Repair/Repair Scanned Document",\ "Repair/Smooth [Anisotropic]",\ "Repair/Smooth [Antialias]",\ "Repair/Smooth [Bilateral]",\ "Repair/Smooth [Block PCA]",\ "Repair/Smooth [Diffusion]",\ "Repair/Smooth [Geometric-Median]",\ "Repair/Smooth [Guided]",\ "Repair/Smooth [IUWT]",\ "Repair/Smooth [Mean-Curvature]",\ "Repair/Smooth [Median]",\ "Repair/Smooth [NL-Means]",\ "Repair/Smooth [Patch-Based]",\ "Repair/Smooth [Patch-PCA]",\ "Repair/Smooth [Perona-Malik]",\ "Repair/Smooth [Selective Gaussian]",\ "Repair/Smooth [Skin]",\ "Repair/Smooth [Thin Brush]",\ "Repair/Smooth [Total Variation]",\ "Repair/Smooth [Wavelets]",\ "Repair/Smooth [Wiener]",\ "Repair/Solidify",\ "Repair/Unpurple",\ "Repair/Unquantize [JPEG Smooth]",\ "Repair/Unstrip",\ "Repair/Upscale [DCCI2x]",\ "Repair/Upscale [Diffusion]",\ "Repair/Upscale [Edge]",\ "Repair/Upscale [Scale2x]" k[${}] sort_list +,n # Generate reference documentation pages for the G'MIC website. # $1 = upload to G'MIC server, can be { 0 | 1 }. update_reference_html : check "isbool(${1=0})" path_current=${-path_current} path_ok=$HOME/work/src/gmic/html/reference/ if ['$path_current']!=['$path_ok'] error[0--3] "Command 'update_reference_html: Command run from wrong path: '"$path_current"', "\ "should be '"$path_ok"'." fi x "rm -f *.pdf" rm e[^-1] "Generate reference documentation pages for the G'MIC website." it $HOME/work/src/gmic/src/gmic_stdlib.gmic a y # Generate image of examples. if $1 _prerelease= fi x "mkdir -p img" _parse_cli_images_path="img/" +parse_cli images parse_cli html # Generate reference pages in html. rm x "ln -fs "$HOME/work/src/gmic/html/header1.html" ." x "ln -fs "$HOME/work/src/gmic/html/footer.html" ." x "cp -rf "$HOME/work/src/gmic-community/reference/images" ." reference html,$HOME/work/src/gmic-community/reference # Upload to G'MIC server. if $1 e "\n > Transfer reference documentation on G'MIC server.\n" x "lftp sftp://"$GMIC_LOGIN":@ovh -e \"mirror -RL . /home/"$GMIC_LOGIN"/www/gmic/reference ; quit\"" fi e "\n > All done.\n" # Generate tutorial pages for the G'MIC website. # $1 = upload to G'MIC server, can be { 0 | 1 }. update_tutorial_html : check "isbool(${1=0})" path_current=${-path_current} path_ok=$HOME/work/src/gmic/html/tutorial/ if ['$path_current']!=['$path_ok'] error[0--3] "Command 'update_tutorial_html: Command run from wrong path: '"$path_current"', "\ "should be '"$path_ok"'." fi rm e[^-1] "Generate tutorial pages for the G'MIC website." path_tutorial=$HOME/work/src/gmic-community/tutorial use_vt100 # Build directory structure. e " > Build directory structure." x "ln -fs "$HOME/work/src/gmic/html/header1.html" ." x "ln -fs "$HOME/work/src/gmic/html/footer.html" ." x "mkdir -p images scripts" files 5,$path_tutorial/* l[] { ({'${}'}:y) s -,{','} for $! { file={0,t} rm[0] basename $file basename=${} 0 => $basename ext={`lowercase(['{x}'])`} rm. if ['$basename']!='img' if isdir(['$file']) files 5,$file/* ({'${}'}:y) s. -,{','} # Folder -> Recursively add files elif isin(['$ext'],'png','jpg','jpeg','gif','mp4','svg','webm') x "cp -f \""$file"\" images/" # Image/videos: Copy to sub-folder 'images/' elif isin(['$ext'],'gmic','py') x "cp -f \""$file"\" scripts/" # Scripts: Copy to sub-folder 'scripts/' elif ['$ext']=='gmd' # G'MIC Markdown -> Copy to current folder './' it[] $file ot. $basename rm. fi fi } } # Generate html pages. files 0,*.gmd files=${} repeat narg({/$files}) { arg0 $>,$files file=${} e " > Generate '"$_vt100_c$file$_vt100_n"'." t0=$| it $file replace_str. "../listmanip/","" replace_str. "../images/","images/" gmd2html 2 ot {n} rm. t1=$| e "\r > Generate '"$_vt100_c$file$_vt100_n"' (done in "$_vt100_g{_round($t1-$t0,0.1)}"s"$_vt100_n")." } x "rm -f *.gmd" # Upload to G'MIC server. if $1 e "\n > Transfer tutorial pages on G'MIC server.\n" x "lftp sftp://"$GMIC_LOGIN":@ovh -e \"mirror -RL . /home/"$GMIC_LOGIN"/www/gmic/tutorial ; quit\"" fi e "\n > All done.\n" # string2ts : ts_file.ts,strings_en.txt,strings_fr.txt # Regenerate .ts file from new translated strings (separated by newline in files strings_*). # Specified .ts file can be an empty argument (""). strings2ts : skip "${1=}" e[^-1] "Regenerate translation file by merging file '$1' and source/translated strings '$2/$3'." # Import data and extract original/translated strings. if narg($1) l[] { it "$1" lang={`" lang = vector256(); p = find(#-1,'language=\"'); p>=0?( p+=10; q = find(#-1,'\">',p); q>=0?copy(lang,i[p],q - p); ); lang"`} e " > File '$1', detected language : "$lang. s -,10 N0=0 foreach { autocrop {'" "'} if s=crop();find(s,'')>=0" && "find(s,'')>=0 discard {''} discard {''} src$N0={t} elif s=crop();find(s,'')>=0" && "find(s,'')>=0 discard {''} discard {''} dest$N0={t} N0+=1 fi } rm } else N0=0 lang= fi if $N0 e " > File '$1' contains "$N0" strings." fi l[] { it "$2" s -,10 N1=$! repeat $! { nsrc$>={$>,t} } rm } l[] { it "$3" replace_str " ;",";" s -,10 N2=$! repeat $! { ndest$>={$>,t} } rm } if $N1!=$N2 error[0--3] "Command 'strings2ts': Number of lines do not match in files '$2' and '$3'." fi e " > Files '$2' and '$3' contain "$N1" strings." # Merge all strings together (set higher priority to strings that were already in the .ts file). l[] { repeat $N1 { ({'${ndest$>}'}) => ${nsrc$>} } y } repeat $N0 { rmn ${src$>} } # Discard already-translated strings in the .ts file if $N0 i[0] 0 l[0] { rm repeat $N0 { i ({'${dest$>}'}) => ${src$>} } y } fi foreach { src={n} dest={t} if lowercase(['$src'])==lowercase(['$dest']) rm fi } sort_list +,n e " > "$!" strings remain after cleaning/merging ("{$!-$N0}" new)." # Generate .ts file. foreach { _strings2ts_src {n} src=${} _strings2ts_dest {t} dest=${} rm ({'" "\n\ " "$src""\n\ " "$dest""\n\ " "\n\n'}) } i[0] ({'""\n\ ""\n\ ""\n\ " "\n\ " FilterTextTranslator"\n\n'}) ({'" "\n\ ""\n'}) y a y _strings2ts_src : ({'"$*"'}) replace_str. "°","°" replace_str. "à","à" replace_str. "&","&" replace_str. "<","<" replace_str. ">",">" replace_str. "\"",""" replace_str. "'","'" u {t} rm. _strings2ts_dest : ({'"$*"'}) html2utf8. replace_str. "°","°" replace_str. "à","à" replace_str. "&","&" replace_str. "<","<" replace_str. ">",">" replace_str. "\"",""" replace_str. "'","'" u {t} rm. # # Output mode 'zart' (output in stdout). # parse_gui_parseparams_zart : u 1 # Tell parser to parse filter parameters parse_gui_trigger_zart : foreach { # Keep only 1-level folders => {`s=[['{n}'],0];p=find(s,_'/',size(s)-1,0);p>=0?(p=find(s,_'/',p-1,0);p>0?copy(s,s[p+1],size(s)-p));s`} } sort_list +,n parse_gui_zart : e " >> Generate output, in 'zart' mode.\n" +e "" +e "\n" current_group= N:=$_nb_filters-1 repeat $_nb_filters { f=$> e "\r >> "$_vt100_c[#$f/$N]$_vt100_n" "{`s=vector48(_'" "');copy(s,['${_f${f}_path}${_f${f}_name}']);s`} # Manage groups. 0 => {`s=['${_f${f}_path}'];s[0,size(s)-1]`} path={b} rm. if ['$current_group']!=['$path'] if ['$current_group']!=0 +e "\n" fi +e "" +e "\n" current_group=$path fi # Filter definition. _parse_gui_zart[] ${_f${f}_name} fname=${} +e "" +e "" +e " "${_f${f}_command_preview}" $""*" repeat ${_f${f}_nb_params} { p=$> _parse_gui_zart ${_f${f}_p{$p}_name} name=${} type=${_f${f}_p{$p}_type} nbargs=${_f${f}_p{$p}_nb_args} arg0= repeat $nbargs { ('${_f${f}_p${p}_a$>}') autocrop. {'\"'} _parse_gui_zart {t} arg$>=${} rm. } if ['$type']=='bool' if lowercase(['$arg0'])=='false' arg0=0 elif lowercase(['$arg0'])=='true' arg0=1 elif !isnum($arg0) arg0=0 fi +e " " elif ['$type']=='choice' default=0 n=0 args= l[] { if isint($arg0) default=$arg0 n+=1 fi onfail } c= repeat $nbargs-$n { a=$> args.=${c}"choice"$>"=\""${arg{$n+$a}}"\"" c=" " } +e " " elif ['$type']=='color' if $nbargs==1" && "['$arg0'][0]==_'#' # Convert colors specified as '#RRGGBB[AA]' l[] { ('$arg0') autocrop. {'#'} s x,-2 nbargs=$! repeat $! { a=$> hex2dec {$>,t} arg$a=${} } rm } fi args= c= repeat $nbargs { a=$> args.=$c${arg$a} c="," } +e " " elif isin(['$type'],'int','float') +e " <"$type" name=\""$name"\" default=\""$arg0"\" min=\""$arg1"\" max=\""$arg2"\" />" elif isin(['$type'],'file','filein','fileout') +e " " elif ['$type']=='folder' +e " " elif ['$type']=='link' align=-1 name= url= n=0 l[] { if isnum($arg0) align=$arg0 n+=1 fi onfail } if $nbargs-$n>1 name=${arg$n} url=${arg{$n+1}} else url,name=${arg$n} fi if !$align align=left elif $align==1 align=right else align=center fi +e " " elif ['$type']=='note' text={/$arg0} +e " " elif ['$type']=='point' +e " " elif ['$type']=='separator' +e " " elif ['$type']=='text' +e " " elif ['$type']=='value' +e " " else # Unsupported parameter +e " " fi } +e "\n" } if narg($current_group) +e "" fi +e "" e "\r >> "${_vt100_g}{`s=vector64(_'" "');copy(s,'"Output done!"');s`}$_vt100_n _parse_gui_zart : l[] { ('"$*"') replace_str "&","#amp;" replace_str. "&","&" replace_str. "<","<" replace_str. ">",">" replace_str. "\"",""" replace_str "#amp;","&" u {t} rm. onfail u "" } # Convert last ascii buffer with html codes (e.g. 配), as an UTF-8 encoded buffer. html2utf8 : if h eval " source = crop(); svalue = vector8(); p = 0; while (1, p = p0 = find(source,'&#',p); p<0?break(); p+=2; p>=size(source)?break(); q = find(source,';',p); q>p && q<=p+8 && !isnan(val=s2v(source,p))?( copy(svalue,source[p],q-p); svalue[q-p] = 0; val = s2v(svalue,0,1); !isnan(val) && isint(val) && val>0?( # Encode in UTF-8. val<=0x007f?( # 7 bits or less -> 1 byte i[p0++] = val; ):val<=0x07ff?( # 11 bits or less -> 2 bytes i[p0++] = (val>>6)|0xc0; i[p0++] = (val&0x3f)|0x80; ):val<=0xffff?( # 16 bits or less -> 3 bytes i[p0++] = (val>>12)|0xe0; i[p0++] = ((val>>6)&0x3f)|0x80; i[p0++] = (val&0x3f)|0x80; ):( # 21 bits or less -> 4 bytes i[p0++] = (val>>18)|0xf0; i[p0++] = ((val>>12)&0x3f)|0x80; i[p0++] = ((val>>6)&0x3f)|0x80; i[p0++] = (val&0x3f)|0x80; ); copy(i[p0],-1,q-p0+1,1,0); p = q + 1; ); ); )" discard. -1 fi # Convert last UTF-8 encoded buffer to ascii buffer with html codes. utf82html : if h eval " write_seq() = ( copy(res[q],'&#'); q+=2; s = v2s(val); l = find(s,0); copy(res[q],s,l); q+=l; res[q++]=_';' ); res = vector(#4*wh); q = 0; repeat (wh,p, i = i[p]; !(i&0x80)?( # 1-byte res[q++] = i; ):(i&0xe0)==0xc0?( # 2-bytes val = (i&0x1f)<<6; i = i[p + 1]; val|= (i&0x3f); write_seq(); ):(i&0xf0)==0xe0?( # 3-bytes val = (i&0xf)<<12; i = i[p + 1]; val|= (i&0x3f)<<6; i = i[p + 2]; val|= (i&0x3f); write_seq(); ):(i&0xf8)==0xf0?( # 4-bytes val = (i&0x7)<<18; i = i[p + 1]; val|= (i&0x3f)<<12; i = i[p + 2]; val|= (i&0x3f)<<6; i = i[p + 3]; val|= (i&0x3f); write_seq(); ); ); store('res',res,1,q)" rm. $res fi # Force certain filters to move from one path to another. _upload_filters_move : m "move_filter : skip \"${""2=}\" nmd 3,\"$""1\" ind=${} if $ind basename \"$""1\" =>[$ind] \"$""2\"/${} else v 0 e[0--4] \"Cannot move unknown filter '\"${_vt100_r}\"$""1\"${_vt100_n}\"' to '\"${_vt100_g}\"$""2\"${_vt100_n}\"'\" fi" sort_list +,n move_filter "Testing/Garagecoder/Anti Alias","Repair" move_filter "Testing/Garagecoder/Auto Balance","Colors" move_filter "Testing/Garagecoder/LMS Adjustment","Colors" move_filter "Testing/Garagecoder/Compression Blur","Repair" move_filter "Testing/Garagecoder/Emboss","Black & White" move_filter "Testing/Garagecoder/JPEG Smooth","Repair" move_filter "Testing/Garagecoder/Quick Tonemap","Details" move_filter "Testing/Garagecoder/Normalize Brightness","Colors" move_filter "Testing/Garagecoder/Sharpen [Gradient]","Details" move_filter "Testing/Garagecoder/Sharpen [Tones]","Details" move_filter "Testing/Garagecoder/Temperature Balance","Colors" move_filter "Testing/Garagecoder/Unquantize [JPEG Smooth]","Repair" move_filter "Testing/Garagecoder/Upscale [Edge]","Repair" move_filter "Testing/Garagecoder/Wiremap","Rendering" move_filter "Testing/Garagecoder/Smooth [Geometric-Median]","Repair" move_filter "Testing/Iain Fergusson/Easy Skin Retouch","Details" move_filter "Testing/Iain Fergusson/Moire Removal","Repair" move_filter "Testing/Iain Fergusson/Halftone Shapes","Patterns" move_filter "Testing/Iain Fergusson/Simple Local Contrast","Details" move_filter "Testing/Iain Fergusson/Turbulent Halftone","Patterns" move_filter "Testing/Joan Rake/Deformations/Ultrawarp++++","Degradations" move_filter "Testing/Naggobot/Blockism","Artistic" move_filter "Testing/Reptorian/Blur [Splinter]","Degradations" move_filter "Testing/Reptorian/Construction Material Texture","Rendering" move_filter "Testing/Reptorian/Emboss-Relief","Details" move_filter "Testing/Reptorian/Fragment Blur","Degradations" move_filter "Testing/Reptorian/Kaleidoscope [Reptorian-Polar]","Deformations" move_filter "Testing/Reptorian/Logarithmic Distortion","Deformations" move_filter "Testing/Reptorian/Nebulous","Rendering" move_filter "Testing/Reptorian/Pixel Push","Deformations" move_filter "Testing/Reptorian/Point Warp","Deformations" move_filter "Testing/Reptorian/Popcorn Fractal","Rendering" move_filter "Testing/Reptorian/Pseudorandom Noise","Rendering" move_filter "Testing/Reptorian/Sinusoidal Water Distortion","Deformations" move_filter "Testing/Samj/Arrays & Tiles/Annular Steiner Chain Round Tile","Arrays & Tiles" move_filter "Testing/Samj/Arrays & Tiles/Reptile","Patterns" move_filter "Testing/Samj/Artistic/Angoisse Anguish","Artistic" move_filter "Testing/Samj/Artistic/Chalk It Up [Fr]","Artistic" move_filter "Testing/Samj/Artistic/Barbouillage Paint Daub","Artistic" move_filter "Testing/Samj/Artistic/Skeletik","Artistic" move_filter "Testing/Samj/Patterns/Denim Texture","Patterns" move_filter "Testing/Samj/Patterns/Soft Random Shades","Patterns" move_filter "Testing/Samj/Rendering/Pythagoras Tree","Rendering" move_filter "Testing/Samj/Rendering/Snowflake 2","Rendering" move_filter "Testing/Samj/Rendering/Twisted Rays [Samj]","Rendering" move_filter "Testing/Souphead/Disco","Rendering" move_filter "Testing/Souphead/Moon2panorama","Deformations" move_filter "Testing/Souphead/Spiral RGB","Rendering" move_filter "Testing/Souphead/Kitaoka Spin Illusion","Rendering" move_filter "Testing/Zonderr/Spiral","Rendering" um move_filter upload_filters : e[^-1] "Upload filter definition file on G'MIC server.\n" rm # Be sure to get the latest version of filters. x "cd "$HOME"/work/src/gmic && git pull" x "cd "$HOME"/work/src/gmic-community && git pull" # Define the list of compatible versions. versions=340,341,342,$_version # Import filters from stdlib and community. files $HOME/work/src/gmic-community/include/*.gmic files=${},$HOME/work/src/gmic/src/gmic_stdlib.gmic repeat narg($files) { l[] { file=${"arg0 $>,"$files} it[] $file basename $file basename=${} if ['$basename']=='sylvie_alexandre.gmic' s +,{'"#@gui "'} i[1--2:2] ('"#@gui ________Testing\n#@gui Samj\n"') y a y elif ['$basename']=='template.gmic' rm 0 fi } } i[1--2] ('"\n#@gui _________________\n"') y a y # Create update files. +l. { e "** Generate filter update files." m "parse_gui_trigger_update : _upload_filters_move" v + parse_gui. update v - um parse_gui_trigger_update url=http://bit.ly/2uaBRMB replace_str "David Tschumperlé","David Tschumperlé" is_nonnative_preview_removed=0 repeat narg($versions) { ver:=arg0($>,$versions) if !$is_nonnative_preview_removed" && "$ver>=342 # Remove non-native preview splitter for version 3.4.2+ l { s +,{'\n'} foreach { if "S='#@gui :Preview Type=choice(\"Full\",';crop(0,0,1,size(S))==S" rm ('"#@gui :_=value(0)"':y) elif "S='#@gui :Preview Split=point(50,50';crop(0,0,1,size(S))==S" rm ('"#@gui :_=point(50,50,-1)_0"':y) fi } a y replace_str "#@gui :_=separator()\n#@gui :_=value(0)\n#@gui :_=point(50,50,-1)_0",\ "#@gui :_=value(0),_=point(50,50,-1)_0" } is_nonnative_preview_removed=1 fi ot ${_path_rc}update$ver.gmic # Plain-text o cimgz:/tmp/update$ver.gmic,uint8 # Binary-compressed } # Upload filter files on G'MIC server. e "** Upload filter update." repeat narg($versions) { ver:=arg0($>,$versions) _upload[] ${_path_rc}update$ver.gmic,plain_update$ver.gmic,1 _upload[] /tmp/update$ver.gmic,update$ver.gmic,1 } rm } # Create JSON file. +l. { e "** Generate JSON filters file." v + parse_gui. json v - # Upload filter files on G'MIC server. e "** Upload JSON filters." ot ${_path_rc}/update$_version.json repeat narg($versions) { ver:=arg0($>,$versions) _upload[] ${_path_rc}/update$ver.json,update$ver.json,1 } rm } # Create filter listing. +l. { e "** Generate filter listing." v + parse_gui. list v - ot /tmp/gui_filters.txt rm } rm _upload : skip "${2=""}","${3=0}" if narg("$2") out="$2" else basename "$1" out=${} fi if !narg($GMIC_LOGIN) GMIC_LOGIN=${"gmic_ftp 0"} GMIC_PASSWD=${"gmic_ftp 1"} fi if narg($GMIC_LOGIN) x $3,"lftp sftp://"$GMIC_LOGIN":@ovh -e \"put -O /home/"$GMIC_LOGIN"/www/gmic/ "\ "\\\"$1\\\" -o \\\""$out"\\\"; quit\" >/dev/null" fi # Upload or list released binaries on the G'MIC web server. # $1=version number (e.g "1.6.7_pre") # $2={ 0:print list of URLs | 1:upload files } upload_binaries : check "isbool(${2=1})" is_pre=${"strcontains $1,_pre"} N=0 file$N=gmic_$1_debian11_bullseye_amd64.deb N+=1 file$N=gmic_$1_debian12_bookworm_amd64.deb N+=1 file$N=gmic_$1_ubuntu20-04_focal_amd64.deb N+=1 file$N=gmic_$1_ubuntu22-04_jammy_amd64.deb N+=1 file$N=gmic_$1_ubuntu24-04_noble_amd64.deb N+=1 file$N=gmic_$1_gimp2.10_debian11_bullseye_amd64.zip N+=1 file$N=gmic_$1_gimp2.10_debian12_bookworm_amd64.zip N+=1 file$N=gmic_$1_gimp2.10_ubuntu20-04_focal_amd64.zip N+=1 file$N=gmic_$1_gimp2.10_ubuntu22-04_jammy_amd64.zip N+=1 file$N=gmic_$1_gimp2.10_ubuntu24-04_noble_amd64.zip N+=1 file$N=gmic_$1_cli_win64.zip N+=1 file$N=gmic_$1_lib_win64.zip N+=1 file$N=gmic_$1_qt_win64.zip N+=1 file$N=gmic_$1_gimp2.10_win64.zip N+=1 file$N=gmic_$1_gimp2.10_win64.exe N+=1 if !$2 # Only get list of URLs e[0--3] "List URLs of released binaries ($1) from the G'MIC web server.\n" repeat $N { file=${file$>} is_win=${strcontains[]" "$file,win} if $is_win folder="windows" else folder="linux" fi e "http://gmic.eu/files/"$folder/$file } e "" else # Upload files e[0--3] "Upload released binaries ($1) on the G'MIC web server." t0=$| n=0 t=0 e "- Waiting for binary files to be build." do repeat $N { file=${file$>} if isfile(['{/$file}']) strreplace $file,_$1_,_ file_short=${} is_win=${strcontains[]" "$file,win} if $is_win folder="windows" else folder="linux" fi e "- Upload file '"$file"' to 'https://gmic.eu/files/prerelease/"$file_short"'." _upload[] $file,"files/prerelease/"$file_short if !$is_pre e "- Upload file '"$file"' to 'https://gmic.eu/files/"$folder/$file"'." _upload[] $file,"files/"$folder/$file fi file$>= n+=1 fi } if $n<$N if !($t%4) remaining= sep= repeat $N { if narg(${file$>}) remaining.=${sep}${file$>} sep=", " fi } e "- Waiting for files: "$remaining"." fi wait 30000 t+=1 fi while $n<$N" && "$|<$t0+60*60*6 if $n<$N e "- Partial uploads done (timeout reached)." else e "- All uploads done !" fi fi # Update version number in html header file. # (works both for CImg and G'MIC !) # $1 = filename # $2 = version number # $3 = is_prerelease = { 0 | 1 } _update_header_html : check "narg(${1=}) && ${2=0}>0 && isbool(${3=0})" filename="$1" l[] { it[] $filename s +,{'\n'} foreach { if h>=64 autocrop {'" "'} autocrop. {'\t'} if "find(#0,['Latest stable version: '])>=0" is_gmic:="find(#-1,'gmic.eu')>=0" is_cimg:="find(#-1,'cimg.eu')>=0" if !$is_gmic" && "!$is_cimg error "Cannot determine CImg or G'MIC header file." fi # Retrieve latest stable version specified in the header file. s -,{'>'} if {2,"inrange(i[0],_'0',_'9') && i[1]==_'.' && inrange(i[2],_'0',_'9') && i[3]==_'.' && inrange(i[4],_'0',_'9')"} stable_ver={2,`crop(0,0,1,5)`} fi if $3 pre_ver=${strver\ $2} else stable_ver=${strver\ $2} pre_ver= fi rm date=${-date} if $is_gmic ('" Latest stable version: "\ ""$stable_ver""') if ['$pre_ver']!=0 ('"        "\ "Current pre-release: "$pre_ver""') fi ('" ("$date")"') else ('" Latest stable version: "\ ""$stable_ver""') if ['$pre_ver']!=0 ('"        "\ "Current pre-release: "$pre_ver""') fi ('" ("$date")"') fi y a y fi fi } a y ot $filename rm } #@cli v : eq. to 'verbose'. : (+) #@cli verbose : level : { + | - } : (+) #@cli : Set or increment/decrement the verbosity level. Default level is 0. #@cli : (eq. to 'v').\n #@cli : When 'level>0', G'MIC log messages are displayed on the standard error (stderr). #@cli : Default value: 'level=1'. #@cli wait : delay : (no arg) : (+) #@cli : Wait for a given delay (in ms), optionally since the last call to 'wait'. #@cli : or wait for a user event occurring on the selected instant display windows. #@cli : 'delay' can be { <0:delay+flush events | 0:event | >0:delay }. #@cli : Command selection (if any) stands for instant display window indices instead of image indices. #@cli : If no window indices are specified and if 'delay' is positive, the command results #@cli : in a 'hard' sleep during specified delay. #@cli : Default value: 'delay=0'. #@cli warn : _force_visible={ 0 | 1 },_message : (+) #@cli : Print specified warning message, on the standard error (stderr). #@cli : Command selection (if any) stands for displayed call stack subset instead of image indices. #@cli w : eq. to 'window'. : (+) #@cli window : _width[%]>=-1,_height[%]>=-1,_normalization,_fullscreen,_pos_x[%],_pos_y[%],_title : (+) #@cli : Display selected images into an instant display window with specified size, normalization type, #@cli : fullscreen mode and title. #@cli : (eq. to 'w').\n #@cli : If 'width' or 'height' is set to -1, the corresponding dimension is adjusted to the window #@cli : or image size. #@cli : Specify 'pos_x' and 'pos_y' arguments only if the window has to be moved to the specified #@cli : coordinates. Otherwise, they can be avoided. #@cli : 'width'=0 or 'height'=0 closes the instant display window. #@cli : 'normalization' can be { -1:keep same | 0:none | 1:always | 2:1st-time | 3:auto }. #@cli : 'fullscreen' can be { -1:keep same | 0:no | 1:yes }. #@cli : You can manage up to 10 different instant display windows by using the numbered variants #@cli : 'w0' (default, eq. to 'w'),'w1',...,'w9' of the command 'w'. #@cli : Invoke 'window' with no selection to make the window visible, if it has been closed by the user. #@cli : Default values: 'width=height=normalization=fullscreen=-1' and 'title=(undefined)'. #--------------------------------- # #@cli :: List Manipulation # #--------------------------------- #@cli k : eq. to 'keep'. : (+) #@cli keep : (+) #@cli : Keep only selected images. #@cli : (eq. to 'k'). #@cli : $ image.jpg split x keep[0-50%:2] append x #@cli : $ image.jpg split x keep[^30%-70%] append x #@cli kn : eq. to 'keep_named'. kn : e[^-1] "Keep images named '$*'." nmd $"*" k[${}] #@cli keep_named : "name1","name2",... #@cli : Keep all images with specified names from the list of images. #@cli : Remove all images if no images with those names exist. #@cli : (eq. to 'kn'). keep_named : e[^-1] "Keep images named '$*'." nmd $"*" k[${}] #@cli mv : eq. to 'move'. : (+) #@cli move : position[%] : (+) #@cli : Move selected images at specified position. #@cli : Images are actually inserted between current positions 'position-1' and 'position'. #@cli : (eq. to 'mv'). #@cli : $ image.jpg split x,3 move[1] 0 #@cli : $ image.jpg split x move[50%--1:2] 0 append x #@cli nm : eq. to 'name'. : (+) #@cli => : eq. to 'name'. : (+) #@cli name : "name1","name2",...,"nameN" : (+) #@cli : Set names of selected images. #@cli : - If no explicit image selection is given, image selection is assumed to be '[-N--1]', \ # where 'N' is the number of specified arguments. #@cli : - If 'N' is higher than the number of images in selection, an error is thrown. #@cli : - If 'N' is lower than the number of images in selection, image names are assigned in a periodic way, i.e. \ # `name(selection[k]) = arg[k%N]`. #@cli : (eq. to '=>' and 'nm'). #@cli : $ image.jpg name image blur[image] 2 #@cli : $$ #@cli rm : eq. to 'remove'. : (+) #@cli remove : (+) #@cli : Remove selected images. #@cli : (eq. to 'rm'). #@cli : $ image.jpg split x remove[30%-70%] append x #@cli : $ image.jpg split x remove[0-50%:2] append x #@cli remove_duplicates #@cli : Remove duplicates images in the selected images list. #@cli : $ (1,2,3,4,2,4,3,1,3,4,2,1) split x remove_duplicates append x remove_duplicates : e[^-1] "Remove duplicates images in selected list of image$?." repeat $! { base=$> off=0 repeat $!-$>-1 { comp:=$base+1+$>-$off if $comp>=$! break fi +-[$base,$comp] abs. is_duplicate:=!is rm. if $is_duplicate rm[$comp] off+=1 fi } } #@cli remove_empty #@cli : Remove empty images in the selected image list. remove_empty : e[^-1] "Remove empty images in selected list of image$?." $!,1,1,1,"!w#x?x:-1" discard. -1 if w rm[{^},-1] else rm. fi #@cli rmn : eq. to 'remove_named'. rmn : e[^-1] "Remove images named '$*'." nmd $"*" rm[${}] #@cli remove_named : "name1","name2",... #@cli : Remove all images with specified names from the list of images. #@cli : Does nothing if no images with those names exist. #@cli : (eq. to 'rmn'). remove_named : e[^-1] "Remove images named '$*'." nmd $"*" rm[${}] #@cli rv : eq. to 'reverse'. : (+) #@cli reverse : (+) #@cli : Reverse positions of selected images. #@cli : (eq. to 'rv'). #@cli : $ image.jpg split x,3 reverse[-2,-1] #@cli : $ image.jpg split x,-16 reverse[50%-100%] append x #@cli sort_list : _ordering={ + | - },_criterion #@cli : Sort list of selected images according to the specified image criterion. #@cli : Default values: 'ordering=+', 'criterion=i'. #@cli : $ (1;4;7;3;9;2;4;7;6;3;9;1;0;3;3;2) split y sort_list +,i append y sort_list : skip ${1=+},${2=i} s0="descending" s1="ascending" e[^-1] "Sort list of image$? in "${s{['$1']=='+'}}" order, according to the image criterion '$2'." if $! if isin('$2','n','N') # Special case : lexicographic order from image names (n: ignore case, N: consider case) op={`;['$1']=='-'?_'>':_'<'`} if ['$2']=='n' fn=lowercase else fn= fi $!,1,1,1,"n = name(#x,1024); find(n,0)%1025" slen:=iM rm. # Largest name length. eval " const lm1 = l - 1; const slen = "$slen"; strcmp(n0,n1) = (for (k = 0, k0, range = pop(); lo = range[0]; hi = range[1]; pivot = int((lo + hi)/2); npivot = name(#pivot,slen); while (lo,$2}) } a[$i--1] y +f. y a[-2,-1] x sort. $1,y z. 1,1 repeat h { nm$>={$>,n} =>[$>] sortlist$> } repeat h { mv[sortlist{i(0,$>)}] -1 } repeat h { =>[$>] ${nm{i(0,$>)}} } rm. fi fi #--------------------------------- # #@cli :: Mathematical Operators # #--------------------------------- #@cli abs : (+) #@cli : Compute the pointwise absolute values of selected images. #@cli : $ image.jpg +sub {ia} abs[-1] #@cli : $ 300,1,1,1,'cos(20*x/w)' +abs display_graph 400,300 #@cli acos : (+) #@cli : Compute the pointwise arccosine of selected images. #@cli : $ image.jpg +normalize -1,1 acos[-1] #@cli : $ 300,1,1,1,'cut(x/w+0.1*u,0,1)' +acos display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli acosh : (+) #@cli : Compute the pointwise hyperbolic arccosine of selected images. #@cli + : eq. to 'add'. : (+) #@cli add : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Add specified value, image or mathematical expression to selected images, \ # or compute the pointwise sum of selected images. #@cli : (eq. to '+'). #@cli : $ image.jpg +add 30% cut 0,255 #@cli : $ image.jpg +blur 5 normalize 0,255 add[1] [0] #@cli : $ image.jpg add '80*cos(80*(x/w-0.5)*(y/w-0.5)+c)' cut 0,255 #@cli : $ image.jpg repeat 9 { +rotate[0] {$>*36},1,0,50%,50% } add div 10 #@cli & : eq. to 'and'. : (+) #@cli and : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise AND of selected images with specified value, image or mathematical \ # expression, or compute the pointwise sequential bitwise AND of selected images. #@cli : (eq. to '&'). #@cli : $ image.jpg and {128+64} #@cli : $ image.jpg +mirror x and #@cli argmax #@cli : Compute the argmax of selected images. Returns a single image #@cli : with each pixel value being the index of the input image with maximal value. #@cli : $ image.jpg sample lena,lion,square +argmax argmax : e[^-1] "Compute argmax of image$?." if !$! return fi 13,$! eval. "!x?copy(i(),[[',i#'],v2s(y,10,10)])" =. 0 discard. 0 str={t} rm. ${-max_whds},"argmax("$str")" k. => [argmax] #@cli argmaxabs #@cli : Compute the argmaxabs of selected images. Returns a single image #@cli : with each pixel value being the index of the input image with maxabs value. argmaxabs : e[^-1] "Compute argmaxabs of image$?." if !$! return fi 13,$! eval. "!x?copy(i(),[[',i#'],v2s(y,10,10)])" =. 0 discard. 0 str={t} rm. ${-max_whds},"argmaxabs("$str")" k. => [argmaxabs] #@cli argmin #@cli : Compute the argmin of selected images. Returns a single image #@cli : with each pixel value being the index of the input image with minimal value. #@cli : $ image.jpg sample lena,lion,square +argmin argmin : e[^-1] "Compute argmin of image$?." if !$! return fi 13,$! eval. "!x?copy(i(),[[',i#'],v2s(y,10,10)])" =. 0 discard. 0 str={t} rm. ${-max_whds},"argmin("$str")" k. => [argmin] #@cli argminabs #@cli : Compute the argminabs of selected images. Returns a single image #@cli : with each pixel value being the index of the input image with minabs value. argminabs : e[^-1] "Compute argminabs of image$?." if !$! return fi 13,$! eval. "!x?copy(i(),[[',i#'],v2s(y,10,10)])" =. 0 discard. 0 str={t} rm. ${-max_whds},"argminabs("$str")" k. => [argminabs] #@cli asin : (+) #@cli : Compute the pointwise arcsine of selected images. #@cli : $ image.jpg +normalize -1,1 asin[-1] #@cli : $ 300,1,1,1,'cut(x/w+0.1*u,0,1)' +asin display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli asinh : (+) #@cli : Compute the pointwise hyperbolic arcsine of selected images. #@cli atan : (+) #@cli : Compute the pointwise arctangent of selected images. #@cli : $ image.jpg +normalize 0,8 atan[-1] #@cli : $ 300,1,1,1,'4*x/w+u' +atan display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli atan2 : [x_argument] : (+) #@cli : Compute the pointwise oriented arctangent of selected images. #@cli : Each selected image is regarded as the y-argument of the arctangent function, while the #@cli : specified image gives the corresponding x-argument. #@cli : $ (-1,1) (-1;1) resize 400,400,1,1,3 atan2[1] [0] keep[1] mod {pi/8} #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli atanh : (+) #@cli : Compute the pointwise hyperbolic arctangent of selected images. #@cli << : eq. to 'bsl'. : (+) #@cli bsl : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise left shift of selected images with specified value, image or \ # mathematical expression, or compute the pointwise sequential bitwise left shift of \ # selected images. #@cli : (eq. to '<<'). #@cli : $ image.jpg bsl 'round(3*x/w,0)' cut 0,255 #@cli >> : eq. to 'bsr'. : (+) #@cli bsr : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise right shift of selected images with specified value, image or \ # mathematical expression, or compute the pointwise sequential bitwise right shift of \ # selected images. #@cli : (eq. to '>>'). #@cli : $ image.jpg bsr 'round(3*x/w,0)' cut 0,255 #@cli cos : (+) #@cli : Compute the pointwise cosine of selected images. #@cli : $ image.jpg +normalize 0,{2*pi} cos[-1] #@cli : $ 300,1,1,1,'20*x/w+u' +cos display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli cosh : (+) #@cli : Compute the pointwise hyperbolic cosine of selected images. #@cli : $ image.jpg +normalize -3,3 cosh[-1] #@cli : $ 300,1,1,1,'4*x/w+u' +cosh display_graph 400,300 #@cli deg2rad #@cli : Convert pointwise angle values of selected images, from degrees to radians (apply 'i*pi/180'). deg2rad : e[^-1] "Convert pointwise angle values of image$?, from degrees to radians." * 0.017453292519943295 #@cli / : eq. to 'div'. : (+) #@cli div : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Divide selected images by specified value, image or mathematical expression, \ # or compute the pointwise quotient of selected images. #@cli : (eq. to '/'). #@cli : $ image.jpg div '1+abs(cos(x/10)*sin(y/10))' #@cli : $ image.jpg +norm add[-1] 1 +div #@cli == : eq. to 'eq'. : (+) #@cli eq : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean equality of selected images with specified value, image or \ # mathematical expression, or compute the boolean equality of selected images. #@cli : (eq. to '=='). #@cli : $ image.jpg round 40 eq {round(ia,40)} #@cli : $ image.jpg +mirror x eq #@cli erf : (+) #@cli : Compute the pointwise error function of selected images. #@cli : $ image.jpg +normalize 0,2 erf[-1] #@cli : $ 300,1,1,1,'7*x/w-3.5+u' +erf display_graph 400,300 #@cli exp : (+) #@cli : Compute the pointwise exponential of selected images. #@cli : $ image.jpg +normalize 0,2 exp[-1] #@cli : $ 300,1,1,1,'7*x/w+u' +exp display_graph 400,300 #@cli >= : eq. to 'ge'. : (+) #@cli ge : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean 'greater or equal than' of selected images with specified value, image #@cli : or mathematical expression, or compute the boolean 'greater or equal than' of selected images. #@cli : (eq. to '>='). #@cli : $ image.jpg ge {ia} #@cli : $ image.jpg +mirror x ge #@cli > : eq. to 'gt'. : (+) #@cli gt : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean 'greater than' of selected images with specified value, image or \ # mathematical expression, or compute the boolean 'greater than' of selected images. #@cli : (eq. to '>'). #@cli : $ image.jpg gt {ia} #@cli : $ image.jpg +mirror x gt #@cli isinf #@cli : Select 'inf' values in selected images. isinf : e[^-1] "Select 'inf' values in image$?." f "isinf(i)" #@cli isnan #@cli : Select 'nan' values in selected images. isnan : e[^-1] "Select 'nan' values in image$?." f "isnan(i)" #@cli <= : eq. to 'le'. : (+) #@cli le : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean 'less or equal than' of selected images with specified value, image or \ # mathematical expression, or compute the boolean 'less or equal than' of selected images. #@cli : (eq. to '<='). #@cli : $ image.jpg le {ia} #@cli : $ image.jpg +mirror x le #@cli < : eq. to 'lt'. : (+) #@cli lt : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean 'less than' of selected images with specified value, image or \ # mathematical expression, or compute the boolean 'less than' of selected images. #@cli : (eq. to '<'). #@cli : $ image.jpg lt {ia} #@cli : $ image.jpg +mirror x lt #@cli log : (+) #@cli : Compute the pointwise base-e logarithm of selected images. #@cli : $ image.jpg +add 1 log[-1] #@cli : $ 300,1,1,1,'7*x/w+u' +log display_graph 400,300 #@cli log10 : (+) #@cli : Compute the pointwise base-10 logarithm of selected images. #@cli : $ image.jpg +add 1 log10[-1] #@cli : $ 300,1,1,1,'7*x/w+u' +log10 display_graph 400,300 #@cli log2 : (+) #@cli : Compute the pointwise base-2 logarithm of selected images #@cli : $ image.jpg +add 1 log2[-1] #@cli : $ 300,1,1,1,'7*x/w+u' +log2 display_graph 400,300 #@cli max : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the maximum between selected images and specified value, image or \ # mathematical expression, or compute the pointwise maxima between selected images. #@cli : $ image.jpg +mirror x max #@cli : $ image.jpg max 'R=((x/w-0.5)^2+(y/h-0.5)^2)^0.5;255*R' #@cli maxabs : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the maxabs between selected images and specified value, image or \ # mathematical expression, or compute the pointwise maxabs between selected images. #@cli m/ : eq. to 'mdiv'. : (+) #@cli mdiv : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the matrix division of selected matrices/vectors by specified value, image or \ # mathematical expression, or compute the matrix division of selected images. #@cli : (eq. to 'm/'). #@cli med #@cli : Compute the median of selected images. #@cli : $ image.jpg sample lena,lion,square +med med : e[^-1] "Compute median of image$?." if !$! return fi 13,$! eval. "!x?copy(i(),[[',i#'],v2s(y,10,10)])" =. 0 discard. 0 str={t} rm. ${-max_whds},"med("$str")" k. => [med] #@cli min : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the minimum between selected images and specified value, image or \ # mathematical expression, or compute the pointwise minima between selected images. #@cli : $ image.jpg +mirror x min #@cli : $ image.jpg min 'R=((x/w-0.5)^2+(y/h-0.5)^2)^0.5;255*R' #@cli minabs : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the minabs between selected images and specified value, image or \ # mathematical expression, or compute the pointwise minabs between selected images. #@cli % : eq. to 'mod'. : (+) #@cli mod : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the modulo of selected images with specified value, image or mathematical \ # expression, or compute the pointwise sequential modulo of selected images. #@cli : (eq. to '%'). #@cli : $ image.jpg +mirror x n. 1,255 round. mod #@cli : $ image.jpg mod 'R=((x/w-0.5)^2+(y/h-0.5)^2)^0.5;255*R' #@cli m* : eq. to 'mmul'. : (+) #@cli mmul : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the matrix right multiplication of selected matrices/vectors by specified value, image or \ # mathematical expression, or compute the matrix right multiplication of selected images. #@cli : If the right-hand side image is vector-valued, this command multiplies each vector-valued pixels by \ # the specified left-hand matrix. #@cli : (eq. to 'm*'). #@cli : $ (0,1,0;0,0,1;1,0,0) (1;2;3) +mmul #@cli : $ (0,1,0;0,0,1;1,0,0) image.jpg +mmul #@cli * : eq. to 'mul'. : (+) #@cli mul : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Multiply selected images by specified value, image or mathematical expression, \ # or compute the pointwise product of selected images. #@cli : (eq. to '*'). #@cli : See also: ''add'', ''sub'', ''div''. #@cli : $ image.jpg +mul 2 cut 0,255 #@cli : $ image.jpg (1,2,3,4,5,6,7,8) ri[-1] [0] mul[0] [-1] #@cli : $ image.jpg mul '1-3*abs(x/w-0.5)' cut 0,255 #@cli : $ image.jpg +luminance negate[-1] +mul #@cli != : eq. to 'neq'. : (+) #@cli neq : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the boolean inequality of selected images with specified value, image or \ # mathematical expression, or compute the boolean inequality of selected images. #@cli : (eq. to '!='). #@cli : $ image.jpg round 40 neq {round(ia,40)} #@cli | : eq. to 'or'. : (+) #@cli or : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise OR of selected images with specified value, image or mathematical \ # expression, or compute the pointwise sequential bitwise OR of selected images. #@cli : (eq. to '|'). #@cli : $ image.jpg or 128 #@cli : $ image.jpg +mirror x or #@cli ^ : eq. to 'pow'. : (+) #@cli pow : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Raise selected images to the power of specified value, image or mathematical \ # expression, or compute the pointwise sequential powers of selected images. #@cli : (eq. to '^'). #@cli : $ image.jpg div 255 +pow 0.5 mul 255 #@cli : $ image.jpg gradient pow 2 add pow 0.2 #@cli rad2deg #@cli : Convert pointwise angle values of selected images, from radians to degrees (apply 'i*180/pi'). rad2deg : e[^-1] "Convert pointwise angle values of image$?, from radians to degrees." * 57.295779513082323 #@cli rol : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise left rotation of selected images with specified value, image or \ # mathematical expression, or compute the pointwise sequential bitwise left rotation of \ # selected images. #@cli : $ image.jpg rol 'round(3*x/w,0)' cut 0,255 #@cli ror : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise right rotation of selected images with specified value, image or \ # mathematical expression, or compute the pointwise sequential bitwise right rotation of \ # selected images. #@cli : $ image.jpg ror 'round(3*x/w,0)' cut 0,255 #@cli sign : (+) #@cli : Compute the pointwise sign of selected images. #@cli : $ image.jpg +sub {ia} sign[-1] #@cli : $ 300,1,1,1,'cos(20*x/w+u)' +sign display_graph 400,300 #@cli sin : (+) #@cli : Compute the pointwise sine of selected images. #@cli : $ image.jpg +normalize 0,{2*pi} sin[-1] #@cli : $ 300,1,1,1,'20*x/w+u' +sin display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli sinc : (+) #@cli : Compute the pointwise sinc function of selected images. #@cli : $ image.jpg +normalize {-2*pi},{2*pi} sinc[-1] #@cli : $ 300,1,1,1,'20*x/w+u' +sinc display_graph 400,300 #@cli sinh : (+) #@cli : Compute the pointwise hyperbolic sine of selected images. #@cli : $ image.jpg +normalize -3,3 sinh[-1] #@cli : $ 300,1,1,1,'4*x/w+u' +sinh display_graph 400,300 #@cli softmax : _temperature>0 #@cli : Compute the softmax of selected images. #@cli : Default value: 'temperature=1'. #@cli : $ 100,1,1,1,"x0" temperature=$1 else temperature=1 noarg fi onfail temperature=1 noarg } e[^-1] "Compute softmax of image$?, with temperature "$temperature"." foreach { eval "S = crop(); draw(softmax(S,"$temperature"),0,0,0,0,w,h,d,s)" } #@cli sqr : (+) #@cli : Compute the pointwise square function of selected images. #@cli : $ image.jpg +sqr #@cli : $ 300,1,1,1,'40*x/w+u' +sqr display_graph 400,300 #@cli sqrt : (+) #@cli : Compute the pointwise square root of selected images. #@cli : $ image.jpg +sqrt #@cli : $ 300,1,1,1,'40*x/w+u' +sqrt display_graph 400,300 #@cli - : eq. to 'sub'. : (+) #@cli sub : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Subtract specified value, image or mathematical expression to selected images, \ # or compute the pointwise difference of selected images. #@cli : (eq. to '-'). #@cli : $ image.jpg +sub 30% cut 0,255 #@cli : $ image.jpg +mirror x sub[-1] [0] #@cli : $ image.jpg sub 'i(w/2+0.9*(x-w/2),y)' #@cli : $ image.jpg +mirror x sub #@cli tan : (+) #@cli : Compute the pointwise tangent of selected images. #@cli : $ image.jpg +normalize {-0.47*pi},{0.47*pi} tan[-1] #@cli : $ 300,1,1,1,'20*x/w+u' +tan display_graph 400,300 #@cli : $$ https://gmic.eu/oldtutorial/trigometric-and-inverse-trigometric-commands #@cli tanh : (+) #@cli : Compute the pointwise hyperbolic tangent of selected images. #@cli : $ image.jpg +normalize -3,3 tanh[-1] #@cli : $ 300,1,1,1,'4*x/w+u' +tanh display_graph 400,300 #@cli xor : value[%] : [image] : 'formula' : (no arg) : (+) #@cli : Compute the bitwise XOR of selected images with specified value, image or mathematical \ # expression, or compute the pointwise sequential bitwise XOR of selected images. #@cli : $ image.jpg xor 128 #@cli : $ image.jpg +mirror x xor #--------------------------------- # #@cli :: Values Manipulation # #--------------------------------- #@cli apply_curve : 0<=smoothness<=1,x0,y0,x1,y1,x2,y2,...,xN,yN #@cli : Apply curve transformation to image values. #@cli : Default values: 'smoothness=1', 'x0=0', 'y0=100'. #@cli : $ image.jpg +apply_curve 1,0,0,128,255,255,0 apply_curve : check "${1=1}>=0 && $1<=1" skip ${2=0},${3=100} e[^-1] "Apply intensity curve with smoothness $1 and keypoints (${2--1}) on image$?." # Determine value mapping. (${^0,1}) r. 2,{w/2},1,1,-1 +z. 0,0 vm,vM:=[im,iM] n. 0,8191 j.. . function1d[] $1,{-2,^} rm[-3,-2] # Apply value mapping. -[^-1] $vm *[^-1] {8191/($vM-$vm)} map[^-1] .,1 rm. #@cli apply_gamma : gamma>=0 #@cli : Apply gamma correction to selected images. #@cli : $ image.jpg +apply_gamma 2 apply_gamma : check $1>=0 e[^-1] "Apply Gamma-correction to image$?, with gamma $1." if $1==1 return fi foreach { mM:=[im,iM] n 0,1 ^ {1/$1} n $mM } #@cli balance_gamma : _ref_color1,... #@cli : Compute gamma-corrected color balance of selected image, with respect to specified reference color. #@cli : Default value: 'ref_color1=128'. #@cli : $ image.jpg +balance_gamma 128,64,64 balance_gamma : check "isnum(${1=128})" e[^-1] "Apply gamma-corrected color balance of image$?, with reference color ("${^0}")." foreach { (${^0}) r. {-2,s},1,1,1,0,1 s.. c /. 255 repeat $!-1 { /[$>] 255 ^[$>] {log({@$>})/log({$>,ia})} *[$>] 255 } rm. a c c 0,255 } #@cli cast : datatype_source,datatype_target #@cli : Cast datatype of image buffer from specified source type to specified target type. #@cli : 'datatype_source' and 'datatype_target' can be \ # { uint8 | int8 | uint16 | int16 | uint32 | int32 | uint64 | int64 | float32 | float64 }. cast : e[^-1] "Cast datatype of image buffer$? from '$1' to '$2'." ssize:=s=['"$1"'];s=='uint8'||s=='int8'?1:s=='uint16'||s=='int16'?2:s=='uint32'||s=='int32'||s=='float32'?4:8 dsize:=s=['"$2"'];s=='uint8'||s=='int8'?1:s=='uint16'||s=='int16'?2:s=='uint32'||s=='int32'||s=='float32'?4:8 foreach { w,h,d,s={w},{h},{d},{s} serialize $1,0,0 s -,{'\n$w\ $h\ $d\ $s\n'} i[1] ('\n1\ {int($w*$h*$d*$s*$ssize/$dsize)}\ 1\ 1\n') y[1] replace_str[0] "$1","$2" a y unserialize } #@cli complex2polar #@cli : Compute complex to polar transforms of selected images. #@cli : $ image.jpg +fft complex2polar[-2,-1] log[-2] shift[-2] 50%,50%,0,0,2 remove[-1] complex2polar : e[^-1] "Compute complex to polar transforms of image$?." repeat int($!/2) { l[{2*$>},{2*$>+1}] { ri[1] [0],3 +atan2[1] [0] => {1,n} sqr[-3,-2] +[-3,-2] sqrt.. } } #@cli compress_clut #@cli : Compress selected color LUTs as sequences of colored keypoints. compress_clut : e[^-1] "Compress color LUT$? as sets of colored keypoints." foreach { nm={b} if iM>1024 / 257 fi # When input is 16bits rs3d 33 round if d==1 S:=round(cbrt(wh)) r $S,$S,$S,100%,-1 fi # When input is a 2D haldclut image e "* Process CLUT '"$nm"' ("{w}"x"{h}"x"{d}").\n" # Detect B&W cluts. if "s==3 && crop(0,0,0,0,w,h,d,1)==crop(0,0,0,1,w,h,d,1) && crop(0,0,0,0,w,h,d,1)==crop(0,0,0,2,w,h,d,1)" channels 0 fi # Compress CLUT: try first with RBFs. if s>1 +compress_to_keypoints. 3,640,0.45,2.5,"deltaE. [0],1" # Color CLUT else +compress_to_keypoints. 3,640,2,10 # B&W CLUT fi +decompress_from_keypoints. deltaE. [0] err_avg_rbf,err_max_rbf:=ia,iM rm. if $err_avg_rbf>0.45" || "$err_max_rbf>2.5 # RBFs failed, try with PDEs store. rbf_keypoints if s>1 +compress_to_keypoints 2,2048,0.45,2.5,"deltaE. [0],1" # Color CLUT else +compress_to_keypoints 2,2048,2,10 # B&W CLUT fi +decompress_from_keypoints. deltaE. [0] err_avg_pde,err_max_pde:=ia,iM rm. if $err_avg_rbf<$err_avg_pde rm. $rbf_keypoints fi fi k. # Remove header and format keypoints. f. "begin(WHD = [ i0,i1,i2 ]); y<2?I:(P = I; [ round(P[0,3]*256/WHD),P[3,size(P)-3] ])" # Renormalize is_rbf:=i(0,1,0,2) rows 2,100% 1,100%,1,1,"P = I(#-1); P[0]*65536 + P[1]*256 + P[2]" rv a x sort {`$is_rbf?_'+':_'-'`},y z. 1,100% strclut $nm => ${} } # compress_cluts : {"file_pattern" | "file_list.txt"} # Batch compress CLUT files. compress_cluts : rm if isfile(['{/"$1"}']) it[] "$1" s -,{'\n'} else files "$1" ('${}') s -,{','} fi foreach { filename={t} 0 => $filename ext={x} rm basename $filename l[] { ('${}') replace_str .$ext,"" basename={t} rm } need_compression=1 cclut=cclut_$basename.gmz if isfile(['{/$cclut}']) i $cclut if "dat = (date(5) + 60*(date(4) + date(2)*24)); fdat = (date(5,'"{/$cclut}"') + 60*(date(4,'"{/$cclut}"') + date(2,'"{/$cclut}"')*24)); !h && dat-fdat<30" e "* Skip file '"$filename"' (CLUT already being compressed)." need_compression=0 elif h>0" && "h!=2048 e "* Skip file '"$filename"' (CLUT already compressed)." need_compression=0 fi rm. fi if $need_compression 0 o. $cclut rm. # Lock current file if lowercase(['$ext'])=='cube' input_cube $filename c. 0,255 elif lowercase(['$ext'])=='png' i $filename S:=round(cbrt(wh)) r $S,$S,$S,100%,-1 else e "* Skip file '"$filename"' (unknown CLUT format)." continue fi e "* Compress file '"$filename"'." if w>33 rs3d 33 fi to_rgb tic compress_clut toc o $cclut fi rm } #@cli compress_huffman : [huffman_tree],_max_leaf_value #@cli : Compress selected images with Huffman coding. #@cli : See also: ''decompress_huffman'', ''huffman_tree''. compress_huffman : check ${"is_image_arg $1"} skip "${2=}" e[^-1] "Compress image$? with Huffman coding, using tree $1." pass$1 # Transform Huffman tree to codebook to speed up encoding. if ['$2']==0 # Retrieve max integer value of all tree leafs eval. ">begin(Mlv = -inf); i2<0 && i3<0?Mlv = max(Mlv,i0); end(set('Mlv',Mlv)); I" else Mlv=$2 fi 1,{$Mlv+1},1,2 # codebook -> [ code,nb_bits ] for each tree leaf eval.. ":i2<0?( # Leaf ind = y; code = nb_bits = 0; while (ind>=0, # Iteratively go up to root node par = i(0,ind,0,1); par>=0?((code<<=1)|=(i(0,par,0,2)!=ind); ++nb_bits); ind = par; ); ncode = 0; repeat (nb_bits,(ncode<<=1)|=(code&1); code>>=1); # Reverse bit order I[#-1,i0] = [ ncode,nb_bits ]; ); I" rm.. # Encode input data. foreach[^-1] { nm={n} pass. 2 0 eval... ">begin(da_push(w,h,d,s); out_stream = nb_bits = 0); C = I[#-2,int(i),1]; (out_stream<<=C[1])|=C[0]; nb_bits+=C[1]; while (nb_bits>=8, # Write completed bytes da_push(out_stream>>(nb_bits-=8)); out_stream%=1< $nm } rm. #@cli compress_rle : _is_binary_data={ 0 | 1 },_maximum_sequence_length>=0 #@cli : Compress selected images as 2xN data matrices, using RLE algorithm. #@cli : Set 'maximum_sequence_length=0' to disable maximum length constraint. #@cli : Default values: 'is_binary_data=0' and 'maximum_sequence_length=0'. #@cli : $ image.jpg rescale2d ,100 quantize 4 round +compress_rle , +decompress_rle[-1] compress_rle : check "isbool(${1=0}) && isint(${2=0},0)" s0=" for binary data" s1="" if $2 s=", with maximal sequence length "$2 else s="" fi e[^-1] "Compress image$? using RLE algorithm"${s{!$1}}$s"." foreach { nm={n} im:=im header:=w,h,d,s,$im,$1!=0 - $im y x ({{0,@-1}+1}) a x r 100%,3 f ">!y?i:y==1?(i(x,0)==i(x+1,0)?-1:x):(i(x-1,1)==-1?i(x-1,2)+1:1)" if $2 # Constrain maximum sequence length. transpose mirror x f. ">x==2?i:!x?(j(0,-1)==$2?1:i!=1?j(0,-1)+1:1):(i==-1&&j(-1)==$2?y:i)" mirror x transpose fi z 0,{w-2} s y,3 y[1] discard[1] -1 warp[0,2] [1],0,0 rm[1] if $1 # Encode for binary data. !=[0] 0 *[0] 2 -[0] 1 * else # Encode for arbitrary data. *. -1 rv a x y discard -1 f ">i(0,y-1)<0 && !i && i(0,y+1)<0?-1:i" discard -1 fi i[0] ($header:y) a y => $nm } #@cli cumulate : { x | y | z | c }...{ x | y | z | c } : (no arg) : (+) #@cli : Compute the cumulative function of specified image data, optionally along the specified axes. #@cli : $ image.jpg +histogram 256 +cumulate[-1] display_graph[-2,-1] 400,300,3 #@cli c : eq. to 'cut'. : (+) #@cli cut : { value0[%] | [image0] },{ value1[%] | [image1] } : [image] : (+) #@cli : Cut values of selected images in specified range. #@cli : (eq. to 'c').\n #@cli : $ image.jpg +add 30% cut[-1] 0,255 #@cli : $ image.jpg +cut 25%,75% #@cli decompress_clut : _width>0,_height>0,_depth>0 #@cli : Decompress selected colored keypoints into 3D CLUTs, using a mixed RBF/PDE approach. #@cli : Default values: 'width=height=depth=33' and 'reconstruction_colorspace=0'. decompress_clut : check "isint(${1=33},1) && isint(${2=$1},1) && isint(${3=$1},1)" e[^-1] "Decompress colored keypoint$? into $1x$2x$3 CLUTs." foreach { +norm r. 1,2,1,1,2 is_rbf:=i[0]0,_height>0,_depth>0 : (no arg) #@cli : Decompress selected sets of keypoints as images (opt. of specified size). #@cli : A set of keypoints is defined as a vector-valued image, such that: #@cli : - The first pixel is a vector which encodes the `[ Width,Height,Depth ]` of the decompressed image. #@cli : - The second pixel is a vector which encodes `[ Min,Max,Use_RBF ]`, where 'Min' and 'Max' defines the \ # value range of the decompressed image, and 'Use_RBF' tells is the decompression scheme must use \ # RBFs ('Use_RBF=1') or Multiscale Diffusion PDE's ('Use_RBF=0'). #@cli : - The remaining pixels define the keypoint coordinates and values, as: #@cli : - `[ x_k,y_k,z_k, v1_k,...,vN_k ]` for a 3D target image of N-valued vectors. #@cli : - `[ x_k,y_k, v1_k,...,vN_k ]` for a 2D target image of N-valued vectors. #@cli : - `[ x_k, v1_k,...,vN_k ]` for a 1D target image of N-valued vectors. #@cli : where the coordinates 'x_k', 'y_k' and 'z_k' are defined respectively in ranges \ # '[0,Width-1]', '[0,Height-1]' and '[0,Depth-1]'. #@cli : If the 'width', 'height' and 'depth' arguments are provided, they define the size of the decompressed image, \ # : overriding then the original image size `[ Width,Height,Depth ]` defined in the keypoints header. decompress_from_keypoints : skip "${1=},${2=},${3=}" # Parse arguments. l[] { is_args:="isint($1,1) && (['$2']==0 || isint($2,1)) && (['$3']==0 || isint($3,1))" check $is_args onfail noarg is_args=0 } if $is_args w0,h0,d0:="[ narg($1)?$1:0, narg($2)?$2:1, narg($3)?$3:1 ]" e[0--3] "Decompress keypoint set$?, with size ("$w0,$h0,$d0")." else e[0--3] "Decompress keypoint set$?." fi pass[$[]] -1 sel=${} # Decompress from keypoints. foreach { nm={n} # Get image information from first pixel. r 1,{whd},1,100%,-1 w,h,d,s,im,iM,use_rbf:="P = I[0]; V = I[1]; W = P[0]; H = P[1]; D = s>2?P[2]:1; S = s - (D>1?3:H>1?2:1); use_rbf = s>2?V[2]:1; [ W,H,D,S,V[0],V[1],use_rbf ]" if $w<=0" || "$h<0" || "$d<0" || "$s<0" || "!isbool($use_rbf) error[0--4] "Command 'decompress_keypoints': Image \#"{arg0($>,$sel)}" = ("{[w,h,d,s]}") "\ "is a not a valid keypoint set." fi # Reconstruct 1D/2D or 3D signal from keypoints. rows 2,100% if $d>1 # 3D keypoints if $use_rbf rbf {$is_args?[$w0,$h0,$d0]:[$w,$h,$d]},0,0,0,{[$w,$h,$d]-1} else {$is_args?[$w0,$h0,$d0]:[$w,$h,$d]},$s 100%,100%,100%,100%,1 eval... "P = I; "$is_args"?( P[0] = round(P[0]*($w0 - 1)/($w - 1)); P[1] = round(P[1]*($h0 - 1)/($h - 1)); P[2] = round(P[2]*($d0 - 1)/($d - 1))); I(#-2,P[0],P[1],P[2]) = P[3,size(P) - 3]; i(#-1,P[0],P[1],P[2]) = 0" inpaint_pde.. .,-8,1,16 rm[-3,-1] fi elif $h>1 # 2D keypoints if $use_rbf rbf {$is_args?[$w0,$h0]:[$w,$h]},0,0,{[$w,$h]-1} else $w,$h,1,$s 100%,100%,1,100%,1 eval... "P = I; "$is_args"?( P[0] = round(P[0]*($w0 - 1)/($w - 1)); P[1] = round(P[1]*($h0 - 1)/($h - 1))); I(#-2,P[0],P[1]) = P[2,size(P) - 2]; i(#-1,P[0],P[1]) = 0" inpaint_pde.. .,-8,1,16 rm[-3,-1] fi else # 1D keypoints rbf {$is_args?$w0:$w},0,{$w-1} fi c $im,$iM => $nm } #@cli decompress_huffman : [huffman_tree] #@cli : Decompress selected images with Huffman decoding. #@cli : See also: ''compress_huffman'', ''huffman_tree''. #@cli : $ image.jpg huffman_tree compress_huffman.. . +decompress_huffman.. . decompress_huffman : check ${"is_image_arg $1"} e[^-1] "Decompress image$? with Huffman decoding, using tree $1." pass$1 foreach[^-1] { nm={n} pass. 2 # Decode compressed data. 0 eval " hvalues = crop(#-2,0,0,0,0,1,h#-2,1,1); hparent = crop(#-2,0,0,0,1,1,h#-2,1,1); hchild0 = crop(#-2,0,0,0,2,1,h#-2,1,1); hchild1 = crop(#-2,0,0,0,3,1,h#-2,1,1); const root = size(hvalues) - 1; siz = prod(crop(#-3,0,0,1,4)); off = 4; mask = byte = bytes_written = 0; ind = root; while (bytes_written>=1; child = bit?hchild1[ind]:hchild0[ind]; val = hvalues[child]; val>=0?( # Leaf da_push(val); ind = root; ++bytes_written; ):(ind = child); ); da_freeze()" r. {"crop(#-3,0,0,1,4)"},-1 k. => $nm } rm. #@cli decompress_rle #@cli : Decompress selected data vectors, using RLE algorithm. decompress_rle : e[^-1] "Decompress data vector$?, using RLE algorithm." foreach { # Retrieve original data dimension and min value. y whds={0,@0-3} im={0,@4} is_binary_data={0,@5} rows 6,100% # Transform RLE data to list of pairs {nb_occurrences,value}. if $is_binary_data # Decode for binary data. +>= 0 abs[0] a x else # Decode for arbitrary data +<. 0 (0;1;1) erode.. .,0 rm. -. 1 a x discard -1 # Get back compressed '0' (minimum) values. +< 0 (0;1;1) dilate.. . rm. *. -2 +. 1 # Get back singletons. rv abs. a x discard -1 r 2,{h/2},1,1,-1 fi # Decompress, using 3D objects. s y,-256 foreach { i[0] ('CImg3d') +[0] 0.5 i[1] ({2*h};{h}) s. x,2 cumulate.. siz={-2,@-1} +shift.. 0,1 -... 1 rv[-3,-1] z[-3,-1] 0,2 a[-3,-1] x i[3] (2,0,1;2,{2*h-2},{2*h-1}) r[3] 3,{h},1,1,3 round[3] r[4] 3 1,100%,1,1,1 y a y $siz j3d. ..,0,0,0,1,2,0,0 rm.. } a x r $whds,-1 + $im } #@cli discard : _value1,_value2,... : { x | y | z | c}...{ x | y | z | c},_value1,_value2,... : (no arg) : (+) #@cli : Discard specified values in selected images or discard neighboring duplicate values, #@cli : optionally only for the values along the first of a specified axis. #@cli : If no arguments are specified, neighboring duplicate values are discarded. #@cli : If all pixels of a selected image are discarded, an empty image is returned. #@cli : $ (1;2;3;4;3;2;1) +discard 2 #@cli : $ (1,2,2,3,3,3,4,4,4,4) +discard x #@cli eigen2tensor #@cli : Recompose selected pairs of eigenvalues/eigenvectors as 2x2 or 3x3 tensor fields. #@cli : $$ eigen2tensor : e[^-1] "Recompose pairs in eigen field$? as 2x2 or 3x3 tensor fields." repeat $!/2 { l[$>,{$>+1}] { nm={0,n} if s==2 # 2D tensors. s. c +sqr. *.. ... sqr... # u^2 uv v^2 sh. +*... -1 sh[-5] # v^2 -uv u^2 a[-3--1] c a[-4--2] c sh... 0 *[-3,-1] # l1*(u^2;uv;v^2) sh... 1 *[-2,-1] # l2*(v^2;-uv;u^2) rm... +[-2,-1] elif s==6 # 3D tensors. s. c l[-6--4] { +sqr.. +*[-2,-3] +sqr... *[-5] [-6] *[-4] [-6] sqr[-6] a c } l[-3--1] { +sqr.. +*[-2,-3] +sqr... *[-5] [-6] *[-4] [-6] sqr[-6] a c } s... c -[-5] ... -[-4] ... *.. [-5] *. [-4] (1^0^0^1^0^1) ri. ... *. [-4] rm[-6--4] +[-3--1] else error[0--3] "Command '$0': Invalid image ["{$!-$>-1}"] : Dimensions "{w}","{h}","{d}","{s}" does not represent a field of 2D or 3D eigenvectors." fi => $nm } } #@cli endian : _datatype : (+) #@cli : Reverse data endianness of selected images, eventually considering the pixel being of the specified datatype. #@cli : 'datatype' can be \ # { bool | uint8 | int8 | uint16 | int16 | uint32 | int32 | uint64 | int64 | float32 | float64 }. #@cli : This command does nothing for 'bool', 'uint8' and 'int8' datatypes. #@cli equalize : _nb_levels>0[%],_value_min[%],_value_max[%] : (no arg) : (+) #@cli : Equalize histograms of selected images. #@cli : If value range is specified, the equalization is done only for pixels in the specified #@cli : value range. #@cli : Default values: 'nb_levels=256', 'value_min=0%' and 'value_max=100%'. #@cli : $ image.jpg +equalize #@cli : $ image.jpg +equalize 4,0,128 #@cli f : eq. to 'fill'. : (+) #@cli fill : value1,_value2,... : [image] : 'formula' : (+) #@cli : Fill selected images with values read from the specified value list, existing image #@cli : or mathematical expression. Single quotes may be omitted in 'formula'. #@cli : (eq. to 'f'). #@cli : $ 4,4 fill 1,2,3,4,5,6,7 #@cli : $ 4,4 (1,2,3,4,5,6,7) fill[-2] [-1] #@cli : $ 400,400,1,3 fill "X=x-w/2; Y=y-h/2; R=sqrt(X^2+Y^2); a=atan2(Y,X); \ # R<=180?255*abs(cos(c+200*(x/w-0.5)*(y/h-0.5))):850*(a%(0.1*(c+1)))" #@cli : $$ #@cli index : { [palette] | palette_name },0<=_dithering<=1,_map_colors={ 0 | 1 } : (+) #@cli : Index selected vector-valued images by specified vector-valued palette. #@cli : 'palette_name' can be { default | hsv | lines | hot | cool | jet | flag | cube | rainbow | algae | amp |\ # balance | curl | deep | delta | dense | diff | haline | ice | matter | oxy | phase | rain | solar | speed | tarn |\ # tempo | thermal | topo | turbid | aurora | hocuspocus | srb2 | uzebox } #@cli : Default values: 'dithering=0' and 'map_colors=0'. #@cli : $ image.jpg +index 1,1,1 #@cli : $ image.jpg (0;255;255^0;128;255^0;0;255) +index[-2] [-1],1,1 #@cli : $$ https://gmic.eu/tutorial/gindex index : check "${2=0}>=0 && $2<=1 && isbool(${3=0})" if ${"is_image_arg $1"} pass$1 e[0--3] "Index values in image$? by LUT $1, with dithering level $2." else names=${-_palette_names} N:=narg($names) l[] { if isint("$1") name=${"arg0 ($1%"$N"),"$names} else name="$1" fi onfail name="$1" } e[0--3] "Index values in image$? by LUT '"$name"', with dithering level $2." palette $1 fi index[^-1] .,$2,$3 rm. #@cli ir : eq. to 'inrange'. ir : check "isbool(${3=1}) && isbool(${4=$3})" _gmic_s="$?" v + _inrange $* #@cli inrange : min[%],max[%],_include_min_boundary={ 0:no | 1:yes },_include_max_boundary={ 0:no | 1:yes } #@cli : Detect pixels whose values are in specified range `[min,max]`, in selected images. #@cli : (eq. to 'ir'). #@cli : Default value: 'include_min_boundary=include_max_boundary=1'. #@cli : $ image.jpg +inrange 25%,75% inrange : check "isbool(${3=1}) && isbool(${4=$3})" _gmic_s="$?" v + _$0 $* _inrange : skip "${3=1},${4=$3}" v:=$3+2*$4 s=${"arg0 "$v",out,\" min\",\" max\",\" min and max\""} if $v%3 b="y" else b="ies" fi e[0--3] "Extract pixel values in range [$1,$2] in image"$_gmic_s", with"$s" boundar"$b" included." foreach { m=$1 M=$2 if ispercentage($1) m:=im+(iM-im)*$1 fi if ispercentage($2) M:=im+(iM-im)*$2 fi f. inrange(i,$m,$M,$3) } #@cli map : [palette],_boundary_conditions : palette_name,_boundary_conditions : (+) #@cli : Map specified vector-valued palette to selected indexed images. #@cli : Each output image has 'M*N' channels, where 'M' and 'N' are the numbers of channels of, respectively, \ # the corresponding input image and the 'palette' image. #@cli : 'palette_name' can be { default | hsv | lines | hot | cool | jet | flag | cube | rainbow | algae | amp | \ # balance | curl | deep | delta | dense | diff | gray | haline | ice | matter | oxy | phase | rain | solar | speed | \ # tarn | tempo | thermal | topo | turbid | aurora | hocuspocus | srb2 | uzebox } #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'boundary_conditions=0'. #@cli : $ image.jpg +luminance map[-1] 3 #@cli : $ image.jpg +rgb2ycbcr split[-1] c (0,255,0) resize[-1] 256,1,1,1,3 map[-4] [-1] remove[-1] append[-3--1] c \ # ycbcr2rgb[-1] #@cli : $$ map : check "isint(${2=0},0,3)" s0,s1,s2,s3=dirichlet,neumann,periodic,mirror boundary=${s$2} names=${-_palette_names} N:=narg($names) l[] { if isint("$1") name=${"arg0 ($1%"$N"),"$names} else name="$1" fi onfail name="$1" } e[^-1] "Map color LUT '"$name"' on image$?, with "$boundary" boundary conditions." palette $1 map[^-1] .,$2 rm. #@cli mix_channels : (a00,...,aMN) : [matrix] #@cli : Apply specified matrix to channels of selected images. #@cli : $ image.jpg +mix_channels (0,1,0;1,0,0;0,0,1) mix_channels : e[^-1] "Apply matrix $* to channels of image$?." if ${"is_image_arg $1"} pass$1 1 else i ${^0} fi foreach[^-1] { nm={n} whd:=[w,h,d] r. {[whd,s]},1,1,-1 pass. 0 mv. 0 m* r $whd,{h},-1 => $nm } rm. #@cli negate : base_value : (no arg) #@cli : Negate image values. #@cli : Default value: 'base_value=(undefined)'. #@cli : $ image.jpg +negate negate : skip "${1=,}" if isnum("$*") e[0--3] "Negate values of image$?, according to base value $*." - {"$*"} * -1 else e[0--3] "Negate values of image$?." repeat $! { -[$>] {$>,iM} } * -1 if ['"$1"']!=',' noarg fi fi #@cli noise_perlin : _scale_x[%]>0,_scale_y[%]>0,_scale_z[%]>0,_seed_x,_seed_y,_seed_z #@cli : Render 2D or 3D Perlin noise on selected images, from specified coordinates. #@cli : The Perlin noise is a specific type of smooth noise, #@cli : described here : . #@cli : Default values: 'scale_x=scale_y=scale_z=16' and 'seed_x=seed_y=seed_z=0'. #@cli : $ 500,500,1,3 noise_perlin , noise_perlin : check "${1=16}>0 && ${2=$1}>0 && ${3=$1}>0 && isnum(${4=0}) && isnum(${5=0}) && isnum(${6=0})" e[^-1] "Render Perlin noise on image$?, with scales (${1-3}) and seeds (${4-6})." init="permutation = [ 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240, 21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88, 237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231, 83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1, 216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198, 173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47, 16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167, 43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251, 34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31, 181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29, 24,72,243,141,128,195,78,66,215,61,156,180 ]; p = [ permutation,permutation ]; fade(t) = (t*t*t*(t*(t*6 - 15) + 10)); _lerp(t,a,b) = lerp(a,b,t); pcmod255 = vectors(); repeat (s,k,pcmod255[k] = p[k%255]); sx = ['$1']; is_px = sx[size(sx) - 1]==_'%'; sy = ['$2']; is_py = sy[size(sy) - 1]==_'%'; sz = ['$3']; is_pz = sz[size(sz) - 1]==_'%'; x0 = $4; y0 = $5; z0 = $6; x1 = x0 + (is_px?1/$1:w/$1); y1 = y0 + (is_py?1/$2:h/$2); z1 = z0 + (is_pz?1/$3:d/$3); fw = (x1 - x0)/max(w - 1,1); fh = (y1 - y0)/max(h - 1,1); fd = (z1 - z0)/max(d - 1,1);" repeat $! { if {$>,d>1} # 3D version f[$>] "*begin("$init" grad(hash,x,y,z) = ( gh = hash&15; gu = gh<8?x:y; gv = gh<4?y:gh==12 || gh==14?x:z; (!(gh&1)?gu:-gu) + (!(gh&2)?gv:-gv)) ); x = x0 + x*fw + pcmod255[c]; y = y0 + y*fh + pcmod255[c]; z = z0 + z*fd + pcmod255[c]; ix = floor(x); iy = floor(y); iz = floor(z); X = ix&255; Y = iy&255; Z = iz&255; fx = x - ix; fy = y - iy; fz = z - iz; u = fade(fx); v = fade(fy); w = fade(fz); A = p[X] + Y; AA = p[A] + Z; AB = p[A + 1] + Z; B = p[X + 1] + Y; BA = p[B] + Z; BB = p[B + 1] + Z; fx1 = fx - 1; fy1 = fy - 1; fz1 = fz - 1; lerp(lerp(lerp(grad(p[AA],fx,fy,fz), grad(p[BA],fx1,fy,fz),u), lerp(grad(p[AB],fx,fy1,fz), grad(p[BB],fx1,fy1,fz),u),v), lerp(lerp(grad(p[AA + 1],fx,fy,fz1), grad(p[BA + 1],fx1,fy,fz1,u), lerp(grad(p[AB + 1],fx,fy1,fz1), grad(p[BB+1],fx1,fy1,fz1),u),v),w)" else # 2D version f[$>] "*begin("$init" grad(hash,x,y) = ( gh = hash&15; gu = gh<8?x:y; gv = gh<4?y:gh==12 || gh==14?x:0; (!(gh&1)?gu:-gu) + (!(gh&2)?gv:-gv)) ); x = x0 + x*fw + pcmod255[c]; y = y0 + y*fh + pcmod255[c]; ix = floor(x); iy = floor(y); X = ix&255; Y = iy&255; fx = x - ix; fy = y - iy; u = fade(fx); v = fade(fy); A = p[X] + Y; B = p[X + 1] + Y; fx1 = fx - 1; fy1 = fy - 1; lerp(lerp(grad(p[A],fx,fy), grad(p[B],fx1,fy),u), lerp(grad(p[A + 1],fx,fy1), grad(p[B + 1],fx1,fy1),u),v)" fi } #@cli noise_poissondisk : _radius[%]>0,_max_sample_attempts>0,_p_norm>0 #@cli : Add poisson disk sampling noise to selected images. #@cli : Implements the algorithm from the article "Fast Poisson Disk Sampling in Arbitrary Dimensions", #@cli : by Robert Bridson (SIGGRAPH'2007). #@cli : Default values: 'radius=8', 'max_sample_attempts=30' and 'p_norm=2'. #@cli : $ 300,300 noise_poissondisk 8 noise_poissondisk : check "${1=8}>0 && isint(${2=30},1) && ${3=2}>0" e[^-1] "Add poisson disk sampling points to image$?, with radius $1, max sample attempts $2 and p-norm $3." R0,K,P=${1-3} foreach { W,H,D={w},{h},{d} value:=iM+(im==iM) R:=ispercentage($R0)?max(w,h,d)*$R0:$R0 C:=$R/sqrt(sum([$W,$H,$D]>1)) # Cell size # Determing optimal set of neighbors. _poissondisk[] $R,$P,$C f. "($W<2 && i0) || ($H<2 && i1) || ($D<2 && i2)?[8,8,8]:I" discard. 8 s. y,3 a[-3--1] x # Discard useless dims neighbors={^} rm. # Start point sampling. {ceil([$W,$H,$D]/$C)},1,-1 => grid # Grid used for quick testing neighboring points 1,32,1,3 => points # List of points 1,32,1,1 => active # List of active indices eval " insert_active_point(X) = ( i(#$grid,int(X/$C)) = new_ind = da_size(#$points); da_push(#$points,X); da_push(#$active,new_ind); I(#0,X) = $value; ); const eps = 1e-8; neighbors = ["$neighbors"]; # Initialize list of active points. X = int(u([$W,$H,$D] - eps)); insert_active_point(X); # Propagate new points. while (siz = da_size(#$active), indX = int(u(siz - eps)); X = I[#$points,i[#$active,indX]]; for (k = 0, k<$K, ++k, U = [ $W>1?u(-1,1):0, $H>1?u(-1,1):0, $D>1?u(-1,1):0 ]; U*=u($R,2*$R)/(norm(U) + eps); Y = round(X + U); min(inrange(Y,0,[$W,$H,$D] - 1))?( P = int(Y/$C); i(#$grid,P)<0?( is_sample_ok = 1; repeat(size(neighbors)/3,n, # Test neighboring points ind = i(#$grid,P + neighbors[3*n,3]); ind>=0?(nY = I[#$points,ind]; norm"$P"(nY - Y)<=$R?(is_sample_ok = 0; break())); # Invalid point ); is_sample_ok?(insert_active_point(Y); break()); # Add new point ); ); ); k>=$K?da_remove(#$active,indX); # Remove point from active list ); resize(#$points,1,da_size(#$points),1,3,0)" k[0] } # Determine optimal list of 3D neighbors to be tested. +_poissondisk : R,P,C=${1-3} 0 eval " dmin(dx,dy,dz,C) = ( _dx = dx*C; _dy = dy*C; _dz = dz*C; norm"$P"(min(abs(_dx),abs(_dx + C),abs(_dx - C)), min(abs(_dy),abs(_dy + C),abs(_dy - C)), min(abs(_dz),abs(_dz + C),abs(_dz - C))); ); for (n = 1, n<4, ++n, is_none_added = 1; for (k = -n, k<=n, ++k, for (j = -n, j<=n, ++j, for (i = -n, i<=n, ++i, abs(maxabs(i,j,k))==n && dmin(i,j,k,$C)<$R?(da_push([i,j,k]); is_none_added = 0) ) ) ); is_none_added?break(); ); resize(#-1,1,da_size(),1,3,0)" #@cli normp : p>=0 #@cli : Compute the pointwise Lp-norm norm of vector-valued pixels in selected images. #@cli : Default value: 'p=2'. #@cli : $ image.jpg +normp[0] 0 +normp[0] 1 +normp[0] 2 +normp[0] inf normp : check "isnum(${1==2}) && $1>=0" e[^-1] "Compute pointwise L"$1"-norm of vectors, in image$?." if !$1 != 0 compose_channels + elif $1==1 abs compose_channels + elif $1==2 norm elif $1==inf abs compose_channels max else ^ $1 compose_channels + ^ {1/$1} fi #@cli norm1 #@cli : Compute the pointwise L1-norm of vector-valued pixels in selected images. #@cli : $ image.jpg +norm1 #@cli : $$ norm1 : e[^-1] "Compute pointwise L1-norm of vectors, in image$?." abs compose_channels + #@cli norm : eq. to 'norm2'. norm : _gmic_s="$?" v + _norm2 #@cli norm2 #@cli : Compute the pointwise L2-norm (euclidean norm) of vector-valued pixels in selected images. #@cli : $ image.jpg +norm #@cli : $$ norm2 : _gmic_s="$?" v + _$0 _norm2 : e[^-1] "Compute pointwise euclidean norm of vectors, in image"$_gmic_s"." sqr compose_channels + sqrt #@cli n : eq. to 'normalize'. : (+) #@cli normalize : { value0[%] | [image0] },{ value1[%] | [image1] },_constant_case_ratio : [image] : (+) #@cli : Linearly normalize values of selected images in specified range. #@cli : (eq. to 'n'). #@cli : $ image.jpg split x,2 normalize[-1] 64,196 append x #@cli : $$ #@cli normalize_l2 #@cli : Normalize selected images such that they have a unit L2 norm. normalize_l2 : e[^-1] "Normalize image$?, s.t they have a unit L2 norm." repeat $! { /[$>] {norm={$>,in};norm!=0?norm:1} } #@cli normalize_sum #@cli : Normalize selected images such that they have a unit sum. #@cli : $ image.jpg +histogram 256 normalize_sum[-1] display_graph[-1] 400,300 normalize_sum : e[^-1] "Normalize image$?, s.t they have a unit sum." foreach { / {is!=0?is:1} } #@cli orientation #@cli : Compute the pointwise orientation of vector-valued pixels in selected images. #@cli : $ image.jpg +orientation +norm[-2] negate[-1] mul[-2] [-1] reverse[-2,-1] #@cli : $$ orientation : e[^-1] "Compute pointwise orientation vectors, in image$?." repeat $! { +norm[$>] replace. 0,1 /[$>,-1] } #@cli oneminus #@cli : For each selected image, compute one minus image. #@cli : $ image.jpg normalize 0,1 +oneminus oneminus : e[^-1] "Compute one minus selected images$?." * -1 + 1 #@cli otsu : _nb_levels>0 #@cli : Hard-threshold selected images using Otsu's method. #@cli : The computed thresholds are returned as a list of values in the status. #@cli : Default value: 'nb_levels=256'. #@cli : $ image.jpg luminance +otsu , otsu : check "isint(${1=256},1)" e[^-1] "Hard-threshold image$? using Otsu\47s method, with $1 histogram levels." foreach { imM:=[im,iM] +histogram $1,$imM otsu:=" sum = sumB = wB = best_variance = best_t = 0; repeat (w,t,sum+=t*i[t]); repeat (w,t, wB+=i[t]; if (!wB, continue()); wF = whds#-2 - wB; if (!wF, break()); sumB+=t*i[t]; mB = sumB/wB; mF = (sum - sumB)/wF; variance = wB*wF*(mB - mF)^2; if (variance>best_variance, best_variance = variance; best_t = t); ); imM = ["$imM"]; imM[0] + best_t*(imM[1] - imM[0])/(w - 1)" rm. >=. $otsu if $> u ${},$otsu else u $otsu fi } #@cli polar2complex #@cli : Compute polar to complex transforms of selected images. polar2complex : e[^-1] "Compute polar to complex transforms of image$?." repeat int($!/2) { l[{2*$>},{2*$>+1}] { ri[1] [0],3 +sin. cos.. *. ... *[-3,-2] } } #@cli quantize : nb_levels>=1,_keep_values={ 0 | 1 },_quantization_type={ -1:median-cut | 0:k-means | 1:uniform } #@cli : Quantize selected images. #@cli : Default value: 'keep_values=1' and 'quantization_type=0'. #@cli : $ image.jpg luminance +quantize 3 #@cli : $ 200,200,1,1,'cos(x/10)*sin(y/10)' +quantize[0] 6 +quantize[0] 4 +quantize[0] 3 +quantize[0] 2 quantize : check "isint($1,1) && isbool(${2=1}) && isint(${3=0},-1,1)" e[^-1] "Quantize image$? using $1 levels, "${arg0\ !$2,with,without}" keeping value range." foreach { if $3==1 # Uniform quantization. if s==1 # Greyscale image. if $2 mM:=[im,iM] n 0,$1 round 1,-1 min {$1-1} n $mM else n 0,$1 round 1,-1 min {$1-1} fi else mM:=[im,iM] uniform_distribution $1,{s} n. $mM index.. .,0,$2 rm. fi else +colormap $1,{!$3},1 index.. .,0,$2 rm. # Non-uniform quantization. fi } #@cli quantize_area : _min_area>0 #@cli : Quantize selected images such that each flat region has an area greater or equal to 'min_area'. #@cli : Default value: 'min_area=10'. #@cli : $ image.jpg quantize 3 +blur 1 round[-1] +quantize_area[-1] 2 quantize_area : check "${1=10}>0" e[^-1] "Quantize image$? by regions of areas greater than $1." if $1==1 return fi foreach { if s>1 +f. "begin(A = resize([ 0,(s-1)/s ],s,3));I+A" norm. round. 0.01 else [0] fi area. 0,0 <. $1 do [0] f.. "* begin( const boundary = 1; offx = [ -1,1,0,0,0,0 ]; offy = [ 0,0,-1,1,0,0 ]; offz = [ 0,0,0,0,-1,1 ]; nb_offs = d>1?6:h>1?4:2; ); is_neighbor = j(-1) && j(-1); if (h>1, is_neighbor&=j(1,0) && j(0,1)); if (d>1, is_neighbor&=j(0,0,-1) && j(0,0,1)); is_neighbor = !is_neighbor; i && is_neighbor?( col0 = I(#0); kmin = -1; distmin = inf; repeat (nb_offs,k, p = offx[k]; q = offy[k]; r = offz[k]; if (!j(p,q,r), col = J(#0,p,q,r); dist = norm(col-=col0); if (dist=0, I(#-1) = J(#0,offx[kmin],offy[kmin],offz[kmin]); 0, 1 ); ):i" rv[0,-1] rm. while iM rm. } #@cli rand : { value0[%] | [image0] },_{ value1[%] | [image1] },_[pdf],_precision : [image] : (+) #@cli : Fill selected images with random values in the specified range. #@cli : If no '[pdf]' (probability density function) is specified, random values follow a uniform distribution. #@cli : Argument 'precision' tells about the number of distinct values that can be generated when a '[pdf]' \ # is specified. #@cli : $ 400,400,1,3 rand -10,10 +blur 10 sign[-1] #@cli : $ (8,2,1) 50,50 rand[-1] 0,255,[-2] #@cli : $ 256 gaussian[-1] 30 line[-1] 47%,0,53%,0,1,0 500,500 rand[-1] 0,255,[-2] +histogram[-1] 256 \ # display_graph[0,2] 640,480,3,0 #@cli rand_sum : sum>0,_random_function #@cli : Fill selected images with strictly positive, random, integer values, that sums to 'sum'. #@cli : For each image, 'sum' must be greater or equal than 'width*height*depth*spectrum'. #@cli : Default value: 'random_function=u'. #@cli : $ 100 rand_sum 1000 rand_sum : check "isint($1,1) && isexpr(${3=u})" e[^-1] "Fill image$? with strictly positive, random, integer values that sums to $1, with random function '$3'." foreach { if whds>$1 error[0--4] "Command 'rand_sum': Image ["$>"] = ("{[w,h,d,s]}") has "{whds}" elements (should be <=$1)." fi f. "abs($3)" *. {($1-whds)/is} round. +. 1 eval " sum = is; while (sum!=$1, off = v(whds - 1); val = i[off]; delta = $1 - sum; dval = sign(delta)*v(abs(delta)); val + dval>0?(i[off]+=dval; sum+=dval); )" } #@cli replace : source,target #@cli : Replace pixel values in selected images. #@cli : $ (1;2;3;4) +replace 2,3 replace : e[^-1] "Replace pixel values $1 with $2 in image$?." f "i==$1?($2):i" #@cli replace_inf : _expression #@cli : Replace all infinite values in selected images by specified expression. #@cli : $ (0;1;2) log +replace_inf 2 replace_inf : e[^-1] "Replace all infinite values in image$? by expression '$1'." f "isinf(i)?($1):i" #@cli replace_infnan : _expression #@cli : Replace all NaN and infinite values in selected images by specified expression. replace_infnan : e[^-1] "Replace all +-inf and NaN values in images$? by expression '$1'." f "isinf(i) || isnan(i)?($1):i" #@cli replace_nan : _expression #@cli : Replace all NaN values in selected images by specified expression. #@cli : $ (-1;0;2) sqrt +replace_nan 2 replace_nan : e[^-1] "Replace all NaN values in images$? by expression '$1'." f "isnan(i)?($1):i" #@cli replace_seq : "search_seq","replace_seq" #@cli : Search and replace a sequence of values in selected images. #@cli : $ (1;2;3;4;5) +replace_seq "2,3,4","7,8" replace_seq : skip "${2=''}" e[^-1] "Replace value sequence '$1' by value sequence '${2--1}' in image$?." y foreach { nm={n} 1,100% eval "str1 = [ $1 ]; str2 = [ ${2--1} ]; copy_block(pd,src,len) = ( l = len; pd + l>=h#1?resize(#1,1,h(#1) + 3*l,1,1,0); copy(i[pd],src,l); pd+=l; ); for (ps = pd = 0, ps=0, qs>ps?copy_block(pd,i[#0,ps],qs - ps); copy_block(pd,str2,size(str2)); ps = qs + size(str1); ); ps $nm } #@cli replace_str : "search_str","replace_str" #@cli : Search and replace a string in selected images (viewed as strings, i.e. sequences of character codes). #@cli : $ ('"Hello there, how are you ?"') +replace_str "Hello there","Hi David" replace_str : skip "${2=}" e[^-1] "Replace string '"{/"$1"}"' by string '"{/"${2--1}"}"' in image$?." replace_seq {``{'"$1"'}},{'"${2--1}"'} #@cli round : rounding_value>=0,_rounding_type : (no arg) : (+) #@cli : Round values of selected images. #@cli : 'rounding_type' can be { -1:backward | 0:nearest | 1:forward }. #@cli : Default value: 'rounding_type=0'. #@cli : $ image.jpg +round 100 #@cli : $ image.jpg mul {pi/180} sin +round #@cli roundify : gamma>=0 #@cli : Apply roundify transformation on float-valued data, with specified gamma. #@cli : Default value: 'gamma=0'. #@cli : $ 1000 fill '4*x/w' repeat 5 { +roundify[0] {$>*0.2} } append c display_graph 400,300 roundify : check $1>=0 e[^-1] "Roundify image$?, with gamma $1." if $1==1 return fi foreach { +round 1 -.. . +*.. 2 abs. ^. $1 sign... *[-3,-1] *.. 0.5 + } #@cli = : eq. to 'set'. : (+) #@cli set : value,_x[%],_y[%],_z[%],_c[%] : (+) #@cli : Set pixel value in selected images, at specified coordinates. #@cli : (eq. to '=').\n #@cli : If specified coordinates are outside the image bounds, no action is performed. #@cli : Default values: 'x=y=z=c=0'. #@cli : $ 2,2 set 1,0,0 set 2,1,0 set 3,0,1 set 4,1,1 #@cli : $ image.jpg repeat 10000 { set 255,{u(100)}%,{u(100)}%,0,{u(100)}% } #@cli threshold : value[%],_is_soft={ 0 | 1 } : #@cli : Threshold values of selected images. #@cli : 'soft' can be { 0:hard-thresholding | 1:soft-thresholding }. #@cli : Default value: 'is_soft=0'. #@cli : $ image.jpg +threshold[0] 50% +threshold[0] 50%,1 #@cli : $$ threshold : check "isexpr($1) && isbool(${2=0})" e[^-1] ${"arg0 !$2,Soft,Hard"}"-threshold image$? by $1." if $2 # Soft thresholding f "begin( str = ['$1']; value = str[size(str)-1]==_'%'?im + (iM-im)*$1:$1 ); i>=value?i - value: i<=-value?i + value:0" else ge $1 fi #@cli vector2tensor #@cli : Convert selected vector fields to corresponding tensor fields. vector2tensor : e[^-1] "Convert vector field$? to tensor field$?." foreach { s c if $!==2 +sqr. *.. ... sqr... elif $!==3 +sqr.. +*... .. +sqr... *[-5,-4] [-6] sqr[-6] else error[0--4] "Command '$0': Invalid image ["{$!-$>-1}"] : Dimensions "{w}","{h}","{d}","{s}" does not represent a field of 2D or 3D vectors." fi a c } #--------------------------------- # #@cli :: Colors # #--------------------------------- #@cli adjust_colors : -100<=_brightness<=100,-100<=_contrast<=100,-100<=_gamma<=100,-100<=_hue_shift<=100,\ # -100<=_saturation<=100,_value_min,_value_max #@cli : Perform a global adjustment of colors on selected images. #@cli : Range of correct image values are considered to be in [value_min,value_max] (e.g. [0,255]). #@cli : If 'value_min==value_max==0', value range is estimated from min/max values of selected images. #@cli : Processed images have pixel values constrained in [value_min,value_max]. #@cli : Default values: 'brightness=0', 'contrast=0', 'gamma=0', 'hue_shift=0', 'saturation=0', 'value_min=value_max=0'. #@cli : $ image.jpg +adjust_colors 0,30,0,0,30 adjust_colors : check "${1=0}>=-100 && $1<=100 && ${2=0}>=-100 && $2<=100 && ${3=0}>=-100 && $3<=100 && ${4=0}>=-100 && $4<=100 && ${5=0}>=-100 && $5<=100" skip ${6=0},${7=0} e[^-1] "Adjust colors of image$?, with brightness $1, contrast $2, gamma $3, hue shift $4, saturation $5 and value range [$6,$7]." foreach { split_opacity l[0] { range:="$6==$7 && !$6?[im,iM]:[min($6,$7),max($6,$7)]" m:=arg(1,$range) M:=arg(2,$range) fact:=255/max(1e-5,$M-$m) - $m * $fact if $4" || "$5 # Adjust Hue/Saturation to_rgb[0] rgb2hsv[0] sh[0] 0 +. {$4*1.8} rm. sh[0] 1 +. {($5%)^(1+($5>0))} c. 0,1 rm. hsv2rgb[0] fi if $3 # Adjust Gamma /[0] 255 ^[0] {10^-($3%)} *[0] 255 fi if $2 # Adjust Contrast -[0] 128 *[0] {exp($2/64)} +[0] 128 fi +[0] {$1*2} # Adjust Brightness /[0] $fact +[0] $m c[0] $range # Renormalize values. a c } a c } #@cli ac : eq. to 'apply_channels'. ac : _gmic_s="$?" v + _apply_channels $"*" #@cli apply_channels : "command",color_channels,_value_action={ 0:none | 1:cut | 2:normalize } #@cli : Apply specified command on the chosen color channel(s) of each selected images. #@cli : (eq. to 'ac').\n #@cli : Argument 'color_channels' refers to a colorspace, and can be basically one of #@cli : { all | rgba | [s]rgb | ryb | lrgb | ycbcr | lab | lch | hsv | hsi | hsl | cmy | cmyk | yiq }. #@cli : You can also make the processing focus on a few particular channels of this colorspace, #@cli : by setting 'color_channels' as 'colorspace_channel' (e.g. 'hsv_h' for the hue). #@cli : All channel values are considered to be provided in the [0,255] range. #@cli : Default value: 'value_action=0'. #@cli : $ image.jpg +apply_channels "equalize blur 2",ycbcr_cbcr apply_channels : _gmic_s="$?" v + _$0 $"*" _apply_channels : check "isint(${3=0},0) && $3<=2" channels=${"_ac_list \"$2\""} e[^-1] "Apply command '$1' on channels '"$channels"' of image"$_gmic_s"." ('$/') id:="h=0;for(k=0,k0,0<=_dithering<=1,_method={ 0:median-cut | 1:k-means } #@cli : Index selected vector-valued images by adapted colormaps. #@cli : Default values: 'dithering=0' and 'method=1'. #@cli : $ image.jpg +autoindex[0] 4 +autoindex[0] 8 +autoindex[0] 16 autoindex : check "isint($1,1) && ${2=0}>=0" skip ${3=1} e[^-1] "Index colors in images$? by adapted colormap with $1 entries, dithering level $2 and "\ ${arg0\ !$3,k-means,median-cut}" method." foreach { if w>h if w>256 +rs 256 else [0] fi else if h>256 +rs ,256 else [0] fi fi colormap[1] $1,$3,0 index[0] [1],$2,1 rm[1] } #@cli bayer2rgb : _GM_smoothness,_RB_smoothness1,_RB_smoothness2 #@cli : Transform selected RGB-Bayer sampled images to color images. #@cli : Default values: 'GM_smoothness=RB_smoothness=1' and 'RB_smoothness2=0.5'. #@cli : $ image.jpg rgb2bayer 0 +bayer2rgb 1,1,0.5 bayer2rgb : skip ${1=1},${2=1},${3=0.5} e[^-1] "Transform RGB-Bayer image$? to color images, with smoothness ($1,$2,$3)." channels 0 foreach { # Expand image size to avoid problems with borders. expand xy,{"2 + 4*$1"} # Compute green-magenta chromaticity. (-1,1;1,-1) r. ..,..,1,1,0,2 +*.. . (0.25,0.5,0.25) convolve.. . transpose. convolve.. . rm. b. $1 *.. . -[-3,-2] # Compute red-blue chromaticity. (1,-1) r. ..,..,1,1,0,2 # Horizontal estimate *. ... (0.25,0.5,0.25) convolve.. . transpose. convolve.. . rm. b. y,$2 b. x,$3 (1;-1) r. ..,..,1,1,0,2 # Vertical estimate *. [-4] (0.25,0.5,0.25) convolve.. . transpose. convolve.. . rm. b. x,$2 b. y,$3 +[-2,-1] /. 2 # Luminance reconstruction. (2,0;0,-2) r. ..,..,1,1,0,2 *. .. -[-4,-1] # RGB reconstruction. a[-3--1] c mix_rgb. 1,-1,2,1,1,0,1,-1,-2 # Shrink to original image size. shrink xy,{"2 + 4*$1"} c 0,255 } #@cli clut : "clut_name",_resolution>0,_cut_and_round={ 0:no | 1:yes } #@cli : Insert one of the 1149 pre-defined CLUTs at the end of the image list.\n #@cli : 'clut_name' can be { 12_years_a_slave | 1917 | 2-strip-process | 60s | 60s_faded | 60s_faded_alt | 7drk_21 | \ # action_magenta_01 | action_red_01 | ad_astra | adventure_1453 | agfa_apx_100 | agfa_apx_25 | agfa_precisa_100 | \ # agfa_ultra_color_100 | agfa_vista_200 | agressive_highligjtes_recovery_5 | aladdin | alberto_street | alien_green | \ # ampio | amstragram | amstragram+ | analog_film_1 | analogfx_anno_1870_color | analogfx_old_style_i | \ # analogfx_old_style_ii | analogfx_old_style_iii | analogfx_sepia_color | analogfx_soft_sepia_i | \ # analogfx_soft_sepia_ii | anime | ant-man | apocalypse_this_very_moment | aqua | aqua_and_orange_dark | aquaman | \ # arabica_12 | asistas | atomic_pink | atusa | autumn | autumn_leaves | ava_614 | avalanche | avengers_endgame | \ # azrael_93 | baby_driver | bad_boys_for_life | basuco | bboyz_2 | bc_darkum | beach_aqua_orange | \ # beach_faded_analog | beati | beauty_and_the_beast | berlin_sky | bisogno | black_and_white | black_panther | \ # black_star | black_white_01 | black_white_02 | black_white_03 | black_white_04 | black_white_05 | black_white_06 | \ # blade_runner | bleach_bypass | bleachbypass_1 | bleachbypass_2 | bleachbypass_3 | bleachbypass_4 | \ # bleech_bypass_green | bleech_bypass_yellow_01 | blue_cold_fade | blue_dark | blue_house | blue_ice | blue_love_39 | \ # blue_mono | blue_shadows_01 | bluearchitecture | bluehour | blues | bob_ford | bohemian_rhapsody | bombshell | \ # bourbon_64 | boyado | bright_green_01 | bright_teal_orange | bright_warm | brightgreen | brown_mobster | brownbm | \ # brownish | bw_1 | bw_10 | bw_2 | bw_3 | bw_4 | bw_5 | bw_6 | bw_7 | bw_8 | bw_9 | bw_but_yellow | byers_11 | \ # calidum | candlelight | captain_marvel | caribe | chemical_168 | chrome_01 | cineblue | cinebm_4k | cinema | \ # cinema_2 | cinema_3 | cinema_4 | cinema_5 | cinema_noir | cinematic-1 | cinematic-10 | cinematic-2 | cinematic-3 | \ # cinematic-4 | cinematic-5 | cinematic-6 | cinematic-7 | cinematic-8 | cinematic-9 | cinematic_01 | cinematic_02 | \ # cinematic_03 | cinematic_04 | cinematic_05 | cinematic_06 | cinematic_07 | cinematic_for_flog | cinematic_forest | \ # cinematic_lady_bird | cinematic_mexico | city | city_7 | city_dust | city_of_god | classic_films_01 | \ # classic_films_02 | classic_films_03 | classic_films_04 | classic_films_05 | classic_teal_and_orange | clayton_33 | \ # clear | clear_teal_fade | clouseau_54 | cobi_3 | coffee_44 | cold_clear_blue | cold_clear_blue_1 | cold_ice | \ # cold_simplicity_2 | coldchrome | color_rich | colore | colorful_0209 | colornegative | conflict_01 | contrail_35 | \ # contrast_with_highlights_protection | contrasty_afternoon | contrasty_green | convold | cosa | creed_2 | \ # crispautumn | crispromance | crispwarm | crispwinter | cross_process_cp_130 | cross_process_cp_14 | \ # cross_process_cp_15 | cross_process_cp_16 | cross_process_cp_18 | cross_process_cp_3 | cross_process_cp_4 | \ # cross_process_cp_6 | crushin | cubicle_99 | culor | d_o_1 | dark_blues_in_sunlight | dark_green_02 | dark_green_1 | \ # dark_man_x | dark_orange_teal | dark_place_01 | darkandsomber | darkness | date_39 | day_4nite | day_for_night | \ # day_to_night_kings_blue | deep | deep_blue | deep_dark_warm | deep_high_contrast | deep_teal_fade | \ # deep_warm_fade | deepskintones_2 | deepskintones_3 | delicatessen | denoiser_simple_40 | desert_gold_37 | \ # dimension | dimmer | directions_23 | django_25 | doctor_strange | domingo_145 | dream_1 | dream_85 | \ # drop_green_tint_14 | dropblues | dunkirk | duotone_blue_red | earth_tone_boost | eda_0_2 | edgyember | \ # elegance_38 | enchanted | ensaya | eterna_for_flog | expired_69 | expired_fade | expired_polaroid | extreme | \ # fade | fade_to_green | faded | faded_47 | faded_alt | faded_analog | faded_extreme | faded_green | faded_pink-ish | \ # faded_print | faded_retro_01 | faded_retro_02 | faded_vivid | fadedlook | fallcolors | falua | farkling | fatos | \ # faux_infrared | faux_infrared_bw_1 | faux_infrared_color_p_2 | faux_infrared_color_p_3 | faux_infrared_color_r_0a | \ # faux_infrared_color_r_0b | faux_infrared_color_yp_1 | fezzle | fg_cinebasic | fg_cinebright | fg_cinecold | \ # fg_cinedrama | fg_cinetealorange_1 | fg_cinetealorange_2 | fg_cinevibrant | fg_cinewarm | fgcinebasic | \ # fgcinebright | fgcinecold | fgcinedrama | fgcinetealorange_1 | fgcinetealorange_2 | fgcinevibrant | fgcinewarm | \ # fight_club | film_0987 | film_9879 | film_gb-19 | film_high_contrast | film_print_01 | film_print_02 | filmic | \ # filo | flat_30 | flat_blue_moon | flavin | flog_to_rec_709 | foggynight | folger_50 | ford_v_ferrari | foresta | \ # formula_b | french_comedy | frosted | frostedbeachpicnic | fuji_160c | fuji_160c_+ | fuji_160c_++ | fuji_160c_- | \ # fuji_3510_constlclip | fuji_3510_constlmap | fuji_3510_cuspclip | fuji_3513_constlclip | fuji_3513_constlmap | \ # fuji_3513_cuspclip | fuji_400h | fuji_400h_+ | fuji_400h_++ | fuji_400h_- | fuji_800z | fuji_800z_+ | \ # fuji_800z_++ | fuji_800z_- | fuji_astia_100_generic | fuji_astia_100f | fuji_fp-100c | fuji_fp-100c_+ | \ # fuji_fp-100c_++ | fuji_fp-100c_+++ | fuji_fp-100c_++_alt | fuji_fp-100c_- | fuji_fp-100c_-- | fuji_fp-100c_alt | \ # fuji_fp-100c_cool | fuji_fp-100c_cool_+ | fuji_fp-100c_cool_++ | fuji_fp-100c_cool_- | fuji_fp-100c_cool_-- | \ # fuji_fp-100c_negative | fuji_fp-100c_negative_+ | fuji_fp-100c_negative_++ | fuji_fp-100c_negative_+++ | \ # fuji_fp-100c_negative_++_alt | fuji_fp-100c_negative_- | fuji_fp-100c_negative_-- | fuji_fp-3000b | \ # fuji_fp-3000b_+ | fuji_fp-3000b_++ | fuji_fp-3000b_+++ | fuji_fp-3000b_- | fuji_fp-3000b_-- | fuji_fp-3000b_hc | \ # fuji_fp-3000b_negative | fuji_fp-3000b_negative_+ | fuji_fp-3000b_negative_++ | fuji_fp-3000b_negative_+++ | \ # fuji_fp-3000b_negative_- | fuji_fp-3000b_negative_-- | fuji_fp-3000b_negative_early | fuji_fp_100c | fuji_hdr | \ # fuji_neopan_1600 | fuji_neopan_1600_+ | fuji_neopan_1600_++ | fuji_neopan_1600_- | fuji_neopan_acros_100 | \ # fuji_provia_100_generic | fuji_provia_100f | fuji_provia_400f | fuji_provia_400x | fuji_sensia_100 | \ # fuji_superia_100 | fuji_superia_100_+ | fuji_superia_100_++ | fuji_superia_100_- | fuji_superia_1600 | \ # fuji_superia_1600_+ | fuji_superia_1600_++ | fuji_superia_1600_- | fuji_superia_200 | fuji_superia_200_xpro | \ # fuji_superia_400 | fuji_superia_400_+ | fuji_superia_400_++ | fuji_superia_400_- | fuji_superia_800 | \ # fuji_superia_800_+ | fuji_superia_800_++ | fuji_superia_800_- | fuji_superia_hg_1600 | fuji_superia_reala_100 | \ # fuji_superia_x-tra_800 | fuji_velvia_100_generic | fuji_velvia_50 | fuji_xtrans_iii_acros | \ # fuji_xtrans_iii_acros+g | fuji_xtrans_iii_acros+r | fuji_xtrans_iii_acros+ye | fuji_xtrans_iii_astia | \ # fuji_xtrans_iii_classic_chrome | fuji_xtrans_iii_mono | fuji_xtrans_iii_mono+g | fuji_xtrans_iii_mono+r | \ # fuji_xtrans_iii_mono+ye | fuji_xtrans_iii_pro_neg_hi | fuji_xtrans_iii_pro_neg_std | fuji_xtrans_iii_provia | \ # fuji_xtrans_iii_sepia | fuji_xtrans_iii_velvia | fusion_88 | futuristicbleak_1 | futuristicbleak_2 | \ # futuristicbleak_3 | futuristicbleak_4 | going_for_a_walk | golden | golden_bright | golden_fade | golden_mono | \ # golden_night_softner_43 | golden_sony_37 | golden_vibrant | goldengate | goldentime | goldfx_bright_spring_breeze | \ # goldfx_bright_summer_heat | goldfx_hot_summer_heat | goldfx_perfect_sunset_01min | goldfx_perfect_sunset_05min | \ # goldfx_perfect_sunset_10min | goldfx_spring_breeze | goldfx_summer_heat | good_morning | green_15 | green_2025 | \ # green_action | green_afternoon | green_and_orange | green_blues | green_book | green_conflict | green_day_01 | \ # green_day_02 | green_g_09 | green_indoor | green_light | green_mono | green_yellow | greenish_contrasty | \ # greenish_fade | greenish_fade_1 | gremerta | greyhound | hackmanite | hallowen_dark | happyness_133 | \ # hard_teal_orange | hardboost | harsh_day | harsh_sunset | helios | herderite | heulandite | hiddenite | \ # highlights_protection | hilutite | hitman | hlg_1_1 | honey_light | hong_kong | horrorblue | howlite | huesio | \ # husmes | huyan | hydracore | hyla_68 | hypersthene | hypnosis | hypressen | i_tonya | ideo | ilford_delta_100 | \ # ilford_delta_3200 | ilford_delta_3200_+ | ilford_delta_3200_++ | ilford_delta_3200_- | ilford_delta_400 | \ # ilford_fp_4_plus_125 | ilford_hp_5 | ilford_hp_5_+ | ilford_hp_5_++ | ilford_hp_5_- | ilford_hp_5_plus_400 | \ # ilford_hps_800 | ilford_pan_f_plus_50 | ilford_xp_2 | inception | indoor_blue | industrial_33 | \ # infrared_-_dust_pink | instantc | j | jarklin | jojo_rabbit | joker | jumanji_the_next_level | \ # jurassic_world_fallen_kingdom | justice_league | justpeachy | jwick_21 | k_tone_vintage_kodachrome | kahve_3 | \ # kh_1 | kh_10 | kh_2 | kh_3 | kh_4 | kh_5 | kh_6 | kh_7 | kh_8 | kh_9 | killstreak | kingsman_the_golden_circle | \ # knives_out | kodak_2383_constlclip | kodak_2383_constlmap | kodak_2383_cuspclip | kodak_2393_constlclip | \ # kodak_2393_constlmap | kodak_2393_cuspclip | kodak_bw_400_cn | kodak_e-100_gx_ektachrome_100 | \ # kodak_ektachrome_100_vs | kodak_ektachrome_100_vs_generic | kodak_ektar_100 | kodak_elite_100_xpro | \ # kodak_elite_chrome_200 | kodak_elite_chrome_400 | kodak_elite_color_200 | kodak_elite_color_400 | \ # kodak_elite_extracolor_100 | kodak_hie_hs_infra | kodak_kodachrome_200 | kodak_kodachrome_25 | \ # kodak_kodachrome_64 | kodak_kodachrome_64_generic | kodak_portra_160 | kodak_portra_160_+ | kodak_portra_160_++ | \ # kodak_portra_160_- | kodak_portra_160_nc | kodak_portra_160_nc_+ | kodak_portra_160_nc_++ | kodak_portra_160_nc_- | \ # kodak_portra_160_vc | kodak_portra_160_vc_+ | kodak_portra_160_vc_++ | kodak_portra_160_vc_- | kodak_portra_400 | \ # kodak_portra_400_+ | kodak_portra_400_++ | kodak_portra_400_- | kodak_portra_400_nc | kodak_portra_400_nc_+ | \ # kodak_portra_400_nc_++ | kodak_portra_400_nc_- | kodak_portra_400_uc | kodak_portra_400_uc_+ | \ # kodak_portra_400_uc_++ | kodak_portra_400_uc_- | kodak_portra_400_vc | kodak_portra_400_vc_+ | \ # kodak_portra_400_vc_++ | kodak_portra_400_vc_- | kodak_portra_800 | kodak_portra_800_+ | kodak_portra_800_++ | \ # kodak_portra_800_- | kodak_portra_800_hc | kodak_t-max_100 | kodak_t-max_3200 | kodak_t-max_400 | kodak_tmax_3200 | \ # kodak_tmax_3200_+ | kodak_tmax_3200_++ | kodak_tmax_3200_- | kodak_tmax_3200_alt | kodak_tri-x_400 | \ # kodak_tri-x_400_+ | kodak_tri-x_400_++ | kodak_tri-x_400_- | kodak_tri-x_400_alt | korben_214 | la_la_land | \ # landscape | landscape_01 | landscape_02 | landscape_03 | landscape_04 | landscape_05 | landscape_1 | landscape_10 | \ # landscape_2 | landscape_3 | landscape_4 | landscape_5 | landscape_6 | landscape_7 | landscape_8 | landscape_9 | \ # lateafternoonwanderlust | latesunset | lavark | lc_1 | lc_10 | lc_2 | lc_3 | lc_4 | lc_5 | lc_6 | lc_7 | lc_8 | \ # lc_9 | lenox_340 | levex | life_giving_tree | light | light_blown | litore | little_women | logan | lomo | \ # lomography_redscale_100 | lomography_x-pro_slide_200 | london_nights | longbeachmorning | loro | lotta | louetta | \ # low_contrast_blue | low_key_01 | lucky_64 | lushgreen | lushgreensummer | mad_max_fury_road | maesky | \ # magenta_day | magenta_day_01 | magenta_dream | magenta_yellow | magentacoffee | magichour | marriage_story | \ # matrix | mckinnon_75 | memories | mercato | metropolis | milo_5 | minimalistcaffeination | modern_film | \ # modern_films_01 | modern_films_02 | modern_films_03 | modern_films_04 | modern_films_05 | modern_films_06 | \ # modern_films_07 | molti | mono_2 | mono_tinted | monochrome | monochrome_1 | monochrome_2 | moody_1 | moody_10 | \ # moody_2 | moody_3 | moody_4 | moody_5 | moody_6 | moody_7 | moody_8 | moody_9 | moonlight | moonlight_01 | \ # moonlight_2 | moonrise | morning_6 | morroco_16 | mostly_blue | mother! | motus | moviz_1 | moviz_10 | moviz_11 | \ # moviz_12 | moviz_13 | moviz_14 | moviz_15 | moviz_16 | moviz_17 | moviz_18 | moviz_19 | moviz_2 | moviz_20 | \ # moviz_21 | moviz_22 | moviz_23 | moviz_24 | moviz_25 | moviz_26 | moviz_27 | moviz_28 | moviz_29 | moviz_3 | \ # moviz_30 | moviz_31 | moviz_32 | moviz_33 | moviz_34 | moviz_35 | moviz_36 | moviz_37 | moviz_38 | moviz_39 | \ # moviz_4 | moviz_40 | moviz_41 | moviz_42 | moviz_43 | moviz_44 | moviz_45 | moviz_46 | moviz_47 | moviz_48 | \ # moviz_5 | moviz_6 | moviz_7 | moviz_8 | moviz_9 | mucca | mute_shift | muted_01 | muted_fade | mysticpurplesunset | \ # nah | natural_vivid | naturalboost | negative | nemesis | neon_770 | neutral | neutral_pump | neutral_teal_orange | \ # neutral_warm_fade | newspaper | night_01 | night_02 | night_03 | night_04 | night_05 | night_blade_4 | \ # night_king_141 | night_spy | night_view | nightfromday | nightlife | nigrum | no_time_to_die | nostalgiahoney | \ # nostalgic | nw-1 | nw-10 | nw-2 | nw-3 | nw-4 | nw-5 | nw-6 | nw-7 | nw-8 | nw-9 | old_west | once_upon_a_time | \ # once_upon_a_time_in_hollywood | onda | only_red | only_red_and_blue | operation_yellow | orange_dark_4 | \ # orange_dark_7 | orange_dark_look | orange_tone | orange_underexposed | orangeandblue | oranges | padre | paladin | \ # paladin_1875 | parasite | partia | pasadena_21 | passing_by | perso | picola | pink_fade | \ # pirates_of_the_caribbean | pitaya_15 | pmcinematic_01 | pmcinematic_02 | pmcinematic_03 | pmcinematic_04 | \ # pmcinematic_05 | pmcinematic_06 | pmcinematic_07 | pmnight_01 | pmnight_02 | pmnight_03 | pmnight_04 | pmnight_05 | \ # polaroid_664 | polaroid_665 | polaroid_665_+ | polaroid_665_++ | polaroid_665_- | polaroid_665_-- | \ # polaroid_665_negative | polaroid_665_negative_+ | polaroid_665_negative_- | polaroid_665_negative_hc | \ # polaroid_667 | polaroid_669 | polaroid_669_+ | polaroid_669_++ | polaroid_669_+++ | polaroid_669_- | \ # polaroid_669_-- | polaroid_669_cold | polaroid_669_cold_+ | polaroid_669_cold_- | polaroid_669_cold_-- | \ # polaroid_672 | polaroid_690 | polaroid_690_+ | polaroid_690_++ | polaroid_690_- | polaroid_690_-- | \ # polaroid_690_cold | polaroid_690_cold_+ | polaroid_690_cold_++ | polaroid_690_cold_- | polaroid_690_cold_-- | \ # polaroid_690_warm | polaroid_690_warm_+ | polaroid_690_warm_++ | polaroid_690_warm_- | polaroid_690_warm_-- | \ # polaroid_polachrome | polaroid_px-100uv+_cold | polaroid_px-100uv+_cold_+ | polaroid_px-100uv+_cold_++ | \ # polaroid_px-100uv+_cold_+++ | polaroid_px-100uv+_cold_- | polaroid_px-100uv+_cold_-- | polaroid_px-100uv+_warm | \ # polaroid_px-100uv+_warm_+ | polaroid_px-100uv+_warm_++ | polaroid_px-100uv+_warm_+++ | polaroid_px-100uv+_warm_- | \ # polaroid_px-100uv+_warm_-- | polaroid_px-680 | polaroid_px-680_+ | polaroid_px-680_++ | polaroid_px-680_- | \ # polaroid_px-680_-- | polaroid_px-680_cold | polaroid_px-680_cold_+ | polaroid_px-680_cold_++ | \ # polaroid_px-680_cold_++_alt | polaroid_px-680_cold_- | polaroid_px-680_cold_-- | polaroid_px-680_warm | \ # polaroid_px-680_warm_+ | polaroid_px-680_warm_++ | polaroid_px-680_warm_- | polaroid_px-680_warm_-- | \ # polaroid_px-70 | polaroid_px-70_+ | polaroid_px-70_++ | polaroid_px-70_+++ | polaroid_px-70_- | polaroid_px-70_-- | \ # polaroid_px-70_cold | polaroid_px-70_cold_+ | polaroid_px-70_cold_++ | polaroid_px-70_cold_- | \ # polaroid_px-70_cold_-- | polaroid_px-70_warm | polaroid_px-70_warm_+ | polaroid_px-70_warm_++ | \ # polaroid_px-70_warm_- | polaroid_px-70_warm_-- | polaroid_time_zero_expired | polaroid_time_zero_expired_+ | \ # polaroid_time_zero_expired_++ | polaroid_time_zero_expired_- | polaroid_time_zero_expired_-- | \ # polaroid_time_zero_expired_--- | polaroid_time_zero_expired_cold | polaroid_time_zero_expired_cold_- | \ # polaroid_time_zero_expired_cold_-- | polaroid_time_zero_expired_cold_--- | portrait | portrait_1 | portrait_10 | \ # portrait_2 | portrait_3 | portrait_4 | portrait_5 | portrait_6 | portrait_7 | portrait_8 | portrait_9 | \ # progressen | protect_highlights_01 | prussian_blue | pseudogrey | purple | purple_2 | quraqqq_12 | randas | \ # red_afternoon_01 | red_day_01 | red_dream_01 | redblueyellow | reds | reds_oranges_yellows | reeve_38 | remy_24 | \ # rest_33 | retro | retro_brown_01 | retro_magenta_01 | retro_summer_3 | retro_yellow_01 | rocketman | \ # rollei_ir_400 | rollei_ortho_25 | rollei_retro_100_tonal | rollei_retro_80s | rotate_muted | rotate_vibrant | \ # rotated | rotated_crush | satid | saturated_blue | saving_private_damon | scala | science_fiction | scrittle | \ # sea | seges | selor | sensum | separation | serenity | seringe_4 | serpent | seventies_magazine | sevsuz | \ # shade_kings_ink | shadow_king_39 | shine | sicario | sino | skin_tones | slog_to_rec709_basic | \ # slog_to_rec709_contrasty | slog_to_rec709_crush_shadows | slog_to_rec709_green_correction | smart_contrast | \ # smokey | smooth_clear | smooth_cromeish | smooth_fade | smooth_green_orange | smooth_sailing | smooth_teal_orange | \ # soft_fade | softblackandwhite | softwarming | solarized_color | solarized_color_2 | soldi | \ # spider-man_far_from_home | spotlight | springmorning | sprocket_231 | spy_29 | standard | \ # star_wars_the_rise_of_skywalker | strano | street | stringa | studio_skin_tone_shaper | subtle_blue | \ # subtle_green | subtle_yellow | sully | summer | summer_alt | sunlight_love_11 | sunlightlove | sunny | sunny_alt | \ # sunny_rich | sunny_warm | sunset | sunset_aqua_orange | sunset_intense_violet_blue | sunset_violet_mood | \ # super_warm | super_warm_rich | sutro_fx | sweet_bubblegum | sweet_gelatto | taşdemirrr_1 | taiga | tarraco | \ # teal-orange_for_flog | teal_fade | teal_moonlight | tealmagentagold | tealorange | tealorange_1 | tealorange_2 | \ # tealorange_3 | technicalfx_backlight_filter | teigen_28 | tenet | tensiongreen_1 | tensiongreen_2 | \ # tensiongreen_3 | tensiongreen_4 | terra_4 | the_dark_knight | the_darkest_hour | the_gentelmen | \ # the_grand_budapest_hotel | the_hurt_locker | the_irishman | the_lighthouse | the_lobster | the_martian | \ # the_matrices | the_revenant | the_shape_of_water | the_social_network | the_two_popes | the_way_back | \ # thor_ragnarok | thriller_2 | tirare | toastedgarden | top_gun_maverick | trent_18 | true_colors_8 | turkiest_42 | \ # tutto | tweed_71 | ultra_water | uncut_gems | undeniable | undeniable_2 | underwater | unknown | upglow | \ # urban_01 | urban_02 | urban_03 | urban_04 | urban_05 | urban_cowboy | uzbek_bukhara | uzbek_marriage | \ # uzbek_samarcande | valize | valsky | velvetia | venom | very_warm_greenish | vfb_21 | vibrant | vibrant_alien | \ # vibrant_contrast | vibrant_cromeish | victory | vintage | vintage_01 | vintage_02 | vintage_03 | vintage_04 | \ # vintage_05 | vintage_163 | vintage_alt | vintage_brighter | vintage_chrome | vintage_mob | vintage_warmth_1 | \ # violet_taste | vireo_37 | vita | vivid | vubes | war_for_the_planet_of_the_apes | warm | warm_dark_contrasty | \ # warm_fade | warm_fade_1 | warm_highlight | warm_neutral | warm_sunset_red | warm_teal | warm_vintage | \ # warm_yellow | wavefire | waves | well_see | western | western_6 | westernlut_2 | westernlut_2_13 | whiter_whites | \ # winterlighthouse | wipe | wolf_of_wall_street | wonder_woman | wooden_gold_20 | x-men_dark_phoenix | yangabuz_8 | \ # yellow_55b | yellow_film_01 | yellowstone | you_can_do_it | zed_32 | zeke_39 | zilverfx_bw_solarization | \ # zilverfx_infrared | zilverfx_vintage_bw | zombieland_double_tap } #@cli : Default values: 'resolution=33' and 'cut_and_round=1'. #@cli : $ clut summer clut alien_green,17 clut orange_dark4,48 +clut : check "isint(${2=33},1) && isbool(${3=1})" strclut "$1" name=${} l[] { e[^-1] "Input CLUT '"$name"' with resolution $2." path_clut=${-path_cache} if isfile(['{/${path_clut}clut_$name.cimgz}']) i ${path_clut}clut_$name.cimgz fi if $!"!=1 || w<$2 || h<$2 || d<$2" # Decompression needed rm input_cached gmic_cluts.gmz k[${"nmd 1,"$name}] if $!!=1 rm i https://gmic.eu/gmic_cluts.gmz o ${path_clut}gmic_cluts.gmz # Try getting newest version of the CLUTs file repeat $! { if ['{$>,n}']==['$name'] k[$>] break fi } if $!!=1 error[0--5] "Command '$0': Unknown CLUT name '"$name"'." fi fi decompress_clut $2,$2,$2 if $3 round c 0,255 to_rgb fi if $2>48 o. ${path_clut}clut_$name.cimgz fi elif "w>$2 || h>$2 || d>$2" r $2,$2,$2,3,2 # Downsize from higher resolution fi k. => $name } #@cli clut2hald #@cli : Convert selected 3D CLUTs to 2D HaldCLUTs. #@cli : $ clut summer +clut2hald clut2hald : e[^-1] "Convert 3D CLUT$? to 2D HaldCLUT format." foreach { n:=ceil(sqrt(max(w,h,d)))^2 r $n,$n,$n,100%,3 r {a=round(sqrt(w^3));[a,a]},1,100%,-1 } #@cli hald2clut #@cli : Convert selected 2D HaldCLUTs to 3D CLUTs. hald2clut : e[^-1] "Convert 2D HaldCLUT$? to 3D CLUT format." foreach { r {a=round(cbrt(whd));[a,a,a]},100%,-1 } #@cli cmy2rgb #@cli : Convert color representation of selected images from CMY to RGB. cmy2rgb : e[^-1] "Convert color representation of image$? from CMY to RGB." rgb2cmy #@cli cmyk2rgb #@cli : Convert color representation of selected images from CMYK to RGB. cmyk2rgb : e[^-1] "Convert color representation of image$? from CMYK to RGB." foreach { s c +/. -255 +. 1 *[0-2] . rm. +[0-2] . rm. a c cmy2rgb } #@cli colorblind : type={ 0:protanopia | 1:protanomaly | 2:deuteranopia | 3:deuteranomaly | \ # 4:tritanopia | 5:tritanomaly | 6:achromatopsia | 7:achromatomaly } #@cli : Simulate color blindness vision. #@cli : Simulation method of Vienot, Brettel & Mollon 1999, \ # "Digital video colourmaps for checking the legibility of displays by dichromats". #@cli : The dichromacy matrices of the paper were adapted to sRGB (RGB->XYZ). #@cli : Anomalous trichromacy simulated via linear interpolation with the identity and a factor of 0.6. #@cli : $ image.jpg +colorblind 0 colorblind : check "isint($1,0,7)" s0="protanopia" s1="protanomaly" s2="deuteranopia" s3="deuteranomaly" s4="tritanopia" s5="tritanomaly" s6="achromatopsia" s7="achromatomaly" e[^-1] "Simulate color blindness of type '"${s$1}"' on image$?." type0=(0.10889,0.89111,0;0.10889,0.89111,0;0.00447,-0.00447,1.0) type1=(0.46533,0.53467,0;0.06533,0.93467,0;0.00268,-0.00268,1) type2=(0.29031,0.70969,0;0.29031,0.70969,0;-0.02197,0.02197,1) type3=(0.57418,0.42582,0;0.17418,0.82582,0;-0.01318,0.01318,1) type4=(1,0.15236,-0.15236;0,0.86717,0.13283;0,0.86717,0.13283) type5=(1,0.09142,-0.09142;0,0.92030,0.07970;0,0.52030,0.47970) type6=(0.299,0.587,0.114;0.299,0.587,0.114;0.299,0.587,0.114) type7=(0.618,0.320,0.062;0.163,0.775,0.062;0.163,0.320,0.516) foreach { split_opacity l[0] { to_rgb srgb2rgb mix_channels ${type$1} rgb2srgb } a c } #@cli colormap : nb_levels>=0,_method={ 0:median-cut | 1:k-means },_sort_vectors #@cli : Estimate best-fitting colormap with 'nb_colors' entries, to index selected images. #@cli : Set 'nb_levels==0' to extract all existing colors of an image. #@cli : 'sort_vectors' can be { 0:unsorted | 1:by increasing norm | 2:by decreasing occurrence }. #@cli : Default value: 'method=1' and 'sort_vectors=1'. #@cli : $ image.jpg +colormap[0] 4 +colormap[0] 8 +colormap[0] 16 #@cli : $$ https://gmic.eu/oldtutorial/_colormap colormap : check "isint($1,0) && isbool(${2=1}) && isint(${3=1},0,2)" m0,m1="median-cut","k-means" s0,s1,s2="",", sorted by increasing norm",", sorted by decreasing occurence" if $1 e[0--3] "Estimate colormap with $1 entries for image$?, by "${m$2}" method"${s$3}"." else e[0--3] "Estimate full colormap for image$?"${s$3}"." fi foreach { nm={b} is_half=${-is_half} r {whd},1,1,100%,-1 if $1 # Estimate quantized colormap. if ${"count_colors. {$1+1}"}<=$1 +colormap 0,0,0 # Check that 'nb_levels' is lower than number of used colors else +_colormap $1 # Median-cut algorithm if $2 # Add k-means step max_diff:=(iM-im+1)/8192 do +index.. . # Find nearest cluster for each color if $is_half ..,1,1,{1,s} eval " csum = vector(#w*s#0); cocc = vector(#w,0); repeat (w#2,k, ind = i[#2,k]; repeat (s#0,c,csum[ind + c*w]+=i(#0,k,0,0,c)); ++cocc[ind]; ); off = 0; repeat (s#0,c, repeat (w,k,occ = cocc[k]; occ?(csum[off++]/=occ)); ); draw(#3,csum)" rm.. else ..,1,1,{1,s+1} f.. ">I[#3,i]+=[ I[#0,x],1 ]" rm.. f. "s = i(x,0,0,s-1); s?I/s:[ I[#1,x], 0 ]" fi +-.. . abs. diff:=iM/w rm. # Compute colormap difference j.. . rm. while $diff>$max_diff fi fi else # Extract full colormap. # Compute hashcode map. +n. 0,255 f. "ret = vectors(); H = 0; repeat (s,p,(H*=31)+=j(0,0,0,p)); ret[0] = H%2048; ret" channels. 0 equalize. 2048 n. 0,2047 round. 1 # Find single occurrence of each color/vector. 0x2048 eval[0] "> begin(sizes = vector2048(); ret = vectors()); col_s = I; H = i#1; indH = H + 2; sH = sizes[H]; !sH || find(#indH,col_s,s#0*(sH - 1),-s#0)<0?( # Color not found sH>=h(#indH)?resize(#indH,s#0,max(8,2*h#indH),1,1,0); copy(i(#indH,0,sH,0,0),col_s); ++sizes[H]; ); ret; end(fill(sizes,k,resize(#k + 2,s#0,sizes[k],1,1,0)))" a[2--1] y permute. yzcx rm.. fi # Sort colormap if requested. if $3==1 k. +norm rv a c sort +,x channels 1,100% # Sort by increasing norm. elif $3==2 index.. .,0,0 histogram.. {[w,0,w-1]} a y sort -,x rows 1 # Sort by decreasing occurrence else k. fi => "[colormap of "$nm"]" } _colormap : # Implementation of the median-cut algorithm. m "__colormap : repeat s { sh[$""1] $> =.. {iM-im},$""1,0,0,$> rm. }" 1,1,1,{s} __colormap 0 # Initialize image of box ranges repeat $1-1 { b,a:=xM,cM # b = box with highest range, a = axis with highest range l[$b] { shift 0,0,0,-$a,2 sort +,x shift 0,0,0,$a,2 xm:=int(w/2) } if {$b,w>1} +z[$b] $xm,100% z[$b] 0,{$xm-1} else 1,1,1,{$b,s} fi rv[-2,-1] r. {w+1},1,1,100%,0 __colormap $b __colormap {$!-2} # Update box ranges } rm. r 1,1,1,100%,2 a x # Average value in each box and append as final colormap um __colormap #@cli compose_channels #@cli : Compose all channels of each selected image, using specified arithmetic operator (+,-,or,min,...). #@cli : Default value: '1=+'. #@cli : $ image.jpg +compose_channels and #@cli : $$ compose_channels : skip ${1="+"} e[^-1] "Compose all channels of image$?, with operator '$1'." foreach { repeat s { sh[0] $> } $1[^0] r[0] 100%,100%,100%,1,-1 k[0] } #@cli count_colors : _count_until={ 0: none | >0: max number of counted colors } #@cli : Count number of distinct colors in selected images until it reaches the specified max number of counted colors. #@cli : Set 'count_until' to '0' to disable limit on counted colors. #@cli : This command returns the number of distinct colors for each image (separated by commas). count_colors : check "isint(${1=0},0)" if $1 e[0--3] "Count number of distinct colors in image$?, until it reaches $1." else e[0--3] "Count number of distinct colors in image$?." fi res,sep= foreach { # Compute hashcode map. +n. 0,255 f. "ret = vectors(); H = 0; repeat (s,p,(H*=31)+=j(0,0,0,p)); ret[0] = H%2048; ret" channels. 0 equalize. 2048 n. 0,2047 round. 1 # Find single occurrence of each color/vector. 0x2048 eval[0] "> const max_nb = $1; begin(sizes = vector2048(); nb = 0; ret = vectors()); !max_nb || nb=h(#indH)?resize(#indH,s#0,max(8,2*h#indH),1,1,0); copy(i(#indH,0,sH,0,0),col_s); ++sizes[H]; ++nb; ); ); ret; end(set('nb',sum(sizes)))" k[0] res.=$sep$nb sep=, } u $res #@cli deltaE : [image],_metric={ 0:deltaE_1976 | 1:deltaE_2000 },"_to_Lab_command" #@cli : Compute the CIE DeltaE color difference between selected images and specified [image]. #@cli : Argument 'to_Lab_command' is a command able to convert colors of [image] into a Lab representation. #@cli : Default values: 'metric=1' and 'to_Lab_command="srgb2lab"'. #@cli : $ image.jpg +blur 2 +deltaE[0] [1],1,srgb2lab deltaE : check ${"is_image_arg $1"}" && isbool(${2=1})" skip "${3=srgb2lab}" e[^-1] "Compute the CIE DeltaE_"${"s0,s1=1976,2000 u $s$2"}" color difference between image$? and image$1, "\ "with to_Lab command '$3'." pass$1 1 needs_to_lab:="s = ['$3']; s!=0 && s!=' '" if $needs_to_lab m "_deltaE_to_lab : $3" +_deltaE_to_lab. rm.. fi repeat $!-1 { l[$>,-1] { nm={0,n} if $needs_to_lab _deltaE_to_lab[0] fi if !$2 -.. . norm.. # DeltaE_1976 else 100%,100%,100%,1,${-math_lib}"deltaE00(I#0,I#1)" rv[0,-1] rm. # DeltaE_2000 fi =>[0] $nm } } rm. um _deltaE_to_lab #@cli direction2rgb #@cli : Compute RGB representation of selected 2D direction fields. #@cli : $ image.jpg luminance gradient append c blur 2 orientation +direction2rgb direction2rgb : e[^-1] "Compute RGB representation of 2D direction field$?." channels 0,1 foreach { nm={n} s c complex2polar round.. 0.001 *. {180/pi} %. 360 100%,100%,1,1,1 mv... $! if im!=iM n. 0,1 else f. 1 fi a c hsv2rgb => $nm } #@cli ditheredbw #@cli : Create dithered B&W version of selected images. #@cli : $ image.jpg +equalize ditheredbw[-1] ditheredbw : e[^-1] "Create dithered B&W version of image$?." foreach { split_opacity luminance[0] n[0] 0,255 (0,255) index[0] .,1,1 rm. a c } #@cli fc : eq. to 'fill_color'. fc : _gmic_s="$?" v + _fill_color $* #@cli fill_color : col1,...,colN #@cli : Fill selected images with specified color. #@cli : (eq. to 'fc'). #@cli : $ image.jpg +fill_color 255,0,255 #@cli : $$ https://gmic.eu/oldtutorial/_fill_color fill_color : _gmic_s="$?" v + _$0 $* _fill_color : e[0--3] "Fill image"$_gmic_s" with color (${^0})." foreach { repeat s { sh[0] $> f. {arg(1+$>,${^0})} } k[0] } #@cli gradient2rgb : _is_orientation={ 0 | 1 } #@cli : Compute RGB representation of 2D gradient of selected images. #@cli : Default value: 'is_orientation=0'. #@cli : $ image.jpg +gradient2rgb 0 equalize[-1] gradient2rgb : check "isbool(${1=0})" arg0 !$1,"orientation ","" e[^-1] "Compute RGB representation of 2D gradient "${}"of image$?." norm foreach { if $1 gradient_orientation 2 else g xy fi a c direction2rgb } #@cli hcy2rgb #@cli : Convert color representation of selected images from HCY to RGB. hcy2rgb : e[^-1] "Convert color representation of image$? from HCY to RGB." to_color f " H = (R/60)%6; X = G*(1 - abs(H%2 - 1)); RGB = arg0(int(H),[G,X,0],[X,G,0],[0,G,X],[0,X,G],[X,0,G],[G,0,X]); m = B - 0.3*RGB[0] - 0.59*RGB[1] - 0.11*RGB[2]; cut((RGB+=m)*=255,0,255)" #@cli hsi2rgb #@cli : Convert color representation of selected images from HSI to RGB. hsi2rgb : e[^-1] "Convert color representation of image$? from HSI to RGB." to_color f " H = (R/60)%6; S = G; I = B; Z = 1 - abs((H%2) - 1); C = I*S/(1 + Z); X = C*Z; m = I*(1 - S)/3; RGB = arg0(int(H),[C,X,0],[X,C,0],[0,C,X],[0,X,C],[X,0,C],[C,0,X]); (RGB+=m)*=3*255" #@cli hsi82rgb #@cli : Convert color representation of selected images from HSI8 to RGB. hsi82rgb : e[^-1] "Convert color representation of image$? from HSI8 to RGB." _hsx82rgb hsi2rgb #@cli hsl2rgb #@cli : Convert color representation of selected images from HSL to RGB. hsl2rgb : e[^-1] "Convert color representation of image$? from HSL to RGB." to_color f " H = (R/60)%6; S = G; L = B; C = (1 - abs(2*L - 1))*S; X = C*(1 - abs(H%2 - 1)); m = L - C/2; RGB = arg0(int(H),[C,X,0],[X,C,0],[0,C,X],[0,X,C],[X,0,C],[C,0,X]); (RGB+=m)*=255" #@cli hsl82rgb #@cli : Convert color representation of selected images from HSL8 to RGB. hsl82rgb : e[^-1] "Convert color representation of image$? from HSL8 to RGB." _hsx82rgb hsl2rgb #@cli hsv2rgb #@cli : Convert color representation of selected images from HSV to RGB. #@cli : $ (0,360;0,360^0,0;1,1^1,1;1,1) resize 400,400,1,3,3 hsv2rgb hsv2rgb : e[^-1] "Convert color representation of image$? from HSV to RGB." to_color f " H = (R/60)%6; S = G; V = B; C = V*S; X = C*(1 - abs(H%2 - 1)); m = V - C; RGB = arg0(int(H),[C,X,0],[X,C,0],[0,C,X],[0,X,C],[X,0,C],[C,0,X]); (RGB+=m)*=255" #@cli hsv82rgb #@cli : Convert color representation of selected images from HSV8 to RGB. hsv82rgb : e[^-1] "Convert color representation of image$? from HSV8 to RGB." _hsx82rgb hsv2rgb _hsx82rgb : foreach { sh[0] 0 /. 0.708333 rm. sh[0] 1,2 /. 255 rm. } #@cli int2rgb #@cli : Convert color representation of selected images from INT24 to RGB. int2rgb : e[^-1] "Convert color representation of image$? from INT24 scalars to RGB." round foreach { +>> 8 &[1] 255 +&[0] 255 >>[0] 16 a c } #@cli ipremula #@cli : Convert selected images with premultiplied alpha colors to normal colors. #@cli : See also: ''premula''. ipremula : e[^-1] "Convert image$? with premultiplied alpha colors to normal colors." to_a foreach { +channels. 100% /. 255 max. 1e-8 sh.. 0,{s#0-2} /. .. k[0] } #@cli jzazbz2rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Jzazbz. #@cli : Default value: 'illuminant=2'. jzazbz2rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Jzazbz to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." jzazbz2xyz xyz2rgb $illu #@cli jzazbz2xyz #@cli : Convert color representation of selected images from RGB to XYZ. jzazbz2xyz : e[^-1] "Convert color representation of image$? from Jzazbz to XYZ." foreach { split_opacity f[0] ${-_jzazbz_const}" tmp = i0 + Jzazbz_d0; Iz = tmp/(1 + Jzazbz_d - Jzazbz_d*tmp); azz = i1; bzz = i2; Lp = Iz + 0.138605043271539*azz + 0.0580473161561189*bzz; Mp = Iz - 0.138605043271539*azz - 0.0580473161561189*bzz; Sp = Iz - 0.0960192420263189*azz - 0.811891896056039*bzz; tmp = Lp^(1/Jzazbz_p); L = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n); tmp = Mp^(1/Jzazbz_p); M = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n); tmp = Sp^(1/Jzazbz_p); S = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n); Xp = 1.92422643578761*L - 1.00479231259537*M + 0.037651404030618*S; Yp = 0.350316762094999*L + 0.726481193931655*M - 0.065384422948085*S; Zp = -0.0909828109828476*L - 0.312728290523074*M + 1.52276656130526*S; X = (Xp + (Jzazbz_b - 1)*Zp)/Jzazbz_b; Y = (Yp + (Jzazbz_g - 1)*X)/Jzazbz_g; Z = Zp; [ X,Y,Z ]/255" a c } # The XYZ<->Jzazbz conversion code has been written by Alan Gibson, # and published on this page: _jzazbz_const : u "const Jzazbz_b = 1.15; const Jzazbz_g = 0.66; const Jzazbz_c1 = 3424/4096; const Jzazbz_c2 = 2413/128; const Jzazbz_c3 = 2392/128; const Jzazbz_n = 2610/16384; const Jzazbz_p = 1.7*2523/32; const Jzazbz_d = -0.56; const Jzazbz_d0 = 1.6295499532821566e-11; const peakLum = 10000;" #@cli lab2lch #@cli : Convert color representation of selected images from Lab to Lch. lab2lch : e[^-1] "Convert color representation of image$? from Lab to Lch." foreach { to_color s c complex2polar[1,2] a c } #@cli lab2rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lab to RGB. #@cli : Default value: 'illuminant=2'. #@cli : $ (50,50;50,50^-3,3;-3,3^-3,-3;3,3) resize 400,400,1,3,3 lab2rgb lab2rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lab to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." lab2xyz $illu xyz2rgb $illu #@cli lab2srgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lab to sRGB. #@cli : Default value: 'illuminant=2'. #@cli : $ (50,50;50,50^-3,3;-3,3^-3,-3;3,3) resize 400,400,1,3,3 lab2rgb lab2srgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lab to sRGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." lab2rgb $illu rgb2srgb #@cli lab82srgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lab8 to sRGB. #@cli : Default value: 'illuminant=2'. #@cli : $ (50,50;50,50^-3,3;-3,3^-3,-3;3,3) resize 400,400,1,3,3 lab2rgb lab82srgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lab8 to sRGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." lab82rgb $illu rgb2srgb #@cli lab2xyz : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lab to XYZ. #@cli : Default value: 'illuminant=2'. lab2xyz : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lab to XYZ, using the "${arg0\ $illu,D50,D65,E}" illuminant." to_color f " begin( const epsilon = 216/24389; const kappa = 24389/27; D65 = [ 0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041 ]; D50 = [ 0.43603516, 0.38511658, 0.14305115, 0.22248840, 0.71690369, 0.06060791, 0.01391602, 0.09706116, 0.71392822 ]; E = [ 0.488718,0.3106803,0.2006017, 0.1762044,0.8129847,0.0108109, 0,0.0102048,0.9897952 ]; white = ("$illu"==2?E:"$illu"==1?D65:D50)*[ 1,1,1 ]; ); fy = (i0 + 16)/116; fz = fy - i2/200; fx = i1/500 + fy; fx3 = fx^3; fz3 = fz^3; XYZ = [ fx3>epsilon?fx3:(116*fx - 16)/kappa, i0>kappa*epsilon?((i0+16)/116)^3:i0/kappa, fz3>epsilon?fz3:(116*fz - 16)/kappa ]; XYZ*=white" #@cli lab82rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lab8 to RGB. #@cli : Default value: 'illuminant=2'. lab82rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lab8 to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." foreach { sh[0] 0 /. 2.55 rm. sh[0] 1 /. 0.85 -. 127 rm. sh[0] 2 /. 0.836 -. 149 rm. } lab2rgb $illu c 0,255 #@cli lch2lab #@cli : Convert color representation of selected images from Lch to Lab. lch2lab : e[^-1] "Convert color representation of image$? from Lch to Lab." foreach { to_color s c polar2complex[1,2] a c } #@cli lch2rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lch to RGB. #@cli : Default value: 'illuminant=2'. lch2rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lch to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." lch2lab lab2rgb $illu #@cli lch82rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from Lch8 to RGB. #@cli : Default value: 'illuminant=2'. lch82rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from Lch8 to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." pi,facth:=[pi,255/(2*pi)] foreach { sh[0] 0 /. 2.55 rm. sh[0] 1 /. 1.1086 rm. sh[0] 2 /. $facth -. $pi rm. } lch2rgb $illu c 0,255 #@cli luminance #@cli : Compute luminance of selected sRGB images. #@cli : $ image.jpg +luminance #@cli : $$ luminance : e[^-1] "Compute luminance of image$?." remove_opacity srgb2rgb foreach { if s==3 sh 0 sh[0] 1 sh[0] 2 *[1] 0.22248840 *[2] 0.71690369 *[3] 0.06060791 +[1-3] rm[1] elif s!=1 norm n 0,255 fi } channels 0 rgb2srgb #@cli lightness #@cli : Compute lightness of selected sRGB images. #@cli : $ image.jpg +lightness lightness : e[^-1] "Compute lightness of image$?." remove_opacity srgb2rgb if s==3 srgb2lab channels 0 * {255/100} elif s!=1 norm n 0,255 rgb2srgb fi #@cli lut_contrast : _nb_colors>1,_min_rgb_value #@cli : Generate a RGB colormap where consecutive colors have high contrast. #@cli : This function performs a specific score maximization to generate the result, so #@cli : it may take some time when 'nb_colors' is high. #@cli : Default values: 'nb_colors=256' and 'min_rgb_value=64'. +lut_contrast : check "isint(${1=256},1) && isnum(${2=48})" e[^-1] "Generate high-contrast RGB colormap with $1 colors and min RGB value $2." l[] { # Initialization by farthest point sampling of the RGB cube. 64,64,64,1 eval "repeat (8,k, x = !!(k&1); y = !!(k&2); z = !!(k&4); i([x,y,z]*(w-1)) = 1)" N:=is e "" do +neq. 0 distance. 1 xyzM:=[xM,yM,zM] rm. col:=round([$xyzM]*255/(w-1)) if max($col)>=$2 =. 1,$xyzM N+=1 else =. -1,$xyzM fi e "\r [ Init ] > Colors \#"$N while $N<$1 >. 0 {is},1,1,3 eval.. ">begin(k = 0); i>0?(I[#-1,k++] = round([ x,y,z ]*255/63))" k. N0=5 # 5 first colors to be preserved. s x repeat $! { if {$>,I==[0,0,0]} rv[$>,0] fi if {$>,I==[255,255,255]} rv[$>,1] fi if {$>,I==[255,0,0]} rv[$>,2] fi if {$>,I==[0,255,0]} rv[$>,3] fi if {$>,I==[0,0,255]} rv[$>,4] fi } a x # Functional optimization. e "" +srgb2lab a c energy_max=${-_lut_contrast.} nb_attempts=1000 do e "\r [ Optim ] > Score = "{_$energy_max}", Attempts = "$nb_attempts" " . eval " do( k0 = v("$N0",w-1); k1 = v("$N0",w-1), k0==k1); tmp = I[k0]; I[k0] = I[k1]; I[k1] = tmp" energy=${-_lut_contrast.} if $energy>$energy_max energy_max=$energy k. nb_attempts=1000 else rm. nb_attempts-=1 fi while $nb_attempts>0 channels 0,2 } _lut_contrast : 100%,1,1,1,"> const N = 10; dist = 0; sumw = 0; RGB0 = (I[#0,x])[3,3]; kmin = max(x-N,0); kmax = min(x+N,w-1); for (k = kmin, k<=kmax, ++k, RGB = (I[#0,k])[3,3]; w = (1 + N - abs(k-x))^1.5; dist+= w*norm(RGB - RGB0); sumw+=w; ); dist/=sumw" u {is} rm. #@cli map_clut : [clut] | "clut_name" #@cli : Map specified RGB color LUT to selected images. #@cli : $ image.jpg uniform_distribution {2^6},3 mirror[-1] x +map_clut[0] [1] map_clut : e[^-1] "Map color LUT $1 on image$?." if !$! return fi to_color if ${"is_image_arg $1"} pass$1 0 to_rgb. else clut "$1" fi l:=round((w*h*d)^(1/3)) if w*h*d!=$l^3 error "Command '$0': Specified CLUT $1 has invalid dimensions "({w},{h},{d},{s}). fi r. $l,$l,$l,3,-1 repeat $!-1 { l[$>,-1] { nm={0,n} split_opacity[0] /[0] {256/$l} +warp. [0],0,1,1 rm[0] mv. 0 a[^-1] c =>[0] $nm } } rm. #@cli match_histogram : [reference_image],_nb_levels>0,_color_channels #@cli : Transfer histogram of the specified reference image to selected images. #@cli : Argument 'color channels' is the same as with command 'apply_channels'. #@cli : Default value: 'nb_levels=256' and 'color_channels=all'. #@cli : $ image.jpg 100,100,1,3,"u([256,200,100])" +match_histogram[0] [1] match_histogram : check ${"is_image_arg $1"}" && ${2=1024}>0" skip "${3=0}" channels=${"_ac_list \"$3\""} e[^-1] "Transfer histogram from image ["${"pass$1 -1"}"] to image$?, "\ "with $2 levels, for channels '"$channels"'." pass$1 1 sref:=s rm. to_colormode $sref if ['$channels']!='all' pass$1 {$sref==3?2:0} to_color ac. "+store _match_histogram_reference",$channels rm. ac "_match_histogram $2",$channels,1 else pass$1 store. _match_histogram_reference foreach { _match_histogram $2 } fi _match_histogram_reference= _match_histogram : $_match_histogram_reference repeat min(s#0,s#1) { sh $> +histogram[-2,-1] $1 cumulate[-2,-1] /.. {-2,i[-1,2]} /. {i[-1,2]} f.. "* const w1 = w -1; val = i; X = x; val=1, nX = max(0,X - step); val(val - vp)?--X); # Rounding ):( step = int((w1 - X)/2); while (X=1, nX = min(w1,X + step); val>i[#-1,nX]?(X = nX):(step = int(step/2))); X(vn - val)?++X); # Rounding ); im#-3 + (iM#-3 - im#-3)*X/w1" n[-4] 0,{w-1} round[-4] map[-4] .. k[0,1] } rm. #@cli match_icp : [reference_image],_precision>0,_transformation_variable #@cli : Transform selected set of d-dimensional vectors to match specified set of reference vectors, \ # using ICP (*Iterative Closest Point*) algorithm. #@cli : A description of ICP is available at . #@cli : Return the L2 alignment error. #@cli : Default values: 'precision=1e-2' and 'transformation_variable=(undefined)'. #@cli : sample lena,earth +match_icp[0] [1] match_icp : check ${"is_image_arg $1"}" && ${2=1e-2}>0" skip "${3=}" e[^-1] "Match vector set$? with reference vector set $1, using ICP with precision $2.\n" pass$1 foreach[^-1] { w,h,d,s={w},{h},{d},{s} pass. mv. 0 # [0]: reference set, [1]: current set if "['$3']!=0" +store. img fi match_pca. .. # Pre-normalize for better initial estimate # Iterate ICP steps. old_err=inf repeat inf { # For each vector, find nearest reference vector. 100%,100%,100%,4,"* P = I(#1); distmin = pmin = qmin = rmin = inf; repeat (d#0,r, repeat (h#0,q, repeat (w#0,p, dist = norm(I(#0,p,q,r) - P); dist Iteration "$>": Error = "{_$err} if !$err" || "abs($err-$old_err)<1e-3 rm. break fi old_err=$err +warp[0] .,0,0 rm.. _match_icp[-2,-1] m*[-2,-1] r. $s,{h/$s},1,1,-1 permute. yzcx r. $w,$h,$d,$s,-1 } k. if "['$3']!=0" $img rv +_match_icp $3={^} rm[0,-2,-1] fi # Output linear transformation u $err } rm. # Compute linear transformation between two sets of paired vectors. # [-2] : Set of vectors. # [-1] : Corresponding set of reference vectors. _match_icp : N,s:=whd,s permute. cxyz y. # Unrolled (i0_ref_k,i1_ref_k,...) l.. { # Generate matrix for the linear system r 1,{whd},1,100%,-1 permute cyxz 1,100%,1,1,1 a x r 100%,{$s*$N},1,1 r {($s+1)^2},100%,1,1,0 r {$s*($s+1)},{($s+1)*$N},1,1,-1 s y rm[$s--1:{$s+1}] a y } solve. .. #@cli match_pca : [reference_image],_color_channels #@cli : Transfer mean and covariance matrix of specified vector-valued reference image to selected images. #@cli : Argument 'color channels' is the same as with command 'apply_channels'. #@cli : Default value: 'color_channels=all'. #@cli : $ sample lena,earth +match_pca[0] [1] match_pca : check ${"is_image_arg $1"} skip "${2=all}" channels=${"_ac_list \"$2\""} e[^-1] "Transfer mean vector and covariance matrix from image ["${"pass$1 -1"}"] to image$?, "\ "for channels '"$channels"'." pass$1 1 sref:=s rm. if $sref<=4 to_colormode[^-1] $sref fi if $sref==1 # Scalar case can to be solved more easily pass$1 var_ref,avg_ref:=[iv,ia] rm. foreach { - {ia} * {sqrt($var_ref/max(1e-8,iv))} + $avg_ref } elif ['$channels']!='all' pass$1 {$sref==3?2:0} to_color ac. "+store _match_pca_reference",$channels rm. ac "_match_pca",$channels,1 else pass$1 store. _match_pca_reference foreach { _match_pca } fi _match_pca_reference= _match_pca : $_match_pca_reference f.. "*begin( cov_ref = [ "${"covariance_vectors[1] _avg_ref"}" ]; cov = [ "${"covariance_vectors[0] _avg"}" ]; avg_ref = [ "$_avg_ref" ]; avg = [ "$_avg" ]; eig_ref = eig(cov_ref); eig = eig(cov); lambda_ref = sqrt(eig_ref[0,s]); lambda = 1/sqrt(1e-6 + eig[0,s]); repeat(s,k, # Align eigenvectors. sks = s + k*s; Uref = eig_ref[sks,s]; U = eig[sks,s]; dot(Uref,U)<0?copy(eig[sks],U*=-1); ); rot_ref = mul(transpose(eig_ref[s,s*s],s),diag(lambda_ref),s); rot = mul(diag(lambda),eig[s,s*s],s); M = mul(rot_ref,rot,s); ); avg_ref + M*(I - avg)" rm. #@cli match_rgb : [target],_gamma>=0,_regularization>=0,_luminosity_constraints>=0,_rgb_resolution>=0,\ # _is_constraints={ 0 | 1 } #@cli : Transfer colors from selected source images to selected reference image (given as argument). #@cli : 'gamma' determines the importance of color occurrences in the matching process (0:none to 1:huge). #@cli : 'regularization' determines the number of guided filter iterations to remove quantization effects. #@cli : 'luminosity_constraints' tells if luminosity constraints must be applied on non-confident matched colors. #@cli : 'is_constraints' tells if additional hard color constraints must be set (opens an interactive window). #@cli : Default values: 'gamma=0.3','regularization=8', 'luminosity_constraints=0.1', 'rgb_resolution=64' and \ # 'is_constraints=0'. #@cli : $ sample pencils,wall +match_rgb[0] [1],0,0.01 match_rgb : check "${2=0.3}>=0 && ${3=8}>=0 && ${4=0.15}>=0 && ${5=64}>=0 && isbool(${6=0})" e[^-1] "Transfer colors of image $1 to image$?." sigma=1.5 repeat $! { pass$1 0 l[$>,-1] { nm_source={0,b} nm_target={1,b} => source,target # Generate matching functions from 3D RGB features. +_match_rgb[source,target] $2,$sigma,$5 => fsource,ftarget n[fsource,ftarget] 0,255 # Manage color constraints. if $6 h0:=2*{*,v}/3 ws0={source,max(1,w*$h0/h)} wt0={target,max(1,w*$h0/h)} w1:=2*{*,u}/3 hs1={source,max(1,h*$w1/w)} ht1={target,max(1,h*$w1/w)} if abs($ws0+$wt0-$w1)$w1 rs[-2,-1] $w1 fi if h>$h0 rs[-2,-1] ,$h0 fi => visu,both w[visu] -1,-1 N=0 do w[] -1,-1,"[G'MIC] Add Color Guide (Constraint ""#"{1+$N}")" +select[$visu] 1 => coords if i==-1 rm. break fi line[$visu] {i[0]},{i[1]},{i[3]},{i[4]},1,0xF0F0F0F0,0 line[$visu] {i[0]},{i[1]},{i[3]},{i[4]},1,0x0F0F0F0F,255 circle[$visu] {i[0]},{i[1]},5,1,0,0,0 circle[$visu] {i[0]},{i[1]},3,1,255,0,0 circle[$visu] {i[3]},{i[4]},5,1,0,0,0 circle[$visu] {i[3]},{i[4]},3,1,0,255,0 s. y,2 rows[-2,-1] 0,1 a[-2,-1] x permute. xczy +warp[$both] .,0,0,1 rm.. *. {($5-1)/255} s. x,2 -. .. *. -1 a[-2,-1] c N+=1 while {*} if $N a[-$N--1] x permute. xczy => constraints fi rm[$visu,$both] w 0 fi # Estimate warping field. if $constraints +pointcloud. 0 r. ...,...,...,3,0 +compose_channels. + a[-2,-1] c displacement[fsource] [ftarget],0.001,5,0,10000,1,. rm[ftarget,constraints,-1] else displacement[fsource] [ftarget],0.005 rm[ftarget] fi =>[fsource] displacement # Generate video of matching. # if videos # N=$! # [displacement],[displacement],[displacement],1,x +f. y +f. z a[-3--1] c *. {255/(w-1)} +. 1 # +_match_rgb[source] 0,$sigma,{w} *.. . distance. 1 *. -1 watershed.. . rm. # +_match_rgb[source] 0,$sigma,{w} a[-2,-1] c # [displacement],[displacement],[displacement],1,x +f. y +f. z a[-3--1] c *. {255/(w-1)} +. 1 # +_match_rgb[target] 0,$sigma,{w} *.. . distance. 1 *. -1 watershed.. . rm. # +_match_rgb[target] 0,$sigma,{w} a[-2,-1] c # warp. [displacement],1,1,1,48 # l[$N--1] { # f3d 1200 # ap "s. c,-3 view_func. 6,.. rm.." # ap "makefig" # append[^0] [0],x rm[0] # rs 1024 # [-1]x20 # o videos/video_${nm_source}_to_${nm_target}.mp4,10 # rm # } # fi # Estimate confidence of the color mapping. +_match_rgb[target] 0,$sigma,{displacement,w} warp. [displacement],1,1,1 c. 0,100% => fconfidence +map_clut[source] . => confidence # Generate transfer CLUT. [displacement],[displacement],[displacement],1,x +f. y +f. z a[-3--1] c *. {255/(w-1)} +. 1 +_match_rgb[target] 0,0,{w} *.. . warp[-2,-1] [displacement],1,0,1 distance. 1 *. -1 watershed.. . rm. -. 1 => clut b[clut] $sigma% # Enforce luminosity constraint for non-confident colors. if $4>0 ^[fconfidence] {$4/10} *[fconfidence] -1 +[fconfidence] 1 +f[fconfidence] x +f. y +f. z a[-3--1] c *. {255/(w-1)} rgb2hsv[clut,-1] channels. 100% j[clut] .,0,0,0,2,1,[fconfidence] rm. hsv2rgb[clut] fi # Map CLUT to get result. +map_clut[source] [clut] => res_noregul # Apply guided smoothing to remove quantization artifacts. if !$3 =>[res_noregul] res else l[source,res_noregul] { rgb2ycbcr # Perform regularization in YCbCr to avoid creation of false colors. +-[1] [0] repeat $3 { guided. [0],5,5 } +. [0] c. 0,255 ycbcr2rgb } => res j[res] [res_noregul],0,0,0,0,1,[confidence] rm[res_noregul] fi k[res] } } # _match_rgb : _gamma>=0,_smoothness>=0,_resolution>0 # Convert selected images into 3D volumetric scalar function for color matching. _match_rgb : l[] { check "${1=0}>=0 && ${2=1.5}>=0 && ${3=128}>0" gamma=$1 smoothness=$2 res=$3 onfail noarg gamma=0 smoothness=1.5 res=128 } e[^-1] "Convert image$? as 3D volumetric scalar functions for color matching, with gamma "$gamma", smoothness "$smoothness" and resolution "$res"." to_rgb foreach { b 0.3% r {w*h},3,1,1,-1 * {($res-1)/255} pointcloud 1,$res,$res,$res f i?i^$gamma:0 b $smoothness% n 0,1 } #@cli mix_rgb : a11,a12,a13,a21,a22,a23,a31,a32,a33 #@cli : Apply 3x3 specified matrix to RGB colors of selected images. #@cli : Default values: 'a11=1', 'a12=a13=a21=0', 'a22=1', 'a23=a31=a32=0' and 'a33=1'. #@cli : $ image.jpg +mix_rgb 0,1,0,1,0,0,0,0,1 #@cli : $$ mix_rgb : skip ${1=1},${2=0},${3=0},${4=0},${5=1},${6=0},${7=0},${8=0},${9=1} e[^-1] "Apply matrix [ $1 $2 $3 ; $4 $5 $6 ; $7 $8 $9 ] to RGB colors of image$?." to_color foreach { sh 0,2 mix_channels. (${1-3};${4-6};${7-9}) rm. } #@cli oklab2rgb #@cli : Convert color representation of selected images from OKlab to RGB. #@cli : (see colorspace definition at: ). #@cli : See also: ''rgb2oklab''. oklab2rgb : e[^-1] "Convert color representation of image$? from Oklab to RGB." foreach { split_opacity to_rgb[0] f[0] " l = (i0 + 0.3963377774*i1 + 0.2158037573*i2)^3; m = (i0 - 0.1055613458*i1 - 0.0638541728*i2)^3; s = (i0 - 0.0894841775*i1 - 1.2914855480*i2)^3; [ 4.0767245293*l - 3.3072168827*m + 0.2307590544*s, -1.2681437731*l + 2.6093323231*m - 0.3411344290*s, -0.0041119885*l - 0.7034763098*m + 1.7068625689*s ]" * 255 a c } #@cli palette : palette_name | palette_number #@cli : Input specified color palette at the end of the image list. #@cli : 'palette_name' can be { default | hsv | lines | hot | cool | jet | flag | cube | rainbow | \ # parula | spring | summer | autumn | winter | bone | copper | pink | vga | \ # algae | amp | balance | curl | deep | delta | dense | diff | gray | haline | ice | \ # matter | oxy | phase | rain | solar | speed | tarn | tempo | thermal | topo | turbid | aurora | hocuspocus | srb2 | \ # uzebox | amiga7800 | amiga7800mess | fornaxvoid1 } #@cli : $ palette hsv +palette : names=${-_palette_names} N:=narg($names) l[] { if isint("$1") name=${"arg0 ($1%"$N"),"$names} else name="$1" fi onfail name="$1" } e[^-1] "Input color palette '"$name"'." _palette_$name => $name _palette_names : u default,hsv,lines,hot,cool,jet,flag,cube,rainbow,\ parula,spring,summer,autumn,winter,bone,copper,pink,vga,\ algae,amp,balance,curl,deep,delta,dense,diff,gray,haline,ice,\ matter,oxy,phase,rain,solar,speed,tarn,tempo,thermal,topo,turbid,aurora,hocuspocus,srb2,uzebox,\ amiga7800,amiga7800mess,fornaxvoid1 _palette2code : # Convert a set of input palettes sort_list +,n foreach { r {whd},1,1,100%,-1 img2base64 0,0 e "_palette_"{n}" : " b64=\"${}\" l[] { ('$b64') s x,-119 ('\\\\\n') a[0--3] .,x rm. a x b64={t} rm } # No more than 120 char / lines e " base642img \\\n"$b64"\n" } # The color palettes below comes from the CImg library 'http://cimg.eu/'. _palette_default : 256,1,1,3,[16+32*int(x>>5),16+32*int((x>>2)&7),32+64*int(x&3)] _palette_hsv : 256,1,1,3,[x*360/w,1,1] hsv2rgb. round. _palette_lines : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM2MTcKeJwNkhGgu1AYxT8YDP4QDB4Eg+BKEATBYBAEQXDhQhAMHgweBBeuXB\ gMgmAwCIIgCIJBEAQPguBKEASDwWDwYDD4/tGx75zf+Q4gAuhCPFVzM0CgvUXFmr0plIpkB2KwW4bppr16aMgRzge16wbwTbxW0L6Y0IxsjiFyZyQvAISoD\ PFMYgWlnTUI/M4bsllO+I5M0GuoWm+LP6kImrtktjhu8sxjaJ9V6zwNDy0FijoRcloRzGvghbDTAiO+Z8/Hv9JoKRbRr79JjwXqpOfBGxRasEejCH2kHyhr\ uCHk2jXJ5xkgGFdjPgp/4IZ4HDTk4LTjXrAED56dzkIskRGG8Gwh9FdqTh8bkMgp0+JG9tb+xXpLf58ugHG/GoPG6ZmnSGh+Bn1OyCkX23dUAjon753DhyT\ JuhiCaxcy8acf79DuuwbQNo/NItIqXXWF491E8R0ECNOppKlesjlaK8wsIqpKwL3QJwnhCygGtbCWbgbvJ0W4GFMOSrgnpIQ9rZHDYMyVS261dE1U1B/hkU\ InqiU/44MDQ/m4BMtHV9Wq5bgqjtJ6laFWKUDtOwsRFvaMLvSAZbHj+u9o/u0itAN5xHX85KSu4GblHN0BJgEHfPGtksZjK4JkzFY1AvkIMTEJBX7tO4HCF\ 6Hov85VvUXaEWosOIqirYUDkWW/OH7FGP18B9h0jjs49tyukEYSKYiZTjCmPnkxI7neAWkOOTxrs4A3blzFEhvJsgMXqmsicxgRGMFil7o5wS3HuIZmAGcc\ g8wvlM8UfPWXpfJN5JTPj+YlnxB6QD6tvGmdD9oUxoBrWc3mf8h5fjM=" _palette_flag : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICMyNAp4nPv/n4Hh/wjGDCMeD4JIGEAMAH3RPtA=" _palette_cube : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjMxIDEgMSAzICMzMzgKeJxj5BCQkFfXN7Nz8wuLS80trWnpmTJn8erNuw6dunTr0cuPP39/e/f83v\ Xzxw/s2LBy4cyJnY1VRVnJ0cHezjYmOqqyoryszFxCUoqahhaOHgER8en55XVtfdPmLV27dc+RM1fuPH79+ffvL2+e3Ll69ui+beuWz5ve395QUZCRGBHo6\ WhlpKUsLczNwsIjIqOsbWzt7BUUlZhZWNnYMWHGguXrt+87fu7avadvv/759enVo1uXTx/es2XNkjlTe1vryvLS4sL83e0tDDQUJQQ5Wdj4xGRVdU1tXX1C\ YpKzi6ubuybNWrhy486DJy/cePD8/fd/DIQAE6eghKKGgYW9u39YXFpeWV1r79Q5S9Zs2XP49OVbj159+vWf9oCQN5kI+oIOgIVbWFpZy8jK0TMwIjGjoKK\ hvX/6vOXrtu07evbqnSdvvvymQ0ABAC/raR8=" r. 256,1,1,3,3 round. _palette_rainbow : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjI1IDEgMSAzICM1NjcKeJxj5ublE+DnF5KQUVBWU9fS0tHV0dXVAVL6RiZm5haWFpaWVpZWVtbW1i\ BsbWMDJq3BwMrK0sLMWF9LRYqLkYGRAQygFAYbG2DkVrH2iUjNq2zpnTp3+eadB09fvvXo+dvPP/7+//v375/fv37++PH9+7dvX799/fr1GwQAWV+/ACEUA\ Bmf3796+uDW1XMnD+3dum75wpkTOhvK81Ii/Z3NDbSUxNhZuPkEhISFhMWl5RRV1LV09AyMjE1MLWwcnJxd3T29vH18/fz9AwIDg4JDQCA0DAjCI0BkRGRk\ dExsQnJaZm5ReW1T54Tp85au3rBp8+ZNmzZt3Lhxw4YN69euXbd2zWpMABTfsHHTpq3bd+7ae+Dg4eMnTp+9cPnC2RNHDuzatnH10gWzJvW01JUXZCbHhvt\ 5OttbW5gYGepDgJ6enq6OjpaWpoa6mqqKorysnJy8goKCopKSopKigrycrLSkuIggHy+/kJCQAB8PFzOHiIyKnqm9R0BYbHJmXmlVY2tn38SpM2bPmTd/wc\ JFi5csWbZ82dJly5avWLFiJRCsAoPVYGL1GqBDN27eugPoyqMnz1+/++gxMCRPH9mzdd3KhbOn9rU3VBZmxEcEByGD4BAwPzgoMDAgwM/P19vT093d1dnJ3\ trK3NhIX0dDTVUJ5GCgi5VVVIFpSkdXT9/A0AAGQH7U1dXW1gJ5UA2E1NTUNdQ1NDTU1dXUVJQU5WQkxYT4efkFhIQE+Xm5mAHNkQH/" r. 256,1,1,3,3 round. # The color palettes below have been converted from # 'https://stackoverflow.com/questions/33273340/matlab-set-color-map-color-range'. _palette_parula : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNjQgMSAxIDMgIzIwMwp4nAHAAD//NTY2NDAoHAwDAwUJDhETFBQSEAwJBwYGBggLEBYdJS44Qk1ZZX\ F7hpGbpK22v8fP2N/n7/b8//79+/j29fX2+C0zOT9FTVZeZGlucnZ6foKGio+UmZ2hpKeprK6xs7W3ubu8vb6/v7+/v76+vby8u7q5ubm7vsLHzNLX3OLp8\ PiMmKWyvsvX3uHh4N7c2tjW1NPS0tHPzcrGwr65tK+ppJ6Yko2HgXx4dHBsaGVhXlpXU09LRj85NDAsKCMfGhUQJK9mpg==" r. 256,1,1,3,3 round. _palette_jet : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNzYgMSAxIDMgIzEzMQp4nGNgwA24JFSN7H2i0ksa+2av3Hb4wr3XP/9jgt/vH145tnPN/ImtMH1MvN\ Iapk7+sVnlLRPnrdl57PLDd7+xaPz55t7Fw9tWzulrKkmP8rU3UpXgYoTbPWXh+j0nrz35+BeLxm8vb587sHnZzO76wpRwLxt9JVF2PL5gYAAAc+1twA==" r. 256,1,1,3,3 round. _palette_hot : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTI0IDEgMSAzICMxNDEKeJzjFRKXVdLQNTK3dXb3DY6IS87IK66oa+nsmzJz3pKV67fu2n/k1IWrt+\ 4/ff3+66//1AUMpABGdl5BcRlFdZBTndx9g0BOzS2urGvuADl18cr1W3buP3LyPMipr4BO/UdNu0kEjFxC0kpaRtbO3kHRydnFNS3dk2cvXr1lz5EzV+88e\ fP1LwA796yq" r. 256,1,1,3,3 round. _palette_cool : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMzIgMSAxIDMgIzc4CnicY+ERkVHRMbFx8QmJSckpqWnpmTJnyZote46cuXLnyZsvf/58efPkzpUzR/\ ZsWbNkzpSelpqSnJSYEB8XGxMdFRkRHpb/BAAAHuY/4Q==" r. 256,1,1,3,3 round. _palette_spring : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjggMSAxIDMgIzcxCnic+/8fN2DhFVPQNLJ28Q1LyCyqbumdtmDlpt1Hzl578PLT79+fXj64dvbI7k\ 0rF0zrbakuykwI83WxNtJUEONlAQDKiDfN" r. 256,1,1,3,3 round. _palette_summer : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjggMSAxIDMgIzcxCnicY+EVU9A0snbxDUvILKpu6Z22YOWm3UfOXnvw8tPvprbuCVNmzl20bPX6LT\ v2Hjx26vyVm3cfPXv94cvPv2l4AACR+i4M" r. 256,1,1,3,3 round. _palette_autumn : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjYgMSAxIDMgIzQyCnic+/8fF2Dhk1DSMXPwDI5NL6xu6ZuxaM22Aycv333+8TcDTgAAaHcm2w==" r. 256,1,1,3,3 round. _palette_winter : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMzEgMSAxIDMgIzc5CnicY2DAC1h4RGRUdU1t3fzC4tLzyxs6JsxYuHLjzkOnLt16/Prz778/v358+/\ Lpwzs3rlw4ffzw/t3bN69bvXzxvFnTJvV1tTUBACEGJp4=" r. 256,1,1,3,3 round. _palette_bone : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KOTkgMSAxIDMgIzI1Mgp4nGNkZuXg4uETFBaVkJKRU1BWVdfU0TMwNjG3tLZzcHJx9/T28w8KCY+Mjk\ tISk3PzM4rKCwpq6iqbWhqaevs7p0wccq0GbPnzl+4ZNnKtRu3bN+9/9Cxk2cuXLl+696jpy/ffPj8/dc/RmKsCI2IiU9KzcjJLyqtqK5rbGnv6ps4Zfqsu\ QsWAw3esHnbzr37Dx09cfrM+YtXrt24fef+w8fPnr968+7j56/ff/35x8TKwcMvJCopLa+koq6lZ2hibmXnCDTZNyA4PDI2ITktE2hwSVlldV1DU2t7Z3cf\ 2N2zwO5esXrtho1btu3YvXf/wSPHcFoBAJTokrQ=" r. 256,1,1,3,3 round. _palette_copper : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTE1IDEgMSAzICMzMzkKeJxj4+TmExQWk5SWU1RW09TRNzKxsLK1d3b18PINCA6NiI5LSE7LyM4rKC\ 6rqK5rbGnv7O2fNGX6rLnzFy1dvmrthk1bd+zee+DQ0ROnz164fO3mnXsPnzx7+eb9xy/ff/359x87YGFl5+Dm5RMQFBYVk5CSlpVXVFZRU9fU1tEzMDIxN\ be0trG1d3R2dfP08vH1DwwKCY2Iio6NT0hKTk3PyMrJKygsLi2rqKyurW9sam1r7+zu7eufOGXa9Jmz5sybv3DxkmUrVq1Zu37Dpi1bt+/avXffgUOHjx5j\ YgZZysnFzcPLxy8oJCwiIiYuISklLSMrp6CopKyiqqauoakFdIG+gaGRsYmpmbmFpZWNrZ29g6OTs4ubu4ent4+vn39AYFBwaFh4RGRUdExsXHxCYnJKalp\ 6RmZWdm5uXn5BUXFJaVl5RWVVTW1dPQCk5ZAs" r. 256,1,1,3,3 round. _palette_pink : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTIxIDEgMSAzICMzMDUKeJxT1DG1cfbwC46ISUzNzC0sraiub2rr7O6bOGX6zDnzFy5ZtnL1ug2bt2\ 7fuXvvgUOHDx85euz4iZOnTp85e+7c+QsXL12+cuXqtes3bt66ffvO3Xv37j94+PDR4ydPnj57/uLFy1evX799++79hw8fP33+8uXr12/ff/z49ev37z9//\ /37zyoso6SurW9sZmVr7+Tq7uXjHxQSFh4ZHRuflJyanpGVnZtfWFRSWl5RWV1b39Ta0d0PddLiZSvXIJx05NiJU2fOXbh89fqNW3fu3X/0BGgzFS1uaGxq\ aW1r7+zu6e2bMHHS5CnTps+YOWv2nHnzFy5avGTpsuUrVq5avWbtuvUbNm4COmn3vgOHj504fRbonhu3795/+OTZi1dv33/68u3Hrz//AH2168E=" r. 256,1,1,3,3 round. _palette_vga : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICMxNzgKeJz7/+fn10/vXz9/8uDOjasXzpw4sv/IiTMXrt648+DJ89fvP339+ec/Bv\ jx8dXj25dOHdi2ZsGU1tIEVwbCwDWhtHXKgjXbDpy6dPvxq48/MM2Q0zF3cPcLiYpPycgtLK2saSAMaipLC3MzUuKjQvzcHcx15IhwB4Yt/zH8v2vr+pWL5\ 07r72goz00KdTPEdDthQEYIYbgMw3c0CSEi/E9G7JIRQoT9T0YYEgYAJsphWQ==" # The color palettes below have been converted from 'https://matplotlib.org/cmocean/'. _palette_algae : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNTAgMSAxIDMgIzE2MQp4nAGWAGn/1M3Hwbq0raegmZKLhH11bWVcU0k+MygeFg8JBwYICg0PEhQVFx\ gZGRkZGRkYFxYVExL38u7p5ODc19PPy8fEwLy4tbGtqqainpqWko2JhH96dnFtaGRfW1ZRTUlEQDw3My4qJczFvbavqKGak42GgHp0bmhjX1pXVFJRUFBPT\ k1MS0lIRkRCPz07ODUyLywpJiMgHBkV1p88Lw==" r. 256,1,1,3,3 round. _palette_amp : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNjEgMSAxIDMgIzE5NAp4nAG3AEj/8O3r6ejm5OLh397c29nY1tXU0tHPzszLycjGxMPBv728uri1s7\ CtqqainpqVkIuFgHp0b2lkXlhTTkhDPurm4dzX0s3Iw765tLCrpqGcmJOOiYSAe3ZxbGdiXVhTTklDPTgyLSgjHhkWEhAODg0ODg4ODg4NDQwLCgnq5N7Y0\ szGwLq0rqihnJWPiYN9eHJsZmBbVU9KRUA7NjIuKygmJCQkJCUmJicoKSkpKCcmJSMhHxwaGBUS64BWpQ==" r. 256,1,1,3,3 round. _palette_balance : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTIxIDEgMSAzICMzNzQKeJwBawGU/hgaHB4gIiMlJigoKSkoJyQgGhMNCgoMERYcIictMzg+Q0lOVV\ thaG51fIKJkJado6mwtrzDyc7V2+Dm7PDu7Oro5+Xj4t/e3dva2NfW1NLR0M7Ny8rIxsXDwcC+vLq4tbOwraqno56alZCLhYB6dG9pY15YU01IQj0dICImK\ CsuMDM2OTs/QUVJTFFVW19kaGxxdHh8gISHi46SlZmcoKOmqqyvs7a5vL/CxcnMz9PW2t3h5ens6OPe2dTPysXAu7axraijnpmVkIuGgXx3cm1pY15ZVE9K\ RD45My4oIx8aFhMQDw4NDg4ODg4ODQ0MCwoJREpRWF5lbHN7goqSmqGpsLa7vb69vby8u7u6urm5ubm5ubq6uru8vL2+v8HDxcbIys3P0dXX2t3f4ubp6+f\ h2tXPyMK9trCqpJ6YkYuGf3l0bWhiXFZQS0ZBPDczLisoJiQkJCQlJSYnKCkpKSgnJiQiIR4cGhcVEqaTs4c=" r. 256,1,1,3,3 round. _palette_curl : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTI0IDEgMSAzICMzODEKeJwTFROXkJCUkpKWlpaRARJSkuJiosJCggKCwmLSCup6Znbu/uHx6fnldW\ 29U2YtXLF+6+6DJ85duXn/6euP3/78+fn96+eP79++efXy+bOnjx89uH/3zu2b169duXT+7KkTRw/u271984Y1K5YsmDN9Ul9nS31VaUF2anxUqL+Xi72Vq\ ZySmpaekZmVraOrp29gSERMQnJGTkFxeXV9U3tX38SpM2bPW7h0xaq1GzZv27F738Ejx0+dvXDl+q27D5+8ePPhy+d3r57cv3X1wumjB/ds37hm+UKgHb0d\ TbUVxXmZKQnR4UF+Xq6OtlZmxvraGqpKCrJgn4kIg/wmwMfr4ubh7esfFBIWGR2bkJSSnpWTV1BUXFpeUVVdU1tXV9/Q2NTc2tbR1Tth8rRZcxcuXbVu846\ 9h0+cvQz2+cfXT+5eO3/i4O4ta5cvnD21v6u1obq8uCA3KyMtJTkpMSEhIT4+PgEG4uPjYqMjw0OD/L09nO2tzACwu7nF" r. 256,1,1,3,3 round. _palette_deep : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNDkgMSAxIDMgIzE1OAp4nAGTAGz/+e7j2M7Dt6yhlYqAdm5mYVxYVVJPTUtKSEZEQ0FAPz49PT0+P0\ BBQT8+Ozk2MzAsKfv38/Ds6OXh3dnV0czIw764s66po56Yk46Ig396dG9qZF5ZU05IQz46NjIvKycjHxvJw724s6+rqKWko6Kjo6Ojo6OjoqKhoJ+dnJuam\ ZeWlZSTkY+Mh4B4b2ZeVU1FPTYvM2JJnA==" r. 256,1,1,3,3 round. _palette_delta : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM3NzAKeJwTEBIWEROXkJSWkZWTV1BUUlZRVVUDAlVlJUV5OVlZGWlpKRCQlp\ aWASmBqFFT19DU0tHVNzA0NjWzsLKxc3Bycff08Q8Ki4xNSMnIKSipqGlo6ejpnzx91rxFS1euWb952869Bw4fP3X2wuVrN+8+ePzs5Zv3n7/9/Pvv7+9fP\ 79/+/r508f37968fvXi2dPHjx7cu3PrxrUrl86fPX3i6OEDe3dt37Jx3arlSxbMnTVtcn9PR2tjXVV5cUFORmpibFRYkL+3u4uDraWZkb62hqqSvIykuIiQ\ AB8vDzcXFzc3Dw8vH7+AINCbIqJiYuLiEhISkmAAZIiLi8srKCmrqqlramnr6ukbGBmbmJpbWFqDPeTm4e3rHxgcFhEVE5eQlJKWkZWTV1BUUlZRWV1b39j\ c2t7Z3ds3YdKUaTNmzZk7f+HipctXrFqzdv3GTVu2bt+xc/eeffsPHjpy9NiJk6fPnLtw8fKVa9dv3r5z78Gjx0+fv3z99t2HT1++/fj15+f3L58+vH398t\ mTR/fv3r55/eqlC+fOnDp5/Ojhgwf27dm9c8e2rZs3bVy/bu2aVStXLF+2ZPGihQvmzZ0ze+aM6dOmTp40sb+vt7urs72tpbmxoa62uqqirLS4sCAvJyszP\ TU5MT4uJioiLCQowM/H29Pd1dnR3tba0tzUxMhAT0dLQ01F2d7JFRJvMQnJ6dl5RWVVtY2tnb0TgL6aOWv27Dlz5gLBvHnz5s+fv2DBgoULFy5atHjxkiVL\ ly5dtmz58hUrVq5ctWr16jVr1q5dt27d+vUbNmzYuHHT5s1btm4DhsEuUCAcOHj4yNHjJ04BQ+H8xUtXrl6/cQsYDPcfPHz0+MnTp6ePH96/e9umdSuXLpw\ zY0p/d3tTXVVpYW5GSkJ0eDAwYp3trS1MDHS11JUVZKUlRIUF+Hi4ONjZWIGAjZ2Dk5uHj18QlIIlpYCpE5J+gYkTmDq1tLV14EBbW1tLS1NDQ11NVQWYsB\ Xk5WSkpSTERUWEAGwPYP8=" _palette_dense : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNzQgMSAxIDMgIzIyNAp4nHt878alM8cO7N66YfXSBbOnT+rram9uqKmqKCstKQaBktLSsvLyioqKys\ pKIFleDpIoLMjLyc5MS0mKj4kMC/L38XBxsDF//+bV8ycP792+ce3ShTMnjx3ev3fXts3r16xYsmDOjCkTejpaGmoqivOz05Pjo8NDAnw8XB3trMyNDfW0N\ FSVFGSlJcXFRISFhQQF+Pnev3398sWzp0+ePH4EA48fP34CBo8fP3p4/+7tm9euXDh78tihfbu2bly7YvG8mVP6u1rqK0vyMpJjwwN93BxtzI30NFUBgy55\ hg==" r. 256,1,1,3,3 round. _palette_diff : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTQzIDEgMSAzICM0NDAKeJwBrQFS/gcJCw0ODxESFBYWGBseIiYqLjI2Oj5CRUlNUVRYXF9jZmtvcn\ Z5fYCEiYyQk5ebn6Onq66yt7q/wsfLz9PX3N/k5+vu8fP19fX08e/s6ebi39zZ1dLOy8jEwr67uLWyr6yppqOgnZqYlZKPjIqHhIF+fHl2c3Bua2hkYF1ZV\ lJPS0hFQT47NzQwLSomIyAdIyYpLC8zNTk7P0JFSEtNUFNVWFteYGNmaGtucHN2eXx+gYSHioyPkpWYm56hpKeqrbC0trq+wMTHy8/R1djc3+Pm6ezu8PHx\ 8O/s6ufj4NzZ1dLOy8fDwLy5trKvq6mlop+bmJWSj4yJhoOAfXp3dHFubGhmY2BeW1hWU1FOTEpHRUI/PTs4NTIwLSooJSJAQ0dKTlFUWFteYWRmaGpsbnB\ yc3V4enx9gIKEhoiKjY+Rk5aYmpyfoaSnqauusLO2uLu+wcTGyszP0tXY29/i5efq7O7v8PDv7Onm4t3Z1dDLxsK9ubSvq6einZqVkY2IhIB8d3Nva2djX1\ tXU09LR0RAOzg0MS0qJyQiISAeHRsbGRgWFRQSEA4ODAoIBjEs26k=" r. 256,1,1,3,3 round. _palette_gray : 256,1,1,1,x r. 100%,1,1,3 _palette_haline : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNDYgMSAxIDMgIzE0OQp4nAGKAHX/KiwuLSgiGxQPDAwPEhcbICUpLTA0Nzo8QEJGSU5TWF9mb3mEkJ\ 6sucXR2+bw+RgaHB8mMDlBSE9VW19laW5zeHyBhouQlZqfpKmus7i9wsbLztLV2Nrd4OPm6e1xfoyZoaKfnJiVkpCOjIuKiYmIiIiHh4aFhIOBfnt4dHBrZ\ 2JeW1xgZW12gIqU7TdADQ==" r. 256,1,1,3,3 round. _palette_ice : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTA5IDEgMSAzICMzMzgKeJwBRwG4/gQGBwoMDhETFRgaHB4gIiQmKCorLS8wMjQ1Nzg5Ojs8PD0+Pj\ 4+Pj4+Pj4+Pj4+Pj4+Pz9AQUJDREVGSEpLTE9QUlRWWFpcXmFjZWhqbXBzdnl8f4OGio6Slpqfo6essLS5vsLGy8/T2Nzg5OkGCAkMDhASFBYYGRwdHyEjJ\ CYoKSwtLzEzNDc4Ojw+QEJER0lLTlBSVVhaXWBiZWdqbW9ydXd6fX+ChIaJjI6Rk5WYmp2goqSnqayvsbS2uLu9v8LEx8rLzdDS1NfZ293f4eTm6evt8PL0\ 9/n7ExYaHSAkJyouMTU5PEBDR0pOUlZZXWFlaW1xdHh8gISIjI+Tlpmcn6KkpqiqrK6vsLKztLW2t7i5urq7vL2+v8DAwcLDxMXGx8jJysvMzc3P0NDS09P\ V1tfY2dvc3t/h4+Xn6evt7/L09fj6/ETzn2w=" r. 256,1,1,3,3 round. _palette_matter : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNjQgMSAxIDMgIzIwMwp4nAHAAD///fz8+/v6+vn4+Pf29fTz8vHw7+3s6ujn5eLg3drX1NDNycXAvL\ ezrqmkn5qVkIqFgHt1cGplYFpVUEpFQDs2Mevl4NrUz8nEvrmzrqijnZiSjYiCfXdybGdiXVhTTkpGQj46NzMwLSsoJiQiIB8dHBsaGhkYGBcXFhYVFBMSE\ Q+uqKOemJOPioWBfHh0cGxpZWJfXFpYVlRTUlJSU1NUVVdYWVtcXV5fYGFiYmNjY2NiYWBfXVxaV1VST0xJRkM/PV5d6A==" r. 256,1,1,3,3 round. _palette_oxy : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM2NTYKeJyzd3RydnP39PL18w8MDg2LiIqJjUtMSknNyMzOyS8oKi4rr6iqqa\ tvaG5pbevo7Oru6e0LDAgMDA4JCQ0Pj4iMigaqj09ISEpOTklLS8/IzMrOyc3LLygsKi4uLSsvr6yqrq6tq2tobGoCGgAzoX/CxEmTp0ydNn3mzFmz586bP\ 3/hosVLli5bvmLV6jVr163fsHHzlq3btu/YuXvP3n0HDh46dOToseMnT50+c+78hYuXr1y9duPmrdt3791/8Ojxk6fPX7x89ebt+w+fPn/5/u3L54/v3719\ /frVq1cvIeAFEDx//vzZs2dPnz558uTx40ePHj18+ODB/fv37t29e+cOKxCwQQE7DED5rGDAwgJSws7Bxc3HLygkKibh6x8QGBQcEhoWHhERFR0dEwcMgMS\ k5JTUtPT0zKys7Ny8vPxCYACUAgOgAhgANbV1DcAAaAGFIDAAenp7+4D+nwTy/wyg/+cAAwDo/8Vg/wMDAOT/TZu3QP2/dz8wAAj7/937T5+//vv75/fPH6\ BQ+PTh3ZvXL58/ffLowb27d25ev3b10sXz586cOnni6JFDB/fv3bN7x/ZtWzdt3LAO4n92dg4OTk4uLi5ubh4eHl4g4AMCfhAAMYB8Hm5uLk5OTg4OdnZvs\ P+DQ8LC4AkgPjEpKTk1FSUBFJWUlpVVVFZW1dTW1jc0NgJTUGt7R2cnSgIABsCcuXPnLYAmgJXAAAAlgE2bNoMSwK7de/bsAwbAYWAAnAAGwNlz585fvHT5\ yrXrN27evnP3HigAngED4DUwAD58fJSRlpIYExUe4uft4epgZ2NlYWZqYmRooK+ro6OtqamhrqaqqqKspKSoIC8vJysrIyMlJSkJADQjlG8=" _palette_phase : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTA2IDEgMSAzICMzMjkKeJwBPgHB/qissLO2uby/wsTHyczO0dPU1tjZ29zd3d7e3t7d3NvZ19XSz8\ zJxcG9ubWxrKeinpiTjYeBe3VuaGFbVE1HQTs1MCsnIyEeHBkXFRMRDw0MCwsOEhgeJS01PkdQWWJqcXh/hIqPlJidoqZ2dHJwbmtoZWNhXVtYVVJPTElFQ\ j47NzQwLSsoJiUlJicpLC8zNjo/Q0dLT1NXW19iZmltcHN2eXx+gYOGiIqLjY6PkJGSk5OUlZWWlpeXmJiZmZmampmZmZiXlpSSkI6MioiGhIF/fXp4DhIX\ Gx4iJiouMjY6PkNHTFFXXGJobnV8g4qSmaGor7e+xMvQ1tvf4+fq7O/w8vPz9PPz8vHv7ero5eHd2dTPysbBu7axrKejnpqVkIyHgnx3cWtlXlhRSUI5Mio\ jHRcTEA8ODQ0NDQ0NDPDwmdk=" r. 256,1,1,3,3 round. _palette_rain : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTE1IDEgMSAzICMzNDIKeJx7++bVy2dPHz96eP/e3Tu3b928cf3q5Yvnz5w8dmjf7u2b169evmTBnB\ lTJvZ2tjbWVpYU5makxEeHBfq4O9taGOtpqirIiIsI8nJxsLGyMDMzs7CwcXBy8woIiYhJSEnLyikoKCopq6ioqIIAkFZRVlZSUnzz6vnTRw/u3bl57cqlC\ 2dPnzh25NCBfXt37dyxfduWzZs2rl+3ds2qlSuWL12yeOGC+fPmzJ41Y/q0qZMnTZzQ19Pd1dHW0txYX1tdWVFaXJifm52ZnpqcGB8bFREWEhTg5+Pl7urs\ aG9rbWluYmSgr6OloaaiKC8r/fHdq+ePH9y9efXimZNHDgA9tm7V0oVzZ06bPLG/p6sTZGZTQ31dbU1VZUV5aWlJcVFhQX5+Xl4uDORBAYidk5OTnZWVmZm\ enpaanJSYEBcTHRkRGgy23RNovZMD0H4rSwDxo6Ac" r. 256,1,1,3,3 round. _palette_solar : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMzUgMSAxIDMgIzExNgp4nAFpAJb/N0BJUltkbXZ/h46VnKGnrLG1ur7BxcjMztHU1tja3N3f3+AVGB\ odHyIlJysvNDpARk5VXGRrc3uDi5OcpK22v8jR2+Xu+RkcHyEjJCQkIiAeHBoYFhQTExITFBUXGh0gJCgsMDU6PkNIm9UqlA==" r. 256,1,1,3,3 round. _palette_speed : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNDkgMSAxIDMgIzE1OAp4nAGTAGz//fn28+/r5+Pf2tXOyMG5samgl46FfHJpYFZNQzoxKCAZEg4LCg\ wOEBMVFxgZGRkYF/rz7efh29bQy8bCvbm1sq6rqKWinpuYlZKOi4eDf3t3c25pZWBbVlFMSEI9ODMvKSTIvbKnnJCFeW5jWE1COS8nHxgRDAcFBQgLDxQYG\ x8iJScpKywsLCwrKiknJCIfGxgUF7w7kA==" r. 256,1,1,3,3 round. _palette_tarn : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTA1IDEgMSAzICMzMjYKeJwBOwHE/hcaHiEjJyktMDU8Q0lQV11kanF4f4aNk5uiqbG4v8XKzc/S1N\ bY2t3f4uPm6evu8PT2+vv8+/j08Ovn4tzWzsa+t6+ooJmSioR9dm5nX1dPRz84MSolIh8eHRwaGRcUExAOCwoICQsODyQoLTE1OT5CRklMT1JVWFpdYGNlZ\ 2psb3F0dnl7fYCEiY6Ump+lq7C2vMHHzdLY3uTq7/T39vPu6eTf2tXRzcrGwr66trOvq6ekoJyZlpKOi4eDf3t3cm5qZGFcV1NOSkVBPTgzLikkIA0ODw8Q\ Dw8ODQwNEBETFhgbHR8hIyUnKisuMTQ3O0FIUlpia3N8hI2Vnqavt8DJ0dri6/H18uzj29HJwLewq6ikop+dm5mXlJKPjYuJh4WEgoF/fnx7enh2c3JvbWt\ paGZkYmFfXVpVUfhNmgM=" r. 256,1,1,3,3 round. _palette_tempo : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTEyIDEgMSAzICMzNDcKeJwBUAGv/v369/Tx7uvn5OHe29fU0c3Kx8PAvLm1sq+rp6OgnJiVkI2JhY\ B9eHRwa2hjX1pVUUxIQz86NTEtKSUhHhsYFhMSEREQEBEREhMUFBUWFxcYGRkaGhsbGxscHBwcHBsbGxsbGhoaGRkZGBgXFxYWFRX08vDu7Oro5eTi4N7c2\ tjW1dPRz87MysnHxcPCwL+9u7q4t7W0srGvrqyqqaempKKhn52cmpiWlZKQj42KiYeEgoB+fHp4dXNxb21raWZkYmBeXFpXVVNRT01LSEZFQ0A+PDo4NjQx\ Ly4rKSclIiAe8+/s6eXi3tvY1dLOy8jFwr+9ure0sa+sqqelo6CenJqYlpSSkY+OjIuKiIeGhYSDg4KBgYGAgH9/fn5+fXx8e3t6eXh4d3Z1dHNycXBvbm1\ samloZmVkY2FgX11cW1pYV1ZUU1JQT05MS0pIR0ZFRA7PoM4=" r. 256,1,1,3,3 round. _palette_thermal : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTA3IDEgMSAzICMzMjUKeJxjYWFhZWVj5+Ti4RcUkZBRUNUyMLWyd/HwCQiJiI5PSsvMyS8qq6ypb2\ rr7J0wefqseQuXrlizYfP2XfsOHj155vzla7fuPnj07MXrt+8/fv7y7fuPn79+/YaAX79+/vzx/dvXL58/fXz/7s2rF8qq6hpa2jp6+gaGRkbGYACkTMzML\ aysbe0dnJxd3T28vH39/AODgkNCwyMio6JjY+MTEpOSU1LTMjKzcnLzC4pKyiqraxua2zp7JkyeNmveoqUr127cumPPgSMnzly4cuPOgycv3nz8+tPE0tbJ\ 3ScwPCYpI6+0urGjb8qM2fPmz58/b+6c2bNmzpg2dcrkSRMn9Pf39fb2dHd1drS3tbY0NzXU11RXlpcWFeRlZ6QmJcREhgUH+nl7uLk6Odrb2dqAgK2tHdS\ lQIcGBoeGRwIArFub1Q==" r. 256,1,1,3,3 round. _palette_topo : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM2OTEKeJxT19TS1tXTNzQyNjE1s7C0sraxtbWzt3dwcAQBBwcHe3s7OztbCA\ Cy7MFyTk7Ozi6urm7u7h6enl7e3j6+fn7+AYFBwSGhYRGR0TFxCUkp6Zk5+UVllbWNrZ29E6fOmrd4+eoNW3bsPXj05LlL127de/Ts9fvP33/z8vELCAoJC\ 4uIiolLSEpKScvIysopKCqpqGvpGhibWVrbOTq7eXr7BQaHRUbHJSSnZWTnFRSXVVTXNTS3dXT3TZg8bebseQuXLF+5Zv2mrTt27zt46PDRY8dPnDx1+szZ\ c+cvXLp85eq16zdu3rp95979Bw8fPX767PmLV6/fvHv/8dPnr99/SEmDrFNWUVMHB4SBobGJmbmllY2tvaOzq5uHt69/YHBoRBTYR2mZ2bn5hcWl5ZXVtfW\ NTS1tHV09fRMmTZk2Y9ac+QsXL12+cvXa9Rs3b92+c/fe/QcPHz1+8tSZcxcuXr567cZNkPVg21+8fP3m7fsPQMu//fj5+4+qmoaWDjD8jYGhD7HVxc3DC2\ htQCAwMMMjIqNiYuPiE5OSU1LTMzKzcnLz8guLikvKyisqq2tq6xoam5pb29o7urp7+vonTJw0ZSrIMfMWLFoCdcy2Hbv27AO5BeSUy1ev37pz78Hjp89fv\ Xn/8fO3H7/+6ugZmphb2tg7uXp4+wWFRkTHJ6UCI664rLKmvqm1vbO7F2zupMmTp0yZOnXatGnTp8+YMXPmzFmzZs+eM2fu3Lnz5s2fv2DBgoULFwHBYgwA\ FVyyZOmy5StWrlq9dt0GRAgdO3EKGPHAmAdHvZS0tLQMMPqBCQAM5MHxoqEJS59m5uagBGptYwNLiSjADgIcXDx8/IPDomITUzKywQmwoaW9u2/S1JlzFix\ evmrdxq079xw4fPzUuUtXb9558AQAVgJv5Q==" _palette_turbid : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMTI2IDEgMSAzICMzNzAKeJx78fzpk8ePHj64f+/undu3bt64fu3qlcuXLl28cP7c2TOnT506cfzY0S\ OHDx08sH/f3j27d+7Yvm3L5k0b1q9ds2rl8mVLFi2YN2fWzOlTJ0/s7+3p6mhraWqoq6mqKC0uzM/NzkxPTU6Ii4kMDw0O9Pf19nR3dXa0t7W2NDc1NtTX1\ dZUV1X+8unD29cvnz15dP/u7RvXrlw6f/b0yWNHDh3Yu3vn9q2bNqxbvXI5xI4Z06ZMmtAHsaKxvra6sqKsBGRJTlZmelpKcmJCXGxMVGREWGhIcGCAv5+P\ t5enh7uri5Ojg72djbWVpYW5qYmxkaG+nq6OlqaGuqqKsqKC/KrlSxbOmz1j6qT+3s72lsa6msryksL8nCyguxPjY6IiQkOCAvx8vb083N1cXZyBZgENs7O\ 1tbWxsbEGASsYsIQACyAwNzc3MzMzNTUxMQbaZ2hgoK8HtFNHW0sTaK2aqoqKshLQZjlZGWkAeF+paA==" r. 256,1,1,3,3 round. # The color palettes below comes from the LoSpec palette list 'https://lospec.com/palette-list'. _palette_aurora : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM2ODYKeJxt0kFo01AYB/B3qYwwaDbCaEG6aS4T3ME4qJrgVpCiLUqolEgYbI\ QirIce9kQLknXbZSplWaGMSaeUYWU0g21QD0XbFLaasimMQqmUOiyBHiyOemgZZfpMql7Ew/v43vtOv+//QN/5yze89x89eZ58++HzMQJjQJnXizmktH4gh\ JSQN6SgbqO/bgHgfnfwejYwG88d1hqtdivEwSVZ1U60PQnSDiitv1Er31R5Cd5zWkmapTnok9ZrKXmRNdtI2ifJalbekDiONg/Stzj4UJIPNVmCHOOckWS5\ gbRaWS3suqnhcSFRKaEWaqNyJRWfvCuEN3fqCBViK9DPDA/1EhghuoHpbH//GTDC+CNesQc3DZgBBSzktGuZzwZdAoMDspedWlzLpMvlkzqqZDJh3uegvPE\ CQp3TemY1Av7xG9iuXz/zOaMjxkLGo/KnilvJeHQhMB18FkumCzVAO7xQkovqS8iN/PVralYSWRvL+fRRTv10cqylxGl9rG/qe1FNG2Cr4Q/o/opmrIOkxj\ kOblbVdFwS+eEBzGIhhjwML2QSsQDvplhhLrpTKn1FzWphIzDqnFgp11EznwpfWd07avxEjXr5aI2/aHf5PP7gi1fb+XSAdzBncfwcjlMOIZLYfQ89Qxdww\ kIAzGoFrD9+zYaPEv/zG0xWzx8ddP3GJhS9GPnrAeSeLkcXpyZnHojh2FYR0Yzhr8iQowdJmvPqMaqy5OWuU2Y7zeoRL0jrnf380qSTdHb9Ta2qqhIc0b+G\ lNzXvw4yrr/zr7W0YjYdj9hJWx/AcBdGCZcYMwZMBAZ4gPG4y+G0mkyEqafXhGGQJ9g71FzEv5H4iFCzdDThEddW2pnqrk7oNFG7VclEb24fFg1S5xShLyi\ ffewTnPaFoJJPh4NXb9t/Aejmgpc=" _palette_hocuspocus : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM2MzQKeJxjk3x5buu8lopLu5f0lsV5mHk7WxlpKkrINhWlhnla6iotmtpakR\ Ht7b5oUlNRUpCT+anN87qK4jzteqtz4/wdjVRfXNy1pK880UdUiJ+Xi52VyUJTXpSPg1ns57Vt08sjLGTeHp5bFW2vovrr3qFVfWVRLk9Ob53TmhMW8OT0p\ tlNOUF2DASAqIiwkKAAPx8vDzcXFycH+9PrZw5uW71g+uJpnQ1lWQkRMGW/Hp3aNKspK1wUAjR/3Tu2blpdWsDzFy9fv3n3/uOHG4fXz2orjGfklTN0DEwq\ bZ26bOfJm6/+MIg83t2fZq1amRUb6GiqIQu0go2NlUVgamtpWpibuUZ+Upifq6WBfn1xerSfk5n29LaytDB3c8OOyowoL0sdhRktxUlBrqYWx3fPbczwMZD\ ePK0ywdtcWevjkRnFweYy3Dc29GT7mcrIf9ndGW+rxM0Mda/AgcWdBVHuRqpk+n9qX3NFYWZ8eKCosCAfDxcHO5Dm5+XmZP9258jKSeUJEcBwnt9ZHOt5cP\ Oymf1NpSncvHwCgkIiouj+F5adluOmIchflwfyhqbslb3LJlQm+bismNJSkOBjo1eQHO4P9L/pjiVTmvPj/ewXT2goiPGxNN+zfEp9TpiLcU9lZoyPvZGqi\ CAo+pkZfz29sHvZxJriO1vA/uZ+cnhZZ7afoQjMP0yioiIiIsLCwkJCQoKCggLK3z69evbo3q1rl86dOnZo365tm9YuWzRv1rRJfV1tTXVVZc115cX52enJ\ 8dHhwf7e7s6/P715/vDOtQtH9+3avnn9mhVPz+9cPLE61R/mf3h6gMY/AyMzCxs7hxyu+AcA2aYUlw==" _palette_srb2 : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM0OTcKeJz7//398/vXzx/fv3398vnT+9vry/PT48P93e3N9dXlxfnZGcAS0/\ ra6kpzU2NDfJz3b169eDZIXXZ8sLe9sfZ/GIAYdBrG/fz0xul9G5dMh8ufX9+f76/P///19cPrZ7cXw4Tf3z+/f/38/vr8eH97fXn+/5+f3z69f/NyqIWh5\ QWZiVFB3s42prpqckD33Du+dmpNipcRAxT8/39///z6ePuXR+aWhzjqSV/dv2pqQ1Yo2Pkg+efH1vYUBxlKMKCB89vnt+eH26v/J+T/5QtmTOhsrCzKSooK\ BjrDuLo4Pzs5PjzY390ZqEZb+fXj25eB2neDgmVyd3P9uzund66Y3gZ2vK+rrRmG/2/vnl0d7awNcbY8uru2L+6vzo72t9dTV5AU4mZl+P/hwYUDGxZMaCh\ ICHAwUOD//+TE2knlUfZQb8vnQ/XB/P/x9eO7186f2D+3vzx+P1ABDv//h4c3If/3dzRUFILiwMvJ2lhbRdob6G9rc2N9bXVleWlxYf77l4/v3rx89uTm6u\ Ls5Ohg/zs7p5eGmknCbXoP9TeMXxzurC8vzM5AwP8wvr+3m6ONuZGuhrKcpAg/F8uKKQ3ZES4Gsujx//nhud3Lm2NMYKENEgX5Hx78z89tndOSHeRvb64uz\ 8/+Hxb/ADJVV38=" _palette_uzebox : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM1Mwp4nGNQ8ciduO3Wf4aRSkOBChR4QEEuFEyEgm1QcAsK/kPBcNFPLgilEK\ yiEPynEAAA16V90A==" _palette_amiga7800 : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM1MzMKeJxjEFQydglNK++YuWr3mbvv/v////3Z1SObF01syI/ztdGWMnQKTiltm7\ 5i56nbb/7+h4CfL64f27pkclNhgr+dfUBiUfOUpduO33j5Cyr9/93dM7tXzewoTwt1MZbVcwhMKm6Zumz7iZsvfwMlP9w/t3fN7K7KjHA3UxVhJgYGDkkta\ 5/YvPoJCzcdvvIUTRIM2MQ1LL2ic2r75m84eGD9vN6a7ChPC3UxVqg0g4CikXNIaln7jJW7Tj+8sH/d3J7qrEgPczVRFqAkn7yBY1BySeu05TtO3nr95///\ b0+vHN60cEJ9XqyPtZYkA5r/v399cvnQxgX9dbkx3laaEuwoll96/AVFMwfYen4FpEC6cXzb0inNRYkB9nqyPJjOu4MUdLoy3EBZLmkdW7/4gsZJi7ccvfb\ 8x/8fz68d3bJ4UmNBvJ+tjjSXlLaNb1x+w8RFm49cffYdHLwoIahu4RmVXdM7b/2Bi48+Q4L//b2ze1bP6qxID3M1UUZ1PVAW3X9o/p/eDIl3TgZkAHVk65\ xNt46sm9FcnBTooC/HC5Jh5peGu/DEvfdfPr97+QRug4+jaUxGCSTyX71+/eb1+y8/voPBJ0g81RZm1EDi/dt/ZAB1ZGWyr5pNUAay9/5+fApPnhZKAtw8g\ mLS8BDcvP/04undVZkR7maqIsJAIMDFzgEGvHL6oHTY1DetCdV/ADmMewY=" _palette_amiga7800mess : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM3MTQKeJxj1LONLG9ZsP3sjbsPXn349v//PyAEgxu7Z5aHOLoXLdh84smf/2jg7+\ fnV/ZvmFsV712/+cT9t7//QDVBNP9+/+T6qd2rpjame3nGpFZNmrts454j567euvfw2dkDG+bPmNBQmBrl76LKycbKo2DmFp6YW905ce7yTbsPX75weP3sy\ b0FYb6exrKycrKyTEyMTEDMKG2XWtY1e+PxCye2LWqrLQ7yM9eQ5GdhYBCTUdbQs3IPjC1s7Z2+dMfOTy9unzu0Y/W87sqcBA9XSx03n9DcCWsOXbz//N33\ vyD3AcGfn28fXti/dXVvYYQjI7r////88uzGhaOrJzZk+JtrKEjKqVoGZjX1zFx35Oixa0+e37p4aNOSGV2VeUl+ZvraKvLiUrKKqjZBCQX1E+at2nni1I7\ 1S+f21hWlR/na6kowMzIys/ApW7v6R6XmljVPmLNq2+XT+zatWjCpuTQjysfWQE2OQ8bQKTg2s7y1d8aS9TsOXnz78sn9OzeObVs5q60wxtNC0ye2oGXm6j\ 3n7zx8/urth88gB/5/8/jm2V1LJteXZod4OASnVfTMWLp+9/nHr7+CJP+8f3jtzP5N83vrC8OCXEyt7N0iynoWbth37OytZ59+/rxy+tjB7WsXTG4tTwm0U\ pWXQvf/mpkVYYG+NsCwF5cQYGNkYBGUU1RSkFfVtQiJzO1YtPrikc3zumszQ73ttVX4BXj5WHjFJWUsQxOT6/rnr9137t6bVy+ePbp17jDQA50lCd7muR0T\ l+07eenuiw8/QYGPlITe3rt6amlzZe/izUfO33rx/R807YAiCMh6c/P88TWTy2Oi8lvmbDt19ws0Xf6Dxb+vuTo4/hkYGBkZeCSV9Z3D4zOnTZ++ujInJNj\ TRl+GixEkycDJxcnJwa2gb+vqm1q75OiVGc2FCTGRfi5WBurywhyszABUd6ys" _palette_fornaxvoid1 : base642img \ "MSB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KMjU2IDEgMSAzICM1MjkKeJyrKi3ISU+OiwwN8HF3srM01Te3d/MLi03NLalp6Z48exEDO6+IlIK6ro\ mVg7tvSFTChwcXDmxYMKGhICHAwUBBgOH/p2e3zh3aunLuxNaKnIQQj8/Pb184snPtwmld9cXpMQGu/6HgwYEFDQlAHTeObJjbWZrkZ6UuzAAC/9/dPbN71\ cyO8rRQF2MlQQYGVk5eITEpOWV1bX0TCxsHRkYGFIChXkBez9YnOquqc8bybUevPP7/6enNM0AXNoDcp6EgsGzT3hOX77388v/Wtom5HioMjy8f2bpsekdl\ ZpS3ja4cP4Oqlr6xhY2jm5d/cERsYlrWvWvnju3btm7ZvGl9bXVleWlnDu/auGLB9L7WmpLspMgAjw8vH925eu7EwV1b1i5bMHNSD7r/+zqaasoKslLiIoJ\ 83BysTG8cWjMN6HG4+9H9r62hpqKkICcjKS4qLMjPy43u//8fHl46vGXptI7KrGgfWz2FBXNnz5w+dfLEvp6ujraWpgYGTiFpFV0ze4/AqOSc0rq2A2tmth\ UnGDDgAuj+/48GXn/6icI/c+Hy9Vt3Hz558frdp68///zH8D+RABb//79/ePHw5qVTh3dvWTuxLNrh/7d3T+9eOXN496ZVC2dO6GhgEFQydglNK++YuWr3m\ bvv/qP7n6B6KHhx4QA4Cfy/v39+fby9PAOXsIyqnjlGeAIAmOWLbw==" # Command that tries to compress a smooth-enough palette. compress_palette : N:=w do N-=1 +r. $N,1,1,3,2 round. ri. ..,3 round. deltaE. .. dEavg,dEmax="[ia,iM]" rm. e "N = "$N", dEavg = "$dEavg", dEmax = "$dEmax while $dEavg<0.75" && "$dEmax<1.25 N+=1 r $N,1,1,3,2 round. e "" img2base64 0,0 rm str=\"${}\" ('$str') s. x,-119 ('\\\n') if $!>2 a[0--3] .,x fi rm. i[0] ('" base642img \\\n"') ('"\n r. 256,1,1,3,3 round.\n"') a x ot pal.txt #@cli premula #@cli : Convert selected images with normal colors to premultiplied alpha colors. #@cli : After conversion, alpha channel of resulting images has value in [0,1] range. #@cli : See also: ''ipremula''. premula : e[^-1] "Convert image$? with normal colors to premultiplied alpha colors." to_a foreach { +channels. 100% /. 255 sh.. 0,{s#0-2} *. .. k[0] } #@cli pseudogray : _max_increment>=0,_JND_threshold>=0,_bits_depth>0 #@cli : Generate pseudogray colormap with specified increment and perceptual threshold. #@cli : If 'JND_threshold' is 0, no perceptual constraints are applied. #@cli : Default values: 'max_increment=5', 'JND_threshold=2.3' and 'bits_depth=8'. #@cli : $ pseudogray 5 pseudogray : check "isint(${1=5},0) && ${2=2.3}>=0 && isint(${3=8},1)" e[^-1] "Generate pseudogray colormap with increment $1, JND threshold $2 and $3 bits depth." # Generate all possible sRGB colors with given increments. {round(2^$3)},1,1,3,'x' if !$1 n. 0,255 return fi {$1+1},{$1+1},{$1+1},1,"x" +f. "y" +f. "z" a[-3--1] c r. {w*h*d},1,1,3,-1 f. "R = i(x,0,0,0); G = i(x,0,0,1); B = i(x,0,0,2); min(R,G,B)?-1:i" permute. cxyz discard. -1 r. 3,{h/3},1,1,-1 permute. yzcx r.. {w*100}% ri. ..,0,2 +[-2,-1] f. "R = i(x,0,0,0); G = i(x,0,0,1); B = i(x,0,0,2); max(R,G,B)>2^$3-1?-1:i" permute. cxyz discard. -1 r. 3,{h/3},1,1,-1 permute. yzcx n. 0,255 +srgb2lab. rv[-2,-1] a[-2,-1] y sort. +,x # Sort by increasing lightness. if !$2 rows. 1 else # Add perceptual constraint if requested. # Constraint 1 : keep colors close enough to equivalent 'pure' grays. s. y rv[-2,-1] . sh. 1,2 f. 0 rm. -[-2,-1] norm. <=. $2 *. 'x+1' discard. 0 -. 1 map. .. rm.. # Constraint 2 : remove neighboring colors that are above the JND. repeat 10000 { +srgb2lab. +shift. 0,{1-2*($>%2)},0,0,1 -[-2,-1] norm. <=. $2 if im rm. break fi *. 'y+1' discard. 0 -. 1 map. .. rm.. } transpose. fi => pseudogray$1 #@cli random_clut #@cli : Generate a 33x33x33 random 3D color LUT. #@cli : $ image.jpg random_clut +map_clut.. . #@cli random_clut : _seed = { >=0 | -1 } #@cli : Generate a 33x33x33 random 3D color LUT. #@cli : If specified 'seed' is positive, it is used as a seed for the random number generator \ #@cli : (so that using the same seed will return the same CLUT). #@cli : $ image.jpg random_clut +map_clut.. . +random_clut : l[] { check "isint(${1=-1},-1)" seed=$1 onfail seed=-1 noarg } e[^-1] "Generate a random 3D color LUT." if $seed>=0 srand $seed fi # Determine a set of random color keypoint. 1,1,1,3 1,8,1,1,"u<0.75 || !y?da_push(255*[ y&1, (y>>1)&1, (y>>2)&1 ])" rm. # Add some of the cube corners 1,{v(2,12)},1,1," do ( RGB = u([255,255,255]); dmin = inf; for (p = 0, p70?(da_push(RGB); break()); 1)" rm. da_freeze. # Randomly modify keypoint colors. rgb2hsl. mirror. c sort. +,y mirror. c +f. "begin(Lmax = 0); H = i0; S = i1; L = i2; u<0.5?( do ( nH = (H + 30*g)%360; nS = cut(S + g/4,0,1); nL = cut(L + g/8,0,1), _(while) nL=0,smoothness[%]>=0,src1,src2,...,dest1,dest2,... #@cli : Replace pixels from/to specified colors in selected images. #@cli : $ image.jpg +replace_color 40,3,204,153,110,255,0,0 replace_color : check "$1>=0 && $2>=0" l[] { (${3--1}) y c s c,2 col1={0,^} col2={1,^} rm } e[^-1] "Replace color ("$col1") by color ("$col2") in image$?, with tolerance $1 and smoothness $2." foreach { 1,1,1,100%,$col1 ri[1] [0] if $1 -[1] [0] norm[1] <=[1] $1 else ==[1] [0] l[1] { s c & } fi b[1] $2 1,1,1,{0,s},$col2 ri[2] [0] j[0] [2],0,0,0,0,1,[1] k[0] } #@cli retinex : _value_offset>0,_colorspace={ hsi | hsv | lab | lrgb | rgb | ycbcr },\ # 0<=_min_cut<=100,0<=_max_cut<=100,_sigma_low>0,_sigma_mid>0,_sigma_high>0 #@cli : Apply multi-scale retinex algorithm on selected images to improve color consistency. #@cli : (as described in the page ). #@cli : Default values: 'offset=1', 'colorspace=hsv', 'min_cut=1', 'max_cut=1', 'sigma_low=15','sigma_mid=80' \ # and 'sigma_high=250'. retinex : check "${1=5}>0 && ${3=1}>=0 && $3<=100 && ${4=1}>=0 && $4<=100 && ${5=15}>0 && ${6=80}>0 && ${7=250}>0" skip "${2=hsv}" e[^-1] "Apply Retinex color consistency algorithm on image$?, with value offset $1, colorspace '$2', cuts ($3,$4) and sigmas (${5-7})." if ['$2']=='hsi' mode=hsi_i elif ['$2']=='hsv' mode=hsv_v elif ['$2']=='lab' mode=lab_l elif ['$2']=='rgb' mode=rgb elif ['$2']=='lrgb' mode=lrgb elif ['$2']=='ycbcr' mode=ycbcr_y else error[0--2] "Command '$0': Invalid colorspace argument '$2'." fi ac "_retinex $1,${3--1}",$mode _retinex : - {im-$1} {[w,h,d,s]},1 repeat 3 { +b[0] {arg(1+$>,${4-6})} +/[0,-1] rm.. *[1,-1] } rm[0] log c $2%,{100-$3}% n 0,255 #@cli rgb2bayer : _start_pattern=0,_color_grid=0 #@cli : Transform selected color images to RGB-Bayer sampled images. #@cli : Default values: 'start_pattern=0' and 'color_grid=0'. #@cli : $ image.jpg +rgb2bayer 0 rgb2bayer : skip ${1=0},${2=0} e[^-1] "Transform image$? to a RGB-Bayer "${arg0\ !$2,color,monochrome}" grid, starting from pattern '$1'." to_rgb foreach { _rgb2bayer$1 ri[1] [0],0,2 * if !$2 s c + fi } _rgb2bayer0 : (1,0;0,0^0,1;1,0^0,0;0,1) _rgb2bayer1 : (0,0;0,1^0,1;1,0^1,0;0,0) _rgb2bayer2 : (0,1;0,0^1,0;0,1^0,0;1,0) _rgb2bayer3 : (0,0;1,0^1,0;0,1^0,1;0,0) #@cli rgb2cmy #@cli : Convert color representation of selected images from RGB to CMY. #@cli : $ image.jpg rgb2cmy split c rgb2cmy : e[^-1] "Convert color representation of image$? from RGB to CMY." to_rgb * -1 + 255 c 0,255 #@cli rgb2cmyk #@cli : Convert color representation of selected images from RGB to CMYK. #@cli : $ image.jpg rgb2cmyk split c #@cli : $ image.jpg rgb2cmyk split c fill[3] 0 append c cmyk2rgb rgb2cmyk : e[^-1] "Convert color representation of image$? from RGB to CMYK." rgb2cmy foreach { s c +min -[0-2] . +/. 255 -. 1 *. -1 +==. 0 +[-2,-1] /[0-2] . rm. a c } #@cli rgb2hcy #@cli : Convert color representation of selected images from RGB to HCY. #@cli : $ image.jpg rgb2hcy split c rgb2hcy : e[^-1] "Convert color representation of image$? from RGB to HCY." to_color f " M = max(R,G,B); C = M - min(R,G,B); H = 60*(!C?0:M==R?((G-B)/C)%6:M==G?(B-R)/C+2:(R-G)/C+4); Y = 0.299*R + 0.587*G + 0.114*B; [ H,C/255,Y/255 ]" #@cli rgb2hsi #@cli : Convert color representation of selected images from RGB to HSI. #@cli : $ image.jpg rgb2hsi split c rgb2hsi : e[^-1] "Convert color representation of image$? from RGB to HSI." to_color f " m = min(R,G,B); M = max(R,G,B); C = M - m; sum = R + G + B; H = 60*(!C?0:M==R?((G - B)/C)%6:M==G?(B - R)/C + 2:(R - G)/C + 4); S = sum<=0?0:1 - 3*m/sum; I = sum/(3*255); [ H, S, I ]" #@cli rgb2hsi8 #@cli : Convert color representation of selected images from RGB to HSI8. #@cli : $ image.jpg rgb2hsi8 split c rgb2hsi8 : e[^-1] "Convert color representation of image$? from RGB to HSI8." rgb2hsi _rgb2hsx8 #@cli rgb2hsl #@cli : Convert color representation of selected images from RGB to HSL. #@cli : $ image.jpg rgb2hsl split c #@cli : $ image.jpg rgb2hsl +split c add[-3] 100 mod[-3] 360 append[-3--1] c hsl2rgb rgb2hsl : e[^-1] "Convert color representation of image$? from RGB to HSL." to_color f " m = min(R,G,B); M = max(R,G,B); C = M - m; H = 60*(!C?0:M==R?((G - B)/C)%6:M==G?(B - R)/C + 2:(R - G)/C + 4); L = 0.5*(m + M)/255; S = L==1 || !L?0:C/(1 - abs(2*L - 1))/255; [ H, S, L ]" #@cli rgb2hsl8 #@cli : Convert color representation of selected images from RGB to HSL8. #@cli : $ image.jpg rgb2hsl8 split c rgb2hsl8 : e[^-1] "Convert color representation of image$? from RGB to HSL8." rgb2hsl _rgb2hsx8 #@cli rgb2hsv #@cli : Convert color representation of selected images from RGB to HSV. #@cli : $ image.jpg rgb2hsv split c #@cli : $ image.jpg rgb2hsv +split c add[-2] 0.3 cut[-2] 0,1 append[-3--1] c hsv2rgb rgb2hsv : e[^-1] "Convert color representation of image$? from RGB to HSV." to_color f " M = max(R,G,B); C = M - min(R,G,B); H = 60*(!C?0:M==R?((G - B)/C)%6:M==G?(B - R)/C + 2:(R - G)/C + 4); S = M<=0?0:C/M; [ H, S, M/255 ]" #@cli rgb2hsv8 #@cli : Convert color representation of selected images from RGB to HSV8. #@cli : $ image.jpg rgb2hsv8 split c rgb2hsv8 : e[^-1] "Convert color representation of image$? from RGB to HSV8." rgb2hsv _rgb2hsx8 _rgb2hsx8 : foreach { sh[0] 0 *. 0.708333 rm. sh[0] 1,2 *. 255 rm. } #@cli rgb2int #@cli : Convert color representation of selected images from RGB to INT24 scalars. #@cli : $ image.jpg rgb2int rgb2int : e[^-1] "Convert color representation of image$? from RGB to INT24 scalars." to_rgb round foreach { s c <<[0] 16 <<[1] 8 + } #@cli rgb2jzazbz : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Jzazbz. #@cli : Default value: 'illuminant=2'. rgb2jzazbz : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to Jzazbz, using the "${arg0\ $illu,D50,D65,E}" illuminant." rgb2xyz $illu xyz2jzazbz #@cli rgb2lab : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Lab. #@cli : Default value: 'illuminant=2'. rgb2lab : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to Lab, using the "${arg0\ $illu,D50,D65,E}" illuminant." rgb2xyz $illu xyz2lab $illu #@cli rgb2lab8 : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Lab8. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg rgb2lab8 split c rgb2lab8 : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to Lab8, using the "${arg0\ $illu,D50,D65,E}" illuminant." rgb2lab $illu foreach { sh[0] 0 *. 2.55 rm. sh[0] 1 +. 127 *. 0.85 rm. sh[0] 2 +. 149 *. 0.836 rm. } c 0,255 #@cli rgb2lch : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Lch. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg rgb2lch split c rgb2lch : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to Lch, using the "${arg0\ $illu,D50,D65,E}" illuminant." rgb2lab $illu lab2lch #@cli rgb2lch8 : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to Lch8. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg rgb2lch8 split c rgb2lch8 : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to Lch8, using the "${arg0\ $illu,D50,D65,E}" illuminant." pi,facth:=[pi,255/(2*pi)] rgb2lch $illu foreach { sh[0] 0 *. 2.55 rm. sh[0] 1 *. 1.1086 rm. sh[0] 2 +. $pi *. $facth rm. } c 0,255 #@cli rgb2luv #@cli : Convert color representation of selected images from RGB to LUV. #@cli : $ image.jpg rgb2luv split c rgb2luv : e[^-1] "Convert color representation of image$? from RGB to LUV." foreach { +rgb2xyz rgb2lab.. channels.. 0 s. c *. 3 +*.. 15 +[-2,-1] +. ... +. 1e-8 # Z <- X+15Y+3Z *... 4 *.. 9 /[-3,-2] . rm. -.. 0.2009 -. 0.4610 +*... 13 *... . *[-2,-1] a c } #@cli rgb2oklab #@cli : Convert color representation of selected images from RGB to Oklab. #@cli : (see colorspace definition at: ). #@cli : See also: ''oklab2rgb''. rgb2oklab : e[^-1] "Convert color representation of image$? from RGB to Oklab." foreach { split_opacity to_rgb[0] /[0] 255 f[0] " l = cbrt(0.4121656120*R + 0.5362752080*G + 0.0514575653*B); m = cbrt(0.2118591070*R + 0.6807189584*G + 0.1074065790*B); s = cbrt(0.0883097947*R + 0.2818474174*G + 0.6302613616*B); [ 0.2104542553*l + 0.7936177850*m - 0.0040720468*s, 1.9779984951*l - 2.4285922050*m + 0.4505937099*s, 0.0259040371*l + 0.7827717662*m - 0.8086757660*s ]" a c } #@cli rgb2ryb #@cli : Convert color representation of selected images from RGB to RYB. #@cli : $ image.jpg rgb2ryb split c rgb2ryb : e[^-1] "Convert color representation of image$? from RGB to RYB." to_color f "red = R; green = G; blue = B; white = min(red,green,blue); red-=white; green-=white; blue-=white; maxgreen = max(red,green,blue); yellow = min(red,green); red-=yellow; green-=yellow; blue>0 && green>0?(blue/=2; green/=2); yellow+=green; blue+=green; maxyellow = max(red,yellow,blue); maxyellow>0?( N = maxgreen/maxyellow; red*=N; yellow*=N; blue*=N; ); red+=white; yellow+=white; blue+=white; [ red,yellow,blue ]" #@cli rgb2srgb #@cli : Convert color representation of selected images from linear RGB to sRGB. rgb2srgb : e[^-1] "Convert color representation of image$? from linear RGB to sRGB." f "val = i/255; sval = val<=0.0031308?val*12.92:1.055*val^0.416667 - 0.055; cut(255*sval,0,255)" #@cli rgb2xyz : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to XYZ. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg rgb2xyz split c rgb2xyz : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to XYZ, using the "${arg0\ $illu,D50,D65,E}" illuminant." if $illu==2 # E mix_rgb {[0.488718,0.3106803,0.2006017,\ 0.1762044,0.8129847,0.0108109,\ 0,0.0102048,0.9897952]/255} elif $illu==1 # D65 mix_rgb {[0.4124564,0.3575761,0.1804375,\ 0.2126729,0.7151522,0.0721750,\ 0.0193339,0.1191920,0.9503041]/255} else # D50 mix_rgb {[0.43603516,0.38511658,0.14305115,\ 0.22248840,0.71690369,0.06060791,\ 0.01391602,0.09706116,0.71392822]/255} fi #@cli rgb2xyz8 : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from RGB to XYZ8. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg rgb2xyz8 split c rgb2xyz8 : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from RGB to XYZ8, using the "${arg0\ $illu,D50,D65,E}" illuminant." rgb2xyz $illu foreach { sh[0] 0 *. 255 rm. sh[0] 1 *. 255 rm. sh[0] 2 *. 231.8182 rm. } #@cli rgb2yiq #@cli : Convert color representation of selected images from RGB to YIQ. #@cli : $ image.jpg rgb2yiq split c rgb2yiq : e[^-1] "Convert color representation of image$? from RGB to YIQ." mix_rgb 0.299,0.587,0.114,\ 0.595716,-0.274453,-0.321263,\ 0.211456,-0.522591,0.311135 #@cli rgb2yiq8 #@cli : Convert color representation of selected images from RGB to YIQ8. #@cli : $ image.jpg rgb2yiq8 split c rgb2yiq8 : e[^-1] "Convert color representation of image$? from RGB to YIQ." rgb2yiq foreach { sh[0] 1 +. 151.908 *. 0.8393238012481239 rm. sh[0] 2 +. 133.261 *. 0.9567690472081104 rm. } #@cli rgb2ycbcr #@cli : Convert color representation of selected images from RGB to YCbCr. #@cli : $ image.jpg rgb2ycbcr split c rgb2ycbcr : e[^-1] "Convert color representation of image$? from RGB to YCbCr." mix_rgb 66,129,25,-38,-74,112,112,-94,-18 foreach { sh[0] 0,2 +. 128 /. 256 rm. sh[0] 0 +. 16 rm. sh[0] 1,2 +. 128 rm. } #@cli rgb2yuv #@cli : Convert color representation of selected images from RGB to YUV. #@cli : $ image.jpg rgb2yuv split c rgb2yuv : e[^-1] "Convert color representation of image$? from RGB to YUV." mix_rgb {[0.299,0.587,0.114,\ -0.14713,-0.28886,0.436,\ 0.615,-0.51498,-0.10001]/255} #@cli rgb2yuv8 #@cli : Convert color representation of selected images from RGB to YUV8. #@cli : $ image.jpg rgb2yuv8 split c rgb2yuv8 : e[^-1] "Convert color representation of image$? from RGB to YUV8." rgb2yuv foreach { sh[0] 0 *. 255 rm. sh[0] 1 +. 0.44 *. 289.773 rm. sh[0] 2 +. 0.62 *. 205.645 rm. } #@cli remove_opacity #@cli : Remove opacity channel of selected images. remove_opacity : e[^-1] "Remove opacity channel of image$?." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==2 channels 0 elif s==4 channels 0,2 fi } #@cli ryb2rgb #@cli : Convert color representation of selected images from RYB to RGB. ryb2rgb : e[^-1] "Convert color representation of image$? from RYB to RGB." to_color f "red = R; yellow = G; blue = B; white = min(red,yellow,blue); red-=white; yellow-=white; blue-=white; maxyellow = max(red,yellow,blue); green = min(yellow,blue); yellow-=green; blue-=green; blue>0 && green>0?(blue*=2; green*=2); red+=yellow; green+=yellow; maxgreen = max(red,green,blue); maxgreen>0?( N = maxyellow/maxgreen; red*=N; green*=N; blue*=N; ); red+=white; green+=white; blue+=white; [ red,green,blue ]" #@cli select_color : tolerance[%]>=0,col1,...,colN #@cli : Select pixels with specified color in selected images. #@cli : $ image.jpg +select_color 40,204,153,110 #@cli : $$ https://gmic.eu/oldtutorial/_select_color select_color : skip ${1=0} e[^-1] "Select color (${2--1}) in image$?, with tolerance $1." foreach { +fc ${2--1} - norm <= $1 } #@cli sepia #@cli : Apply sepia tones effect on selected images. #@cli : $ image.jpg sepia sepia : e[^-1] "Apply sepia tones effect on image$?." (0,44,115,143,196,244^0,20,84,119,184,235^0,5,44,73,144,200) r. 256,1,1,3,3 foreach[^-1] { pass. split_opacity[0] luminance[0] map[0] . rm. a c } rm. #@cli solarize #@cli : Solarize selected images. #@cli : $ image.jpg solarize solarize : e[^-1] "Solarize image$?." luminance n 0,128 map 1 #@cli split_colors : _tolerance>=0,_max_nb_outputs>0,_min_area>0 #@cli : Split selected images as several image containing a single color. #@cli : One selected image can be split as at most 'max_nb_outputs' images. #@cli : Output images are sorted by decreasing area of extracted color regions and have an additional alpha-channel. #@cli : Default values: 'tolerance=0', 'max_nb_outputs=256' and 'min_area=8'. #@cli : $ image.jpg quantize 5 +split_colors , display_rgba split_colors : check "${1=0}>=0 && isint(${2=256},1) && ${3=8}>=1" e[^-1] "Split image$? as single color outputs, with tolerance $1, $2 maximal outputs and minimal color area $3." foreach { +label 0,1 norm. area. 0,1 # Loop over biggest color regions. repeat $2-1 { coordsM={1,[xM,yM,zM,cM]} if {1,i($coordsM)<$3} break fi color={0,I($coordsM)} +select_color[0] $1,$color ==. 0 *[1] . ==. 0 } if iM#1 !=[1] 0 mv[1] $! # Residual mask, if any. else rm[1] fi r[^0] [0],[0],[0],{0,s+1} N:=$!-1 sh[^0] 0,{s-2} *[-$N--1] [0] rm[-$N--1] sh[^0] 100% *[-$N--1] 255 rm[0,-$N--1] } #@cli split_opacity #@cli : Split color and opacity parts of selected images. #@cli : This command returns 1 or 2 images for each selected image, whether it has an opacity channel or not. split_opacity : e[^-1] "Split color and opacity parts of image$?." foreach { s c,{s==4?-3:s==2?-1:-s} } #@cli split_vector : keep_splitting_values={ + | - },value1,_value2,... #@cli : Split selected images into multiple parts, where specified vector '[value1,_value2,...]' is the separator. split_vector : e[^-1] "Split image$?, according to vector [${2--1}]." foreach { s:=s permute cxyz s $1,${2--1} $!,1,1,1,"resize(#x,$s,h#x/$s,1,1,-1)" rm. permute cyzx } #@cli srgb2lab : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from sRGB to Lab. #@cli : Default value: 'illuminant=2'. #@cli : $ image.jpg srgb2lab split c #@cli : $ image.jpg srgb2lab +split c mul[-2,-1] 2.5 append[-3--1] c lab2srgb srgb2lab : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from sRGB to Lab, using the "${arg0\ $illu,D50,D65,E}" illuminant." srgb2rgb rgb2lab $illu #@cli srgb2lab8 : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from sRGB to Lab8. #@cli : Default value: 'illuminant=2'. srgb2lab8 : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from sRGB to Lab8, using the "${arg0\ $illu,D50,D65,E}" illuminant." srgb2rgb rgb2lab8 $illu #@cli srgb2rgb #@cli : Convert color representation of selected images from sRGB to linear RGB. srgb2rgb : e[^-1] "Convert color representation of image$? from sRGB to linear RGB." f "sval = i/255; val = sval<=0.04045?sval/12.92:((sval + 0.055)/(1.055))^2.4; cut(255*val,0,255)" #@cli to_a #@cli : Force selected images to have an alpha channel. to_a : e[^-1] "Force image$? to have an alpha channel." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==1||s==3 channels 0,{s} sh. {s-1} f. 255 rm. fi } #@cli to_color #@cli : Force selected images to be in color mode (RGB or RGBA). to_color : e[^-1] "Force image$? to be in color mode." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==2 r 100%,100%,1,4,0,1,0,0,0,1 elif s==1 r 100%,100%,1,3,1 fi } #@cli to_colormode : mode={ 0:adaptive | 1:G | 2:GA | 3:RGB | 4:RGBA } #@cli : Force selected images to be in a given color mode. #@cli : Default value: 'mode=0'. to_colormode : check "isint(${1=0},0,4)" s,err:="$1?[ $1,$1 ]:(rgb = a = e = 0; repeat (l,k,rgb|=s#k>=3; a|=!(s#k%2); e|=!s#k||s#k>4); [ (rgb?3:1) + a,e ])" if $err foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." fi } fi ss=${"arg "$s",gray,graya,rgb,rgba"} e[^-1] "Force image$? to be in "{`uppercase(['$ss'])`}" mode." to_$ss #@cli to_gray #@cli : Force selected images to be in GRAY mode. #@cli : $ image.jpg +to_gray to_gray : e[^-1] "Force image$? to be in GRAY mode." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s>=3 luminance elif s==2 channels 0 fi } #@cli to_graya #@cli : Force selected images to be in GRAYA mode. to_graya : e[^-1] "Force image$? to be in GRAYA mode." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==4 s c,-3 luminance.. a c elif s==3 luminance channels 0,1 sh. 1 f. 255 rm. elif s==1 channels 0,1 sh. 1 f. 255 rm. fi } #@cli to_pseudogray : _max_step>=0,_is_perceptual_constraint={ 0 | 1 },_bits_depth>0 #@cli : Convert selected scalar images ([0-255]-valued) to pseudo-gray color images. #@cli : Default values: 'max_step=5', 'is_perceptual_constraint=1' and 'bits_depth=8'. #@cli : The original pseudo-gray technique has been introduced by Rich Franzen . #@cli : Extension of this technique to arbitrary increments for more tones, has been done by David Tschumperlé. to_pseudogray : check "isint(${1=5},0) && isint(${3=8},1)" skip ${2=1} e[^-1] "Convert scalar image$? to pseudo-gray color images, with steps $1." channels 0 srgb2rgb pseudogray $1,{2.3*$2},$3 # Compute colormap with 65336 entries, to have match corresponding lightness. +srgb2lab. channels. 0 *. {65535/100} round. rows. 0,2 rv[-2,-1] permute. xcyz +. 1 a[-2,-1] y pointcloud. 0 +norm. !=. 0 distance. 1 *. -1 watershed.. . rm. -. 1 # Map colormap to images, with lightness preservation. repeat $!-1 { to_rgb[$>] rgb2lab[$>] channels[$>] 0 *[$>] {65535/100} round[$>] c[$>] 0,65535 map[$>] . } rm. #@cli to_rgb #@cli : Force selected images to be in RGB mode. to_rgb : e[^-1] "Force image$? to be in RGB mode." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==4 channels 0,2 elif s==2 channels 0 r 100%,100%,100%,3 elif s==1 r 100%,100%,100%,3 fi } #@cli to_rgba #@cli : Force selected images to be in RGBA mode. to_rgba : e[^-1] "Force image$? to be in RGBA mode." foreach { if !s||s>4 error[0--5] "Command '$0': Image ["$>"] is not a G,GA,RGB or RGBA image ("{s}" channels)." elif s==3 channels 0,3 sh. 3 f. 255 rm. elif s==2 r 100%,100%,100%,4,0,1,0,0,0,1 elif s==1 r 100%,100%,100%,4 sh. 3 f. 255 rm. fi } #@cli to_automode #@cli : Force selected images to be in the most significant color mode. #@cli : This commands checks for useless alpha channel (all values equal to 255), as well as #@cli : detects grayscale images encoded as color images. to_automode : e[^-1] "Force image$? to be in most significant color mode." foreach { # Check for useless alpha channel. if s==2||s==4 # GREYA or RGBA sh 100% if im==iM" && "im==255 rm. channels 0,{s-2} else rm. fi fi # Check for useless color. if s==3||s==4 # RGB or RGBA eval. "begin(is_gray = 1); is_gray && (i0!=i1 || i0!=i2)?(is_gray = 0); end(merge(is_gray,&&); set('is_gray',is_gray))" if $is_gray s c,-3 channels[0] 0 a c fi fi } #@cli xyz2jzazbz #@cli : Convert color representation of selected images from XYZ to RGB. xyz2jzazbz : e[^-1] "Convert color representation of image$? from XYZ to Jzazbz." foreach { split_opacity f[0] ${-_jzazbz_const}" X = i0*255; Y = i1*255; Z = i2*255; Xp = Jzazbz_b*X - (Jzazbz_b - 1)*Z; Yp = Jzazbz_g*Y - (Jzazbz_g - 1)*X; Zp = Z; L = 0.41478972*Xp + 0.579999*Yp + 0.0146480*Zp; M = -0.2015100*Xp + 1.120649*Yp + 0.0531008*Zp; S = -0.0166008*Xp + 0.264800*Yp + 0.6684799*Zp; tmp = (L/peakLum)^Jzazbz_n; Lp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p; tmp = (M/peakLum)^Jzazbz_n; Mp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p; tmp = (S/peakLum)^Jzazbz_n; Sp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p; Iz = 0.5*Lp + 0.5*Mp; az = 3.52400*Lp - 4.066708*Mp + 0.542708*Sp; bz = 0.199076*Lp + 1.096799*Mp - 1.295875*Sp; Jz = (1 + Jzazbz_d)*Iz/(1 + Jzazbz_d*Iz) - Jzazbz_d0; [ Jz,az,bz ]" a c } #@cli xyz2lab : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from XYZ to Lab. #@cli : Default value: 'illuminant=2'. xyz2lab : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from XYZ to Lab, using the "${arg0\ $illu,D50,D65,E}" illuminant." to_color f " begin( const epsilon = 216/24389; const kappa = 24389/27; lab(x) = (x>epsilon?cbrt(x):(x*kappa + 16)/116); D65 = [ 0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041 ]; D50 = [ 0.43603516, 0.38511658, 0.14305115, 0.22248840, 0.71690369, 0.06060791, 0.01391602, 0.09706116, 0.71392822 ]; E = [ 0.488718,0.3106803,0.2006017, 0.1762044,0.8129847,0.0108109, 0,0.0102048,0.9897952 ]; white = ("$illu"==2?E:"$illu"==1?D65:D50)*[ 1,1,1 ]; ); xr = i0/white[0]; yr = i1/white[1]; zr = i2/white[2]; fx = lab(xr); fy = lab(yr); fz = lab(zr); [ cut(116*fy - 16,0,100), 500*(fx - fy), 200*(fy - fz) ]" #@cli xyz2rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from XYZ to RGB. #@cli : Default value: 'illuminant=2'. xyz2rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from XYZ to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." if $illu==2 # E mix_rgb {[2.3706743,-0.9000405,-0.4706338,\ -0.513885,1.4253036,0.0885814,\ 0.0052982,-0.0146949,1.0093968]*255} elif $illu # D65 mix_rgb {[3.2404542,-1.5371385,-0.4985314,\ -0.9692660,1.8760108,0.0415560,\ 0.0556434,-0.2040259,1.0572252]*255} else # D50 mix_rgb {[3.134274799724,-1.617275708956,-0.490724283042,\ -0.978795575994,1.916161689117,0.033453331711,\ 0.071976988401,-0.228984974402,1.405718224383]*255} fi c 0,255 #@cli xyz82rgb : illuminant={ 0:D50 | 1:D65 | 2:E } : (no arg) #@cli : Convert color representation of selected images from XYZ8 to RGB. #@cli : Default value: 'illuminant=2'. xyz82rgb : skip "${1=,}" l[] { if isnum("$1") illu:="$1>1?2:$1>0" else if ["'$1'"]!=',' noarg fi illu=2 fi onfail noarg illu=2 } e[^-1] "Convert color representation of image$? from XYZ8 to RGB, using the "${arg0\ $illu,D50,D65,E}" illuminant." foreach { sh[0] 0 /. 255 rm. sh[0] 1 /. 255 rm. sh[0] 2 /. 231.8182 rm. } xyz2rgb $illu #@cli ycbcr2rgb #@cli : Convert color representation of selected images from YCbCr to RGB. ycbcr2rgb : e[^-1] "Convert color representation of image$? from YCbCr to RGB." foreach { sh[0] 0 -. 16 rm. sh[0] 1,2 -. 128 rm. sh[0] 0,2 mix_rgb. 298,0,409,\ 298,-100,-208,\ 298,516,0 +. 128 /. 256 c. 0,255 rm. } #@cli yiq2rgb #@cli : Convert color representation of selected images from YIQ to RGB. yiq2rgb : e[^-1] "Convert color representation of image$? from YIQ to RGB." mix_rgb 1,0.9563,0.6210,\ 1,-0.2721,-0.6474,\ 1,-1.1070,1.7046 c 0,255 #@cli yiq82rgb #@cli : Convert color representation of selected images from YIQ8 to RGB. yiq82rgb : e[^-1] "Convert color representation of image$? from YIQ8 to RGB." foreach { sh[0] 1 /. 0.8393238012481239 -. 151.908 rm. sh[0] 2 /. 0.9567690472081104 -. 133.261 rm. } mix_rgb 1,0.9563,0.6210,\ 1,-0.2721,-0.6474,\ 1,-1.1070,1.7046 c 0,255 #@cli yuv2rgb #@cli : Convert color representation of selected images from YUV to RGB. yuv2rgb : e[^-1] "Convert color representation of image$? from YUV to RGB." mix_rgb {[1,0,1.13983,\ 1,-0.39465,-0.5806,\ 1,2.03211,0]*255} c 0,255 #@cli yuv82rgb #@cli : Convert selected images from YUV8 to RGB color bases. yuv82rgb : e[^-1] "Convert color representation of image$? from YUV8 to RGB." foreach { sh[0] 0 /. 255 rm. sh[0] 1 /. 289.773 -. 0.44 rm. sh[0] 2 /. 205.645 -. 0.62 rm. } yuv2rgb #--------------------------------- # #@cli :: Geometry Manipulation # #--------------------------------- #@cli a : eq. to 'append' : (+) #@cli append : [image],axis,_centering : axis,_centering : (+) #@cli : Append specified image to selected images, or all selected images together, along specified axis. #@cli : (eq. to 'a').\n #@cli : 'axis' can be { x | y | z | c }. #@cli : Usual 'centering' values are { 0:left-justified | 0.5:centered | 1:right-justified }. #@cli : Default value: 'centering=0'. #@cli : $ image.jpg split y,10 reverse append y #@cli : $ image.jpg repeat 5 { +rows[0] 0,{10+18*$>}% } remove[0] append x,0.5 #@cli : $ image.jpg append[0] [0],y #@cli append_tiles : _M>=0,_N>=0,0<=_centering_x<=1,0<=_centering_y<=1 #@cli : Append MxN selected tiles as new images. #@cli : If 'N' is set to 0, number of rows is estimated automatically. #@cli : If 'M' is set to 0, number of columns is estimated automatically. #@cli : If 'M' and 'N' are both set to '0', auto-mode is used. #@cli : If 'M' or 'N' is set to 0, only a single image is produced. #@cli : 'centering_x' and 'centering_y' tells about the centering of tiles when they have different sizes. #@cli : Default values: 'M=0', 'N=0', 'centering_x=centering_y=0.5'. #@cli : $ image.jpg split xy,4 append_tiles , append_tiles : check "isint(${1=0},0) && isint(${2=0},0) && inrange(${3=0},0,1) && inrange(${4=$3},0,1)" if !$! e[0--3] "Append image$? as a 0x0-tiled image." return elif !$1" && "!$2 # Auto-mode N:=int(sqrt($!)) M:=ceil($!/$N) e[0--3] "Append image$? as a "${M}x${N}"-tiled image (auto-mode)." elif !$2 # Auto-rows M=$1 N:=round($!/$1,1,1) e[0--3] "Append image$? as a "${M}x${N}"-tiled image." elif !$1 # Auto-columns M:=round($!/$2,1,1) N=$2 e[0--3] "Append image$? as a "${M}x${N}"-tiled image." else e[0--3] "Append image$?, as $1x$2-tiled images." M=$1 N=$2 fi W,H=${-max_wh} rs $W,$H,2,1 MN:=$M*$N if $!%$MN 0x{$MN-($!%$MN)} fi repeat $!/$MN { l[$>-{$>+$MN-1}] { repeat $!/$M { a[$>-{$>+$M-1}] x,$3 } a y,$4 } } #@cli apply_scales : "command",number_of_scales>0,_min_scale[%]>=0,_max_scale[%]>=0,_scale_gamma>0,_interpolation #@cli : Apply specified command on different scales of selected images. #@cli : 'interpolation' can be { 0:none | 1:nearest | 2:average | 3:linear | 4:grid | 5:bicubic | 6:lanczos }. #@cli : Default value: 'min_scale=25%', 'max_scale=100%' and 'interpolation=3'. #@cli : $ image.jpg apply_scales "blur 5 sharpen 1000",4 apply_scales : check "isint($2,1) && ${3=25%}>=0 && ${4=100%}>=0 && ${5=1}>0 && isint(${6=3},0)" skip "${1=}" s0="no" s1="nearest-neighbor" s2="average" s3="linear" s4="grid" s5="bicubic" s6="lanczos" e[^-1] "Apply command '$1' on image$? for $2 scales ($3 -> $4) and "${s{min(6,$6)}}" interpolation." foreach { nm={n} scale0:=ispercentage($3)?$3*max(w,h,d):$3 scale1:=ispercentage($4)?$4*max(w,h,d):$4 repeat $2 { scale:=$scale0+($scale1-$scale0)*($>/max(1,$2-1))^$5 w={0,w==1?1:max(1,round($scale*w/max(w,h,d)))} h={0,h==1?1:max(1,round($scale*h/max(w,h,d)))} d={0,d==1?1:max(1,round($scale*d/max(w,h,d)))} +r[0] $w,$h,$d,100%,$6 if narg("$1") l. { $1 } fi } rm[0] =>[^] $nm } #@cli autocrop : _axes,_value1,_value2,... #@cli : Autocrop selected images according to specified axes and values. #@cli : 'axes' can be { x | y | z | c | xy | xz | xc | yz | yc | zc | xyz | xyc | xzc | yzc | xyzc }. #@cli : If no 'axes' are provided, autocrop is assumed to be spatial only (e.g. 'axes=xyz'). #@cli : If no value arguments are provided, cropping value is automatically guessed. #@cli : Default values: 'axes=xyz'. #@cli : See also: ''autocrop_coords''. #@cli : $ 400,400,1,3 fill_color 64,128,255 ellipse 50%,50%,120,120,0,1,255 +autocrop autocrop : skip "${1=xyz}" __$0_coords_ ${1--1} if ${} noarg fi e[^-1] "Autocrop image$?, for axe"$s" '"$axes"' and value '"$value"'." foreach { _autocrop_coords $axes,$is_x,$is_y,$is_z,$is_c,$is_auto,$value if narg(${}) z ${} fi } #@cli autocrop_components : _threshold[%],_min_area[%]>=0,_is_high_connectivity={ 0 | 1 },\ # _output_type={ 0:crop | 1:segmentation | 2:coordinates } #@cli : Autocrop and extract connected components in selected images, according to a mask given as the last channel of #@cli : each of the selected image (e.g. alpha-channel). #@cli : Default values: 'threshold=0%', 'min_area=0.1%', 'is_high_connectivity=0' and 'output_type=1'. #@cli : $ 256,256 noise 0.1,2 eq 1 dilate_circ 20 label_fg 0,1 normalize 0,255 +neq 0 *[-1] 255 append c \ # +autocrop_components , autocrop_components : skip ${1=0%} check "${2=0.1%}>=0 && isbool(${3=0}) && isint(${4=1},0,2)" e[^-1] "Autocrop connected components from image$?, with threshold $1, minimal area $2, "\ ${arg0\ $3,low,high}" connectivity "\ "and output type set to '"${arg0\ $4,crop,segmentation,coordinates}"'.\n" foreach { min_area:=max(1,round(ispercentage($2)?$2*w*h:$2)) +channels 100% >. $1 area_fg. 0,$3 >=. $min_area # Discard background and small objects. +area. 0,1 <. $min_area -|[-2,-1] label_fg. 0,1 # Fill small holes in objects. # Extract detected objects. N:=iM repeat iM { n:=1+$> e "\r > "$n/$N rprogress {100*$n/$N} +==[1] $n +*[0,-1] rm.. if !$4 coords=${autocrop_coords.\ xyz} rm. +z[0] $coords elif $4==1 autocrop. else coords=${autocrop_coords.\ xyz} rm. ($coords) y. fi } rm[0,1] if !$! 0 fi if $4==2 a x fi } #@cli autocrop_coords : _axes,_value1,_value2,... #@cli : Return coordinates of the bounding box that would be used to autocrop selected images, \ # according to specified axes and values. #@cli : 'axes' can be { x | y | z | c | xy | xz | xc | yz | yc | zc | xyz | xyc | xzc | yzc | xyzc }. #@cli : If no 'axes' are provided, autocrop is assumed to be spatial only (e.g. 'axes=xyz'). #@cli : If no value arguments are provided, cropping value is automatically guessed. #@cli : Default values: 'axes=xyz'. #@cli : See also: ''autocrop''. __autocrop_coords_ : skip "${1=xyz},${2=}" is_noarg=0 if isin(['$1'],'x','y','z','c','xy','xz','xc','yz','yc','zc','xyz','xyc','xzc','yzc','xyzc') axes=$1 if isnum($2) value=${2--1} else value=auto fi else axes=xyz if isnum($1) value=${1--1} else value=auto is_noarg=1 fi fi s= if size(['$axes'])>1 s=s fi is_x,is_y,is_z,is_c:=s='$axes';[find(s,_'x')>=0,find(s,_'y')>=0,find(s,_'z')>=0,find(s,_'c')>=0] is_auto:=['$value']=='auto' u $is_noarg autocrop_coords : skip "${1=xyz},${2=}" __$0_ ${1--1} if ${} noarg fi e[^-1] "Return autocrop coordinates for image$?, for axe"$s" '"$axes"' and value '"$value"'." _autocrop_coords $axes,$is_x,$is_y,$is_z,$is_c,$is_auto,$value # _autocrop_coords : axes,is_x,is_y,is_z,is_c,is_auto,value1,... _autocrop_coords : l_value=${7--1} res,sep= foreach { x0,y0,z0,c0,x1,y1,z1,c1=-1 if $6 l_value:="const w1 = w - 1; const h1 = h - 1; const d1 = d - 1; vmed(I[0],I[0],I(w1,0,0),I(w1,h1,0),I(0,h1,0), # I[0] must be doubled! I(0,0,d1),I(w1,0,0),I(w1,h1,d1),I(0,h1,d1))" fi eval. " begin( x0 = y0 = z0 = c0 = inf; x1 = y1 = z1 = c1 = -inf; value = $5?(["$l_value"][0]):resize([ "$l_value" ],s,0,2); ); (!$5 && I!=value) || ($5 && i!=value)?( $2?(x0 = min(x0,x); x1 = max(x1,x)); $3?(y0 = min(y0,y); y1 = max(y1,y)); $4?(z0 = min(z0,z); z1 = max(z1,z)); $5?(c0 = min(c0,c); c1 = max(c1,c)); ); end( $2?(merge(x0,min); merge(x1,max); set('x0',isinf(x0)?0:x0); set('x1',isinf(x1)?w - 1:x1)); $3?(merge(y0,min); merge(y1,max); set('y0',isinf(y0)?0:y0); set('y1',isinf(y1)?h - 1:y1)); $4?(merge(z0,min); merge(z1,max); set('z0',isinf(z0)?0:z0); set('z1',isinf(z1)?d - 1:z1)); $5?(merge(c0,min); merge(c1,max); set('c0',isinf(c0)?0:c0); set('c1',isinf(c1)?s - 1:c1)); )" if !$2" && "max(${3-5}) x0,x1=0,{w-1} fi if !$3" && "max(${4-5}) y0,y1=0,{h-1} fi if !$4" && "$5 z0,z1=0,{d-1} fi ($x0,$y0,$z0,$c0;$x1,$y1,$z1,$c1) discard. -1 res.=$sep{^} sep=, rm. } u $res #@cli autocrop_seq : value1,value2,... | auto #@cli : Autocrop selected images using the crop geometry of the last one by specified vector-valued intensity, #@cli : or by automatic guessing the cropping value. #@cli : Default value: auto mode. #@cli : $ image.jpg +fill[-1] 0 ellipse[-1] 50%,50%,30%,20%,0,1,1 autocrop_seq 0 autocrop_seq : skip ${1=auto} e[^-1] "Auto-crop image$? using crop geometry of last image by vector '$*'." if !$! return fi is_auto:=['"$1"']=='auto' if $!==1 _autocrop$is_auto ${1--1} return fi coords=${autocrop_coords.\ ${1--1}} x0,y0,z0,x1,y1,z1:=[$coords][0,6] if $x0>$x1" || "$y0>$y1" || "$z0>$z1 i[0--2] 0 rm[1--1:2] else z $x0,$y0,$z0,$x1,$y1,$z1 fi #@cli channels : c0[%],_c1[%],_boundary_conditions #@cli : Keep only specified channels of selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'c1=c0' and 'boundary_conditions=0'. #@cli : $ image.jpg channels 1 #@cli : $ image.jpg luminance channels 0,2 channels : check "isint(${3=0},0,3)" skip ${2=$1} e[^-1] "Keep channels $1...$2 of image$?, with "${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." z 0,0,0,$1,100%,100%,100%,$2,$3 #@cli columns : x0[%],_x1[%],_boundary_conditions #@cli : Keep only specified columns of selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'x1=x0' and 'boundary_conditions=0'. #@cli : $ image.jpg columns -25%,50% columns : check "isint(${3=0},0,3)" skip ${2=$1} e[^-1] "Keep columns $1...$2 of image?, with "${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." z $1,$2,$3 #@cli z : eq. to 'crop'. : (+) #@cli crop : x0[%],x1[%],_boundary_conditions : x0[%],y0[%],x1[%],y1[%],_boundary_conditions : \ # x0[%],y0[%],z0[%],x1[%],y1[%],z1[%],_boundary_conditions : \ # x0[%],y0[%],z0[%],c0[%],x1[%],y1[%],z1[%],c1[%],_boundary_conditions : (+) #@cli : Crop selected images with specified region coordinates. #@cli : (eq. to 'z').\n #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'boundary_conditions=0'. #@cli : $ image.jpg +crop -230,-230,280,280,1 crop[0] -230,-230,280,280,0 #@cli : $ image.jpg crop 25%,25%,75%,75% #@cli diagonal #@cli : Transform selected vectors as diagonal matrices. #@cli : $ 1,10,1,1,'y' +diagonal diagonal : e[^-1] "Transform vector$? as diagonal matrix." y foreach { r {h+1},100%,1,1,0 r {h},100%,1,1,-1 } # downsize_aliased : 100<=factor<=0 # Downsize selected images with a specific algorithm to better render anti-aliased rendering # over transparent background (from aliased transparent images, manage transparent border pixels with more care). downsize_aliased : check "${1=50}>=0 && $1<=100" if $1==100 return elif !$1 r 1,1,1,100%,2 return fi N:=ceil(1+100/$1) foreach { split_opacity if $!==1 continue fi +dilate.. $N +==.. 0 j[0] ..,0,0,0,0,1,. rm[-2,-1] a c r $1%,$1%,$1%,100%,2 } #@cli elevate : _depth,_is_plain={ 0 | 1 },_is_colored={ 0 | 1 } #@cli : Elevate selected 2D images into 3D volumes. #@cli : Default values: 'depth=64', 'is_plain=1' and 'is_colored=1'. elevate : check "${1=64}>0" skip ${2=1},${3=1} e[^-1] "Elevate 2D image$? into $1-slices volume(s)." r 100%,100%,1,100% foreach { nm={n} +norm 100%,100%,$1,{$3?{0,s}:1} m={-2,im} d={-2,iM-$m} repeat $1 { if $2 +>=[1] {$m+$d*($>+1)/$1} else +ir[1] {$m+$d*$>/$1},{$m+$d*($>+1)/$1} fi r. 100%,100%,1,.. if $3 *. [0] fi j.. .,0,0,$> rm. } rm[0,1] => $nm } #@cli expand : axes,size[%],_boundary_conditions={ 0:dirichlet | 1:neumann | 2:periodic | 3:mirror } #@cli : Expand selected images along the specified axes. #@cli : 'axes' can be { x | y | z | c | xy | xz | xc | yz | yc | zc | xyz | xyc | xzc | yzc | xyzc }. #@cli : Default value: 'boundary_conditions=0'. #@cli : $ image.jpg expand xy,30 expand : check "isint(${3=0},0,3)" e[^-1] "Expand image$? along ax"{`size(['$1'])>1?_'e':'i'`}"s '$1', with size $2 and "\ ${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." no_x,no_y,no_z,no_c:="arg = ['$1']; [ find(arg,_'x')<0, find(arg,_'y')<0, find(arg,_'z')<0, find(arg,_'c')<0 ]" $!,1,1,1," const ipc = ispercentage($2); const siz2 = 2*$2; nw = w#x + ($no_x?0:ipc?floor(w#x*siz2):siz2); nh = h#x + ($no_y?0:ipc?floor(h#x*siz2):siz2); nd = d#x + ($no_z?0:ipc?floor(d#x*siz2):siz2); ns = s#x + ($no_c?0:ipc?floor(s#x*siz2):siz2); nw>0 && nh>0 && nd>0 && nd>0?resize(#x,nw,nh,nd,ns,0,$3,0.5,0.5,0.5,0.5):resize(#x,0); " rm. #@cli extract : "condition",_output_type={ 0:xyzc-coords | 1:xyz-coords | 2:scalar-values | 3:vector-values | \ # 4:xyzc-coords + scalar value | 5:xyz-coords + vector-values } #@cli : Extract a list of coordinates or values from selected image, where #@cli : specified mathematical condition holds. #@cli : For N coordinates matching, result is a 1xNx1x4 image. #@cli : Default values: 'output_type=0'. #@cli : $ sp lena +extract "norm(I)>128",3 extract : check "isint(${2=0},0,5)" s0,s1,s2,s3,s4,s5=xyzc-coordinates,xyz-coordinates,scalar-values,vector-values,\ "xyzc-coords + scalar value","xyz-coords + vector-values" e[^-1] "Extract "$s$2" from image$? verifying condition '$1'." str=">begin(run('1,32,1,',arg0($2,4,3,1,s,5,3 + s)));($1)?(" if !$2 str.="da_push([x,y,z,c]));i" elif $2==1 str.="da_push([x,y,z]));I" elif $2==2 str.="da_push(i));i" elif $2==3 str.="da_push(I));I" elif $2==4 str.="da_push([x,y,z,c,i]));i" else str.="da_push([x,y,z,I]));I" fi str.=";end(da_freeze())" repeat $! { nm={$>,n} eval[$>] $str => $nm rv[$>,-1] rm. } #@cli extract_region : [label_image],_extract_xyz_coordinates={ 0 | 1 },_label_1,...,_label_M #@cli : Extract all pixels of selected images whose corresponding label in '[label_image]' is equal to 'label_m', #@cli : and output them as M column images. #@cli : Default value: 'extract_xyz_coordinates=0'. #@cli : $ image.jpg +blur 3 quantize. 4,0 +extract_region[0] [1],0,1,3 extract_region : check ${"is_image_arg $1"}" && isnum(${2=0})" if $#<3 e[0--3] "Extract pixels of image$? for labels [] in image $1, with"${"arg0 !!$2,out"}" coordinates -> no labels provided, ignoring." return fi e[^-1] "Extract pixels of image$? for labels {${3--1}} in image $1, with"${"arg0 !!$2,out"}" coordinates." pass$1 mv. 0 repeat $!-1 { l[0,{1+$<}] { nm={n} 1,16,1,{s+($2?3:0)} if $#>3 .x{$#-3} fi f[0] "> begin( const N = iM + 1; # Number of labels R = [ ${3--1} ]; # Requested labels hash = vectorN(0); # Correspondence table repeat (size(R),k, hash[R[k]] = k + 2); ); (ind = hash[i])>0?( $2?da_push(#ind,[ I(#1),x,y,z ]):da_push(#ind,I(#1)); ); i; end( repeat (l - 2,k, resize(#k + 2,1,da_size(#k + 2),1,-100,0)) )" rm[1] =>[^] $nm } } rm[0] #@cli montage : "_layout_code",_montage_mode={ 0<=centering<=1 | 2<=scale+2<=3 },\ # _output_mode={ 0:single layer | 1:multiple layers },"_processing_command" #@cli : Create a single image montage from selected images, according to specified layout code : #@cli : - 'X' to assemble all images using an automatically estimated layout. #@cli : - 'H' to assemble all images horizontally. #@cli : - 'V' to assemble all images vertically. #@cli : - 'A' to assemble all images as an horizontal array. #@cli : - 'B' to assemble all images as a vertical array. #@cli : - 'Ha:b' to assemble two blocks 'a' and 'b' horizontally. #@cli : - 'Va:b' to assemble two blocks 'a' and 'b' vertically. #@cli : - 'Ra' to rotate a block 'a' by 90 deg. ('RRa' for 180 deg. and 'RRRa' for 270 deg.). #@cli : - 'Ma' to mirror a block 'a' along the X-axis ('MRRa' for the Y-axis). #@cli : A block 'a' can be an image index (treated periodically) or a nested layout expression 'Hb:c','Vb:c','Rb' or #@cli : 'Mb' itself. #@cli : For example, layout code 'H0:V1:2' creates an image where image [0] is on the left, and images [1] and [2] #@cli : vertically packed on the right. #@cli : Default values: 'layout_code=X', 'montage_mode=2', output_mode='0' and 'processing_command=""'. #@cli : $ image.jpg sample ? +plasma[0] 1 shape_cupid 256 normalize 0,255 frame xy,3,0 frame xy,10,255 to_rgb \ # +montage A +montage[^-1] H1:V0:VH2:1H0:3 montage : check "isnum(${2=2}) && $2>=0 && $2<=3" skip "${1=X}",${3=0},"${4=}" if $2<=1 e[0--3] "Create aligned montage from image$?, with layout code '$1' and centering $2." else e[0--3] "Create scaled montage from image$?, with layout code '$1' and scale "{$2-2}"." fi to_colormode 0 # Manage automatic layout. if lowercase(['"$1"'])=='x' +l { foreach { => $> } repeat $!-1 { if {-2,w>h}" && "w>h mode=V # Both landscape. elif {-2,h>w}" && "h>w mode=H # Both portrait. elif {-2,w>h}" && "h>w # Landscape - portrait. if {-2,h/w}<(w/h) mode=V else mode=H fi else # Portrait - landscape. if {-2,w/h}<(h/w) mode=H else mode=V fi fi name=$mode{-2,n}:{n} montage[-2,-1] $mode,$2 mv. 0 =>[0] $name } layout={0,n} rm } montage $layout,$2,$3,"$4" else # Non-automatic layout. # Format and check validity of layout code. N=$! l[] { _scode="$1" _mode=$2 if lowercase(['"$1"'])=='h' if $N>1 {$N-1},1,1,1,-1 $N,1,1,1,x a x y else return fi # Simple horizontal montage. elif lowercase(['"$1"'])=='v' if $N>1 {$N-1},1,1,1,-2 $N,1,1,1,x a x y else return fi # Simple vertical montage. elif isin(lowercase(['"$1"']),'a','b') # Montage as an array. if $N<2 return fi nr:=round(sqrt($N)) nc:=round($N/$nr,1,1) # Horizontal array. if lowercase(['"$1"'])=='b' n=$nr nr=$nc nc=$n fi # Vertical array. $N,1,1,1,x s x,-{round(w/$nr,1,1)} foreach { if w>1 i[0] {w-1},1,1,1,-1 a x fi } a x if $nr>1 i[0] {$nr-1},1,1,1,-2 a x fi y else # Other complex montage. ('"$1"') f "i==72 || i==104?-1: i==86 || i==118?-2: i==82 || i==114?-3: i==77 || i==109?-4: i>=48 && i<=57?i-48:-5" s +,-1 s +,-2 s +,-3 s +,-4 s +,-5 foreach { if im>=0 ++. 48 =.. {t} rm. rows 0 fi } a y discard -5 fi f "i<0?i:i%$N" } if $!==$N rm return fi # Empty layout code. =>[^-1] 0 repeat h { c:=i[$>] # Duplicate multiple references. if $c>=0 if {$c,n} i.. [$c] i:=$!-2 =. $i,0,$> ref$i=$c else =>[$c] 1 ref$c=$c fi fi } _code={^} _lcode:=narg($_code) rm. # Determine image positions and sizes. N=$! repeat $N { ($>,0,0,{$>,w},{$>,h},0,0,0) } l[$N--1] { _p=1 k[${-_montage}] w:=i[3] h:=i[4] f i(0,y)<0?-1:i discard -1 y r 8,{h/8},1,1,-1 onfail error[0--3] "Too many input images." } # Render montage. if narg("$4") m "__montage : $4 k[0]" else m "__montage : if $""7%2 mirror x fi if $""8%2 mirror y fi rotate {90*$""6} r {max(1,round($""4,1,1))},{max(1,round($""5,1,1))},1,100%,3" fi s=${max_s[^-1]} repeat h { i,xi,yi,wi,hi,ai,mxi,myi:=crop(0,$>,w,1) if $3||!$> i.. $w,$h,1,$s fi __montage[$i] ${ref$i},$xi,$yi,{max(1,$wi)},{max(1,$hi)},$ai,$mxi,$myi j.. [$i],$xi,$yi } um __montage rm[0-{$N-1},-1] fi => "[Montage '$1']" _montage : if $_p>$_lcode error "Command 'montage': Incomplete layout code '"$_scode"'." fi c:=arg($_p,$_code) if $c>=0 _p+=1 u $c # Single index. elif $c==-4 # Mirror. _p+=1 l=${-_montage} f[$l] "a=i(5,y)%2;(x==7&&a)||(x==6&&!a)?!i:x==1?i(3,0)-i(3,y)-i:i" u $l elif $c==-3 # Rotation. _p+=1 l=${-_montage} l[$l] { s x +[2] [4] rv[1,2] *[1] -1 +[1] {4,@0} rv[3,4] +[5] 1 a x } u $l else # Merge. _p+=1 l=${-_montage} lw={$l,@3} lh={$l,@4} r=${-_montage} rw={$r,@3} rh={$r,@4} if $c==-1 # Horizontal merge. if $_mode<2 h:=max($lh,$rh) +[$l] '0,0,{($h-$lh)*min(1,$_mode)},0,0,0,0,0' +[$r] '0,$lw,{($h-$rh)*min(1,$_mode)},0,0,0,0,0' else h:=($_mode-2)*max($lh,$rh)+(3-$_mode)*min($lh,$rh) lf:=$h/$lh rf:=$h/$rh lw:=$lw*$lf rw:=$rw*$rf *[$l] '1,$lf,$lf,$lf,$lf,1,1,1' *[$r] '1,$rf,$rf,$rf,$rf,1,1,1' +[$r] '0,$lw,0,0,0,0,0,0' fi i[$l] (-1,0,0,{$lw+$rw},$h,0,0,0) a[$l,{$l+1}] y a[$l] [$r],y r[$r] 1,1,1,1,0 else # Vertical merge. if $_mode<2 w:=max($lw,$rw) +[$l] '0,{($w-$lw)*min(1,$_mode)},0,0,0,0,0,0' +[$r] '0,{($w-$rw)*min(1,$_mode)},$lh,0,0,0,0,0' else w:=($_mode-2)*max($lw,$rw)+(3-$_mode)*min($lw,$rw) lf:=$w/$lw rf:=$w/$rw lh:=$lh*$lf rh:=$rh*$rf *[$l] '1,$lf,$lf,$lf,$lf,1,1,1' *[$r] '1,$rf,$rf,$rf,$rf,1,1,1' +[$r] '0,0,$lh,0,0,0,0,0' fi i[$l] (-1,0,0,$w,{$lh+$rh},0,0,0) a[$l,{$l+1}] y a[$l] [$r],y r[$r] 1,1,1,1,0 fi u $l fi #@cli mirror : { x | y | z }...{ x | y | z } : (+) #@cli : Mirror selected images along specified axes. #@cli : $ image.jpg +mirror y +mirror[0] c #@cli : $ image.jpg +mirror x +mirror y append_tiles 2,2 #@cli permute : permutation_string : (+) #@cli : Permute selected image axes by specified permutation. #@cli : 'permutation' is a combination of the character set {x|y|z|c}, #@cli : e.g. 'xycz', 'cxyz', ... #@cli : $ image.jpg permute yxzc #@cli rs : eq. to 'rescale2d'. rs : _gmic_s="$?" v + _rescale2d $* #@cli rescale2d : _width[%]={ 0:any | >0 },_height[%]={ 0:any | >0 },-1=<_interpolation<=6,\ # _mode={ 0:inside | 1:padded-inside | 2:outside | 3:cropped-outside } #@cli : Resize selected 2D images while preserving aspect ratio. #@cli : 'interpolation' can be { -1:status only | 0:none | 1:nearest | 2:average | 3:linear | \ # 4=grid | 5=bicubic | 6=lanczos }. #@cli : When 'interpolation==-1', image size is actually not modified, but the size that would have been used \ # for the last selected image is returned in the status value. #@cli : Each resized image size is computed according to the specified 'mode': #@cli : - If 'mode==0', image size is __at most__ '(width,height)'. #@cli : - If 'mode==1' or 'mode==3', image size is __exactly__ '(width,height)'. #@cli : - If 'mode==2', image size is __at least__ '(width,height)'. #@cli : (eq. to 'rs'). #@cli : Default values: 'width=height=0', 'interpolation=2' and 'mode=0'. rescale2d : _gmic_s="$?" v + _$0 $* _rescale2d : check "${1=0}>=0 && ${2=0}>=0 && isin(${3=2},-1,0,1,2,3,4,5,6) && isin(${4=0},0,1,2,3)" e[0--3] "Resize 2D image"$_gmic_s" to ("${"arg0 !!$1,any,$1"},${"arg0 !!$2,any,$2"}"), "\ "with "${"arg0 \"max(0,$3)\",no,nearest,average,linear,grid,bicubic,lanczos"}" interpolation "\ "and '"${"arg0 $4,inside,padded-inside,outside,cropped-outside"}"' mode." if !$! return fi 1,$!,1,1,"w = max(w#y,1); h = max(h#y,1); d = max(d#y,1); s = max(s#y,1); aw = round(ispercentage($1)?max(1,w*$1):$1); ah = round(ispercentage($2)?max(1,h*$2):$2); aw || ah?( ratio = aw && ah?($4<2?min(aw/w,ah/h):max(aw/w,ah/h)): # W&H constraints aw?aw/w: # W constraint ah/h; # H constraint bw = max(1,round(ratio*w)); bh = max(1,round(ratio*h)); $4==1?(cw = max(aw,bw); ch = max(ah,bh)): $4==3?(cw = min(aw,bw); ch = min(ah,bh)): (cw = bw; ch = bh); $3>=0?(resize(#y,bw,bh,d,s,$3,0,0.5,0.5); isin($4,1,3)?resize(#y,cw,ch,d,s,0,0,0.5,0.5)); ); end($3<0?set('{}',string(cw,',',ch)))" rm. #@cli rs3d : eq. to 'rescale3d'. rs3d : _gmic_s="$?" v + _rescale3d $* #@cli rescale3d : _width[%]={ 0:any | >0 },_height[%]={ 0:any | >0 },_depth[%]={ 0:any | >0 },-1=<_interpolation<=6,\ # _mode={ 0:inside | 1:padded-inside | 2:outside | 3: cropped-outside } #@cli : Resize selected 3D images while preserving aspect ratio. #@cli : 'interpolation' can be { -1:status only | 0:none | 1:nearest | 2:average | 3:linear | \ # 4=grid | 5=bicubic | 6=lanczos }. #@cli : When 'interpolation==-1', image size is actually not modified, but the size that would have been used \ # for the last selected image is returned in the status value. #@cli : Each resized image size is computed according to the specified 'mode': #@cli : - If 'mode==0', image size is __at most__ '(width,height)'. #@cli : - If 'mode==1' or 'mode==3', image size is __exactly__ '(width,height)'. #@cli : - If 'mode==2', image size is __at least__ '(width,height)'. #@cli : (eq. to 'rs3d'). #@cli : Default values: 'width=height=depth=0', 'interpolation=2' and 'mode=0'. rescale3d : _gmic_s="$?" v + _$0 $* _rescale3d : check "${1=0}>=0 && ${2=0}>=0 && ${3=0}>=0 && isin(${4=2},-1,0,1,2,3,4,5,6) && isin(${5=0},0,1,2,3)" e[0--3] "Resize 3D image"$_gmic_s" to ("${"arg0 !!$1,any,$1"},${"arg0 !!$2,any,$2"},${"arg0 !!$3,any,$3"}"), "\ "with "${"arg0 \"max(0,$4)\",no,nearest,average,linear,grid,bicubic,lanczos"}" interpolation "\ "and '"${"arg0 $5,inside,padded-inside,outside,cropped-outside"}"' mode." 1,$!,1,1,"w = max(w#y,1); h = max(h#y,1); d = max(d#y,1); s = max(s#y,1); aw = round(ispercentage($1)?max(1,w*$1):$1); ah = round(ispercentage($2)?max(1,h*$2):$2); ad = round(ispercentage($3)?max(1,d*$3):$3); aw || ah || ad?( ratio = aw && ah && ad?($5<2?min(aw/w,ah/h,ad/d):max(aw/w,ah/h,ad/d)): # W&H&D constraints aw && ah && !ad?($5<2?min(aw/w,ah/h):max(aw/w,ah/h)): # W&H constraints aw && !ah && ad?($5<2?min(aw/w,ad/d):max(aw/w,ad/d)): # W&D constraints !aw && ah && ad?($5<2?min(ah/h,ad/d):max(ah/h,ad/d)): # H&D constraints aw?aw/w: # W constraint ah?ah/h: # H constraint ad/d; # D constraint bw = max(1,round(ratio*w)); bh = max(1,round(ratio*h)); bd = max(1,round(ratio*d)); $5==1?(cw = max(aw,bw); ch = max(ah,bh); cd = max(ad,bd)): $5==3?(cw = min(aw,bw); ch = min(ah,bh); cd = min(ad,bd)): (cw = bw; ch = bh; cd = bd); $4>=0?(resize(#y,bw,bh,bd,s,$4); isin($5,1,3)?resize(#y,cw,ch,cd,s,0,0,0.5,0.5,0.5)); ); end($4<0?set('{}',string(cw,',',ch,',',cd)))" rm. #@cli r : eq. to 'resize'. : (+) #@cli resize : {[image_w] | width>0[%]},_{[image_h] | height>0[%]},_{[image_d] | depth>0[%]},\ # _{[image_s] | spectrum>0[%]},_interpolation,_boundary_conditions,_ax,_ay,_az,_ac : (+) #@cli : Resize selected images with specified geometry. #@cli : (eq. to 'r').\n #@cli : 'interpolation' can be { -1:none (memory content) | 0:none | 1:nearest | 2:average | 3:linear | \ # 4=grid | 5=bicubic | 6=lanczos }. #@cli : 'boundary_conditions' has different meanings, according to the chosen 'interpolation' mode : #@cli : . When 'interpolation=={ -1 | 1 | 2 | 4 }', 'boundary_conditions' is meaningless. #@cli : . When 'interpolation==0', 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : . When 'interpolation=={ 3 | 5 | 6 }', 'boundary_conditions' can be { 0:none | 1:neumann }. #@cli : 'ax,ay,az,ac' set the centering along each axis when 'interpolation=0 or 4' #@cli : (set to '0' by default, must be defined in range [0,1]). #@cli : Default values: 'interpolation=1', 'boundary_conditions=0' and 'ax=ay=az=ac=0'. #@cli : $ image.jpg +resize[-1] 256,128,1,3,2 +resize[-1] 120%,120%,1,3,0,1,0.5,0.5 \ # +resize[-1] 120%,120%,1,3,0,0,0.2,0.2 +resize[-1] [0],[0],1,3,4 #@cli ri : eq. to 'resize_as_image'. ri : skip "${2=1},${3=0},${4=0},${5=0},${6=0},${7=0}" # Faster than the non-shortcut version of the command pass$1 r[^-1] .,.,.,.,${2--1} rm. #@cli resize_as_image : [reference],_interpolation,_boundary_conditions,_ax,_ay,_az,_ac #@cli : Resize selected images to the geometry of specified [reference] image. #@cli : (eq. to 'ri'). #@cli : Default values: 'interpolation=1', 'boundary_conditions=0' and 'ax=ay=az=ac=0'. #@cli : $ image.jpg sample duck +resize_as_image[-1] [-2] resize_as_image : check ${"is_image_arg $1"}" && isint(${2=1},-1,6) && "\ "isint(${3=0},0,3) && isnum(${4=0}) && isnum(${5=0}) && isnum(${6=0}) && isnum(${7=0})" pass$1 r[^-1] .,.,.,.,${2--1} rm. #@cli resize_mn : width[%]>=0,_height[%]>=0,_depth[%]>=0,_B_value,_C_value #@cli : Resize selected images with Mitchell-Netravali filter (cubic). #@cli : For details about the method, see: . #@cli : Default values: 'height=100%', 'depth=100%', 'B=0.3333' and 'C=0.3333'. #@cli : $ image.jpg rescale2d 32 resize_mn 800%,800% resize_mn : check "${2=100%}>=0 && ${3=100%}>=0" skip "${4=0.333},${5=0.333}" e[^-1] "Resize image$? to $1x$2x$3 using Mitchell-Netravali filter (B=$4, C=$5)." lib="const B = $4; const C = $5; const boundary = 1; const interp = 0; mn(P0,P1,P2,P3,d) = ( ( (-B/6-C)*P0 + (-3*B/2-C+2)*P1 + (3*B/2+C-2)*P2 + (B/6+C)*P3 )*d^3 + ( (B/2+2*C)*P0 + (2*B+C-3)*P1 + (-5*B/2-2*C+3)*P2 -C*P3)*d^2 + ( (-B/2-C)*P0 + (B/2+C)*P2)*d + B/6*P0 + (-B/3+1)*P1 + B/6*P2);" foreach { nm={n} nw:=ispercentage($1)?max(1,round($1*w)):round($1) nh:=ispercentage($2)?max(1,round($2*h)):round($2) nd:=ispercentage($3)?max(1,round($3*d)):round($3) if !$nw" || "!$nh" || "!$nd rm 0 elif !w rm $nw,$nh,$nd,1 else if w==1||$nww $nw,100%,100%,100%,${lib}"X = x*(w#-1-1)/(w-1); d = X - int(X); P0 = I(#-1,X-1); P1 = I(#-1,X); P2 = I(#-1,X+1); P3 = I(#-1,X+2); mn(P0,P1,P2,P3,d);" k. fi if h==1||$nh $nm } #@cli resize_pow2 : _interpolation,_boundary_conditions,_ax,_ay,_az,_ac #@cli : Resize selected images so that each dimension is a power of 2. #@cli : 'interpolation' can be { -1:none (memory content) | 0:none | 1:nearest | 2:average | 3:linear | \ # 4:grid | 5:bicubic | 6:lanczos }. #@cli : 'boundary_conditions' has different meanings, according to the chosen 'interpolation' mode : #@cli : . When 'interpolation=={ -1 | 1 | 2 | 4 }', 'boundary_conditions' is meaningless. #@cli : . When 'interpolation==0', 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : . When 'interpolation=={ 3 | 5 | 6 }', 'boundary_conditions' can be { 0:none | 1:neumann }. #@cli : 'ax,ay,az,ac' set the centering along each axis when 'interpolation=0' #@cli : (set to '0' by default, must be defined in range [0,1]). #@cli : Default values: 'interpolation=0', 'boundary_conditions=0' and 'ax=ay=az=ac=0'. #@cli : $ image.jpg +resize_pow2[-1] 0 resize_pow2 : check "isint(${1=0},-1,6)" skip ${2=0},${3=0},${4=0},${5=0},${6=0} e[^-1] "Resize image$? so that each dimension is a power of 2." foreach { r {2^(ceil(log2([w,h,d])))},100%,${1-6} } #@cli rotate : angle,_interpolation,_boundary_conditions,_center_x[%],_center_y[%] : \ # u,v,w,angle,interpolation,boundary_conditions,_center_x[%],_center_y[%],_center_z[%] : (+) #@cli : Rotate selected images with specified angle (in deg.), and optionally 3D axis (u,v,w). #@cli : 'interpolation' can be { 0:none | 1:linear | 2:bicubic }. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : When a rotation center (cx,cy,_cz) is specified, the size of the image is preserved. #@cli : Default values: 'interpolation=1', 'boundary_conditions=0' and 'center_x=center_y=(undefined)'. #@cli : $ image.jpg +rotate -25,1,2,50%,50% rotate[0] 25 #@cli rotate_tileable : angle,_max_size_factor>=0 #@cli : Rotate selected images by specified angle and make them tileable. #@cli : If resulting size of an image is too big, the image is replaced by a 1x1 image. #@cli : Default values: 'max_size_factor=8'. rotate_tileable : check ${2=8}>=0 e[^-1] "Rotate image$? with angle $1 deg. and make them tileable." # Reduce angle to known fraction. angle:=$1%360 if $angle>=270 rotate 270 angle-=270 elif $angle>=180 rotate 180 angle-=180 elif $angle>=90 rotate 90 angle-=90 fi # List of known fractions. (0,1;1,8;1,7;1,6;1,5;1,4;1,5;1,3;2,5;1,2;2,5;3,5;2,3;3,4;4,5;1,1;5,4;7,5;3,2;8,5;9,5;2,1;3,1;4,1;5,1;6,1;7,1;8,1) s. x,2 +/[-2,-1] atan. *. {180/pi} # Compute corresponding angles. ($angle) index. .. rm.. p={-3,@{^}} q={-2,@{^}} rm[-3--1] # Find nearest fraction p/q to atan(angle). if !$p||!$q return fi foreach { # Compute width and height of tile. theta:=atan2($p,$q) gcd:=gcd(h*$q,w*$p) pw:=h*$q/$gcd nw:=round($pw*w/cos($theta)) gcd:=gcd(h*$p,w*$q) qh:=w*$q/$gcd nh:=round($qh*h/cos($theta)) # Rotate and make tileable (may result in very large images!). if !$2" || "($nw<$2*w" && "$nh<$2*h) r {1.5*$nw},{1.5*$nh},1,100%,0,2 rotate {$theta*180/pi},1,2,50%,50% r $nw,$nh,1,100%,0,2,0.5,0.5 else error[0--4] "Command '$0': Invalid image dimension "({w},{h},{d},{s}). fi } #@cli rows : y0[%],_y1[%],_boundary_conditions #@cli : Keep only specified rows of selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'y1=y0' and 'boundary_conditions=0'. #@cli : $ image.jpg rows -25%,50% rows : check "isint(${3=0},0,3)" skip ${2=$1} e[^-1] "Keep rows $1...$2 of image$?, with "${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." z 0,$1,100%,$2,$3 #@cli scale2x #@cli : Resize selected images using the Scale2x algorithm. #@cli : $ image.jpg threshold 50% resize 50%,50% +scale2x scale2x : e[^-1] "Double xy-dimensions of image$?, using Scale2x algorithm." foreach { r 200%,200% f "dx=x&1;dy=y&1;A=j(0,-2,0,0,0,1);B=j(2,0,0,0,0,1);C=j(-2,0,0,0,0,1);D=j(0,2,0,0,0,1); !dy*(!dx*(C==A&&C!=D&&A!=B?A:i) + dx*(A==B&&A!=C&&B!=D?B:i)) + dy*(dx*(B==D&&B!=A&&D!=C?D:i) + !dx*(D==C&&D!=B&&C!=A?C:i))" } #@cli scale3x #@cli : Resize selected images using the Scale3x algorithm. #@cli : $ image.jpg threshold 50% resize 33%,33% +scale3x scale3x : e[^-1] "Triple xy-dimensions of image$?, using Scale3x algorithm." foreach { r 300%,300% f "dx=x%3;dy=y%3;c0=!dx;c1=(dx==1);c2=(dx==2); A=j(-3,-3,0,0,0,1);B=j(0,-3,0,0,0,1);C=j(3,-3,0,0,0,1); D=j(-3,0,0,0,0,1);F=j(3,0,0,0,0,1); G=j(-3,3,0,0,0,1);H=j(0,3,0,0,0,1);I=j(3,3,0,0,0,1); !dy*(c0*(D==B&&D!=H&&B!=F?D:i) + c1*((D==B&&D!=H&&B!=F&&i!=C)||(B==F&&B!=D&&F!=H&&i!=A)?B:i) + c2*(B==F&&B!=D&&F!=H?F:i)) + (dy==1)*(c0*((H==D&&H!=F&&D!=B&&i!=A)||(D==B&&D!=H&&B!=F&&i!=G)?D:i) + c1*i + c2*((B==F&&B!=D&&F!=H&&i!=I)||(F==H&&F!=B&&H!=D&&i!=C)?F:i)) + (dy==2)*(c0*(H==D&&H!=F&&D!=B?D:i) + c1*((F==H&&F!=B&&H!=D&&i!=G)||(H==D&&H!=F&&D!=B&&i!=I)?H:i) + c2*(F==H&&F!=B&&H!=D?F:i))" } #@cli scale_dcci2x : _edge_threshold>=0,_exponent>0,_extend_1px={ 0:false | 1:true } #@cli : Double image size using directional cubic convolution interpolation, #@cli : as described in . #@cli : Default values: 'edge_threshold=1.15', 'exponent=5' and 'extend_1px=0'. #@cli : $ image.jpg +scale_dcci2x , scale_dcci2x : check "${1=1.15}>=0 && ${2=5}>=0" skip ${3=0} e[^-1] "Double xy-dimensions of image$?, using DCCI2x algorithm." foreach { r {2*w-(!$3)},{2*h-(!$3)},1,100%,4 # Estimate diagonal values. f "begin( const threshold = $1; const exponent = $2; interpolation = 0; boundary = 1; j1(x,y) = P[7*y + x + 24]; # eq. to 'j(x,y)', but faster j2(x,y) = P[7*x - y + 24]; # eq. to 'j(-y,x)', but faster interp(k) = -k#(-3,-3) + 9*k#(-1,-1) + 9*k#(1,1) - k#(3,3); d(k) = sum(abs([ k#(-1,-3) - k#(-3,-1), k#(1,-3) - k#(-1,-1), k#(3,-3) - k#(1,-1), k#(-1,-1) - k#(-3,1), k#(1,-1) - k#(-1,1), k#(3,-1) - k#(1,1), k#(-1,1) - k#(-3,3), k#(1,1) - k#(-1,3), k#(3,1) - k#(1,3) ])); ); if (!((x*y)%2),i, P = crop(x - 3,y - 3,0,c,7,7,1,1); d1 = d(j1); d2 = d(j2); ratio = (1 + d1)/(1 + d2); value = ratio>threshold ? interp(j1): # Up-right edge ratio<(1/threshold) ? interp(j2): # Down-right edge (w1 = 1/(1 + d1^exponent); w2 = 1/(1 + d2^exponent); (interp(j1)*w1 + interp(j2)*w2)/(w1 + w2)); # Smooth area value/=16)" # Estimate remaining values. f "begin( const threshold = $1; const exponent = $2; interpolation = 0; boundary = 1; j1(x,y) = P[7*y + x + 24]; # eq. to 'j(x,y)', but faster j2(x,y) = P[7*x - y + 24]; # eq. to 'j(-y,x)', but faster interp(k) = -k#(0,-3) + 9*k#(0,-1) + 9*k#(0,1) - k#(0,3); d(k) = sum(abs([ k#(-1,-2) - k#(1,-2), k#(-2,-1) - k#(0,-1), k#(0,-1) - k#(2,-1), k#(-3,0) - k#(-1,0), k#(-1,0) - k#(1,0), k#(1,0) - k#(3,0), k#(-2,1) - k#(0,1), k#(0,1) - k#(2,1), k#(-1,2) - k#(1,2) ])); ); if ((x%2) + (y%2)!=1,i, P = crop(x - 3,y - 3,0,c,7,7,1,1); d1 = d(j1); d2 = d(j2); ratio = (1 + d1)/(1 + d2); value = ratio>threshold ? interp(j1) : # Horizontal edge ratio<(1/threshold) ? interp(j2) : # Vertical edge (w1 = 1/(1 + d1^exponent); w2 = 1/(1 + d2^exponent); (interp(j1)*w1 + interp(j2)*w2)/(w1 + w2)); # Smooth area value/=16)" } #@cli seamcarve : _width[%]>=0,_height[%]>=0,_is_priority_channel={ 0 | 1 },_is_antialiasing={ 0 | 1 },\ # _maximum_seams[%]>=0 #@cli : Resize selected images with specified 2D geometry, using the seam-carving algorithm. #@cli : Default values: 'height=100%', 'is_priority_channel=0', 'is_antialiasing=1' and 'maximum_seams=25%'. #@cli : $ image.jpg seamcarve 60% # The main code of this algorithm has been done by Andy (Garagecoder). seamcarve : check "${2=100%}>=0 && ${5=25%}>=0" skip ${3=0},${4=1} e[^-1] "Resize image$? to $1x$2 using seam-carving algorithm, "${arg0\ !$3,with,without}" priority channel, "\ ${arg0\ !$4,with,without}" anti-aliasing and maximum seams $5." foreach { nw:=max(1,round(ispercentage($1)?$1*w:$1)) nh:=max(1,round(ispercentage($2)?$2*h:$2)) if $nw!=w _seamcarve $nw,$3,$4,$5 fi if $nh!=h transpose _seamcarve $nh,$3,$4,$5 transpose fi } # Subroutine to remove/add vertical seams/ # $1 = desired width. # $2 = is_priority_channel={ 0 | 1 } # $3 = is_antialiasing={ 0 | 1 } # $4 = max number of seams added/removed at once. _seamcarve : do max_seams:=max(1,round(ispercentage($4)?$4*w:$4)) ssms:=max(min(round($1-w),w),1-w) sms:=min($max_seams,abs($ssms)) # Compute potential map. if $2 s[0] c,{1-s} /. 256 fi +gradient[0] a[-2,-1] c abs. compose_channels. + n. 0,1 if $2 +. .. a[0,1] c fi # Add x-coordinates channel for anti-aliasing. if $3 100%,1,1,1,x r. [0],[0] a[0,-1] c fi # Calculate low matrix (backwards propagation). . repeat h { +rows. {$<+1} erode. 3 j.. .,0,$<,0,0,-1 rm. } # Initialise seams, top matrix. 100%,100% +rows[1] 0 => grad,low,seam,top repeat h#0-1 { nr:=$>+1 +rows[low] $nr # Find optimum matches between two 1D matrices. +*[4,5] +shift[4] 1 *. [5] +shift[5] 1 *. [4] +[-2,-1] j[5] [4] a[-3--1] c f. ">c?i:max(j(-1)+j(0,0,0,1),j(-2)+j(0,0,0,2))" s. c shift... 1 +.. ... shift... 1 +[-3,-1] >[-2,-1] f. " # Distribute matched pixels in top matrix. a[-2,-1] c f. "j(i,0,0,-1)" channels. 1 # Add next energy row to top matrix. +rows[grad] $nr +[top,-1] } # Add / remove seams. max:=iM*2 repeat $sms { =. $max,{xm} } j[grad] .,0,100% rm[low,top] a[-2,-1] c f. "0 # Add seams. -. 2 s[0] c repeat $s { if $><($s-1) . fi a[$>,-1] c } permute cxyz a c discard -1 f "i<0?j(0,-1):i" r {$w+$sms},$h,1,$s,-1 fi # Perform anti-aliasing step. if $3 s c,{1-s} g. x,1 round !=. 1 (0.5,0.5) +convolve[0] . rm.. j[0] .,0,0,0,0,1,[1] rm[-2,-1] fi rprogress {a=w/$1;a<1?a*100:100/a} while w!=$1 #@cli shift : vx[%],_vy[%],_vz[%],_vc[%],_boundary_conditions,_interpolation={ 0:nearest_neighbor | 1:linear } : (+) #@cli : Shift selected images by specified displacement vector. #@cli : Displacement vector can be non-integer in which case linear interpolation should be chosen. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'boundary_conditions=0' and 'interpolation=0'. #@cli : $ image.jpg +shift[0] 50%,50%,0,0,0 +shift[0] 50%,50%,0,0,1 +shift[0] 50%,50%,0,0,2 #@cli shrink : axes,size[%],_boundary_conditions={ 0:dirichlet | 1:neumann | 2:periodic | 3:mirror } #@cli : Shrink selected images along the specified axes. #@cli : 'axes' can be { x | y | z | c | xy | xz | xc | yz | yc | zc | xyz | xyc | xzc | yzc | xyzc }. #@cli : Default value: 'boundary_conditions=0'. #@cli : $ image.jpg shrink xy,100 shrink : check "isint(${3=0},0,3)" e[^-1] "Shrink image$? along ax"{`size(['$1'])>1?_'e':'i'`}"s '$1', with size $2 and "\ ${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." expand "$1",{-$2},$3 #@cli slices : z0[%],_z1[%],_boundary_conditions #@cli : Keep only specified slices of selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'z1=z0' and 'boundary_conditions=0'. slices : check "isint(${3=0},0,3)" skip ${2=$1} e[^-1] "Keep slices $1...$2 of image$?, with "${"arg0 $3,dirichlet,neumann,periodic,mirror"}" boundary conditions." z 0,0,$1,100%,100%,$2,$3 #@cli sort : _ordering={ + | - },_axis={ x | y | z | c } : (+) #@cli : Sort pixel values of selected images. #@cli : If 'axis' is specified, the sorting is done according to the data of the first column/row/slice/channel #@cli : of selected images. #@cli : Default values: 'ordering=+' and 'axis=(undefined)'. #@cli : $ 64 rand 0,100 +sort display_graph 400,300,3 #@cli s : eq. to 'split'. : (+) #@cli split : { x | y | z | c }...{ x | y | z | c },_split_mode : \ # keep_splitting_values={ + | - },_{ x | y | z | c }...{ x | y | z | c },value1,_value2,... : (no arg) : (+) #@cli : Split selected images along specified axes, or regarding to a sequence of scalar values #@cli : (optionally along specified axes too). #@cli : (eq. to 's').\n #@cli : 'split_mode' can be { 0:split according to constant values | >0:split in N parts | \ # <0:split in parts of size -N }. #@cli : Default value: 'split_mode=-1'. #@cli : $ image.jpg split c #@cli : $ image.jpg split y,3 #@cli : $ image.jpg split x,-128 #@cli : $ 1,20,1,1,"1,2,3,4" +split -,2,3 append[1--1] y #@cli : $ (1,2,2,3,3,3,4,4,4,4) +split x,0 append[1--1] y #@cli split_tiles : M!=0,_N!=0,_is_homogeneous={ 0 | 1 } #@cli : Split selected images as a MxN array of tiles. #@cli : If M or N is negative, it stands for the tile size instead. #@cli : Default values: 'N=M' and 'is_homogeneous=0'. #@cli : $ image.jpg +local split_tiles 5,4 blur 3,0 sharpen 700 append_tiles 4,5 done split_tiles : skip ${2=$1},${3=0} if $3 e[^-1] "Split image$? as a $1x$2 array of homogeneous tiles." else e[^-1] "Split image$? as a $1x$2 array of tiles." fi foreach { nm={n} s y,$2 s x,$1 if $3 r [0],[0],100%,100%,0 fi =>[^] $nm } #@cli undistort : -1<=_amplitude<=1,_aspect_ratio,_zoom,_center_x[%],_center_y[%],_boundary_conditions #@cli : Correct barrel/pincushion distortions occurring with wide-angle lens. #@cli : References: #@cli : [1] Zhang Z. (1999). Flexible camera calibration by viewing a plane from unknown orientation. #@cli : [2] Andrew W. Fitzgibbon (2001). Simultaneous linear estimation of multiple view geometry and lens distortion. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'amplitude=0.25', 'aspect_ratio=0', 'zoom=0', 'center_x=center_y=50%' \ # and 'boundary_conditions=0'. undistort : check "${1=0.1}>=-1 && $1<=1 && ${6=0}>=0 && $6<=3" skip ${2=0},${3=0},${4=50%},${5=50%} e[^-1] "Undistort barrel/pincushion effect in image$?, with amplitude $1, aspect ratio $2, zoom factor $3, center ($4,$5) and "${"arg0 $6,dirichlet,neumann,periodic,mirror"}" boundary conditions." foreach { center_x:=ispercentage($4)?w*$4:$4 center_y:=ispercentage($5)?h*$5:$5 f " const interpolation = 1; const boundary = $6; const center_x = "$center_x"; const center_y = "$center_y"; const alpha = cut($1,-0.999,0.999); const ratio = $2>=0?1+$2:1/(1-$2); const zoom = $3>=0?1+$3:1/(1-$3); const M = max(w,h); x = 2*(x - center_x)/(zoom*ratio*M); y = 2*(y - center_y)/(zoom*M); r = norm(x,y); nr = r/(1 - alpha*r^2); if (r>0, nx = nr/r*x; ny = nr/r*y, nx = x; ny = y ); x = 0.5*nx*ratio*M + center_x; y = 0.5*ny*M + center_y; I(x,y)" } #@cli y : eq. to 'unroll'. : (+) #@cli unroll : _axis={ x | y | z | c } : (+) #@cli : Unroll selected images along specified axis. #@cli : (eq. to 'y'). #@cli : Default value: 'axis=y'. #@cli : $ (1,2,3;4,5,6;7,8,9) +unroll y #@cli upscale_smart : width[%],_height[%],_depth,_smoothness>=0,_anisotropy=[0,1],sharpening>=0 #@cli : Upscale selected images with an edge-preserving algorithm. #@cli : Default values: 'height=100%', 'depth=100%', 'smoothness=2', 'anisotropy=0.4' and 'sharpening=10'. #@cli : $ image.jpg rescale2d ,100 +upscale_smart 500%,500% append x upscale_smart : skip ${2=100%},${3=100%} check "${4=2}>=0 && ${5=0.4}>=0 && $5<=1 && ${6=10}>=0" e[^-1] "Upscale image$? to $1x$2x$3, with smoothness $4, anisotropy $5 and sharpening $6." foreach { w,h={w},{h} +r. $1,$2,$3,1,0 # Compute desired dimensions. if w<$w" && "h<$h # Test for downscaling rm. r. $1,$2,$3,100%,2 else rm. +diffusiontensors 0,$5,1.2,1.2 r[-2,-1] $1,$2,$3,100%,5 smooth.. .,$4 rm. ac "sharpen. $6,10",ycbcr_y fi } #@cli volumetric2d : _x[%],_y[%],_z[%],_separator_size>=0 #@cli : Convert selected 3D volumetric images into a 2D representation. #@cli : Default values: 'x=y=z=50%' and 'separator_size=0'. #@cli : $ image.jpg rescale2d 64 animate noise,0,100,50 cut 0,255 append z volumetric2d 50%,50%,50%,1 volumetric2d : skip ${1=50%},${2=50%},${3=50%} check "isint(${4=0},0)" e[^-1] "Convert 3D volumetric image$? into a 2D representation, with coordinates (${1-3}) and separator size $4." foreach { nm={n} {[w+d+$4,h+d+$4]},1,100%,{im} +slices[0] $3 j.. . rm. +rows[0] $2 permute. xzyc j.. .,0,{0,h+$4} rm. +columns[0] $1 permute. zyxc j.. .,{0,w+$4},0 k.. => $nm } #--------------------------------- # #@cli :: Filtering # #--------------------------------- #@cli bandpass : _min_freq[%],_max_freq[%] #@cli : Apply bandpass filter to selected images. #@cli : Default values: 'min_freq=0' and 'max_freq=20%'. #@cli : $ image.jpg bandpass 1%,3% #@cli : $$ https://gmic.eu/oldtutorial/_bandpass bandpass : skip ${1=0},${2=20%} e[^-1] "Apply bandpass filter [$1,$2] to image$?." foreach { 100%,100%,100% f. "sqrt((x/w-0.5)^2 + (y/h-0.5)^2 + (z/d-0.5)^2)" n. 0,1 ir. $1,$2 shift. {int(w/2)},{int(h/2)},{int(d/2)},0,2 fft.. *... . *[-2,-1] ifft rm. } #@cli bilateral : [guide],std_deviation_s[%]>=0,std_deviation_r[%]>=0,_sampling_s>=0,_sampling_r>=0 : \ # std_deviation_s[%]>=0,std_deviation_r[%]>=0,_sampling_s>=0,_sampling_r>=0 : (+) #@cli : Blur selected images by anisotropic (eventually joint/cross) bilateral filtering. #@cli : If a guide image is provided, it is used for drive the smoothing filter. #@cli : A guide image must be of the same xyz-size as the selected images. #@cli : Set 'sampling' arguments to '0' for automatic adjustment. #@cli : $ image.jpg repeat 5 { bilateral 10,10 } #@cli b : eq. to 'blur'. : (+) #@cli blur : std_deviation>=0[%],_boundary_conditions,_kernel : \ # axes,std_deviation>=0[%],_boundary_conditions,_kernel : (+) #@cli : Blur selected images by a deriche or gaussian filter (recursive implementation). #@cli : (eq. to 'b').\n #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : 'kernel' can be { 0:deriche | 1:gaussian }. #@cli : When specified, argument 'axes' is a sequence of { x | y | z | c }. #@cli : Specifying one axis multiple times apply also the blur multiple times. #@cli : Default values: 'boundary_conditions=1' and 'kernel=1'. #@cli : $ image.jpg +blur 5,0 +blur[0] 5,1 #@cli : $ image.jpg +blur y,10% #@cli : $$ https://gmic.eu/oldtutorial/_blur #@cli blur_angular : amplitude[%],_center_x[%],_center_y[%] #@cli : Apply angular blur on selected images. #@cli : Default values: 'center_x=center_y=50%'. #@cli : $ image.jpg blur_angular 2% #@cli : $$ https://gmic.eu/oldtutorial/_blur_angular blur_angular : skip ${2=50%},${3=50%} e[^-1] "Apply angular blur on image$?, with amplitude $1 and center point ($2,$3)." euclidean2polar $2,$3,1.3,1 foreach { 1,100% =. 1,50%,50% b. y,$1 convolve_fft.. . rm. } polar2euclidean $2,$3,1.3,1 #@cli blur_bloom : _amplitude>=0,_ratio>=0,_nb_iter>=0,_blend_operator={ + | max | min },\ # _kernel={ 0:deriche | 1:gaussian | 2:box | 3:triangle | 4:quadratic },\ # _normalize_scales={ 0 | 1 },_axes #@cli : Apply a bloom filter that blend multiple blur filters of different radii, #@cli : resulting in a larger but sharper glare than a simple blur. #@cli : When specified, argument 'axes' is a sequence of { x | y | z | c }. #@cli : Specifying one axis multiple times apply also the blur multiple times. #@cli : Reference: Masaki Kawase, "Practical Implementation of High Dynamic Range Rendering", GDC 2004. #@cli : Default values: 'amplitude=1', 'ratio=2', 'nb_iter=5', 'blend_operator=+', 'kernel=1', 'normalize_scales=0' \ # and 'axes=(all)' #@cli : $ image.jpg blur_bloom , blur_bloom : check "${1=1}>=0 && ${2=2}>=0 && isint(${3=5},0) && isint(${5=1},0,4) && isnum(${6=0})" skip "${4=+},${7=}" e[^-1] "Apply bloom effect on image$?, with amplitude $1, ratio $2, $3 iterations, blend operator '$4' and "\ ${"arg0 !$6,\"\",\"no \""}"scale normalization." if narg("$7") axes=$7, fi m "_bloom0 : b "$axes"$""1" m "_bloom1 : b "$axes"$""1,1,1" m "_bloom2 : boxfilter "$axes"{1+2*$""1},0,1" m "_bloom3 : boxfilter "$axes"{1+2*$""1},0,1,2" m "_bloom4 : boxfilter "$axes"{1+2*$""1},0,1,3" foreach { nm={n} mM:=[im,iM] [0] repeat $3 { sigma:=$1*($2^$>) +_bloom$5[0] $sigma if $6 n. $mM fi -$4[1,-1] } n. $mM k. => $nm } um _bloom0,_bloom1,_bloom2,_bloom3,_bloom4 #@cli blur_linear : amplitude1[%],_amplitude2[%],_angle,_boundary_conditions={ 0:dirichlet | 1:neumann } #@cli : Apply linear blur on selected images, with specified angle and amplitudes. #@cli : Default values: 'amplitude2=0', 'angle=0' and 'boundary_conditions=1'. #@cli : $ image.jpg blur_linear 10,0,45 #@cli : $$ https://gmic.eu/oldtutorial/_blur_linear blur_linear : skip ${2=0},${3=0},${4=1} e[^-1] "Apply linear blur on image$?, with angle $3 deg. and amplitudes ($1,$2)." std1:=ispercentage($1)?$1*max(w,h):$1 std2:=ispercentage($2)?$2*max(w,h):$2 stdM:=round(1.25*max($std1,$std2)) if $stdM<=0 return fi foreach { expand xy,$stdM,{$4!=0} {s=2*$stdM;[s,s]} gaussian. $1,$2,$3 normalize_sum. convolve_fft[0] [1] rm. shrink xy,$stdM } #@cli blur_radial : amplitude[%],_center_x[%],_center_y[%] #@cli : Apply radial blur on selected images. #@cli : Default values: 'center_x=center_y=50%'. #@cli : $ image.jpg blur_radial 2% #@cli : $$ https://gmic.eu/oldtutorial/_blur_radial blur_radial : skip ${2=50%},${3=50%} e[^-1] "Apply radial blur on image$?, with amplitude $1 and center point ($2,$3)." euclidean2polar $2,$3,5,1 b x,$1 polar2euclidean $2,$3,5,1 #@cli blur_selective : sigma>=0,_edges>0,_nb_scales>0 #@cli : Blur selected images using selective gaussian scales. #@cli : Default values: 'sigma=5', 'edges=0.5' and 'nb_scales=5'. #@cli : $ image.jpg noise 20 cut 0,255 +local[-1] repeat 4 { blur_selective , } done #@cli : $$ https://gmic.eu/oldtutorial/_blur_selective blur_selective : check "${1=5}>=0 && ${2=0.5}>=0 && isint(${3=5},1)" e[^-1] "Blur image$? using $3 selective gaussian scales, with sigma $1 and edges $2." foreach { nm={n} +gradient_norm +. 1 ^. {-max(0.01,$2)} quantize. {$3+1},0,1 min. {$3-1} ri. .. repeat $3 { +==. $> *. ... +[-2,-1] b.. {$1/($3+1)} } rm.. => $nm } #@cli boxfilter : size>=0[%],_order,_boundary_conditions,_nb_iter>=0 : \ # axes,size>=0[%],_order,_boundary_conditions,_nb_iter>=0 : (+) #@cli : Blur selected images by a box filter of specified size (fast recursive implementation). #@cli : 'order' can be { 0:smooth | 1:1st-derivative | 2:2nd-derivative }. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : When specified, argument 'axes' is a sequence of { x | y | z | c }. #@cli : Specifying one axis multiple times apply also the blur multiple times. #@cli : Default values: 'order=0', 'boundary_conditions=1' and 'nb_iter=1'. #@cli : $ image.jpg +boxfilter 5% #@cli : $ image.jpg +boxfilter y,3,1 #@cli bump2normal #@cli : Convert selected bumpmaps to normalmaps. #@cli : $ 300,300 circle 50%,50%,128,1,1 blur 5% bump2normal bump2normal : e[^-1] "Convert bumpmap$? to normalmap." foreach { channels 0 g xy,1 +f. 1 a c orientation * 127 + 128 round c 0,255 } #@cli closing : size>=0 : size_x>=0,size_y>=0,_size_z>=0 : \ # [kernel],_boundary_conditions,_is_real={ 0:binary-mode | 1:real-mode } #@cli : Apply morphological closing to selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_z=1', 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +closing 10 closing : skip "${2=},${3=}" boundary,is_kernel,sel=1,0,^ if "isnum($1)" # Full square kernel if "['$2']==0 && ['$3']==0" sx,sy,sz=$1 e[0--4] "Apply morphological closing to image$?, with kernel of size "$sx"." else sx=$1 if ['$2']==0 sy=1 else sy=$2 fi if ['$3']==0 sz=1 else sz=$3 fi e[0--4] "Apply morphological closing to image$?, with "${sx}"x"${sy}"x"${sz}" kernel." fi elif ${"is_image_arg $1"}" && narg("${"pass$1 -1"}")==1" # Custom kernel pass$1 sx,sy,sz={w},{h},{d} is_kernel,sel,r0,r1,b0,b1,b2,b3=1,^-1,binary,real,dirichlet,neumann,periodic,mirror is_real=0 if ['$2']!=0 boundary:=cut($2,0,3) fi if ['$3']!=0 is_real=$3 fi e[0--3] "Apply morphological closing to image$? with kernel ["${"pass$1 -1"}"] and "${b$boundary}" boundary "\ "conditions, in "${r$is_real}" mode." else error[0--3] "Command 'closing': Invalid arguments '$*'." fi if $sx>0||$sy>0||$sz>0 sx1,sx2:="s=int(($sx-1)/2);[s,$sx-s-1]" sy1,sy2:="s=int(($sy-1)/2);[s,$sy-s-1]" sz1,sz2:="s=int(($sz-1)/2);[s,$sz-s-1]" foreach[$sel] { nm={n} if d>1 # 3D r {[w+$sx+1,h+$sy+1,d+$sz+1]},100%,0,$boundary,0.5,0.5,0.5 if $is_kernel pass. dilate.. .,0,$is_real erode.. .,0,$is_real rm. else dilate $sx,$sy,$sz erode $sx,$sy,$sz fi z {$sx1+1},{$sy1+1},{$sz1+1},{w-$sx2-2},{h-$sy2-2},{d-$sz2-2} => $nm elif h>1 # 2D r {[w+$sx+1,h+$sy+1]},1,100%,0,$boundary,0.5,0.5 if $is_kernel pass. dilate.. .,0,$is_real erode.. .,0,$is_real rm. else dilate $sx,$sy erode $sx,$sy fi z {$sx1+1},{$sy1+1},{w-$sx2-2},{h-$sy2-2} => $nm else # 1D r {w+$sx+1},1,1,100%,0,$boundary,0.5 if $is_kernel pass. dilate.. .,0,$is_real erode.. .,0,$is_real rm. else dilate $sx,1 erode $sx,1 fi z {$sx1+1},{w-$sx2-2} => $nm fi } fi if $is_kernel rm. fi #@cli closing_circ : _size>=0,_is_real={ 0 | 1 } #@cli : Apply circular dilation of selected images by specified size. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +closing_circ 7 closing_circ : check "$1>=0 && isbool(${2=0})" r0,r1=binary,real e[^-1] "Apply morphologicel closing of image$? by circular kernel of size $1, in "${r$2}" mode." if $1<2 return fi shape_circle $1 closing[^-1] .,$2 rm. #@cli compose_freq #@cli : Compose selected low and high frequency parts into new images. #@cli : $ image.jpg split_freq 2% mirror[-1] x compose_freq compose_freq : e[^-1] "Compose low and high frequency part$? into new images." repeat int($!/2) { +[$>,{$>+1}] } #@cli convolve : [mask],_boundary_conditions,_is_normalized={ 0 | 1 },_channel_mode,\ # _xcenter,_ycenter,_zcenter,_xstart,_ystart,_zstart,_xend,_yend,_zend,_xstride,_ystride,_zstride,\ # _xdilation,_ydilation,_zdilation,interpolation_type : (+) #@cli : Convolve selected images by specified mask. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : 'channel_mode' can be { 0:all | 1:one-for-one | 2:partial sum | 3:full sum }. #@cli : 'interpolation_type' can be { 0:nearest-neighbor | 1:linear }. #@cli : Default values: 'boundary_conditions=1', 'is_normalized=0', 'channel_mode=1', \ # 'xcenter=ycenter=zcenter=(undefined)', 'xstart=ystart=zstart=0', 'xend=yend=zend=(max-coordinates)', \ # 'xstride=ystride=zstride=1', 'xdilation=ydilation=zdilation=1' and 'interpolation_type=0'. #@cli : $ image.jpg (0,1,0;1,-4,1;0,1,0) convolve[-2] [-1] keep[-2] #@cli : $ image.jpg (0,1,0) resize[-1] 130,1,1,1,3 +convolve[0] [1] #@cli : $$ https://gmic.eu/oldtutorial/_convolve #@cli convolve_fft : [mask],_boundary_conditions #@cli : Convolve selected images with specified mask, in the fourier domain. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : $ image.jpg 100%,100% gaussian[-1] 20,1,45 +convolve_fft[0] [1] convolve_fft : check ${is_image_arg\ $1}" && isin(${2=2},0,1,2,3)" e[^-1] "Convolve image$? with mask $1, in the fourier domain." pass$1 store. kernel foreach { if w w0,h0,d0={w},{h},{d} $kernel if $2!=2 r[0] {[w#0,h#0,d#0]+2*round([w#1>1?w#1:0,h#1>1?h#1:0,d#1>1?d#1:0]/2)},100%,0,$2,0.5,0.5 fi r ${-max_whd},100%,0,0,0.5,0.5 r 100%,100%,100%,${-max_s} fft[1] fft[0] +*[1,2] +*[0,3] +[-2,-1] *[1,3] *[0,2] -[0,1] ifft rm. shift {-int(([w,h,d]-1)/2)},0,2 r $w0,$h0,$d0,100%,0,0,0.5,0.5 fi } #@cli correlate : [mask],_boundary_conditions,_is_normalized={ 0 | 1 },_channel_mode,\ # _xcenter,_ycenter,_zcenter,_xstart,_ystart,_zstart,_xend,_yend,_zend,_xstride,_ystride,_zstride,\ # _xdilation,_ydilation,_zdilation,interpolation_type : (+) #@cli : Correlate selected images by specified mask. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : 'channel_mode' can be { 0:all | 1:one-for-one | 2:partial sum | 3:full sum }. #@cli : 'interpolation_type' can be { 0:nearest-neighbor | 1:linear }. #@cli : Default values: 'boundary_conditions=1', 'is_normalized=0', 'channel_mode=1', \ # 'xcenter=ycenter=zcenter=-1', 'xstart=ystart=zstart=0', 'xend=yend=zend=(max-coordinates)', \ # 'xstride=ystride=zstride=1', 'xdilation=ydilation=zdilation=1' and 'interpolation_type=0'. #@cli : $ image.jpg (0,1,0;1,-4,1;0,1,0) correlate[-2] [-1] keep[-2] #@cli : $ image.jpg +crop 40%,40%,60%,60% +correlate[0] [-1],0,1 #@cli cross_correlation : [mask] #@cli : Compute cross-correlation of selected images with specified mask. #@cli : $ image.jpg +shift -30,-20 +cross_correlation[0] [1] cross_correlation : check ${is_image_arg\ $1} e[^-1] "Compute cross-correlation of image$? with mask $1." repeat $! { pass$1 0 l[$>,-1] { norm fft.. fft. [-2,-1] *.. [-5] *. [-6] -[-2,-1] *[-5,-3] *[-3,-2] +[-3,-2] ifft rm. } } #@cli curvature #@cli : Compute isophote curvatures on selected images. #@cli : $ image.jpg blur 10 curvature curvature : e[^-1] "Compute isophote curvatures on image$?." foreach { if d==1 +g xy,0 hessian... xxxyyy # ixx ixy iyy ix iy *... .. *[-4] . *[-4] -2 # ixx -2iyixy ixiyy ix iy +[-4,-3] *... .. # ixx -2ixiyixy+ix^2iyy ix iy sqr[-2,-1] *[-4] . +[-4,-3] # iy^2ixx-2ixiyixy+ix^2iyy ix^2 iy^2 +[-2,-1] +. 0.1 ^. 1.5 / # (iy^2ixx+2ixiyixy+ix^2iyy)/(ix^2+iy^2) else +inn +gradient_norm.. laplacian... # inn+iee inn in -[-3,-2] +. 0.1 /[-2,-1] # iee in +inn. laplacian.. - # iee/in fi } #@cli dct : _{ x | y | z }...{ x | y | z } : (no arg) #@cli : Compute the discrete cosine transform of selected images, optionally along the specified axes only. #@cli : Output images are always evenly sized, so this command may change the size of the selected images. #@cli : Default values: (no arg) #@cli : See also: ''idct''. #@cli : $ image.jpg +dct +idct[-1] abs[-2] +[-2] 1 log[-2] #@cli : $$ https://gmic.eu/oldtutorial/_dct-and-idct dct : skip ${1=0} ('"$1"') is_axes:="im>=_'x' && iM<=_'z'" if $is_axes e[0--3] "Compute discrete cosine transform of image$? along axes '$1'." repeat w { axis:=i[$>] if $axis==_'x' foreach[^-1] { if w>1 _dct fi } elif $axis==_'y' foreach[^-1] { if h>1 permute yxzc _dct permute yxzc fi } elif $axis==_'z' foreach[^-1] { if d>1 permute zxyc _dct permute yzxc fi } fi } rm. else rm. e[0--3] "Compute discrete cosine transform of image$?." noarg foreach { if w>1 _dct fi if h>1 permute yxzc _dct permute yxzc fi if d>1 permute zxyc _dct permute yzxc fi } fi # 1D direct transform (DCT-II) along the x-axis, for a single image. _dct : if w%2 r {w+1},100%,100%,100%,0,1 fi s x l[1--1:2] { a x mirror x } mv[1] $! a x fft x 100%,1,1,1,2*cos(-x*pi/(2*w)) *[0,2] 100%,1,1,1,2*sin(-x*pi/(2*w)) *[1,2] - +z[0] 0,0 /. {sqrt(2)} j.. .,0,0,0 rm. * {sqrt(2/w)} # Make the transform orthogonal. #@cli deblur : amplitude[%]>=0,_nb_iter>=0,_dt>=0,_regul>=0,_regul_type={ 0:Tikhonov | 1:meancurv. | 2:TV } #@cli : Deblur image using a regularized Jansson-Van Cittert algorithm. #@cli : Default values: 'nb_iter=10', 'dt=20', 'regul=0.7' and 'regul_type=1'. #@cli : $ image.jpg blur 3 +deblur 3,40,20,0.01 deblur : check "${2=10}>=0 && ${3=20}>=0 && ${4=0.7}>=0" skip ${5=1} e[^-1] "Deblur image$? with a regularized Jansson-Van Cittert algorithm, with sigma $1, $2 iterations, time step $3 and regularization $4." foreach { nm={n} [0] repeat $2 { if $5>=2 +curvature. # TV regularization. elif $5>=1 +iee. # Meancurv. regularization. else +laplacian. # Tikhonov regularization. fi *. $4 +b.. $1 -. [-4] # Data fidelity term. -[-2,-1] *. {$3/(0.0001+max(abs(im),abs(iM)))} # Adaptive time step. +[-2,-1] # Update image. } rm.. => $nm } #@cli deblur_goldmeinel : sigma>=0,_nb_iter>=0,_acceleration>=0,_kernel_type={ 0:deriche | 1:gaussian }. #@cli : Deblur selected images using Gold-Meinel algorithm #@cli : Default values: 'nb_iter=8', 'acceleration=1' and 'kernel_type=1'. #@cli : $ image.jpg +blur 1 +deblur_goldmeinel[-1] 1 ###### : (contribution from Jérôme Boulanger). deblur_goldmeinel : check "$1>=0 && ${2=8}>=0 && ${3=1}>=0" skip ${4=1} e[^-1] "Deblur image$? using Gold-Meinel algorithm, with sigma $1, $2 iterations, acceleration $3 and "\ ${arg0\ !$4,"",quasi-}"gaussian kernel." foreach { [0] repeat $2 { +b. $1,1,$4 +/[0,-1] rm.. ^. $3 *[-1,-2] # u *= f / Hu } rm[0] } #@cli deblur_richardsonlucy : sigma>=0, nb_iter>=0, _kernel_type={ 0:deriche | 1:gaussian }. #@cli : Deblur selected images using Richardson-Lucy algorithm. #@cli : Default values: 'nb_iter=50' and 'kernel_type=1'. #@cli : $ image.jpg +blur 1 +deblur_richardsonlucy[-1] 1 ###### : (contribution from Jérôme Boulanger). deblur_richardsonlucy : check "$1>=0 && ${2=50}>=0" skip ${3=1} e[^-1] "Deblur image$? using Richardson-Lucy algorithm, with sigma $1, $2 iterations and "\ ${arg0\ !$3,"",quasi-}"gaussian kernel." foreach { [0] repeat $2 { +b. $1,1,{$3!=0} max. 1e-6 +/[0,-1] rm.. b. $1,1,{$3!=0} *[-1,-2] # u *= H ( f / Hu ) } rm[0] } #@cli deconvolve_fft : [kernel],_regularization>=0 #@cli : Deconvolve selected images by specified mask in the fourier space. #@cli : Default value: 'regularization>=0'. #@cli : $ image.jpg +gaussian 5 +convolve_fft[0] [1] +deconvolve_fft[-1] [1] deconvolve_fft : check ${is_image_arg\ $1}" && ${2=.001}>=0" e[^-1] "Deconvolve image$? with mask $1 and regularization $2, in the fourier domain." repeat $! { pass$1 0 l[$>,-1] { w2,h2,d2={0,int([w,h,d]/2)} r[1] [0],[0],[0],1,0,0,0.5,0.5,0.5,0.5 shift[1] -$w2,-$h2,-$d2,0,2 fft[0] fft[2] # a b a' b' +l[-1,-2] { sqr + + $2 } # a b a' b' (a'^2+b'^2+eps) +*[-4] ... # a b a' b' (a'^2+b'^2) ba' +*[-6] ... # a b a' b' (a'^2+b'^2) ba' ab' -[-2,-1] # a b a' b' (a'^2+b'^2) ba'-ab' *[-6,-4] # aa' b b' (a'^2+b'^2) ba'-ab' *[-4,-3] # aa' bb' (a'^2+b'^2) ba'-ab' +[-4,-3] # aa'+bb' (a'^2+b'^2) ba'-ab' /. .. /[-3,-2] # divide (aa'+bb') and (ba'-ab') by (a'^2+b'^2) ifft rm. } } #@cli deinterlace : _method={ 0 | 1 } #@cli : Deinterlace selected images ('method' can be { 0:standard or 1:motion-compensated }). #@cli : Default value: 'method=0'. #@cli : $ image.jpg +rotate 3,1,1,50%,50% resize 100%,50% resize 100%,200%,1,3,4 shift[-1] 0,1 add +deinterlace 1 deinterlace : skip ${1=0} e[^-1] "Deinterlace image$? with "${arg0\ !$1,motion-compensated,standard}" method." foreach { wh:=[w,h] s y a[0--1:2] y a[^0] y ri.. .,0 r 100%,200%,1,100%,5 if $1!=0 +displacement. ..,0.05 warp... .,1,1,1 rm. fi + / 2 c 0,255 r $wh } #@cli denoise : [guide],std_deviation_s[%]>=0,std_deviation_r[%]>=0,_patch_size>0,_lookup_size>0,_smoothness,\ # _fast_approx={ 0 | 1 } : \ # std_deviation_s[%]>=0,std_deviation_r[%]>=0,_patch_size>0,_lookup_size>0,_smoothness,\ # _fast_approx={ 0 | 1 } : (+) #@cli : Denoise selected images by non-local patch averaging. #@cli : Default values: 'patch_size=5', 'lookup_size=6' and 'smoothness=1'. #@cli : $ image.jpg +denoise 5,5,8 #@cli denoise_haar : _threshold>=0,_nb_scales>=0,_cycle_spinning>0 #@cli : Denoise selected images using haar-wavelet thresholding with cycle spinning. #@cli : Set 'nb_scales==0' to automatically determine the optimal number of scales. #@cli : Default values: 'threshold=1.4', 'nb_scale=0' and 'cycle_spinning=10'. #@cli : $ image.jpg noise 20 cut 0,255 +denoise_haar[-1] 0.8 denoise_haar : check "${1=1.4}>=0 && isint(${2=0},0) && isint(${3=10},1)" e[^-1] "Denoise image$? using haar-wavelet thresholding, with threshold $1, "\ ${arg0\ ($2>0),auto,$2}" scales and $3 spinning cycles." foreach { nm={n} nb_scales:=min($2?$2:32,int(log2(min(w,h))-1)) w,h,d={w},{h},{d} sigma=${-std_noise} r {round(w,2^($nb_scales+1),1)},{round(h,2^($nb_scales+1),1)},{d==1?1:round(d,2^($nb_scales+1),1)},100%,0,0 +f 0 repeat $3 { dx:=u(4*$nb_scales) dy:=u(4*$nb_scales) dz:=$d==1?0:v(4*$nb_scales) +shift[0] $dx,$dy,$dz,0,2 haar. $nb_scales threshold. {$1*$sigma},1 ihaar. $nb_scales shift. {-$dx},{-$dy},{-$dz},0,2 +[-2,-1] } rm[0] / $3 r $w,$h,$d,100%,0 => $nm } #@cli denoise_cnn : _noise_type={ 0:soft | 1:heavy | 2:heavy (faster) | 3:poisson+gaussian | \ # 4:poisson+gaussian2 },_patch_size>0 #@cli : Denoise selected images using a convolutional neural network (CNN). #@cli : Input value range should be [0,255]. Output value range is [0,255]. #@cli : Default value: 'patch_size=64'. #@cli : $ image.jpg noise 20 cut 0,255 +denoise_cnn denoise_cnn : skip "${1=0},${2=64}" N=$! if isint($1,0,4) check "isint($2,1)" type=$1 else type=0 noarg fi s0,s1,s2,s3,s4=soft,heavy,heavy,poisson+gaussian,poisson+gaussian2 e[^-1] "Denoise image$? using a convolutional neural network (for "${s$type}"-noise), with patch size $2." # Load denoising network. if 0${_is_denoise_cnn_$type} ${_denoise_cnn_$type} else l[] { l[] { input_cached gmic_denoise_cnn.gmz onfail error[0--5] "Command 'denoise_cnn': Unable to load network file 'gmic_denoise_cnn.gmz'." } k[$type] unserialize +store _denoise_cnn_$type _is_denoise_cnn_$type=1 } fi ps=$2 replace_str. "nn_layer_","nn_" # Upward compatibility replace_str. "nn_input X,32,32,1,3","nn_input X,"$ps","$ps",1,3" # Set patch size # Preserve upward compatibility. if !$1 replace_str. "nn_crop OUT,OUT0,2,2,0,29,29,0","nn_crop OUT,OUT0,2,2,0,0,29,29,0,2" elif inrange($1,1,4) replace_str. "nn_crop OUT,OUT0,2,2,0,29,29,0","nn_crop OUT,OUT0,2,2,0,0,29,29,0,2" replace_str. ",1,1,0 ",",1,1,0,1,0 " replace_str. ",1,1,1 ",",1,1,0,1,1 " fi run {t} rm[-2,-1] # Process images. repeat $N { l[$>,$N--1] { w,h:=w#0,h#0 to_color[0] if $type==3 srgb2rgb[0] elif $type==4 srgb2rgb[0] /[0] 255 fi # Denoise tiles. img2patches[0] $ps,6,3 1,1,{0,d},1,*${-nn_lib}"X = crop(#0,0,0,z,0,$ps,$ps,1,3);"${_nn_forward}"draw(#0,OUT0,0,0,z,0,$ps,$ps,1,3)" rm. patches2img[0] $w,$h,6,30% c[0] 0,255 if $type==3 rgb2srgb[0] elif $type==4 *[0] 255 rgb2srgb[0] fi } } rm[$N--1] #@cli denoise_patchpca : _strength>=0,_patch_size>0,_lookup_size>0,_spatial_sampling>0 #@cli : Denoise selected images using the patch-pca algorithm. #@cli : Default values: 'patch_size=7', 'lookup_size=11', 'details=1.8' and 'spatial_sampling=5'. #@cli : $ image.jpg +noise 20 cut[-1] 0,255 +denoise_patchpca[-1] , denoise_patchpca : check "${1=1.8} && $1>=0 && isint(${2=7},1) && isint(${3=11},1) && isint(${4=5},1)" e[^-1] "Denoise image$? using patch-pca, with strength $1, patch size $2, lookup size $3 and spatial sampling $4." foreach { nm={n} N2:=$2^2 M2:=$3^2 stdnoise=${-std_noise} 100%,100%,1,100% => aggreg 100%,100% => weights f[0] "* begin( n1 = int($2/2); n2 = $2 - n1 - 1; m1 = int($3/2); m2 = $3 - m1 - 1; patch(x,y) = crop(x - n1,y - n1,0,c,$2,$2,1,1,1); ngauss(x) = exp(-x*x/(2*n1*n1)); zero = mask = vector"$N2"(0); for (l = 0; q = -n1, q<=n2, ++q, for (p = -n1, p<=n2, ++p, mask[l++] = ngauss(p)*ngauss(q) ) ) ); if (!(x%$4) && !(y%$4), # Sub-sampling X = patch(x,y); # Build correlation matrix for PCA. M = vector"{$N2*$N2}"(0); for (q = -m1, q<=m2, ++q, for (p = -m1, p<=m2, ++p, Xk = patch(x + p,y + q) - X; M += mul(Xk,Xk,"$N2"); ) ); M/="$M2"; eig = eig(M); lambda = sqrt(abs(eig[0,"$N2"])); # Determine number of lambdas to keep and project neighboring patches. for (k = 0, k=$1*"$stdnoise", ++k); Qt = eig["$N2","{$N2*$N2}"]; Q = transpose(Qt,"$N2"); for (q = -m1, q<=m2, ++q, for (p = -m1, p<=m2, ++p, pY = Qt*(patch(x + p,y + q) - X); copy(pY[k],zero[0],size(pY) - k); (Y = Q*pY)+=X; draw(#"$aggreg",Y,x + p - n1,y + q - n1,0,c,$2,$2,1,1,-1,mask); draw(#"$weights",mask,x + p - n1,y + q - n1,0,c,$2,$2,1,1,-1); ) ); 0);0" max[weights] 0.01 /[aggreg,weights] k[aggreg] => $nm } #@cli deriche : std_deviation>=0[%],order={ 0 | 1 | 2 },axis={ x | y | z | c },_boundary_conditions : (+) #@cli : Apply Deriche recursive filter on selected images, along specified axis and with #@cli : specified standard deviation, order and boundary conditions. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'boundary_conditions=1'. #@cli : $ image.jpg deriche 3,1,x #@cli : $ image.jpg +deriche 30,0,x deriche[-2] 30,0,y add #@cli : $$ https://gmic.eu/oldtutorial/_deriche #@cli dilate : size>=0 : size_x>=0,size_y>=0,size_z>=0 : \ # [kernel],_boundary_conditions,_is_real={ 0:binary-mode | 1:real-mode } : (+) #@cli : Dilate selected images by a rectangular or the specified structuring element. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_z=1', 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +dilate 10 #@cli dilate_circ : _size>=0,_boundary_conditions,_is_real={ 0 | 1 } #@cli : Apply circular dilation of selected images by specified size. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +dilate_circ 7 dilate_circ : check "$1>=0 && isint(${2=1},0,3) && isbool(${3=0})" r0,r1=binary,real e[^-1] "Apply circular dilation of image$? by size $1, boundary conditions $2, in "${r$3}" mode." if $1<2 return fi shape_circle $1 dilate[^-1] .,$2,$3 rm. #@cli dilate_oct : _size>=0,_boundary_conditions,_is_real={ 0 | 1 } #@cli : Apply octagonal dilation of selected images by specified size. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +dilate_oct 7 dilate_oct : check "$1>=0 && isint(${2=1},0,3) && isbool(${3=0})" r0,r1=binary,real e[^-1] "Apply octagonal dilation of image$? by size $1, boundary conditions $2, in "${r$3}" mode." if $1<2 return fi if $1&1 ss=$1 else ss:=$1+1 fi i[0] (0,1,0;1,1,1;0,1,0) i[1] (1,1,1;1,1,1;1,1,1) repeat $!-2 { r:=round(($ss-1)*sqrt(2)/(1+sqrt(2))/2) q:=round(($ss-1)/(1+sqrt(2))/2) if $r>0 repeat $r { dilate. [0],$2,$3 } fi if $q>0 repeat $q { dilate. [1],$2,$3 } fi mv. 2 } rm[0,1] _kr_circle : if !($1%2) 2,2,1,1,1 else 1,1,1,1,1 fi r. $1,$1,1,1,0,0,0.5,0.5 distance. 1 round. 0.5 ir. 0,{$1/2} _jf_circle : {round($1)},{round($1)} center:=0.5*(w-1) f. "sqrt((x - $center)^2 + (y - $center)^2)" if !(w%2) round. 0.0001,-1 t1:=sqrt(((round($1)-1)/2)^2+0.25) t2:=sqrt(((round($1)+1)/2)^2+0.25) k:=$1-round($1)+0.5 t:=$t1+($t2-$t1)*$k ir. 0,$t else ir. 0,{$1/2-0.25} fi #@cli dilate_threshold : size_x>=1,size_y>=1,size_z>=1,_threshold>=0,_boundary_conditions #@cli : Dilate selected images in the (X,Y,Z,I) space. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_y=size_x', 'size_z=1', 'threshold=255' and 'boundary_conditions=1'. dilate_threshold : check "isint($1,1) && isint(${2=$1},1) && isint(${3=1},1) && ${4=255}>=0 && isbool(${5=1})" e[^-1] "Dilate image$? with mask $1x$2x$3, threshold $4 and "${arg0\ $5,dirichlet,neumann}" boundary conditions." l[] { dx1:=int($1/2) dx2:=$1-$dx1-1 dy1:=int($2/2) dy2:=$2-$dy1-1 dz1:=int($3/2) dz2:=$3-$dz1-1 (-$dx1,$dx1) (-$dy1;$dy1) (-$dz1/$dz1) r $1,$2,$3,1,3 a c round r {w*h*d},3,1,1,-1 transpose. i.. 1,100%,1,1,254 1,100%,1,1,255 a x ('{^}') rm.. replace_str "254,","(v=j(" replace_str ",255",",0,0,$5);abs(v-i)<=$4?v:-1e20)" list={t} rm } f max($list) #@cli divergence #@cli : Compute divergence of selected vector fields. #@cli : $ image.jpg luminance +gradient append[-2,-1] c divergence[-1] divergence : e[^-1] "Compute divergence of vector field$?." foreach { if s==1 g x,0 elif s==2 s c g.. x,0 g. y,0 + elif s==3 s c g... x,0 g.. y,0 g. z,0 + else error[] "Command '$0': Cannot compute divergence of image ["$>"] (has "{s}">3 channels)." fi } #@cli dog : _sigma1>=0[%],_sigma2>=0[%] #@cli : Compute difference of gaussian on selected images. #@cli : Default values: 'sigma1=2%' and 'sigma2=3%'. #@cli : $ image.jpg dog 2,3 dog : check "${1=2%}>=0 && ${2=3%}>=0" e[^-1] "Compute difference of gaussian on image$?, with standard deviations $1 and $2." foreach { [0] parallel "b[0] $1","b[1] $2" - abs } #@cli diffusiontensors : _sharpness>=0,0<=_anisotropy<=1,_alpha[%],_sigma[%],is_sqrt={ 0 | 1 } #@cli : Compute the diffusion tensors of selected images for edge-preserving smoothing algorithms. #@cli : Default values: 'sharpness=0.7', 'anisotropy=0.3', 'alpha=0.6', 'sigma=1.1' and 'is_sqrt=0'. #@cli : $ image.jpg diffusiontensors 0.8 abs pow 0.2 #@cli : $$ https://gmic.eu/oldtutorial/_diffusiontensors diffusiontensors : check "${1=0.7}>=0 && ${2=0.3}>=0 && $2<=1" skip ${3=0.6},${4=1.1},${5=0} e[^-1] "Compute diffusion tensors for image$?, with sharpness $1, anisotropy $2, alpha $3 and sigma $4." p1:=($5?0.5:1)*max($1,1e-2) p2:=$p1/(1e-7+1-$2) b $3 n 0,255 structuretensors 0 b $4 foreach { eigen max.. 0 if s==2 s.. c +[-3,-2] +.. 1 +^.. -$p1 ^... -$p2 a[-3,-1] c # 2D else s.. c +[-4--2] +.. 1 +^.. -$p1 r. 100%,100%,100%,2 ^... -$p2 a[-3,-1] c # 3D fi eigen2tensor } #@cli edges : _threshold[%]>=0 #@cli : Estimate contours of selected images. #@cli : Default value: 'edges=15%' #@cli : $ image.jpg +edges 15% edges : skip ${1=15%} e[^-1] "Estimate image contours of image$?, with threshold $1." gradient_norm b 0.5 >= $1 distance 0 equalize negate c 30%,70% n 0,1 #@cli erode : size>=0 : size_x>=0,size_y>=0,_size_z>=0 : \ # [kernel],_boundary_conditions,_is_real={ 0:binary-mode | 1:real-mode } : (+) #@cli : Erode selected images by a rectangular or the specified structuring element. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_z=1', 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +erode 10 #@cli erode_circ : _size>=0,_boundary_conditions,_is_real={ 0 | 1 } #@cli : Apply circular erosion of selected images by specified size. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +erode_circ 7 erode_circ : check "$1>=0 && isint(${2=1},0,3) && isbool(${3=0})" r0,r1=binary,real e[^-1] "Apply circular erosion of image$? by size $1, boundary conditions $2, in "${r$3}" mode." if $1<2 return fi shape_circle $1 erode[^-1] .,$2,$3 rm. #@cli erode_oct : _size>=0,_boundary_conditions,_is_real={ 0 | 1 } #@cli : Apply octagonal erosion of selected images by specified size. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +erode_oct 7 erode_oct : check "$1>=0 && isint(${2=1},0,3) && isbool(${3=0})" r0,r1=binary,real e[^-1] "Apply octagonal erosion of image$? by size $1, boundary conditions $2, in "${r$3}" mode." if $1<2 return fi if $1&1 ss=$1 else ss:=$1+1 fi i[0] (0,1,0;1,1,1;0,1,0) i[1] (1,1,1;1,1,1;1,1,1) repeat $!-2 { r:=round(($ss-1)*sqrt(2)/(1+sqrt(2))/2) q:=round(($ss-1)/(1+sqrt(2))/2) if $r>0 repeat $r { erode. [0],$2,$3 } fi if $q>0 repeat $q { erode. [1],$2,$3 } fi mv. 2 } rm[0,1] #@cli erode_threshold : size_x>=1,size_y>=1,size_z>=1,_threshold>=0,_boundary_conditions #@cli : Erode selected images in the (X,Y,Z,I) space. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_y=size_x', 'size_z=1', 'threshold=255' and 'boundary_conditions=1'. erode_threshold : check "isint($1,1) && isint(${2=$1},1) && isint(${3=1},1) && ${4=255}>=0 && isbool(${5=1})" e[^-1] "Erode image$? with mask $1x$2x$3, threshold $4 and "${arg0\ $5,dirichlet,neumann}" boundary conditions." l[] { dx1:=int($1/2) dx2:=$1-$dx1-1 dy1:=int($2/2) dy2:=$2-$dy1-1 dz1:=int($3/2) dz2:=$3-$dz1-1 (-$dx1,$dx1) (-$dy1;$dy1) (-$dz1/$dz1) r $1,$2,$3,1,3 a c round r {w*h*d},3,1,1,-1 transpose. i.. 1,100%,1,1,254 1,100%,1,1,255 a x ('{^}') rm.. replace_str "254,","(v=j(" replace_str ",255",",0,0,$5);abs(v-i)<=$4?v:1e20)" list={t} rm } f min($list) #@cli fft : _{ x | y | z }...{ x | y | z } : (+) #@cli : Compute the direct fourier transform (real and imaginary parts) of selected images, #@cli : optionally along the specified axes only. #@cli : See also: ''ifft''. #@cli : $ image.jpg luminance +fft append[-2,-1] c norm[-1] log[-1] shift[-1] 50%,50%,0,0,2 #@cli : $ image.jpg w2:=int(w/2) h2:=int(h/2) fft shift $w2,$h2,0,0,2 ellipse $w2,$h2,30,30,0,1,0 \ # shift -$w2,-$h2,0,0,2 ifft remove[-1] #@cli : $$ https://gmic.eu/oldtutorial/_fft #@cli g : eq. to 'gradient'. : (+) g : _gmic_s="$?" axes,scheme,boundary,is_noarg=${"v + _gradient_get_args $*"} _gradient $axes,$scheme,$boundary v - if $is_noarg noarg fi #@cli gradient : { x | y | z | c }...{ x | y | z | c },_scheme,_boundary_conditions : (no arg) #@cli : Compute the gradient components (first derivatives) of selected images, along specified axes. #@cli : (eq. to 'g').\n #@cli : 'scheme' can be { -1:backward | 0:centered | 1:forward | 2:sobel | 3:rotation-invariant (default) | \ # 4:deriche | 5:vanvliet }. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : (no arg) compute all significant components. #@cli : Default values: 'scheme=0' and 'boundary_conditions=1'. #@cli : $ image.jpg gradient #@cli : $$ https://gmic.eu/oldtutorial/_gradient gradient : _gmic_s="$?" axes,scheme,boundary,is_noarg=${"v + _gradient_get_args $*"} _gradient $axes,$scheme,$boundary v - if $is_noarg noarg fi # Parse and check arguments. _gradient_get_args : skip "${1=},${2=},${3=}" is_noarg=0 l[] { if ['$1']==0 axes,scheme,boundary=,0,1 is_noarg=1 # No 1st arg else is_axes:="s=['$1'];fill(s,k,isin(s[k],_'x',_'y',_'z',_'c'));min(s)" if !$is_axes axes,scheme,boundary=,0,1 is_noarg=1 # Invalid 1st arg elif ['$2']==0 axes,scheme,boundary=$1,0,1 # No 2nd arg else is_scheme:="isint($2,-1,5)" if !$is_scheme axes,scheme,boundary=,0,1 is_noarg=1 # Invalid 2nd arg elif ['$3']==0 axes,scheme,boundary=$1,$2,1 # No 3rd arg else is_boundary:="isint($3,0,3)" if !$is_boundary axes,scheme,boundary=,0,1 is_noarg=1 # Invalid 3rd arg else axes,scheme,boundary=${1-3} fi fi fi fi onfail axes,scheme,boundary=,0,1 is_noarg=1 # Invalid arguments } if ['$axes']!=0 s_axes=" along axes '"$axes"'" fi e[0--4] "Compute gradient of image"$_gmic_s$s_axes", with "\ ${"arg0 $scheme+1,backward,centered,forward,sobel,rotation-invariant,deriche,vanvliet"}" scheme and "\ ${"arg0 $boundary,dirichlet,neumann,periodic,mirror"}" boundary conditions." u $axes,$scheme,$boundary,$is_noarg # Process images. _gradient : skip "${1=}" sx,sy,sz,sc=",",;,/,^ foreach { nm={n} l_axes=$1 if ['$l_axes']==0 if d>1 l_axes=xyz else l_axes=xy fi fi ('$l_axes') repeat w { a={`i[#1,$>]`} s=${s$a} if 0${gradient_$a}>0 [gradient_$a] elif $2==-1 # Backward (-1${s}1${s}0) +correlate[0] .,$3 rm.. elif !$2 # Centered (-0.5${s}0${s}0.5) +correlate[0] .,$3 rm.. elif $2==1 # Forward (0${s}-1${s}1) +correlate[0] .,$3 rm.. elif $2==2 # Sobel if _'$a'==_'x' (-1,0,1;-2,0,2;-1,0,1) elif _'$a'==_'y' (-1,-2,-1;0,0,0;1,2,1) else (-0.5${s}0${s}0.5) fi +correlate[0] .,$3 rm.. elif $2==3 # Rotation-invariant A,B:=[0.25*(2-sqrt(2)),0.5*(sqrt(2)-1)] if _'$a'==_'x' (-$A,0,$A;-$B,0,$B;-$A,0,$A) elif _'$a'==_'y' (-$A,-$B,-$A;0,0,0;$A,$B,$A) else (-0.5${s}0${s}0.5) fi +correlate[0] .,$3 rm.. else # Deriche/Vanvliet s4,s5=deriche,vanvliet com=${s$2} if $3<2 +$com[0] 0,1,$a,$3 else if _'$a'==_'x' +r[0] {0,[w+2,h,d,s]},0,$3,0.5 $com. 0,1,$a shrink. x,1 elif _'$a'==_'y' +r[0] {0,[w,h+2,d,s]},0,$3,0,0.5 $com. 0,1,$a shrink. y,1 elif _'$a'==_'z' +r[0] {0,[w,h,d+2,s]},0,$3,0,0,0.5 $com. 0,1,$a shrink. z,1 fi fi fi => gradient_$a } rm[0,1] =>[^] $nm } #@cli gradient_norm #@cli : Compute gradient norm of selected images. #@cli : $ image.jpg gradient_norm equalize #@cli : $$ https://gmic.eu/oldtutorial/_gradient_norm gradient_norm : e[^-1] "Compute gradient norm of image$?." foreach { g sqr s c + sqrt } #@cli gradient_orientation : _dimension={ 1 | 2 | 3 } #@cli : Compute N-d gradient orientation of selected images. #@cli : Default value: 'dimension=3'. #@cli : $ image.jpg +gradient_orientation 2 gradient_orientation : check "isint(${1=3},1,3)" e[^-1] "Compute $1-d gradient orientation of image$?." foreach { if $1==1 g x +abs. +. 1e-8 -/ elif $1==2 g xy +sqr +[-2,-1] +. 1e-8 sqrt. /... . /[-2,-1] else g xyz +sqr +[-3--1] +. 1e-8 sqrt. /[-4,-3] . /[-2,-1] fi } #@cli guided : [guide],radius[%]>=0,regularization[%]>=0 : radius[%]>=0,regularization[%]>=0 : (+) #@cli : Blur selected images by guided image filtering. #@cli : If a guide image is provided, it is used to drive the smoothing process. #@cli : A guide image must be of the same xyz-size as the selected images. #@cli : This command implements the filtering algorithm described in: #@cli : He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering", #@cli : IEEE Transactions on Pattern Analysis and Machine Intelligence, vol.35, no.6, pp.1397,1409, June 2013 #@cli : $ image.jpg +guided 5,400 #@cli haar : scale>0 #@cli : Compute the direct haar multiscale wavelet transform of selected images. #@cli : See also: ''ihaar''. #@cli : $$ https://gmic.eu/oldtutorial/_haar haar : check "isint(${1=1},0)" e[^-1] "Compute haar transform of image$? with $1 scales." foreach { _haar repeat $1-1 { w,h,d:="vmax(0,round([w,h,d]/2^(1+"$>"))-1)" +z 0,0,0,$w,$h,$d _haar. j.. . rm. } } _haar : # Mono-scale direct haar transform. _haar_x _haar_y _haar_z _haar_x : # Direct haar transform along the x-axis. if w<=1 return fi if w%2 error[0--6] "Command 'haar': Invalid image width="{w}" (is not even)." fi +shift -1 r 50% +-[1] [0] +[0,1] / {sqrt(2)} a x _haar_y : # Direct haar transform along the y-axis. if h<=1 return fi if h%2 error[0--6] "Command 'haar': Invalid image height="{h}" (is not even)." fi +shift 0,-1 r 100%,50% +-[1] [0] +[0,1] / {sqrt(2)} a y _haar_z : # Direct haar transform along the z-axis. if d<=1 return fi if d%2 error[0--6] "Command 'haar': Invalid image depth="{h}" (is not even)." fi +shift 0,0,-1 r 100%,100%,50% +-[1] [0] +[0,1] / {sqrt(2)} a z #@cli heat_flow : _nb_iter>=0,_dt,_keep_sequence={ 0 | 1 } #@cli : Apply iterations of the heat flow on selected images. #@cli : Default values: 'nb_iter=10', 'dt=30' and 'keep_sequence=0'. #@cli : $ image.jpg +heat_flow 20 heat_flow : skip ${1=10},${2=30},${3=0} e[^-1] "Apply $1 iterations of the heat flow on image$?, with time step $2." pde_flow $1,$2,laplacian,$3 #@cli hessian : { xx | xy | xz | yy | yz | zz }...{ xx | xy | xz | yy | yz | zz },_boundary_conditions : (no arg) : #@cli : Compute the hessian components (second derivatives) of selected images along specified axes. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : (no arg) compute all significant components. #@cli : Default value: 'boundary_conditions=1'. #@cli : $ image.jpg hessian hessian : skip "${1=},${2=}" # Parse arguments. l[] { if ['$1']==0 axes,boundary=,1 noarg # No 1st arg else is_axes:="s=['$1'];fill(s,k,isin(s[k],_'x',_'y',_'z'));min(s)" if !$is_axes axes,boundary=,1 noarg # Invalid 1st arg elif ['$2']==0 axes,boundary=$1,1 # No 2nd arg else is_boundary:="isint($2,0,3)" if !$is_boundary axes,boundary=,1 noarg # Invalid 2nd arg else axes,boundary=$1,$2 fi fi fi onfail axes,boundary=,1 noarg # Invalid arguments } if ['$axes']!=0 s_axes=" along axes '"$axes"'" fi e[^-1] "Compute hessian of image$?"$s_axes", with "\ ${"arg0 $boundary,dirichlet,neumann,periodic,mirror"}" boundary conditions." # Process images. foreach { nm={n} l_axes=$axes if ['$l_axes']==0 if d>1 l_axes=xxxyxzyyyzzz else l_axes=xxxyyy fi fi ('$l_axes') repeat int(w/2) { a1,a2={`i[#1,2*$>]`},{`i[#1,2*$>+1]`} if _'$a1'>_'$a2' a1,a2=$a2,$a1 fi if 0${hessian_$a1$a2}>0 [hessian_$a1$a2] elif _'$a1'==_'x'" && "_'$a2'==_'x' (1,-2,1) +correlate[0] .,$boundary rm.. elif _'$a1'==_'x'" && "_'$a2'==_'y' (0.25,0,-0.25;0,0,0;-0.25,0,0.25) +correlate[0] .,$boundary rm.. elif _'$a1'==_'x'" && "_'$a2'==_'z' (0.25,0,-0.25/0,0,0/-0.25,0,0.25) +correlate[0] .,$boundary rm.. elif _'$a1'==_'y'" && "_'$a2'==_'y' (1;-2;1) +correlate[0] .,$boundary rm.. elif _'$a1'==_'y'" && "_'$a2'==_'z' (0.25;0;-0.25/0;0;0/-0.25;0;0.25) +correlate[0] .,$boundary rm.. elif _'$a1'==_'z'" && "_'$a2'==_'z' (1/-2/1) +correlate[0] .,$boundary rm.. fi => hessian_$a1$a2 } rm[0,1] =>[^] $nm } #@cli idct : _{ x | y | z }...{ x | y | z } : (no arg) #@cli : Compute the inverse discrete cosine transform of selected images, optionally along the specified axes only. #@cli : Output images are always evenly sized, so this command may change the size of the selected images. #@cli : (dct images obtained with the 'dct' command are evenly sized anyway). #@cli : Default values: (no arg) #@cli : See also: ''dct''. #@cli : $$ https://gmic.eu/oldtutorial/_dct-and-idct idct : skip ${1=0} ('"$1"') is_axes:="im>=_'x' && iM<=_'z'" if $is_axes e[0--3] "Compute inverse discrete cosine transform of image$? along axes '$1'." repeat w { axis:=i[$>] if $axis==_'x' foreach[^-1] { if w>1 _idct fi } elif $axis==_'y' foreach[^-1] { if h>1 permute yxzc _idct permute yxzc fi } elif $axis==_'z' foreach[^-1] { if d>1 permute zxyc _idct permute yzxc fi } fi } rm. else rm. e[0--3] "Compute inverse discrete cosine transform of image$?." noarg foreach { if w>1 _idct fi if h>1 permute yxzc _idct permute yxzc fi if d>1 permute zxyc _idct permute yzxc fi } fi # 1D inverse transform (DCT-III) along the x-axis, for a single image. _idct : if w%2 r {w+1},100%,100%,100%,0,0 fi / {sqrt(2/w)} +z[0] 0,0 *. {sqrt(2)} j.. .,0,0,0 rm. # Make the transform orthogonal. +mirror x shift. 1 *. -1 100%,1,1,1,cos(x*pi/(2*w)) 100%,1,1,1,sin(x*pi/(2*w)) +*[0,3] +*[1,2] +[-2,-1] *[0,2] *[1,2] -[0,1] ifft x k[0] / 2 s x,2 mirror. x r[0] 200%,100%,100%,100%,4,0,0 r[1] 200%,100%,100%,100%,4,0,1 + #@cli iee #@cli : Compute gradient-orthogonal-directed 2nd derivative of image(s). #@cli : $ image.jpg iee iee : e[^-1] "Compute gradient-orthogonal-directed 2nd derivative of image$?." foreach { if d==1 +g xy,0 hessian... xxxyyy # ixx ixy iyy ix iy *... .. *[-4] . *[-4] -2 # ixx -2iyixy ixiyy ix iy +[-4,-3] *... .. # ixx -2ixiyixy+ix^2iyy ix iy sqr[-2,-1] *[-4] . +[-4,-3] # iy^2ixx-2ixiyixy+ix^2iyy ix^2 iy^2 +[-2,-1] +. 1e-8 / # (iy^2ixx+2ixiyixy+ix^2iyy)/(ix^2+iy^2) else +inn laplacian.. - fi } #@cli ifft : _{ x | y | z }...{ x | y | z } : (+) #@cli : Compute the inverse fourier transform (real and imaginary parts) of selected images. #@cli : optionally along the specified axes only. #@cli : See also: ''fft''. #@cli : $$ https://gmic.eu/oldtutorial/_fft #@cli ihaar : scale>0 #@cli : Compute the inverse haar multiscale wavelet transform of selected images. #@cli : See also: ''haar''. #@cli : $$ https://gmic.eu/oldtutorial/_haar ihaar : check "isint(${1=1},0)" e[^-1] "Compute inverse haar transform of image$? with $1 scales." foreach { repeat $1-1 { w,h,d:="vmax(0,round([w,h,d]/2^(1+"$<"))-1)" +z 0,0,0,$w,$h,$d _ihaar. j.. . rm. } _ihaar } _ihaar : # Mono-scale inverse haar transform. _ihaar_x _ihaar_y _ihaar_z _ihaar_x : # Inverse haar transform along the x-axis. if w<=1 return fi if w%2 error[0--6] "Command 'ihaar': Invalid image width="{w}" (is not even)." fi s x,2 r 200% (-1,1) *[-2,-1] + / {sqrt(2)} _ihaar_y : # Inverse haar transform along the y-axis. if h<=1 return fi if h%2 error "Command 'ihaar': Invalid image height="{h}" (is not even)." fi s y,2 r 100%,200% (-1;1) r. {-2,w} *[-2,-1] + / {sqrt(2)} _ihaar_z : # Inverse haar transform along the z-axis. if d<=1 return fi if d%2 error "Command 'ihaar': Invalid image depth="{h}" (is not even)." fi s z,2 r 100%,100%,200% (-1/1) r. {-2,w},{-2,h} *[-2,-1] + / {sqrt(2)} #@cli ilaplacian : { nb_iterations>0 | 0 },_[initial_estimate] #@cli : Invert selected Laplacian images. #@cli : If given 'nb_iterations' is '0', inversion is done in Fourier space (single iteration), #@cli : otherwise, by applying 'nb_iterations' of a Laplacian-inversion PDE flow. #@cli : Note that the resulting inversions are just estimation of possible/approximated solutions. #@cli : Default values: 'nb_iterations=0', 'axes=(undefined)' and '[initial_estimated]=(undefined)'. #@cli : $ image.jpg +laplacian +ilaplacian[-1] 0 ilaplacian : check "${1=0}>=0" skip "${2=}" nb_iter:=round($1) is_estimate=${"is_image_arg $2"} if !$nb_iter # Inversion in Fourier space if $is_estimate e[0--4] "Invert Laplacian image$? in Fourier space, with initial estimate $2." pass$2 1 ia=${-average_vectors} rm. else e[0--4] "Invert Laplacian image$? in Fourier space." ia=0 fi foreach { nm={n} fft .,.,.,1,"const pi2 = 2*pi; 2*(cos(x*pi2/w) + cos(y*pi2/h) + cos(z*pi2/d)) - 6" =. 1 /[-3,-2] . rm. = 0 ifft rm. => $nm } if $ia!=0 + '"begin(S = resize(["$ia"],s,0)); S"' fi else # Inversion with PDE-flow if $is_estimate e[0--4] "Invert Laplacian image$? using $1 iterations of PDE flow and initial estimate $2." repeat $! { nm={$>,n} pass$2 0 l[$>,-1] { *[0] 5 i[1] (0,5,0;5,0,5;0,5,0) repeat $1 { +convolve. [1] -. [0] +[-2,-1] /. 21 } k. # Optimized semi-implicit scheme, with dt=5 } => $nm } else e[0--4] "Invert Laplacian image$? using $1 iterations of PDE flow." foreach { nm={n} * 5 (0,5,0;5,0,5;0,5,0) +f.. 0 repeat $1 { +convolve. [1] -. [0] +[-2,-1] /. 21 } k. # Optimized semi-implicit scheme, with dt=5 => $nm } fi fi #@cli inn #@cli : Compute gradient-directed 2nd derivative of image(s). #@cli : $ image.jpg inn inn : e[^-1] "Compute gradient-directed 2nd derivative of image$?." foreach { if d==1 +g xy,0 hessian... xxxyyy # ixx ixy iyy ix iy *[-5] .. *[-4] . *[-4] 2 # ixixx 2iyixy iyy ix iy +[-5,-4] *[-4] .. # ix^2ixx+2ixiyixy iyy ix iy sqr[-2,-1] *... . +[-4,-3] # ix^2ixx+2ixiyixy+iy^2iyy ix^2 iy^2 +[-2,-1] +. 1e-8 / # (ix^2ixx+2ixiyixy+iy^2iyy)/(ix^2+iy^2) else +g xyz,0 hessian[-4] xxxyxzyyyzzz # ixx ixy ixz iyy iyz izz ix iy iz *[-9] ... *[-8] .. *[-8] 2 *[-7] . *[-7] 2 # ixixx 2iyixy 2izixz iyy iyz izz ix iy iz +[-9--7] *[-7] ... # ix^2ixx+2ixiyixy+2ixizixy iyy iyz izz ix iy iz *[-6] .. *[-5] . *[-5] 2 # ix^2ixx+2ixiyixy+2ixizixy iyiyy 2iziyz izz ix iy iz +[-6,-5] *[-5] .. +[-6,-5] # ix^2ixx+2ixiyixy+2ixizixy+iy^2iyy+2iyiziyz izz ix iy iz sqr[-3--1] *[-4] . +[-5,-4] # ix^2ixx+2ixiyixy+2ixizixy+iy^2iyy+2iyiziyz+iz^2izz ix^2 iy^2 iz^2 +[-3--1] +. 1e-8 / # (ix^2ixx+2ixiyixy+2ixizixy+iy^2iyy+2iyiziyz+iz^2izz)/(ix^2+iy^2+iz^2) fi } #@cli inpaint : [mask] : [mask],0,_fast_method : \ # [mask],_patch_size>=1,_lookup_size>=1,_lookup_factor>=0,_lookup_increment!=0,_blend_size>=0,\ # 0<=_blend_threshold<=1,_blend_decay>=0,_blend_scales>=1,_is_blend_outer={ 0 | 1 } : (+) #@cli : Inpaint selected images by specified mask. #@cli : If no patch size (or 0) is specified, inpainting is done using a fast average or median algorithm. #@cli : Otherwise, it used a patch-based reconstruction method, that can be very time consuming. #@cli : 'fast_method' can be { 0:low-connectivity average | 1:high-connectivity average | 2:low-connectivity median | \ # 3:high-connectivity median }. #@cli : Default values: 'patch_size=0', 'fast_method=1', 'lookup_size=22', 'lookup_factor=0.5', 'lookup_increment=1', \ # 'blend_size=0', 'blend_threshold=0', 'blend_decay=0.05', 'blend_scales=10' and 'is_blend_outer=1'. #@cli : $ image.jpg 100%,100% ellipse 50%,50%,30,30,0,1,255 ellipse 20%,20%,30,10,0,1,255 +inpaint[-2] [-1] remove[-2] #@cli : $ image.jpg 100%,100% circle 30%,30%,30,1,255,0,255 circle 70%,70%,50,1,255,0,255 \ # +inpaint[0] [1],5,15,0.5,1,9,0 remove[1] #@cli inpaint_pde : [mask],_nb_scales[%],_diffusion_type={ 0:isotropic | 1:Delaunay-guided | \ # 2:edge-guided | 3:mask-guided },_diffusion_iter>=0 #@cli : Inpaint selected images by specified mask using a multiscale transport-diffusion algorithm. #@cli : Argument 'nb_scales' sets the number of scales used in the multi-scale resolution scheme. #@cli : - When the '%' qualifier is used for 'nb_scales', the number of used scales is relative \ # to `nb_scales_max = ceil(log2(max(w,h,d)))`. #@cli : - When 'nb_scales<0', it determines the minimum image size encountered at the lowest scale. #@cli : If 'diffusion_type==3', non-zero values of the mask (e.g. a distance function) are used #@cli : to guide the diffusion process. #@cli : Default values: 'nb_scales=-9', 'diffusion_type=1' and 'diffusion_iter=20'. #@cli : $ image.jpg 100%,100% ellipse[-1] 30%,30%,40,30,0,1,255 +inpaint_pde[0] [1] inpaint_pde : check ${is_image_arg\ $1}" && isexpr(${2=-9}) && isint(${3=1},0,3) && isint(${4=20},0)" s0="isotropic" s1="Delaunay-guided" s2="edge-guided" s3="mask-guided" e[^-1] "Inpaint image$? by mask $1, using a multiscale diffusion algorithm with $2 scales"\ "and $4 iterations of "${s$3}" diffusion." repeat $! { nm={n} pass$1 l[$>,-1] { nb_scales:="const nb_scales_max = ceil(log2(max(w,h,d))); nb_scales = ispercentage($2)?nb_scales_max*$2: $2<0?1 + log2((d>1?min(w,h,d):min(w,h))/(-$2)): $2; floor(cut(nb_scales,1,nb_scales_max))" nb_iter:=max(0,$4) repeat $nb_scales { {0,"S = 2^"$<"; round([ max(1,w/S), max(1,h/S), max(1,d/S), s ])"} 100%,100%,100% eval[1] "const wl1 = w#-1 - 1; const hl1 = h#-1 - 1; const dl1 = d#-1 - 1; const w1 = max(1,w - 1); const h1 = max(1,h - 1); const d1 = max(1,d - 1); !i?( X = round(x*wl1/w1); Y = round(y*hl1/h1); Z = round(z*dl1/d1); I(#-2,X,Y,Z) += I(#0,x,y,z); ++i(#-1,X,Y,Z) );I" +max. 1 /[-3,-1] !=. 0 if !$> # First scale: Initialize by value propagation im={-2,im} +-.. {$im-1} *. .. +distance.. 1 *. -1 watershed.. . rm. +. {$im-1} mv. -3 fi if $>>0" || "$nb_scales==1 # Apply diffusion iterations. ri... ..,3 if !$3 # Isotropic diffusion repeat $nb_iter { j... ..,0,0,0,0,1,. b... 0.5 } elif $3==1 # Delaunay-guided +distance. 1 100%,100%,100%,{d==1?2:3} eval.. "* # Apply specific gradient scheme for distance function const boundary = 1; maxabs(a,b) = (abs(a)>abs(b)?a:b); ix = maxabs(j(1) - i,i - j(-1)); iy = maxabs(j(0,1) - i,i - j(0,-1)); d>1?( iz = maxabs(j(0,0,1) - i,i - j(0,0,-1)); copy(I(#-1),[ ix,iy,iz ],3,whd); ):copy(I(#-1),[ ix,iy ],2,whd)" rm.. orientation. repeat $nb_iter { j[-4] ...,0,0,0,0,1,.. +warp[-4] .,1,2,1 *.. -1 warp[-5] ..,1,2,1 +[-5,-1] /[-4] 2 } rm. elif $3==2 # Edge-guided repeat $nb_iter { +diffusiontensors... 0,1,1.5,0.5 j[-4] ...,0,0,0,0,1,.. smooth[-4] .,1,10,0 rm. } else # Mask-guided +r[1] .,2 g. a[-{d==1?2:3}--1] c orientation. repeat $nb_iter { j[-4] ...,0,0,0,0,1,.. +warp[-4] .,1,2,1 *.. -1 warp[-5] ..,1,2,1 +[-5,-1] /[-4] 2 } rm. fi j... ..,0,0,0,0,1,. fi rm[-2,-1] } => $nm rv[0,-1] rm. } rm. } #@cli inpaint_flow : [mask],_nb_global_iter>=0,_nb_local_iter>=0,_dt>0,_alpha>=0,_sigma>=0 #@cli : Apply iteration of the inpainting flow on selected images. #@cli : Default values: 'nb_global_iter=10', 'nb_local_iter=100', 'dt=5', 'alpha=1' and 'sigma=3'. #@cli : $ image.jpg 100%,100% ellipse[-1] 30%,30%,40,30,0,1,255 inpaint_flow[0] [1] inpaint_flow : check ${is_image_arg\ $1}" && ${2=10}>=0 && ${3=100}>=0 && ${4=5}>0 && ${5=1}>=0 && ${6=3}>=0" e[^-1] "Apply $2x$3 iterations of the inpainting flow on image$?, with mask $1, time step $4, alpha $5 and sigma $6." repeat $! { pass$1 0 l[$>,-1] { r. [0],[0],[0],1,0 inpaint.. [1] repeat $2 { progress {100*$>/($2-1)} +diffusiontensors.. 0,1,$5,$6,0 *. .. smooth... .,$3,$4,0 rm. } progress 100 } rm. } #@cli inpaint_holes : maximal_area[%]>=0,_tolerance>=0,_is_high_connectivity={ 0 | 1 } #@cli : Inpaint all connected regions having an area less than specified value. #@cli : Default values: 'maximal_area=4', 'tolerance=0' and 'is_high_connectivity=0'. #@cli : $ image.jpg noise 5%,2 +inpaint_holes 8,40 inpaint_holes : check "${1=4}>=0 && ${2=0}>=0" skip ${3=0} e[^-1] "Inpaint holes with area less than $1 pixels in image$?, with tolerance $2 and "\ ${arg0\ !$3,high,low}" connectivity." foreach { 100%,100%,100% area:=ispercentage($1)?$1*w*h*d:$1 repeat s#0 { sh[0] $> +area. $2,$3 <=. $1 -|[1,-1] rm. } if im k[0] whd={w},{h},{d} r 1,1,1,100%,2 r $whd,100% else inpaint[0] [1],0,{2*!$2+!!$3} k[0] fi } #@cli inpaint_morpho : [mask] #@cli : Inpaint selected images by specified mask using morphological operators. #@cli : $ image.jpg 100%,100% ellipse[-1] 30%,30%,40,30,0,1,255 +inpaint_morpho[0] [1] inpaint_morpho : check ${is_image_arg\ $1} e[^-1] "Inpaint image$? by mask $1, using morphological operators." repeat $! { pass$1 0 l[$>,-1] { nm={0,n} im,iM={0,[im,iM]} im1,iM1:=$im-1,$iM+1 channels. 0 ==. 0 +f[0] $im1 j. [0],0,0,0,0,1,.. do +dilate. 3 replace.. $im1,$iM1 erode.. 3 replace.. $iM1,$im1 +[-2,-1] /. 2 j. ...,0,0,0,0,1,.. while im==$im1 k. => $nm } } #@cli inpaint_matchpatch : [mask],_nb_scales={ 0:auto | >0 },_patch_size>0,_nb_iterations_per_scale>0,\ # _blend_size>=0,_allow_outer_blending={ 0 | 1 },_is_already_initialized={ 0 | 1 } #@cli : Inpaint selected images by specified binary mask, using a multi-scale matchpatch algorithm. #@cli : Default values: 'nb_scales=0', 'patch_size=9', 'nb_iterations_per_scale=10', 'blend_size=5',\ # 'allow_outer_blending=1' and 'is_already_initialized=0'. #@cli : $ image.jpg 100%,100% ellipse[-1] 30%,30%,40,30,0,1,255 +inpaint_matchpatch[0] [1] inpaint_matchpatch : check ${is_image_arg\ $1}"&& ${2=0}>=0 && isint(${3=9},1) && isint(${4=10},1) && isint(${5=5},0)" skip ${6=1},${7=0} e[^-1] "Inpaint image$? with mask $1, using a multiscale patch-matching algorithm with "\ ${"if $2 u \"$2 \" else u auto- fi"}\ "scales, $3x$3 patches, $4 iterations per scale and blending size $5." repeat $! { pass$1 0 l[$>,-1] { nm={0,n} # Init variables and images. => img,mask nb_scales:=max(1,round($2?$2:log2(min(w,h)/16),1,1)) visu_size=${fitscreen[]" "{0,[w,h,1]},25%,50%} slices[img] 0 r[mask] [img],[img],1,1,0 !=[mask] 0 if !$7 inpaint_pde[img] [mask],75% fi # Quick first estimate. im={img,im} -[img] $im first_iter=1 iter=0 repeat $nb_scales { scale:=100*(0.5^$<) e "> Process scale "{1+$>}"/"$nb_scales" -> "$scale% progress {100*$>/max(1,$nb_scales-1)} # Compute image and mask at current scales. +r[img,mask] $scale%,$scale%,1,100%,2 => scaled_img,scaled_mask >=[scaled_mask] 0.95 if {scaled_mask,!iM} rm[scaled_img,scaled_mask] continue fi # Skip scale (if too low). +f[scaled_img] -4096 +j[scaled_img] .,0,0,0,0,1,[scaled_mask] rm.. => scaled_reference coef:=0.5^($nb_scales-$iter) patch_size:="v=round(max(min($3,5),$3*$coef));v+(1-(v%2))" patch_size:=min(w,h,$patch_size) blend_size:="v=$5?round(max(3,$5*$coef)):0;v+(1-(v%2))" iter+=1 ==[scaled_mask] 0 if $first_iter # First iteration. # Estimate initial correspondence map. 100%,100%,1,1,x +f. y mv[scaled_mask] $! a[-3--1] c matchpatch[scaled_img] [scaled_reference],$patch_size,$patch_size,1,4,4,0,0,. rm[scaled_reference,-1] => correspondence first_iter=0 else # Standard iteration. # Upscale correspondence map from previous scale. *[correspondence] 2 r[correspondence] 200%,200%,1,2 r[correspondence] [scaled_img],[scaled_img],1,2,0,1 100%,100%,1,1,x +f. y a[-2,-1] c f[scaled_mask] "*i?1:( upc = i(#"$correspondence",x-1,y,0,0); vpc = i(#"$correspondence",x-1,y,0,1); ucp = i(#"$correspondence",x,y-1,0,0); vcp = i(#"$correspondence",x,y-1,0,1); ucc = i(#"$correspondence",x,y,0,0); vcc = i(#"$correspondence",x,y,0,1); i(#-1,x,y,0,0) = (ucc==upc && vcc==vpc)?upc + 1:ucc; i(#-1,x,y,0,1) = (ucc==ucp && vcc==vcp)?vcp + 1:vcc; 0)" rm[correspondence] => correspondence a[correspondence] [scaled_mask],c # Refine correspondence map iteratively with matchpatch. nbs1:=max(1,$nb_scales-1) nb_iter:=round(max(1,$4*(($<+1)/$nbs1)^2)) repeat $nb_iter { _inpaint_matchpatch[scaled_img] [correspondence],[scaled_mask],$blend_size,$6 +matchpatch[scaled_img] [scaled_reference],$patch_size,$patch_size,1,4,4,0,0,[correspondence] j[correspondence] . rm. if {*1} w1[scaled_img] $visu_size,0 fi if {*2} w2[correspondence] $visu_size,1 fi } rm[scaled_img,scaled_mask,scaled_reference] channels[correspondence] 0,1 fi } progress 100 # Generate final result. if $correspondence ==[mask] 0 _inpaint_matchpatch[img] [correspondence],[mask],$5,$6 rm[correspondence] fi +[img] $im =>[0] $nm } rm[mask] } # _inpaint_matchpatch : [correspondence_map],[mask],blend_size>0,_allow_outer_blending={ 0 | 1 } _inpaint_matchpatch : pass$1 1 pass$2 {!$3" || "!$4} if !$3 warp[0] [1],0,0,1 else if $4 erode. $3 fi f[0] "*begin( boundary = 1; const patch_size = $3; const p2 = int(patch_size/2); const p1 = patch_size - p2 - 1; avg = resize([0],s#0); # Pre-compute gaussian kernel for patch blending. wpq = resize([0],patch_size^2); g = 0; for (q = -p1, q<=p2, ++q, for (p = -p1, p<=p2, ++p, wpq[g++] = exp(-(p^2 + q^2)/(2*(0.3*patch_size)^2)); ); ); ); if (i#2,I, g = 0; avg = 0; norm = 0; for (q = -p1, q<=p2, ++q, for (p = -p1, p<=p2, ++p, U = I(#1,x + p,y + q); w = wpq[g++]; norm+=w; avg+=w*I(#0,U[0,2] - [p,q]); ); ); avg/norm)" fi k[0] # _inpaint_warping2d : fill-in zero-valued vectors in absolute 2D warping field (works even when 'spectrum>2'). _inpaint_warping2d : foreach { 100%,100%,100%,2,"> begin(const S = s#0; zero0 = vectorS(); zero1 = [0,0]; N = 0); I(#-1)==zero0?zero1:[++N,1]" s. c distance. 1 *. -1 watershed.. . rm. # Propagate offsets in each distinct voronoi cell. repeat 2 { f.. ">i?I:( # Forward propagation nP = vectors(); const sP = size(nP); r = i(#-1); (P = J(-1,-1))[0] && j(#-1,-1,-1)==r?(nP[0] = ++P[0]; nP[1] = ++P[1]; sP>2?copy(nP[2],P[2],sP-2)): (P = J(0,-1))[0] && j(#-1,0,-1)==r ?(nP[0] = P[0]; nP[1] = ++P[1]; sP>2?copy(nP[2],P[2],sP-2)): (P = J(1,-1))[0] && j(#-1,1,-1)==r ?(nP[0] = --P[0]; nP[1] = ++P[1]; sP>2?copy(nP[2],P[2],sP-2)): (P = J(-1,0))[0] && j(#-1,-1,0)==r ?(nP[0] = ++P[0]; nP[1] = P[1]; sP>2?copy(nP[2],P[2],sP-2)); nP)" f.. "2?copy(nP[2],P[2],sP-2)): (P = J(0,1))[0] && j(#-1,0,1)==r ?(nP[0] = P[0]; nP[1] = --P[1]; sP>2?copy(nP[2],P[2],sP-2)): (P = J(-1,1))[0] && j(#-1,-1,1)==r?(nP[0] = ++P[0]; nP[1] = --P[1]; sP>2?copy(nP[2],P[2],sP-2)): (P = J(1,0))[0] && j(#-1,1,0)==r ?(nP[0] = --P[0]; nP[1] = P[1]; sP>2?copy(nP[2],P[2],sP-2)); nP)" } rm. } #@cli kuwahara : size>0 #@cli : Apply Kuwahara filter of specified size on selected images. #@cli : $ image.jpg kuwahara 9 kuwahara : check $1>0 e[^-1] "Apply Kuwahara filter of size $1 on image$?." foreach { s:=s +dilate $1 compose_channels. min +erode[0] $1 compose_channels. max -[-2,-1] $1,1,1,1,{1/$1} convolve[0] . transpose. convolve[0] . rm. p:=int($1/2) a[-2,-1] c f "v1=i(x-"$p",y-"$p",0,"$s",0,1); \ v2=i(x+"$p",y-"$p",0,"$s",0,1); \ v3=i(x-"$p",y+"$p",0,"$s",0,1); \ v4=i(x+"$p",y+"$p",0,"$s",0,1); \ vm=min(v1,v2,v3,v4); \ c>="$s"?i: \ vm==v1?i(x-"$p",y-"$p",0,c,0,1): vm==v2?i(x+"$p",y-"$p",0,c,0,1): vm==v3?i(x-"$p",y+"$p",0,c,0,1): i(x+"$p",y+"$p",0,c,0,1)" channels 0,{s-2} } #@cli laplacian #@cli : Compute Laplacian of selected images. #@cli : $ image.jpg laplacian laplacian : e[^-1] "Compute Laplacian of image$?." foreach { hessian ${arg0\ (d==1),xxyyzz,xxyy} + } #@cli lic : _amplitude>0,_channels>0 #@cli : Render LIC representation of selected vector fields. #@cli : Default values: 'amplitude=30' and 'channels=1'. #@cli : $ 400,400,1,2,'!c?x-w/2:y-h/2' +lic 200,3 quiver[-2] [-2],10,1,1,1,255 lic : skip ${1=30},${2=1} e[^-1] "Render LIC representation of 2D vector field$?, with amplitude $1 and $2 channel(s)." foreach { nm={n} channels 0,1 / {max(abs(im),abs(iM))} vector2tensor 100%,100%,100%,$2 rand. 0,255 smooth. ..,$1 rm.. equalize => $nm } #@cli map_tones : _threshold>=0,_gamma>=0,_smoothness>=0,nb_iter>=0 #@cli : Apply tone mapping operator on selected images, based on Poisson equation. #@cli : Default values: 'threshold=0.1', 'gamma=0.8', 'smoothness=0.5' and 'nb_iter=30'. #@cli : $ image.jpg +map_tones , map_tones : skip ${1=0.1},${2=0.8},${3=0.5},${4=30} e[^-1] "Apply tone mapping operator on image$?, with threshold $1, gamma $2, smoothness $3 and $4 iterations." foreach { # Estimate target divergence for each channel. +l { s c foreach { g xy,1 a c +norm orientation.. m,M:=[im,iM] b. $3 n. $m,$M *. 'alpha=$1*iM;(alpha/(1e-10+i))*(i/(1e-10+alpha))^$2' * s c g.. x,-1 g. y,-1 + } a c * 0.25 } # Start Poisson-PDE iterations repeat $4 { +laplacian.. *. 0.25 +. ... -. .. *. 800 +[-3,-1] /.. 801 c.. 0,255 } rm. } #@cli map_tones_fast : _radius[%]>=0,_power>=0 #@cli : Apply fast tone mapping operator on selected images. #@cli : Default values: 'radius=3%' and 'power=0.3'. #@cli : $ image.jpg +map_tones_fast , map_tones_fast : check "${1=3%}>=0 && ${2=0.3}>=0" e[^-1] "Apply fast tone mapping operator on image$?, with radius $1 and power $2." foreach { +luminance b. $1 n 0,1 +*. 2 -. 1 abs. *. {$2*log(10)} exp. <=.. 0.5 ri. ... +*... -1 +. 1 ^. .. *. -1 +. 1 *. ... ^[-4,-2] ==.. 0 *[-3,-2] + } n 0,255 #@cli meancurvature_flow : _nb_iter>=0,_dt,_keep_sequence={ 0 | 1 } #@cli : Apply iterations of the mean curvature flow on selected images. #@cli : Default values: 'nb_iter=10', 'dt=30' and 'keep_sequence=0'. #@cli : $ image.jpg +meancurvature_flow 20 meancurvature_flow : skip ${1=10},${2=30},${3=0} e[^-1] "Apply $1 iterations of the mean curvature flow on image$?, with time step $2." pde_flow $1,$2,iee,$3 #@cli median : size>=0,_threshold>0 : (+) #@cli : Apply (opt. thresholded) median filter on selected images with structuring element size x size. #@cli : $ image.jpg +median 5 #@cli merge_alpha #@cli : Merge selected alpha detail scales into a single image. #@cli : Alpha detail scales have been obtained with command ''split_alpha''. merge_alpha : e[^-1] "Merge alpha detail$? into a single image." repeat $!-1 { r[0] [1],[1],1,100%,5 c[0] 0,255 blend[0,1] alpha } if s=['{b}'];find(s,'_0')==size(s)-2 ext={x} if ['$ext']!=0 ext..=. fi ({'{f}{b}'}) z. 0,{w-3} bm={t} rm. => $bm$ext fi #@cli nlmeans : [guide],_patch_radius>0,_spatial_bandwidth>0,_tonal_bandwidth>0,_patch_measure_command : \ # _patch_radius>0,_spatial_bandwidth>0,_tonal_bandwidth>0,_patch_measure_command #@cli : Apply non local means denoising of Buades et al, 2005. on selected images. #@cli : The patch is a gaussian function of 'std_patch_radius'. #@cli : The spatial kernel is a rectangle of radius 'spatial_bandwidth'. #@cli : The tonal kernel is exponential (`exp(-d^2/_tonal_bandwidth^2)`) #@cli : with `d` the euclidean distance between image patches. #@cli : Default values: 'patch_radius=4', 'spatial_bandwidth=4', 'tonal_bandwidth=10' and 'patch_measure_command=-norm'. #@cli : $ image.jpg +noise 10 nlmeans[-1] 4,4,{0.6*${-std_noise}} nlmeans: if ${"is_image_arg $1"} # Guided-filtering check "${2=4}>0 && ${3=4}>0 && ${4=10}>0" skip "${5=-norm}" e[^-1] "Apply non-local means denoising on image$?, with guide $1, patch size $2, spatial bandwidth $3, tonal bandwidth $4 and patch measure command '$5'." pass$1 0 l. { $5 k[0] } # [1] preprocessed image used to compute weights. repeat $!-1 { l[$>,-1] { 100%,100%,100%,100%,{-1.0/($4*$4)} # [2] compute a scaling. nlmeans_core[0] [1],[2],$2,$3 rm. # Apply the NLM denoising with image 1 and 2 as parameter. } } rm. else # Non-guided filtering check "${1=4}>0 && ${2=4}>0 && ${3=10}>0" skip "${4=-norm}" e[^-1] "Apply non-local means denoising on image$?, with patch size $1, spatial bandwidth $2, tonal bandwidth $3 and patch measure command '$4'." foreach { +l { $4 k[0] } # [1] preprocessed image used to compute weights. 100%,100%,100%,100%,{-1.0/($3*$3)} # [2] compute a scaling. nlmeans_core[0] [1],[2],$1,$2 k[0] # Apply the NLM denoising with image 1 and 2 as parameter. } fi #@cli nlmeans_core: _reference_image,_scaling_map,_patch_radius>0,_spatial_bandwidth>0 #@cli : Apply non local means denoising using a image for weight and a map for scaling nlmeans_core : check ${is_image_arg\ $1}" && "${is_image_arg\ $2}" && $3>0 && $4>0" e[^-1] "Apply non-local means denoising using weight images $1, scaling map $2, patch size $3 and spatial bandwidth $4." pass$1 0 pass$2 0 repeat $!-2 { l[$>,-1,-2] { # [0] original, [1] weights, [2] scaling [3] sum(weights * patch), [4] sum(weights), [5] max(weights) 100%,100%,100%,{0,s},0 100%,100%,100%,{1,s},0 100%,100%,100%,{1,s},1e-6 if d#0==1 repeat 2*$4+1 { j:=$>-$4 repeat 2*$4+1 { i:=$>-$4 if $i!=0||$j!=0 # Compute shifted images [6] and weight [7] +shift[0,1] $i,$j,0,0,2 -[7] [1] sqr[7] b[7] $3 *[7] [2] exp[7] # Accumulate weights *[6] [7] max[5] [7] +[4,7] +[3,6] fi } } else repeat 2*$4+1 { k:=$>-$4 repeat 2*$4+1 { j:=$>-$4 repeat 2*$4+1 { i:=$>-$4 if $i!=0||$j!=0||$k!=0 # Compute shifted images [6] and weight [7] +shift[0,1] $i,$j,0,0,2 -[7] [1] sqr[7] b[7] $3 *[7] [2] exp[7] # Accumulate weights *[6] [7] max[5] [7] +[4,7] +[3,6] fi } } } fi rm[1,2] *[0] [3] +[1,0] +[1,2] # Add central patch / # Normalize } } #@cli normalize_local : _amplitude>=0,_radius>0,_n_smooth>=0[%],_a_smooth>=0[%],_is_cut={ 0 | 1 },_min=0,_max=255 #@cli : Normalize selected images locally. #@cli : Default values: 'amplitude=3', 'radius=16', 'n_smooth=4%', 'a_smooth=2%', 'is_cut=1', 'min=0' and 'max=255'. #@cli : $ image.jpg normalize_local 8,10 normalize_local : check "${1=3}>=0 && ${2=16}>0 && isbool(${5=1})" skip ${3=4%},${4=2%},${6=0},${7=255} e[^-1] "Normalize image$? locally, with amplitude $1, radius $2, neighborhood smoothness $3 and average smoothness $4." foreach { +l { erode {2*$2+1} s c min } +l.. { dilate {2*$2+1} s c max } +b... $4 b[-3,-2] $3 +-.. ... +. 0.01 -[-5] [-4] /[-5,-1] *[-3,-2] {$1+1} *. -$1 +... . +[-2,-1] if $5 max.. $6 min. $7 fi -. .. *[-3,-1] + if $5 c $6,$7 fi } #@cli normalized_cross_correlation : [mask] #@cli : Compute normalized cross-correlation of selected images with specified mask. #@cli : $ image.jpg +shift -30,-20 +normalized_cross_correlation[0] [1] normalized_cross_correlation : check ${is_image_arg\ $1} e[^-1] "Compute normalized cross-correlation of image$? with mask $1." pass$1 0 norm repeat $!-1 { . l[$>,-1] { fft.. fft. [-2,-1] *.. [-5] *. [-6] -[-2,-1] *[-5,-3] *[-3,-2] +[-3,-2] [-2,-1] a[-2,-1] c norm. /... . /[-2,-1] ifft rm. } } rm. #@cli opening : size>=0 : size_x>=0,size_y>=0,_size_z>=0 : \ # [kernel],_boundary_conditions,_is_real={ 0:binary-mode | 1:real-mode } #@cli : Apply morphological opening to selected images. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'size_z=1', 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +opening 10 opening : skip "${2=},${3=}" boundary,is_kernel,sel=1,0,^ if "isnum($1)" # Full square kernel if "['$2']==0 && ['$3']==0" sx,sy,sz=$1 e[0--4] "Apply morphological opening to image$?, with kernel of size "$sx"." else sx=$1 if ['$2']==0 sy=1 else sy=$2 fi if ['$3']==0 sz=1 else sz=$3 fi e[0--4] "Apply morphological opening to image$?, with "${sx}"x"${sy}"x"${sz}" kernel." fi elif ${"is_image_arg $1"}" && narg("${"pass$1 -1"}")==1" # Custom kernel pass$1 sx,sy,sz={w},{h},{d} is_kernel,sel,r0,r1,b0,b1,b2,b3=1,^-1,binary,real,dirichlet,neumann,periodic,mirror is_real=0 if ['$2']!=0 boundary:=cut($2,0,3) fi if ['$3']!=0 is_real=$3 fi e[0--3] "Apply morphological opening to image$? with kernel ["${"pass$1 -1"}"] and "${b$boundary}" boundary "\ "conditions, in "${r$is_real}" mode." else error[0--3] "Command 'opening': Invalid arguments '$*'." fi if $sx>0||$sy>=0||$sz>=0 sx1,sx2:="s=int(($sx-1)/2);[s,$sx-s-1]" sy1,sy2:="s=int(($sy-1)/2);[s,$sy-s-1]" sz1,sz2:="s=int(($sz-1)/2);[s,$sz-s-1]" foreach[$sel] { nm={n} if d>1 # 3D r {[w+$sx+1,h+$sy+1,d+$sz+1]},100%,0,$boundary,0.5,0.5,0.5 if $is_kernel pass. erode.. .,0,$is_real dilate.. .,0,$is_real rm. else erode $sx,$sy,$sz dilate $sx,$sy,$sz fi z {$sx1+1},{$sy1+1},{$sz1+1},{w-$sx2-2},{h-$sy2-2},{d-$sz2-2} => $nm elif h>1 # 2D r {[w+$sx+1,h+$sy+1]},1,100%,0,$boundary,0.5,0.5 if $is_kernel pass. erode.. .,0,$is_real dilate.. .,0,$is_real rm. else erode $sx,$sy dilate $sx,$sy fi z {$sx1+1},{$sy1+1},{w-$sx2-2},{h-$sy2-2} => $nm else # 1D r {w+$sx+1},1,1,100%,0,$boundary,0.5 if $is_kernel pass. erode.. .,0,$is_real dilate.. .,0,$is_real rm. else erode $sx,1 dilate $sx,1 fi z {$sx1+1},{w-$sx2-2} => $nm fi } fi if $is_kernel rm. fi #@cli opening_circ : _size>=0,_is_real={ 0 | 1 } #@cli : Apply circular opening of selected images by specified size. #@cli : Default values: 'boundary_conditions=1' and 'is_real=0'. #@cli : $ image.jpg +opening_circ 7 opening_circ : check "$1>=0 && isbool(${2=0})" r0,r1=binary,real e[^-1] "Apply morphological opening of image$? with circular kernel of size $1, in "${r$2}" mode." if $1<2 return fi shape_circle $1 opening[^-1] .,$2 rm. #@cli percentile : [mask],0<=_min_percentile[%]<=100,0<=_max_percentile[%]<=100. #@cli : Apply percentile averaging filter to selected images. #@cli : Default values: 'min_percentile=0' and 'max_percentile=100'. #@cli : $ image.jpg shape_circle 11,11 +percentile[0] [1],25,75 percentile : check ${"is_image_arg $1"}" && inrange(${2=0},0,100) && inrange(${3=100},0,100) && $2<=$3" vmin,vmax:=_[ispercentage($2)?100*$2:$2,ispercentage($3)?100*$3:$3] e[^-1] "Apply percentile averaging filter to image$?, with mask $1, "\ "min percentile "$vmin"% and max percentile "$vmax"%." # Generate code for masking. pass$1 0 !=. 0 N:=is if !$N rm. return fi 128,$N eval.. "> begin( p = 0; const w2 = int(w/2); const h2 = int(h/2); ); i?( out = string('N[',p,']=j(',x - w2,',',y - h2,');'); copy(i(#-1,0,p++),out,size(out)); )" discard. 0 code={t} rm[-2,-1] # Apply filter. f " begin( N = vector"$N"() ); const boundary = 1; const sS = size(N) - 1; const s0 = round(sS*"$vmin"%); const s1 = round(sS*"$vmax"%); const ds = 1 + s1 - s0; "$code" S = sort(N); res = 0; for (s = s0, s<=s1, ++s, res+=S[s]); res/=ds" #@cli peronamalik_flow : K_factor>0,_nb_iter>=0,_dt,_keep_sequence={ 0 | 1 } #@cli : Apply iterations of the Perona-Malik flow on selected images. #@cli : Default values: 'K_factor=20', 'nb_iter=5', 'dt=5' and 'keep_sequence=0'. #@cli : $ image.jpg +heat_flow 20 peronamalik_flow : check "${1=20}>0 && ${2=5}>=0" skip ${3=5},${4=0} e[^-1] "Apply $2 iterations of the Perona-Malik flow on image$?, with K factor $1 and time step $3." m "_peronamalik_flow : +gradient xy,0 a[-2,-1] c norm. b. 0.8 /. $1 sqr. *. -1 exp. a[-2,-1] c f. \"s1 = s-1; C = i(x,y,z,s-1); c>=s1?0: (C+i(x+1,y,z,s-1,0,1))*(j(1,0,0,0,0,1)-i) - (C+i(x-1,y,z,s-1,0,1))*(i-j(-1,0,0,0,0,1)) + (C+i(x,y+1,z,s-1,0,1))*(j(0,1,0,0,0,1)-i) - (C+i(x,y-1,z,s-1,0,1))*(i-j(0,-1,0,0,0,1))\"" pde_flow $2,$3,_peronamalik_flow,$4 um _peronamalik_flow #@cli phase_correlation : [destination] #@cli : Estimate translation vector between selected source images and specified destination. #@cli : $ image.jpg +shift -30,-20 +phase_correlation[0] [1] unroll[-1] y phase_correlation : check ${"is_image_arg $1"} e[^-1] "Estimate shift between source image$? and destination $1." pass$1 repeat $!-1 { normalized_cross_correlation[$>] . l[$>] { eval " store('res', [xM>=w/2?xM - w:xM, yM>=h/2?yM - h:yM, zM>=d/2?zM - d:zM]*=-1,1,1,1,3)" } $res => "[phase correlation]" rv[$>,-1] rm. } rm. #@cli pde_flow : _nb_iter>=0,_dt,_velocity_command,_keep_sequence={ 0 | 1 } #@cli : Apply iterations of a generic PDE flow on selected images. #@cli : Default values: 'nb_iter=10', 'dt=30', 'velocity_command=laplacian' and 'keep_sequence=0'. #@cli : $ image.jpg +pde_flow 20 pde_flow : skip ${1=10},${2=30},${3=laplacian},${4=0} e[^-1] "Apply $1 iterations of the velocity flow '$3' on image$?, with time step $2." foreach { repeat $1 { +$3. *. {$2/(0.01+max(abs(im),abs(iM)))} if $4 +. .. else +[-2,-1] fi } if $4 rm[0] fi a x } if $4 s x,$1 fi #@cli periodize_poisson #@cli : Periodize selected images using a Poisson solver in Fourier space. #@cli : $ image.jpg +periodize_poisson array 2,2,2 periodize_poisson : e[^-1] "Periodize image$? using Poisson solver in Fourier space." foreach { s c foreach { mM:=[im,iM] sum={0,ia} laplacian ilaplacian 0 + $sum c $mM } a c } #@cli rbf : dx,_x0,_x1,_phi(r) : dx,dy,_x0,_y0,_x1,_y1,_phi(r) : dx,dy,dz,x0,y0,z0,x1,y1,z1,phi(r) #@cli : Reconstruct 1D/2D or 3D image from selected sets of keypoints, by RBF-interpolation. #@cli : A set of keypoints is represented by a vector-valued image, where each pixel represents a single keypoint. #@cli : Vector components of a keypoint have the following meaning: #@cli : - For 1D reconstruction: [ x_k, f1(k),...fN(k) ]. #@cli : - For 2D reconstruction: [ x_k,y_k, f1(k),...,fN(k) ]. #@cli : - For 3D reconstruction: [ x_k,y_k,z_k, f1(k),...,fN(k) ]. #@cli : Values 'x_k','y_k' and 'z_k' are the spatial coordinates of keypoint 'k'. #@cli : Values 'f1(k),..,fN(k)' are the 'N' components of the vector value of keypoint 'k'. #@cli : The command reconstructs an image with specified size 'dx'x'dy'x'dz', with 'N' channels. #@cli : Default values: 'x0=y0=z0=0', 'x1=dx-1', 'y1=dy-1', 'z1=dz-1', 'phi(r)=r^2*log(1e-5+r)'. #@cli : $ sample colorful,400 100%,100% noise_poissondisk. 10 1,{is},1,5 \ # eval[-2] "begin(p=0);i?(I[#-1,p++]=[x,y,I(#0)])" to_rgb[1] mul[0,1] dilate_circ[0] 5 +rbf[-1] {0,[w,h]} c[-1] 0,255 #@cli : $ 32,1,1,5,u([400,400,255,255,255]) rbf 400,400 c 0,255 rbf : $=a default_phi_r="r^2*log(1 + r)" if isin($#,1,3,4) # 1D reconstruction dx,x0,x1=$a1,{$#>1?[$a2,$a3]:[0,$a1-1]} phi_r={`$#>3?['$a4']:'$default_phi_r'`} check $dx>0 e[^-1] "Reconstruct 1D image from keypoint set$?, with size "$dx", "\ "from ("$x0") to ("$x1") and phi(r) = "$phi_r. foreach { nm={n} if !w $dx elif whd==1 channels. 1,100% r. $dx,1,1,100% else r 1,{whd},1,100%,-1 permute. cyzx $dx,1,1,{w-1},"* begin( phi(r) = ("$phi_r"); X = crop(#0,0,0,0,0,1,h#0,1,1); F = crop(#0,1,0,0,0,s,h#0,1,1,1); M = vector(#h#0^2); repeat (h#0,k, for (l = 0, l<=k, ++l, r = abs(X[k] - X[l]); M[k*h#0 + l] = M[l*h#0 + k] = phi(r); ) ); W = solve(M,F,s,1); const fx = ("$x1-$x0")/(w-1); ); res = vectors(); x = (x - "$x0")*fx; repeat (h#0,k, r = abs(x - X[k]); res+=W[s*k,s]*phi(r)); res" k. => $nm fi } elif isin($#,2,6,7) # 2D reconstruction dx,dy,x0,y0,x1,y1=$a1,$a2,{$#>2?[$a3,$a4,$a5,$a6]:[0,0,[$a1,$a2]-1]} phi_r={`$#>6?['$a7']:'$default_phi_r'`} check $dx>0" && "$dy>0 e[^-1] "Reconstruct 2D image from keypoint set$?, with size "$dx,$dy", "\ "from ("$x0,$y0") to ("$x1,$y1") and phi(r) = "$phi_r. foreach { nm={n} if !w $dx,$dy elif whd==1 channels 2,100% r. $dx,$dy,1,100% else r 1,{whd},1,100%,-1 permute. cyzx $dx,$dy,1,{w-2},"* begin( phi(r) = ("$phi_r"); X = crop(#0,0,0,0,0,1,h#0,1,1); Y = crop(#0,1,0,0,0,1,h#0,1,1); F = crop(#0,2,0,0,0,s,h#0,1,1,1); M = vector(#h#0^2); repeat (h#0,k, for (l = 0, l<=k, ++l, r = norm(X[k] - X[l],Y[k] - Y[l]); M[k*h#0 + l] = M[l*h#0 + k] = phi(r); ) ); W = solve(M,F,s,1); const fx = ("$x1-$x0")/(w-1); const fy = ("$y1-$y0")/(h-1); ); res = vectors(); x = (x - "$x0")*fx; y = (y - "$y0")*fy; repeat (h#0,k, r = norm(x - X[k], y - Y[k]); res+=W[s*k,s]*phi(r)); res" k. => $nm fi } elif isin($#,3,9,10) # 3D reconstruction dx,dy,dz,x0,y0,z0,x1,y1,z1=$a1,$a2,$a3,{$#>3?[$a4,$a5,$a6,$a7,$a8,$a9]:[0,0,0,[$a1,$a2,$a3]-1]} phi_r={`$#>9?['$arg10']:'$default_phi_r'`} check $dx>0" && "$dy>0" && "$dz>0 e[^-1] "Reconstruct 3D image from keypoint set$?, with size "$dx,$dy,$dz", "\ "from ("$x0,$y0,$z0") to ("$x1,$y1,$z1") and phi(r) = "$phi_r. foreach { nm={n} if !w $dx,$dy,$dz elif whd==1 channels 3,100% r. $dx,$dy,$dz,100% else r 1,{whd},1,100%,-1 permute. cyzx $dx,$dy,$dz,{w-3},"* begin( phi(r) = ("$phi_r"); X = crop(#0,0,0,0,0,1,h#0,1,1); Y = crop(#0,1,0,0,0,1,h#0,1,1); Z = crop(#0,2,0,0,0,1,h#0,1,1); F = crop(#0,3,0,0,0,s,h#0,1,1,1); M = vector(#h#0^2); repeat (h#0,k, for (l = 0, l<=k, ++l, r = norm(X[k] - X[l],Y[k] - Y[l],Z[k] - Z[l]); M[k*h#0 + l] = M[l*h#0 + k] = phi(r); ) ); W = solve(M,F,s,1); const fx = ("$x1-$x0")/(w - 1); const fy = ("$y1-$y0")/(h - 1); const fz = ("$z1-$z0")/(d - 1); ); res = vectors(); x = (x - "$x0")*fx; y = (y - "$y0")*fy; z = (z - "$z0")*fz; repeat (h#0,k, r = norm(x - X[k], y - Y[k], z - Z[k]); res+=W[s*k,s]*phi(r)); res" k. => $nm fi } else error[0--2] "Command 'rbf': invalid arguments '$*'." fi #@cli red_eye : 0<=_threshold<=100,_smoothness>=0,0<=attenuation<=1 #@cli : Attenuate red-eye effect in selected images. #@cli : Default values: 'threshold=75', 'smoothness=3.5' and 'attenuation=0.1'. #@cli : $ image.jpg +red_eye , red_eye : skip ${1=75},${2=3.5},${3=0.1} e[^-1] "Attenuate red-eye effect in image$?, with threshold $1, smoothness $2 and attenuation $3." to_rgb rgb2ycbcr foreach { s c -. 128 +>=. $1% b. $2 sqrt. *. -1 +. 1 n. $3,1 *[-2,-1] +. 128 a c ycbcr2rgb } #@cli remove_hotpixels : _mask_size>0, _threshold[%]>0 #@cli : Remove hot pixels in selected images. #@cli : Default values: 'mask_size=3' and 'threshold=10%'. #@cli : $ image.jpg noise 10,2 +remove_hotpixels , remove_hotpixels : check ${1=3}>0 skip ${2=10%} e[^-1] "Remove hot pixels in image$?, with mask size $1 and threshold $2." foreach { +median $1 +- abs. >=. $2 *.. . ==. 0 *[-3,-1] + } #@cli remove_pixels : number_of_pixels[%]>=0 #@cli : Remove specified number of pixels (i.e. set them to 0) from the set of non-zero pixels in selected images. #@cli : $ image.jpg +remove_pixels 50% remove_pixels : check "$1>=0" e[^-1] "Remove $1 of the non-zero pixels in image$?." foreach { +norm !=. 0 N:=is # Number of non-zero pixels. n:=round(ispercentage($1)?$N*$1:$1) # Number of pixels to remove. if $n<=0 rm. # No pixels to remove. elif $n>=$N rm. f 0 # All pixels to remove. elif $n>int($N/2) # More pixels to remove than to keep. remove_pixels. {$N-$n} ==. 0 * else # Less pixels to remove than to keep. d:=d r 100%,{d*h},1,100%,-1 # Force image to be in 2D. # Retrieve coordinates of all non-zero pixels. 100%,1,1,1,x 1,{-2,h},1,1,y +[-2,-1] 1 r[-2,-1] ..,. *[-2,-1] ... rm... y[-2,-1] a[-2,-1] x discard. y,0 # Generate a 1xN vector with at least n non-zero pixels. do 1,100%,1,1 rand. 0,{h} <=. {$n*1.25} if is>=$n break else rm. fi while 1 # Generate a 1xn vector of coordinates to 'remove'. r. 2 *[-2,-1] discard. y,0 i.. 1,100% rand.. 0,1 a[-2,-1] x sort. +,y rows. 0,{$n-1} -. 1 z. 1,3 # Set those pixels to 0 using a 3D object. i.. ({'CImg3d'},{h},{h}) 1,100%,1,1,1 1,100%,1,1,y a[-2,-1] x 3,100% 1,100%,1,1,1 y[-5--1] a[-5--1] y if s#0<=3 j3d.. .,0,0,0,1,0,0,0,0 else [0],[0],1,1,1 j3d. ..,0,0,0,1,0,0,0,0 *[0,-1] fi rm. r 100%,{h/$d},$d,100%,-1 # Resize to original dimension (eventually 3D). fi } #@cli rolling_guidance : std_deviation_s[%]>=0,std_deviation_r[%]>=0,_precision>=0 #@cli : Apply the rolling guidance filter on selected image. #@cli : Rolling guidance filter is a fast image abstraction filter, described in: #@cli : "Rolling Guidance Filter", Qi Zhang Xiaoyong, Shen Li, Xu Jiaya Jia, ECCV'2014. #@cli : Default values: 'std_deviation_s=4', 'std_deviation_r=10' and 'precision=0.5'. #@cli : $ image.jpg +rolling_guidance , +- rolling_guidance : check "${1=4}>=0 && ${2=10}>=0 && ${3=0.5}>=0" e[^-1] "Apply rolling guidance filter on image$?, with standard deviations ($1,$2) and precision $3." precision:=2^-$3 foreach { nm={n} +b $1 repeat 100 { if c>1 +norm. +bilateral... .,$1,$2 rm.. else +bilateral.. .,$1,$2 fi -.. . std={-2,id} rm.. if $std<$precision break fi } k. => $nm } #@cli sharpen : amplitude>=0 : amplitude>=0,edge>=0,_alpha[%],_sigma[%] #@cli : Sharpen selected images by inverse diffusion or shock filters methods. #@cli : 'edge' must be specified to enable shock-filter method. #@cli : Default values: 'edge=0', 'alpha=0' and 'sigma=0'. #@cli : $ image.jpg sharpen 300 #@cli : $ image.jpg blur 5 sharpen 300,1 sharpen : check "$1>=0 && ${2=0}>=0 && ${3=0}>=0 && ${4=0}>=0" if $2>0 # Shock filters e[0--3] "Sharpen image$? with shock filters, amplitude $1, edge $2, alpha $3 and sigma $4." foreach { im,iM:=[im,iM] +b $3 structuretensors. 0 b. $4 eigen. l.. { max 0 s c + + 1 ^ {-0.5*$2} *. -1 +. 1 } if {0,d>1} # 3D +f[0] "const boundary = 1; minmod(a,b) = (a*b<=0?0:minabs(a,b)); u = i(#-1,x,y,z,0); v = i(#-1,x,y,z,1); w = i(#-1,x,y,z,2); amp = i(#-2,x,y,z,0); Ippp = j(-1,-1,-1); Icpp = j(0,-1,-1); Inpp = j(1,-1,-1); Ipcp = j(-1,0,-1); Iccp = j(0,0,-1); Incp = j(1,0,-1); Ipnp = j(-1,1,-1); Icnp = j(0,1,-1); Innp = j(1,1,-1); Ippc = j(-1,-1,0); Icpc = j(0,-1,0); Inpc = j(1,-1,0); Ipcc = j(-1,0,0); Iccc = i; Incc = j(1,0,0); Ipnc = j(-1,1,0); Icnc = j(0,1,0); Innc = j(1,1,0); Ippn = j(-1,-1,1); Icpn = j(0,-1,1); Inpn = j(1,-1,1); Ipcn = j(-1,0,1); Iccn = j(0,0,1); Incn = j(1,0,1); Ipnn = j(-1,1,1); Icnn = j(0,1,1); Innn = j(1,1,1); ixx = Incc + Ipcc - 2*Iccc; ixy = 0.25*(Innc + Ippc - Inpc - Ipnc); ixz = 0.25*(Incn + Ipcp - Incp - Ipcn); iyy = Icnc + Icpc - 2*Iccc; iyz = 0.25*(Icnn + Icpp - Icnp - Icpn); izz = Iccn + Iccp - 2*Iccc; ixf = Incc - Iccc; ixb = Iccc - Ipcc; iyf = Icnc - Iccc; iyb = Iccc - Icpc; izf = Iccn - Iccc; izb = Iccc - Iccp; itt = u^2*ixx + v^2*iyy + w^2*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz; it = u*minmod(ixf,ixb) + v*minmod(iyf,iyb) + w*minmod(izf,izb); amp*sign(itt)*abs(it)" else # 2D +f[0] "const boundary = 1; minmod(a,b) = (a*b<=0?0:minabs(a,b)); u = i(#-1,x,y,z,0); v = i(#-1,x,y,z,1); amp = i(#-2,x,y,z,0); Ipp = j(-1,-1); Icp = j(0,-1); Inp = j(1,-1); Ipc = j(-1,0); Icc = i; Inc = j(1,0); Ipn = j(-1,1); Icn = j(0,1); Inn = j(1,1); ixx = Inc + Ipc - 2*Icc; ixy = 0.25*(Ipp + Inn - Ipn - Inp); iyy = Icn + Icp - 2*Icc; ixf = Inc - Icc; iyf = Icn - Icc; ixb = Icc - Ipc; iyb = Icc - Icp; itt = u^2*ixx + v^2*iyy + 2*u*v*ixy; it = y*minmod(ixf,ixb) + v*minmod(iyf,iyb); amp*sign(itt)*abs(it)" fi vM:=abs(maxabs(im,iM)) if $vM>1e-8 *. {$1/$vM} rm[-3,-2] - else rm[-3--1] fi c $im,$iM } else # Inverse diffusion e[0--3] "Sharpen image$? with inverse diffusion and amplitude $1." foreach { im,iM:=[im,iM] +laplacian vM:=abs(maxabs(im,iM)) if $vM>1e-8 *. {$1/abs(maxabs(im,iM))} - else rm. fi c $im,$iM } fi #@cli sharpen_alpha : _amplitude[%]>=0,_nb_scales>0,0<=_anisotropy<=1,0<=_minimize_alpha<=1 #@cli : Sharpen selected images using a multi-scale and alpha boosting algorithm. #@cli : Default values: 'amplitude=1', 'nb_scales=5', 'anisotropy=0' and 'minimize_alpha=1'. sharpen_alpha : check "${1=1}>=0 && isint(${2=5},1) && inrange(${3=0},0,1) && inrange(${4=1},0,1)" e[^-1] "Sharpen image$? with a multi-scale and alpha boosting algorithm, with amplitude $1, $2 scales, "\ "anisotropy $3 and alpha minimization factor $4." foreach { split_opacity l[0] { split_alpha $2,0,$3,$4 foreach[^0] { sh 100% *. {$/($>+$<)):(1+$1)} c. 0,255 rm. } merge_alpha } a c } #@cli smooth : amplitude[%]>=0,_sharpness>=0,0<=_anisotropy<=1,_alpha[%],_sigma[%],_dl>0,_da>0,\ # _precision>0,_interpolation,_fast_approx={ 0 | 1 } : \ # nb_iterations>=0,_sharpness>=0,_anisotropy,_alpha,_sigma,_dt>0,0 : [tensor_field],_amplitude>=0,_dl>0,_da>0,\ # _precision>0,_interpolation,_fast_approx={ 0 | 1 } : \ # [tensor_field],_nb_iters>=0,_dt>0,0 : (+) #@cli : Smooth selected images anisotropically using diffusion PDE's, with specified field of #@cli : diffusion tensors. #@cli : 'interpolation' can be { 0:nearest | 1:linear | 2:runge-kutta }. #@cli : Default values: 'sharpness=0.7', 'anisotropy=0.3', 'alpha=0.6', 'sigma=1.1', 'dl=0.8', 'da=30', \ # 'precision=2', 'interpolation=0' and 'fast_approx=1'. #@cli : $ image.jpg repeat 3 smooth 40,0,1,1,2 done #@cli : $ image.jpg 100%,100%,1,2 rand[-1] -100,100 repeat 2 smooth[-1] 100,0.2,1,4,4 done warp[0] [-1],1,1,1 #@cli : $$ https://gmic.eu/oldtutorial/_smooth #@cli split_freq : smoothness>0[%] #@cli : Split selected images into low and high frequency parts. #@cli : $ image.jpg split_freq 2% split_freq : e[^-1] "Split image$? into low and high frequency parts, with smoothness $1." foreach { +b $1 -[0] [1] rv } #@cli solve_poisson : "laplacian_command",_nb_iterations>=0,_time_step>0,_nb_scales>=0 #@cli : Solve Poisson equation so that applying 'laplacian[n]' is close to the result of 'laplacian_command[n]'. #@cli : Solving is performed using a multi-scale gradient descent algorithm. #@cli : If 'nb_scales=0', the number of scales is automatically determined. #@cli : Default values: 'nb_iterations=60', 'dt=5' and 'nb_scales=0'. #@cli : $ image.jpg command "foo : gradient x" +solve_poisson foo +foo[0] +laplacian[1] solve_poisson : check "${2=60}>=0 && ${3=5}>0 && ${4=0}>=0" e[^-1] "Solve Poisson equation for image$?, for laplacian command '$1', with $2 iterations, time step $3 and "\ ${arg0\ !$4,$4,auto}" scales." foreach { [0] repeat $4?$4:int(max(log2(max(w,h))-1,1)) { f:=2^$< r[1] {0,max(1,w/$f)},{0,max(1,h/$f)},1,100%,3 +ri[0] [1],2 l. { $1 k[0] } repeat $2 { +laplacian.. -. .. *. {$3/max(1e-8,abs(im),abs(iM))} +[-3,-1] } rm. } rm[0] } #@cli split_alpha : _nb_scales[%]={ 0:auto | -S<0 | N>0 },_subsample={ 0:no | 1:yes },0<=_anisotropy<=1,\ # 0<=_minimize_alpha<=1 #@cli : Split selected images into alpha detail scales. #@cli : If 'nb_scales==-S', the lowest scale has a size of at least 'SxS'. #@cli : Parameter 'anisotropy' is only considered when 'subsample=0'. #@cli : Image reconstruction is done with command ''merge_alpha''. #@cli : Default values: 'nb_scales=0', 'subsample=0', 'anisotropy=0' and 'minimize_alpha=1'. split_alpha : check "(isint(${1=0}) || ispercentage($1)) && isbool(${2=0}) && inrange(${3=0},0,1) && "\ "inrange(${4=1},0,1)" s= if $1 s="$1 " fi s0,s1="out", e[^-1] "Split image$? into "${s}"alpha detail scales, with"$s$2" subsampling, anisotropy $3 "\ "and alpha minimization factor $4." foreach { bm={f}{b} ext={x} if ['$ext']!=0 ext..=. fi nb_scales_max:=int(log2(min(w,h))) nb_scales:=cut(ispercentage($1)?round($nb_scales_max*$1):\ $1<0?int(log2(min(w,h)/(-$1)))+1:\ $1?$1:$nb_scales_max,\ 1,$nb_scales_max) if $nb_scales>1 repeat $nb_scales-1 { if $2 # With cubic subsampling +r. {int([w,h]/2)},1,100%,2 +r. ..,..,1,100%,5 c. 0,255 sub_alpha... .,$4 rm. else # Without subsampling if $3 +bilateral. {0.25+2^$>},{(1-$3)*100} b. {0.5+$3} else +b. {0.25+2^$>} fi sub_alpha.. .,$4 fi } rv foreach { => ${bm}_$>$ext } fi } #@cli split_details : _nb_scales[%]={ 0:auto | -S<0 | N>0 },_base_scale[%]>=0,_detail_scale[%]>=0 #@cli : Split selected images into 'nb_scales' detail scales. #@cli : If 'base_scale' = 'detail_scale' = 0, the image decomposition is done with 'a trous' wavelets. #@cli : Otherwise, it uses laplacian pyramids with linear standard deviations. #@cli : Default values: 'nb_scales=0', 'base_scale=0' and 'detail_scale=0'. #@cli : $ image.jpg split_details , split_details : check "(isint(${1=0}) || ispercentage($1)) && ${2=0}>=0 && ${3=0}>=0" if !max($2,$3) e[0--3] "Split image$? using $1 scales and 'a trous' wavelets." else e[0--3] "Split image$? using $1 scales, with base scale $2 and detail scale $3." fi foreach { bm={f}{b} ext={x} if ['$ext']!=0 ext..=. fi nb_scales_max:=int(log2(min(w,h))) nb_scales:=cut(ispercentage($1)?round($nb_scales_max*$1):\ $1<0?int(log2(min(w,h)/(-$1)))+1:\ $1?$1:$nb_scales_max,\ 1,$nb_scales_max) if !max($2,$3) repeat $nb_scales-1 { +f. "begin(interpolation = 0; boundary = 1; d = 2^"$>"; d2 = d*2); i(x - d2) + i(x + d2) + 4*i(x - d) + 4*i(x + d) + 6*i;" /. 16 if h>1 f. "begin(interpolation = 0; boundary = 1; d = 2^"$>"; d2 = d*2); i(x,y - d2) + i(x,y + d2) + 4*i(x,y - d) + 4*i(x,y + d) + 6*i;" /. 16 fi if d>1 f. "begin(interpolation = 0; boundary = 1; d = 2^"$>"; d2 = d*2); i(x,y,z - d2) + i(x,y,z + d2) + 4*i(x,y,z - d) + 4*i(x,y,z + d) + 6*i;" /. 16 fi -.. . } else ss:=max(0.25,ispercentage($3)?$3*max(w,h):$3) se:=max(0.25,ispercentage($2)?$2*max(w,h):$2) repeat $nb_scales-1 { +b. {lerp($ss,$se,$>/($>+$<))} -.. . } fi rv foreach { => ${bm}_$>$ext } } #@cli structuretensors : _scheme={ 0:centered | 1:forward/backward } #@cli : Compute the structure tensor field of selected images. #@cli : Default value: 'scheme=0'. #@cli : $ image.jpg structuretensors abs pow 0.2 #@cli : $$ https://gmic.eu/oldtutorial/_structuretensors structuretensors : skip "${1=}" l[] { if "isbool($1)" scheme=$1 else scheme=0 noarg fi onfail scheme=0 noarg } s0,s1=centered,forward-backward e[^-1] "Compute structure tensor field of image$?, with "${s$scheme}" scheme." foreach { if d>1 # 3D if $scheme # Fwd/bwd +g xyz,-1 +g[0] xyz,1 sqr[^0] +[1,4] +[2,4] +[3,4] /[^0] 2 g[0] xyz,0 [2] *. [1] *[2] [0] *[0,1] compose_channels + mv[2] 0 mv. -2 a c else # Centered g xyz,0 +*[0,1] +*[0,2] +*[1,2] sqr[0-2] compose_channels + mv[3,4] 1 rv[4,5] a c fi else # 2D if $scheme # Fwd/bwd +g xy,-1 +g[0] xy,1 sqr[^0] +[1,3] +[2,3] /[^0] 2 g[0] xy,0 *[0,1] compose_channels + mv[0] 2 a c else # Centered g xy,0 +* sqr[0,1] compose_channels + rv[1,2] a c fi fi } #@cli solidify : _smoothness[%]>=0,_diffusion_type={ 0:isotropic | 1:Delaunay-guided | 2:edge-oriented },\ # _diffusion_iter>=0 #@cli : Solidify selected transparent images. #@cli : Default values: 'smoothness=75%', 'diffusion_type=1' and 'diffusion_iter=20'. #@cli : $ image.jpg 100%,100% circle[-1] 50%,50%,25%,1,255 append c +solidify , display_rgba solidify : check "${1=75%}>=0 && isint(${2=1},0,2) && ${3=20}>=0" s0="isotropic" s1="Delaunay-guided" s2="edge-oriented" e[^-1] "Solidify transparent image$? with smoothness $1 and $3 iterations of "${s$2}" diffusion." foreach { split_opacity if $!>1 <=. 128 inpaint_pde.. [1],${1-3} rm. c 0,255 fi } #@cli syntexturize : _width[%]>0,_height[%]>0 #@cli : Resynthetize 'width'x'height' versions of selected micro-textures by phase randomization. #@cli : The texture synthesis algorithm is a straightforward implementation of the method described in : #@cli : . #@cli : Default values: 'width=height=100%'. #@cli : $ image.jpg crop 2,282,50,328 +syntexturize 320,320 syntexturize : check "${1=100%}>0 && ${2=$1}>0" e[^-1] "Resynthetize $1x$2 versions of texture$? by phase randomization." foreach { # Prepare input image data. mM:=im,iM repeat s { sh. $> sum$>:=is var$>:=iv rm. } # Retrieve some stats for post-normalization. nw:=ispercentage($1)?$1*w:$1 nh:=ispercentage($2)?$2*h:$2 repeat s { sum$>*={$nw*$nh/(w*h)} } # Re-estimate output (0,0) frequency. if $nw>w||$nh>h # Spot extension required when rendering on bigger image. periodize_poisson 100%,100% rectangle. 5,5,{w-6},{h-6},1,1 b. 2 n. 0,1 $nw,$nh,1,{-2,s} fc. ${average_vectors...} j. ...,{(w-{-2,w})/2},{(h-{-2,h})/2},0,0,1,.. rm[-3,-2] else r $nw,$nh,1,100%,0,0,0.5,0.5 periodize_poisson fi fft # Compute coherent random phase. 100%,100% rand. {[-pi,pi]} =. 0 if !(w%2) =. {(u<0.5)*pi},{int(w/2)} fi if !(h%2) =. {(u<0.5)*pi},0,{int(h/2)} fi if !(h%2)&&!(h%2) =. {(u<0.5)*pi},{int(w/2)},{int(h/2)} fi # Add random phase to fft of input image. +sin. cos.. +*[-4,-1] +*[-4,-3] +[-2,-1] *[-5,-3] *[-3,-2] -[-3,-2] # Get synthetised result and normalize it. repeat s { =.. ${sum$>},0,0,0,$> =. 0,0,0,0,$> } ifft rm. repeat s { sh. $> avg:=ia -. $avg *. {sqrt(${var$>}/(iv?iv:1))} +. $avg rm. } c $mM } #@cli syntexturize_matchpatch : _width[%]>0,_height[%]>0,_nb_scales>=0,_patch_size>0,_blending_size>=0,_precision>=0 #@cli : Resynthetize 'width'x'height' versions of selected micro-textures using a patch-matching algorithm. #@cli : If 'nbscales==0', the number of scales used is estimated from the image size. #@cli : Default values: 'width=height=100%', 'nb_scales=0', 'patch_size=7', 'blending_size=5' and 'precision=1'. #@cli : $ image.jpg crop 25%,25%,75%,75% syntexturize_matchpatch 512,512 syntexturize_matchpatch : check "${1=100%}>0 && ${2=$1}>0 && isint(${3=0},0) && isint(${4=7},1) && "\ "${5=5}>=0 && ${6=1}>=0" e[^-1] "Resynthetize $1x$2 version(s) of texture$? using a patch-matching algorithm with "\ ${"if $3 u \"$3 \" else u auto- fi"}"scales, $4x$4 patches, blending size $5 and precision $6." foreach { nb_scales:=round($3?$3:log2(min(w,h)/16),1,1) width:=ispercentage($1)?round(w*$1,1,1):$1 height:=ispercentage($2)?round(h*$2,1,1):$2 repeat $nb_scales { scale:=100*(0.5^$<) +r[0] $scale%,$scale%,1,3,2 if !$> # Initialization. {1+round(w*$width/{0,w},1,1)},{1+round(h*$height/{0,h},1,1)},1,1 noise. 0.2,2 ==. 1 +distance. 1 *. -1 label_fg.. 0 watershed.. . rm. 100%,100%,1,1,x +f. y a[-2,-1] c channels. 0,2 +blend. ..,shapeaverage -.. . rm. channels. 0,1 {-2,iM+1} rand. 0,{-4,w} +rand. 0,{-4,h} a[-2,-1] c map... . rm. +[-2,-1] round. s. c %.. {-3,w} %. {-3,h} a[-2,-1] c else # Upscale. rv[-2,-1] channels. 0,1 *. 2 r. 200%,200%,1,2,1 f. "*upc = i(x - 1,y,0,0); vpc = i(x - 1,y,0,1); ucp = i(x,y - 1,0,0); vcp = i(x,y - 1,0,1); ucc = i(x,y,0,0); vcc = i(x,y,0,1); if (ucc==upc && vcc==vpc && !c, upc + 1, if (ucc==ucp && vcc==vcp && c==1, vcp + 1,i))" fi # Synthesis. psize={-2,min(w,h,$4)} repeat 1+$6*$< { psynth:=int(max(3,$5*$scale%)) +warp_patch.. .,$psynth matchpatch. ...,$psize,$psize,1,4,4,0,0,.. rm.. } rm.. } warp_patch.. .,$5 rm. r $width,$height,1,100%,0,0,0.5,0.5 } # _syntexturize_matchpatch : [correspondence_map],blend_size>0 _syntexturize_matchpatch : check ${is_image_arg\ $1}" && isint(${2=3},0)" if $2<=1 pass$1 warp[^-1] .,0 rm. else repeat $! { pass$1 l[$>,-1] { [1],[1],1,[0] f. "*begin( boundary = 1; const patch_size = $2; const p2 = int(patch_size/2); const p1 = patch_size - p2 - 1; avg = resize([0],s#0); # Pre-compute gaussian kernel. wpq = resize([0],patch_size^2); g = 0; for (q = -p1, q<=p2, ++q, for (p = -p1, p<=p2, ++p, wpq[g++] = exp(-(p^2 + q^2)/(2*(0.3*patch_size)^2)); ); ); ); g = 0; avg = 0; norm = 0; for (q = -p1, q<=p2, ++q, for (p = -p1, p<=p2, ++p, U = I(#1,x + p,y + q); w = wpq[g++]; avg+=w*I(#0,U - [p,q]); norm+=w; ); ); avg/norm" k. } } fi #@cli tv_flow : _nb_iter>=0,_dt,_keep_sequence={ 0 | 1 } #@cli : Apply iterations of the total variation flow on selected images. #@cli : Default values: 'nb_iter=10', 'dt=30' and 'keep_sequence=0'. #@cli : $ image.jpg +tv_flow 40 tv_flow : skip ${1=10},${2=30},${3=0} e[^-1] "Apply $1 iterations of the total variation flow on image$?, with time step $2." pde_flow $1,$2,curvature,$3 #@cli unsharp : radius[%]>=0,_amount>=0,_threshold[%]>=0 #@cli : Apply unsharp mask on selected images. #@cli : Default values: 'amount=2' and 'threshold=0'. #@cli : $ image.jpg blur 3 +unsharp 1.5,15 cut 0,255 unsharp : check "${2=2}>=0" skip ${3=0} e[^-1] "Apply unsharp mask on image$?, with radius $1, amount $2 and threshold $3." foreach { +b $1 -. .. if $3 +norm. >=. $3 *[-2,-1] fi *. $2 - } #@cli unsharp_octave : _nb_scales>0,_radius[%]>=0,_amount>=0,threshold[%]>=0 #@cli : Apply octave sharpening on selected images. #@cli : Default values: 'nb_scales=4', 'radius=1', 'amount=2' and 'threshold=0'. #@cli : $ image.jpg blur 3 +unsharp_octave 4,5,15 cut 0,255 unsharp_octave : check "${1=4}>0 && ${3=2}>=0" skip ${2=1},${4=0} e[^-1] "Apply octave sharpening on image$?, with $1 scales, radius $2, amount $3 and threshold $4." foreach { nm={n} +f 0 weight=0 repeat $1 { +unsharp[0] {$2*2^-$<},$3,$4 *. {2^-$>} weight+=2^-$> +[1,-1] } rm[0] / $weight => $nm } #@cli vanvliet : std_deviation>=0[%],order={ 0 | 1 | 2 | 3 },axis={ x | y | z | c },_boundary_conditions : (+) #@cli : Apply Vanvliet recursive filter on selected images, along specified axis and with #@cli : specified standard deviation, order and boundary conditions. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'boundary_conditions=1'. #@cli : $ image.jpg +vanvliet 3,1,x #@cli : $ image.jpg +vanvliet 30,0,x vanvliet[-2] 30,0,y add #@cli voronoi #@cli : Compute the discrete Voronoi diagram of non-zero pixels in selected images. #@cli : $ 400,400 noise 0.2,2 eq 1 +label_fg 0 voronoi[-1] +gradient[-1] xy,1 append[-2,-1] c \ # norm[-1] ==[-1] 0 map[-2] 2,2 mul[-2,-1] normalize[-2] 0,255 dilate_circ[-2] 4 reverse max voronoi : e[^-1] "Compute the discrete Voronoi diagram of non-zero pixels in image$?." foreach { s c foreach { +!=. 0 distance. 1 *. -1 watershed.. . rm. } a c } #@cli watermark_fourier : text,_size>0 #@cli : Add a textual watermark in the frequency domain of selected images. #@cli : Default value: 'size=33'. #@cli : $ image.jpg +watermark_fourier "Watermarked!" +display_fft remove[-3,-1] normalize 0,255 \ # append[-4,-2] y append[-2,-1] y watermark_fourier : check ${2=33}>0 e[^-1] "Add textual watermark '$1' with size $2 in the frequency domain of image$?." i[0] 0 t[0] "$1",0,0,$2,1,1 >=[0] 0.5 autocrop[0] 0 repeat $!-1 { w2,h2:=int([w,h]/2) fft. shift[-2,-1] $w2,$h2,0,0,2 [0],[0],1,{s} x0,y0,x1,y1:=" const x = round(w#-2/3); const y = round(h#-2/3); const w2 = int(w/2); const h2 = int(h/2); x0 = x - w2; y0 = y - h2; x1 = w#-2 - 1 - x - w2; y1 = h#-2 - 1 - y - h2; [ x0,y0,x1,y1 ]" j[-3,-2] .,$x0,$y0,0,0,1,[0] mirror[0] x j[-3,-2] .,$x1,$y0,0,0,1,[0] mirror[0] y j[-3,-2] .,$x1,$y1,0,0,1,[0] mirror[0] x j[-3,-2] .,$x0,$y1,0,0,1,[0] mirror[0] y rm. shift[-2,-1] -$w2,-$h2,0,0,2 ifft[-2,-1] rm. mv. 1 } rm[0] #@cli watershed : [priority_image],_is_high_connectivity={ 0 | 1 } : (+) #@cli : Compute the watershed transform of selected images. #@cli : Default value: 'is_high_connectivity=1'. #@cli : $ 400,400 noise 0.2,2 eq 1 +distance 1 mul[-1] -1 label[-2] watershed[-2] [-1] mod[-2] 256 map[-2] 0 reverse #--------------------------------- # #@cli :: Features Extraction # #--------------------------------- #@cli area : tolerance>=0,is_high_connectivity={ 0 | 1 } #@cli : Compute area of connected components in selected images. #@cli : Default values: 'is_high_connectivity=0'. #@cli : $ image.jpg luminance stencil[-1] 1 +area 0 #@cli : $$ https://gmic.eu/oldtutorial/_area area : check "$1>=0" skip ${2=0} e[^-1] "Compute area of connected components in image$?, with tolerance $1 and "\ ${arg0\ !$2,high,low}" connectivity." foreach { s c foreach { label $1,$2 nb:=iM+1 +histogram $nb,0,{$nb-1} map.. . rm. } a c } #@cli area_fg : tolerance>=0,is_high_connectivity={ 0 | 1 } #@cli : Compute area of connected components for non-zero values in selected images. #@cli : Similar to 'area' except that 0-valued pixels are not considered. #@cli : Default values: 'is_high_connectivity=0'. #@cli : $ image.jpg luminance stencil[-1] 1 +area_fg 0 area_fg : check "$1>=0" skip ${2=0} e[^-1] "Compute area of foreground connected components in image$?, with tolerance $1 and "\ ${arg0\ !$2,high,low}" connectivity." foreach { s c foreach { label_fg $1,$2 nb:=1+iM +histogram $nb,0,{$nb-1} =. 0 map.. . rm. } a c } #@cli at_line : x0[%],y0[%],z0[%],x1[%],y1[%],z1[%] #@cli : Retrieve pixels of the selected images belonging to the specified line (x0,y0,z0)-(x1,y1,z1). #@cli : $ image.jpg +at_line 0,0,0,100%,100%,0 line[0] 0,0,100%,100%,1,0xFF00FF00,255,0,0 at_line : check ${7=100%}>=0 e[^-1] "Retrieve pixels of image$?, belonging to line ($1,$2,$3)-($4,$5,$6)." foreach { x0:=ispercentage($1)?(w-1)*$1:$1 y0:=ispercentage($2)?(h-1)*$2:$2 z0:=ispercentage($3)?(d-1)*$3:$3 x1:=ispercentage($4)?(w-1)*$4:$4 y1:=ispercentage($5)?(h-1)*$5:$5 z1:=ispercentage($6)?(d-1)*$6:$6 ($x0,$x1^$y0,$y1^$z0,$z1) r. {1+max(abs($x1-$x0),abs($y1-$y0),abs($z1-$z0))},1,1,3,3 round. 1 warp[0] .,0,0,0 rm. } #@cli at_quadrangle : x0[%],y0[%],x1[%],y1[%],x2[%],y2[%],x3[%],y3[%],_interpolation,_boundary_conditions : \ # x0[%],y0[%],z0[%],x1[%],y1[%],z1[%],x2[%],y2[%],z2[%],x3[%],y3[%],z3[%],_interpolation,_boundary_conditions #@cli : Retrieve pixels of the selected images belonging to the specified 2D or 3D quadrangle. #@cli : 'interpolation' can be { 0:nearest-neighbor | 1:linear | 2:cubic }. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : $ image.jpg params=5%,5%,95%,5%,60%,95%,40%,95% +at_quadrangle $params polygon.. 4,$params,0.5,255 at_quadrangle : check "$#>=8 && $#<=14 && $#!=11" _at_quadrangle{$#<12?2:3} $* _at_quadrangle2 : check "${9=1}>=0 && $9<=2 && ${10=0}>=0 && $10<=3" foreach { x0:=round(ispercentage($1)?(w-1)*$1:$1) y0:=round(ispercentage($2)?(h-1)*$2:$2) x1:=round(ispercentage($3)?(w-1)*$3:$3) y1:=round(ispercentage($4)?(h-1)*$4:$4) x2:=round(ispercentage($5)?(w-1)*$5:$5) y2:=round(ispercentage($6)?(h-1)*$6:$6) x3:=round(ispercentage($7)?(w-1)*$7:$7) y3:=round(ispercentage($8)?(h-1)*$8:$8) ($x0,$x1;$x3,$x2^$y0,$y1;$y3,$y2) r. {P0=[$x0,$y0];P1=[$x1,$y1];P2=[$x2,$y2];P3=[$x3,$y3];\ 1+round([max(norm(P1-P0),norm(P3-P2)),max(norm(P3-P0),norm(P2-P1))])},1,2,3 warp.. .,0,$9,$10 rm. } _at_quadrangle3 : check "${13=1}>=0 && $13<=2 && ${14=0}>=0 && $14<=3" foreach { x0:=round(ispercentage($1)?(w-1)*$1:$1) y0:=round(ispercentage($2)?(h-1)*$2:$2) z0:=round(ispercentage($3)?(h-1)*$3:$3) x1:=round(ispercentage($4)?(w-1)*$4:$4) y1:=round(ispercentage($5)?(h-1)*$5:$5) z1:=round(ispercentage($6)?(h-1)*$6:$6) x2:=round(ispercentage($7)?(w-1)*$7:$7) y2:=round(ispercentage($8)?(h-1)*$8:$8) z2:=round(ispercentage($9)?(h-1)*$9:$9) x3:=round(ispercentage($10)?(w-1)*$10:$10) y3:=round(ispercentage($11)?(h-1)*$11:$11) z3:=round(ispercentage($12)?(h-1)*$12:$12) ($x0,$x1;$x3,$x2^$y0,$y1;$y3,$y2^$z0,$z1;$z3,$z2) r. {P0=[$x0,$y0,$z0];P1=[$x1,$y1,$z1];P2=[$x2,$y2,$z2];P3=[$x3,$y3,$z2];\ 1+round([max(norm(P1-P0),norm(P3-P2)),max(norm(P3-P0),norm(P2-P1))])},1,3,3 warp.. .,0,$13,$14 rm. } #@cli barycenter #@cli : Compute the barycenter vector of pixel values. #@cli : $ 256,256 ellipse 50%,50%,20%,20%,0,1,1 deform 20 +barycenter +ellipse[-2] {@0,1},5,5,0,10 barycenter : e[^-1] "Compute the barycenter vector of pixel values of image$?." norm foreach { nm={0,b} sum:=is if $sum>0 if d>1 +* 'z' z:=is rm. else z=0 fi if h>1 +* 'y' y:=is rm. else y=0 fi * 'x' x:=is rm. ({$x/$sum};{$y/$sum};{$z/$sum}) else ({w/2},{h/2},{d/2}) rm.. fi => "[barycenter of '"$nm"']" } #@cli betti #@cli : Compute Betti numbers B0,B1 and B2 from selected 3D binary shapes. #@cli : Values B0,B1 and B2 are returned in the status. \ # When multiple images are selected, the B0,B1,B2 of each image are concatenated in the status. #@cli : (see 'https://en.wikipedia.org/wiki/Betti_number' for details about Betti numbers). betti : e[^-1] "Compute Betti numbers B0,B1 and B2 from binary shape$?." res,c= foreach { # Compute Euler characteristic. {[2*w+1,2*h+1,2*d+1]} eval.. "*i?( # Compute volume of 0D,1D,2D and 3D primitives. X = 2*x; Y = 2*y; Z = 2*z; X1 = X + 1; Y1 = Y + 1; Z1 = Z + 1; X2 = X + 2; Y2 = Y + 2; Z2 = Z + 2; # Set 8 points i(#-1,X,Y,Z) = i(#-1,X2,Y,Z) = i(#-1,X2,Y2,Z) = i(#-1,X,Y2,Z) = i(#-1,X,Y,Z2) = i(#-1,X2,Y,Z2) = i(#-1,X2,Y2,Z2) = i(#-1,X,Y2,Z2) = 1; # Set 12 segments. i(#-1,X1,Y,Z) = i(#-1,X2,Y1,Z) = i(#-1,X1,Y2,Z) = i(#-1,X,Y1,Z) = i(#-1,X1,Y,Z2) = i(#-1,X2,Y1,Z2) = i(#-1,X1,Y2,Z2) = i(#-1,X,Y1,Z2) = i(#-1,X,Y,Z1) = i(#-1,X2,Y,Z1) = i(#-1,X,Y2,Z1) = i(#-1,X2,Y2,Z1) = 2; # Set 6 surfaces. i(#-1,X1,Y1,Z) = i(#-1,X1,Y1,Z2) = i(#-1,X1,Y,Z1) = i(#-1,X1,Y2,Z1) = i(#-1,X,Y1,Z1) = i(#-1,X2,Y1,Z1) = 3; # Set 1 volume. i(#-1,X1,Y1,Z1) = 4; )" histogram. 4,1,4 Xi:=i[0]-i[1]+i[2]-i[3] rm. # Euler characteristic # Compute Betti numbers B0, B1 and B2. expand. xyz,1 # Make sure borders are 0 +label. 0,1 {iM+1} eval.. "*i(#-3)?(i[#-1,i] = 1)" B0:=is rm[-2,-1] # Number of connected components +label. 0,0 {iM+1} eval.. "*!i(#-3) && i?(i[#-1,i] = 1)" B2:=is rm[-2,-1] # Number of cavities B1:=$B0+$B2-$Xi shrink. xyz,1 res.=$c$B0,$B1,$B2 c=, } u $res #@cli canny : _sigma[%]>=0,_low_threshold>=0,_high_threshold>=0 #@cli : Locate image edges using Canny edge detector. #@cli : Default values: 'sigma=1', 'low_threshold=0.05', 'high_threshold=0.15'. #@cli : $ image.jpg canny 1 canny : check "${1=1}>=0 && ${2=0.05}>=0 && ${3=0.15}>=0 && isint(${4=0},0,2)" e[^-1] "Apply Canny edge detection on image$?, with sigma $1, low threshold $2 and high threshold $3." low,high:=$2*$3,$3 foreach { nm={n} b $1 (1,0,-1;2,0,-2;1,0,-1^1,2,1;0,0,0;-1,-2,-1) convolve.. .,1,0,0 rm. s. c,2 # Gaussian + Sobel +atan2.. . mul. {4/pi} round. 1 mod. 4 # Quantized gradient orientation sqr[0,1] +[0,1] sqrt.. # Gradient norm f.. " # Suppress non-maximum gradients A = i#1; Y = A<1?max(j(0,-1),j(0,1)): A<2?max(j(-1,-1),j(1,1)): A<3?max(j(-1),j(1)): max(j(-1,1),j(1,-1)); i>=Y?i:0" rm. div {iM} +ge $high ge.. $low # Double thresholding # Hysteresis. f. ">max(crop(x-1,y-1,z,c,3,3,1,1)) && i#0?1:i" f. " $nm } #@cli delaunay : _output_type={ 0:image | 1:coordinates/triangles } #@cli : Generate discrete 2D Delaunay triangulation of non-zero pixels in selected images. #@cli : Input images must be scalar. #@cli : Each pixel of the output image is a triplet (a,b,c) meaning the pixel belongs to #@cli : the Delaunay triangle 'ABC' where 'a','b','c' are the labels of the pixels 'A','B','C'. #@cli : $ 400,400 rand 32,255 100%,100% noise. 0.4,2 eq. 1 mul +delaunay #@cli : $ image.jpg 100%,100% noise. 2,2 eq. 1 delaunay. +blend shapeaverage0 delaunay : skip "${1=0}" mode=0 if isin(['"$1"'],'0','1') mode=$1 else mode=0 noarg fi s0,s1=image,coordinates e[^-1] "Generate discrete 2D Delaunay triangulation of non-zero pixels in image$?, in "${s$mode}" mode." foreach { bnm={b} nm={n} # Extract and label non-zero pixels. 1,64,1,2 =. 1,0,100% f.. "begin(N = 0); I!=0?(da_push([x,y]);++N):0; end(resize(#-1,1,da_size(),1,2,0))" # Construct discrete voronoi diagram. +neq.. 0 distance. 1 *. -1 watershed... . rm. # Extract Delaunay triangles from discrete voronoi. 1,64,1,3 eval... " V = crop(x,y,2,2); min(V)?( V[1]==V[0] && V[2]!=V[0] && V[3]!=V[0] && V[3]!=V[2]?( # [ a,a,b,c ] da_push([ V[0],V[2],V[3] ]); ):(V[2]==V[0] && V[1]!=V[0] && V[3]!=V[0] && V[3]!=V[1]) || # [ a,b,a,c ] (V[1]==V[2] && V[0]!=V[1] && V[3]!=V[0] && V[3]!=V[1])?( # [ a,b,b,c ] da_push([ V[0],V[1],V[3] ]); ):(V[3]==V[0] && V[1]!=V[0] && V[2]!=V[0] && V[2]!=V[1]) || # [ a,b,c,a ] (V[1]==V[3] && V[0]!=V[1] && V[2]!=V[0] && V[2]!=V[1]) || # [ a,b,c,b ] (V[2]==V[3] && V[0]!=V[1] && V[0]!=V[2] && V[1]!=V[2]) ?( # [ a,b,c,c ] da_push([ V[0],V[1],V[2] ]); ):V[0]!=V[1] && V[0]!=V[2] && V[0]!=V[3] && V[1]!=V[2] && V[1]!=V[3] && V[2]!=V[3]?( # [ a,b,c,d ] da_push([ V[0],V[1],V[2] ]); da_push([ V[1],V[3],V[2] ]); ); end(resize(#-1,1,da_size(),1,3,0)); )" # Return output in expected mode. if $mode k[-2,-1] => ${bnm}_points,${bnm}_faces else {0,[w,h,1,3]} eval.. "polygon(#-1,3,I[#1,i0],I[#1,i1],I[#1,i2],1,i0,i1,i2)" k. => $nm fi } #@cli detect_skin : 0<=tolerance<=1,_skin_x,_skin_y,_skin_radius>=0 #@cli : Detect skin in selected color images and output an appartenance probability map. #@cli : Detection is performed using CbCr chromaticity data of skin pixels. #@cli : If arguments 'skin_x', 'skin_y' and 'skin_radius' are provided, skin pixels are learnt #@cli : from the sample pixels inside the circle located at ('skin_x','skin_y') with radius 'skin_radius'. #@cli : Default value: 'tolerance=0.5' and 'skin_x=skiny=radius=-1'. detect_skin : check "${1=0.5}>=0 && $1<=1" skip ${2=-1},${3=-1},${4=-1} if $2<0||$3<=0||$4<=0 e[0--3] "Detect skin in image$?, using tolerance $1." m0=120.9292108800069 m1=142.5745272918084 A=0.09749985486268997 B=0.06388871371746063 C=0.05250053107738495 to_rgb srgb2rgb rgb2ycbcr channels 1,2 foreach { whd={w},{h},{d} r {whd},2,1,1,-1 s y -[0] $m0 -[1] $m1 a y i[0] ($A,$B;$B,$C) +m* rm[0] * s y + *. {$1-1} exp. r $whd,1,-1 } else e[0--3] "Detect skin in image$?, using tolerance $1 and target circle at ($2,$3) with radius $4." to_rgb srgb2rgb rgb2ycbcr channels 1,2 foreach { 100%,100% circle[1] $2,$3,$4,1,1 +f[1] "i?y:-1" f[1] "i?x:-1" discard[1,2] -1 a[1,2] c +warp[0] [1],0,0,1 rm[1] s[1] c m0={1,ia} -[1] $m0 m1={2,ia} -[2] $m1 M:=h a[1,2] x +transpose[1] rv[1,2] m*[1,2] /[1] $M invert[1] rv whd={w},{h},{d} r[1] {whd},2,1,1,-1 s[1] y -[1] $m0 -[2] $m1 a[1,2] y +m* rm[0] * s y + *. {$1-1} exp. r $whd,1,-1 } fi #@cli displacement : [source_image],_smoothness,_precision>=0,_nb_scales>=0,_iteration_max>=0,is_backward={ 0 | 1 },\ # _[guide] : (+) #@cli : Estimate displacement field between specified source and selected target images. #@cli : If 'smoothness>=0', regularization type is set to isotropic, else to anisotropic. #@cli : If 'nbscales==0', the number of scales used is estimated from the image size. #@cli : Default values: 'smoothness=0.1', 'precision=5', 'nb_scales=0', 'iteration_max=10000', 'is_backward=1' \ # and '[guide]=(unused)'. #@cli : $ image.jpg +rotate 3,1,0,50%,50% +displacement[-1] [-2] quiver[-1] [-1],15,1,1,1,{1.5*iM} #@cli distance : isovalue[%],_metric : isovalue[%],[metric],_method : (+) #@cli : Compute the unsigned distance function to specified isovalue, opt. according to a custom metric. #@cli : 'metric' can be { 0:chebyshev | 1:manhattan | 2:euclidean | 3:squared-euclidean }. #@cli : 'method' can be { 0:fast-marching | 1:low-connectivity dijkstra | 2:high-connectivity dijkstra | \ # 3:1+return path | 4:2+return path }. #@cli : Default value: 'metric=2' and 'method=0'. #@cli : $ image.jpg threshold 20% distance 0 pow 0.3 #@cli : $ 400,400 set 1,50%,50% +distance[0] 1,2 +distance[0] 1,1 distance[0] 1,0 mod 32 threshold 16 append c #@cli : $$ https://gmic.eu/oldtutorial/_distance #@cli edgels : x0,y0,_n0,_is_high_connectivity={ 0 | 1 } #@cli : Extract one or several lists of edgels (and their normals) that defines a 2D binary silhouette. #@cli : When specified (i.e. '!=-1'), arguments 'x0,y0,n0' are the coordinates of the starting edgel, \ # which must be located on an edge of the binary silhouette. #@cli : - If 'x0,y0' and 'n0' are specified, only a single list of edgels is returned. #@cli : - If only 'x0,y0' are specified (meaning 'n0=-1'), up to 4 lists of edgels can be returned, \ # all starting from the same point (x0,y0). #@cli : - If no arguments are specified (meaning 'x0=y0=n0=-1'), all possible lists of edgels are returned. #@cli : A list of edgels is returned as an image with 3 channels '[x,y,n]' where 'x' and 'y' are the 2D coordinates \ # of the edgel pixel, and 'n' is the orientation of its associated canonical normal \ # (which can be { 0:[1,0] | 1:[0,1] | 2:[-1,0] | 3:[0,-1] }. #@cli : Default values: 'x0=y0=n0=-1' and 'is_high_connectivity=1'. edgels : check "isint(${1=-1}) && $1>=(${2=-1}>=0?0:-1) && isint($2) && $2>=($1>=0?0:-1) && "\ "isint(${3=-1}) && ($1>=0?inrange($3,-1,3):($3==-1)) && isbool(${4=1})" is_xy0,is_xyn0:="a=min($1,$2)>=0;[a,a && $3>=0]" if $is_xyn0 s= opt=", starting from edgels ($1,$2,$3)." elif $is_xy0 s=s opt=", starting from pixel ($1,$2)." else s=s opt="." fi e[^-1] "Extract list"$s" of edgels in image$?"$opt pass[$[]] -1 sel=${} code=" P0 = P = [ x,y,n ]; do ( da_push(P); !$is_xyn0?(i(#1,P[0],P[1],0,0)|=(1<,$sel)}"]." fi 1,1,1,3 eval ${-math_lib}"const x = $1; const y = $2; const n = $3; "$code elif $is_xy0 # Pixel coordinates specified if "!i($1,$2)" error[0--5] "Command 'edgels': Point ($1,$2) is in background, in image ["{arg0($>,$sel)}"]." fi 100%,100% # Already visited edgels (bitwise storage) 1,1,1,3 eval ${-math_lib}" const x = $1; const y = $2; sep = [ -1,-1,-1 ]; repeat (4,n, !(i(#1,x,y)&(1<${-math_lib}"begin(sep = [ -1,-1,-1 ]); i(x,y)?repeat (4,n, !(i(#1,x,y)&(1<0 $!,1,1,1,"resize(#x,3,h#x/3,1,1,-1)" rm. else 0 fi permute zycx fi } #@cli fftpolar #@cli : Compute fourier transform of selected images, as centered magnitude/phase images. #@cli : $ image.jpg fftpolar ellipse 50%,50%,10,10,0,1,0 ifftpolar fftpolar : e[^-1] "Compute fourier transform of image$?, as centered magnitude/phase images." foreach { fft complex2polar shift {-round(w/2)},{-round(h/2)},{-round(d/2)},0,2 } #@cli histogram : nb_levels>0[%],_min_value[%],_max_value[%] : (+) #@cli : Compute the histogram of selected images. #@cli : If value range is set, the histogram is estimated only for pixels in the specified #@cli : value range. Argument 'max_value' must be specified if 'min_value' is set. #@cli : Default values: 'min_value=0%' and 'max_value=100%'. #@cli : $ image.jpg +histogram 64 display_graph[-1] 400,300,3 #@cli histogram_masked : [mask],nb_levels>0[%],_min_value[%],_max_value[%] #@cli : Compute the masked histogram of selected images. #@cli : Default values: 'min_value=0%' and 'max_value=100%'. histogram_masked : check ${"is_image_arg $1"}" && isint($2,1)" skip ${3=0%},${4=100%} e[^-1] "Compute histogram of image$?, with mask $1, $2 levels in range [$3,$4]." pass$1 foreach[^-1] { vmin,vmax:="1 + (ispercentage($3)?$3:($3 - im)/(iM - im)),"\ "1 + (ispercentage($4)?$4:($4 - im)/(iM - im))" n 1,2 pass$1 !=. 0 * discard 0 histogram $2,$vmin,$vmax } rm. #@cli histogram_nd : nb_levels>0[%],_value0[%],_value1[%] #@cli : Compute the 1D,2D or 3D histogram of selected multi-channels images (having 1,2 or 3 channels). #@cli : If value range is set, the histogram is estimated only for pixels in the specified #@cli : value range. #@cli : Default values: 'value0=0%' and 'value1=100%'. #@cli : $ image.jpg channels 0,1 +histogram_nd 256 histogram_nd : check $1>0 skip ${2=0%},${3=100%} e[^-1] "Compute histogram of multi-channels image$?, using $1 levels in range [$1,$2]." foreach { nm={n} vmin,vmax:="const a = ispercentage($2)?lerp(im,iM,$2):$2; const b = ispercentage($3)?lerp(im,iM,$3):$3; [ min(a,b), max(a,b) ]" nb_levels:=max(1,round(ispercentage($1)?(1+$vmax-$vmin)*$1:$1)) r {whd},1,1,{min(3,s)},-1 if s==1 # 1d histogram $nb_levels,$vmin,$vmax elif s==2 # 2d $nb_levels,$nb_levels eval.. "ind(p) = cut(int($nb_levels*(p - $vmin)/($vmax - $vmin)),0,$nb_levels - 1); inrange(i0,$vmin,$vmax) && inrange(i1,$vmin,$vmax)? ++i(#-1,ind(i0),ind(i1),0,0); I" else # 3d $nb_levels,$nb_levels,$nb_levels eval.. "ind(p) = cut(int($nb_levels*(p - $vmin)/($vmax - $vmin)),0,$nb_levels - 1); inrange(i0,$vmin,$vmax) && inrange(i1,$vmin,$vmax) && inrange(i2,$vmin,$vmax)? ++i(#-1,ind(i0),ind(i1),ind(i2),0); I" fi k. => $nm } #@cli histogram_cumul : _nb_levels>0,_is_normalized={ 0 | 1 },_val0[%],_val1[%] #@cli : Compute cumulative histogram of selected images. #@cli : Default values: 'nb_levels=256', 'is_normalized=0', 'val0=0%' and 'val1=100%'. #@cli : $ image.jpg +histogram_cumul 256 histogram[0] 256 display_graph 400,300,3 histogram_cumul : check ${1=256}>0 skip ${2=0},${3=0%},${4=100%} arg0 !$2,"normalized ","" e[^-1] "Compute "${}"cumulative histogram of image$?, using $1 levels." histogram $1,$3,$4 cumulate if $2 foreach { / {iM} } fi #@cli histogram_pointwise : nb_levels>0[%],_value0[%],_value1[%] #@cli : Compute the histogram of each vector-valued point of selected images. #@cli : If value range is set, the histogram is estimated only for values in the specified #@cli : value range. #@cli : Default values: 'value0=0%' and 'value1=100%'. histogram_pointwise : skip ${2=0%},${3=100%} e[^-1] "Compute the pointwise histogram of vector-valued points in image$?, with $1 levels." foreach { nm={n} nb_levels:=round(ispercentage($1)?(iM-im)*$1:$1) value0:=ispercentage($2)?im+(iM-im)*$2:$2 value1:=ispercentage($3)?im+(iM-im)*$3:$3 - $value0 * {$nb_levels/max(1,abs($value1-$value0))} c 0,{$nb_levels-1} round w,h,d={w},{h},{d} r {whd},{s},1,1,-1 i.. (0,{w-1}) r.. .,.,1,1,3 round.. r[-2,-1] 300%,100%,1,1,4 shift. 1 +[-2,-1] y. i.. ({'CImg3d'},{h/3},{h/3}) (1,0;1,{h/3-1}) r. 2,{-2,h/3},1,1,3 round. 3,100%,1,1,1 1,100%,1,1,-1 y[-5,-3,-2] a[-5--1] y {$w*$h*$d},$nb_levels j3d. ..,0,0,0,1,0,0,0 rm.. r $w,$h,$d,$nb_levels,-1 => $nm } #@cli hough : _width>0,_height>0,gradient_norm_voting={ 0 | 1 } #@cli : Compute hough transform (theta,rho) of selected images. #@cli : Default values: 'width=512', 'height=width' and 'gradient_norm_voting=1'. #@cli : $ image.jpg +blur 1.5 hough[-1] 400,400 blur[-1] 0.5 add[-1] 1 log[-1] hough : check "${1=512}>0 && ${2=$1}>0" skip ${3=1} e[^-1] "Compute $1x$2 hough transform of image$?, "${arg0\ !$3,with,without}" gradient norm voting." slices 50% luminance foreach { nm={n} rhomax:=sqrt(w^2+h^2)/2 g (0,{w-1}) (0;{{-2,h}-1}) r[-2,-1] {-3,w},{-3,h},1,1,3 -.. {w/2} -. {h/2} complex2polar[-4--1] -. ... polar2complex[-2,-1] rm. +<. 0 *. {pi} +[-3,-1] abs. %.. {2*pi} *. {$2/$rhomax} *.. {0.5*$1/pi} y[-3--1] x {w} mv[-4] $! if !$3 f. 1 fi a y pointcloud 1 r $1,$2,1,1,0 => $nm } #@cli huffman_tree #@cli : Generate Huffman coding tree from the statistics of all selected images. #@cli : Huffman tree is returned as a 1xN image inserted at the end of the image list, \ # representing the 'N' vector-valued leafs/nodes of the tree, encoded as `[ value,parent,child0,child1 ]`. #@cli : Last row of the returned image corresponds to the tree root. #@cli : Selected images must contain only positive integer values. #@cli : Return maximal value of the input data in the status. #@cli : See also: ''compress_huffman'', ''decompress_huffman''. +huffman_tree : e[^-1] "Generate Huffman coding tree from the statistic of image$?." Mlv:="val=-inf;for(k=0,k list of active leafs/nodes ( id,occurence,value,parent,child0,child1 ) eval " id = da_size(); active = 0; do ( # Find the two minimal occurrences in active nodes. omin1 = omin2 = inf; oind1 = oind2 = 0; for (k = da_size() - 1, k>=active, --k, occ = i(0,k,0,1); occ<=omin1?(oind2 = oind1; omin2 = omin1; oind1 = k; omin1 = occ): occ<=omin2?(oind2 = k; omin2 = occ); ); oind1>oind2?swap(oind1,oind2); # Merge them as a new active node. i(0,oind1,0,3) = i(0,oind2,0,3) = id; da_push([ id++,omin1 + omin2,-1,-1,i[oind1],i[oind2] ]); swap(I[oind1],I[active++]); # Move children in set of inactive nodes swap(I[oind2],I[active++]); ,_(while) active "[Huffman Tree]" u $Mlv #@cli ifftpolar #@cli : Compute inverse fourier transform of selected images, from centered magnitude/phase images. ifftpolar : e[^-1] "Compute inverse fourier transform of image$?, from centered magnitude/phase images." repeat int($!/2) { l[$>,{$>+1}] { shift {round(w/2)},{round(h/2)},{round(d/2)},0,2 polar2complex ifft rm. } } #@cli img2patches : patch_size>0,_overlap[%]>0,_boundary_conditions #@cli : Decompose selected 2D images into (possibly overlapping) patches and stack them along the z-axis. #@cli : 'overlap' must be in range '[0,patch_size-1]'. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default values: 'overlap=0' and 'boundary_conditions=0'. #@cli : See also: ''patches2img''. #@cli : $ image.jpg img2patches 64 img2patches : check "isint($1,1) && ${2=0}>=0 && isint(${3=0},0,3)" e[^-1] "Decompose 2D images$? as $1x$1 patch-image, with overlap $2." foreach { if d>1 error[0--4] "Command 'img2patches': Image ["$>"] is not a 2D image (size: "{[w,h,d,s]}")." fi nm={n} psiz=$1 if ispercentage($2) overlap:=round(lerp(0,$psiz-1,$2)) else overlap:=int($2) fi if $overlap<0" || "$overlap>=$psiz error[0--4] "Command 'img2patches': Specified overlap $2 (value: "$overlap") is larger than "\ "patch size (value: "$psiz")." fi step:=$psiz-$overlap Nw,Nh:=round([w,h]/$step,1,1) # Extract patches. $1,$1,{$Nw*$Nh},100% eval.. "* begin(ret = vector(#s); const boundary = $3); (x%$step) + (y%$step)==0?( P = crop(x,y,$psiz,$psiz); ind = (y*$Nw + x)/$step; draw(#1,P,0,0,ind,0,$psiz,$psiz,1,s); ); ret" k. => $nm } #@cli isophotes : _nb_levels>0 #@cli : Render isophotes of selected images on a transparent background. #@cli : Default value: 'nb_levels=64' #@cli : $ image.jpg blur 2 isophotes 6 dilate_circ 5 display_rgba isophotes : skip ${1=64} e[^-1] "Render isophote maps from images$?, with $1 levels." to_rgba foreach { +luminance repeat $1 { +isoline3d[1] {$>*255/($1-1)} } rm[1] +3d[^0] col3d. 1 [0],[0] j3d. ..,0,0,0,1,0,0,0 rm.. * } #@cli label : _tolerance>=0,is_high_connectivity={ 0 | 1 },_is_L2_norm={ 0 | 1 } : (+) #@cli : Label connected components in selected images. #@cli : If 'is_L2_norm=1', tolerances are compared against L2-norm, otherwise L1-norm is used. #@cli : Default values: 'tolerance=0', 'is_high_connectivity=0' and 'is_L2_norm=1'. #@cli : $ image.jpg luminance threshold 60% label normalize 0,255 map 0 #@cli : $ 400,400 set 1,50%,50% distance 1 mod 16 threshold 8 label mod 255 map 2 #@cli : $$ https://gmic.eu/oldtutorial/_label #@cli label_fg : tolerance>=0,is_high_connectivity={ 0 | 1 },_is_L2_norm={ 0 | 1 } #@cli : Label connected components for non-zero values (foreground) in selected images. #@cli : Similar to 'label' except that 0-valued pixels are not labeled. #@cli : If 'is_L2_norm=1', tolerances are compared against L2-norm, otherwise L1-norm is used. #@cli : Default value: 'is_high_connectivity=0'. label_fg : check "$1>=0 && isbool(${2=0}) && isbool(${3=1})" e[^-1] "Label foreground connected components on image [1], with tolerance $1 (L"{$3+1}"-norm) and "\ ${arg0\ !$2,high,low}" connectivity." foreach { if d>1 +z -1,-1,-1,100%,100%,100% label. $1,$2,$3 z. 1,1,1,100%,100%,100% else +z -1,-1,100%,100% label. $1,$2,$3 z. 1,1,100%,100% fi norm.. !=.. 0 * +histogram {1+iM} =. 0 >. 0 cumulate. map.. . rm. } #@cli laar #@cli : Extract the largest axis-aligned rectangle in non-zero areas of selected images. #@cli : Rectangle coordinates are returned in status, as a sequence of numbers x0,y0,x1,y1. #@cli : $ shape_cupid 256 coords=${-laar} normalize 0,255 to_rgb rectangle $coords,0.5,0,128,0 laar : e[^-1] "Extract the largest axis-aligned rectangle in non-zero areas of image$?." res= sep= foreach { +channels 0 gt. 0 => shape +cumulate[shape] xy => cumul val:=i[-1,2] if !$val res.=-1,-1,-1,-1 # All black elif $val==wh res.=0,0,{[w,h]-1} # All white else # Step 1: Compute largest square. Rin,Rout=0,{min(w,h)+1} P0,P= do Rmid:=int(($Rin+$Rout)/2) Q=${_laar\ $Rmid,$Rmid,$P} if narg($Q) Rin=$Rmid P0=$Q P=$Q else Rout=$Rmid P= fi while $Rin!=$Rout-1 if $Rin==1 P=${_laar\ 1,1} fi # Step 2: Compute largest rectangle, for W>H. maxA,maxW,maxH=0 maxcoords= P=$P0 W,H=$Rin for $H>0 { A:=$W*$H if $A>$maxA maxA,maxW,maxH=$A,$W,$H maxP:=[$P][0,2] fi nW:=$W+1 Q=${_laar\ $nW,$H,$P} if narg($Q) W=$nW P=$Q elif $H>1 pH:=$H-1 Q=${_laar\ $nW,$pH} if narg($Q) W,H=$nW,$pH P=$Q else H-=2 P= fi else break fi } # Step 3: Compute largest rectangle, for H>W. P=$P0 W,H=$Rin for $W>0 { A:=$W*$H if $A>$maxA maxA,maxW,maxH=$A,$W,$H maxP:=[$P][0,2] fi nH:=$H+1 Q=${_laar\ $W,$nH,$P} if narg($Q) H=$nH P=$Q elif $W>1 pW:=$W-1 Q=${_laar\ $pW,$nH} if narg($Q) W,H=$pW,$nH P=$Q else W-=2 P= fi else break fi } res.=$sep$maxP,{[$maxP]+[$maxW,$maxH]-1} sep=, fi rm[shape,cumul] } u $res # $1,$2: Rectangle width and height. # ${3--1}: Coordinates of candidate locations. If empty, full-search is done. # Return the list of upper-left rectangle coordinates that fit. # Images [shape] and [cumul] must be defined. _laar : skip "${3=}" fn="cumul(x0,y0,x1,y1) = ( _px0 = x0 - 1; _py0 = y0 - 1; i(#"$cumul,"x1,y1) + i(#"$cumul",_px0,_py0) - i(#"$cumul",x1,_py0) - i(#"$cumul",_px0,y1); );" res= if narg($3) # Test suggested locations (${3--1}) r. 2,{w/2},1,1,-1 s. x a[-2,-1] c f. ${fn}"x1 = i0 + $1 - 1; y1 = i1 + $2 - 1; i(#"$shape",i0,i1) && x1=1 #@cli : Return locations of maximal values in local patch-based neighborhood of given size for selected images. #@cli : Default value: 'patch_size=16'. #@cli : $ image.jpg norm +max_patch 16 max_patch : check "isint(${1=16},1)" e[^-1] "Return locations of maximal values in local patch neighborhood of size $1, in image$?." foreach { +dilate $1 == } #@cli min_patch : _patch_size>=1 #@cli : Return locations of minimal values in local patch-based neighborhood of given size for selected images. #@cli : Default value: 'patch_size=16'. #@cli : $ image.jpg norm +min_patch 16 min_patch : check "isint(${1=16},1)" e[^-1] "Return locations of minimal values in local patch neighborhood of size $1, in image$?." foreach { +erode $1 == } #@cli minimal_path : x0[%]>=0,y0[%]>=0,z0[%]>=0,x1[%]>=0,y1[%]>=0,z1[%]>=0,_is_high_connectivity={ 0 | 1 } #@cli : Compute minimal path between two points on selected potential maps. #@cli : Default value: 'is_high_connectivity=0'. #@cli : $ image.jpg +gradient_norm fill[-1] 1/(1+i) minimal_path[-1] 0,0,0,100%,100%,0 pointcloud[-1] 0 *[-1] 280 \ # to_rgb[-1] ri[-1] [-2],0 or minimal_path : check "$1>=0 && $2>=0 && $3>=0" skip ${7=0} e[^-1] "Compute minimal path between points ($1,$2,$3) and ($4,$5,$6) for potential map$?, with "\ ${arg0\ $7,low,high}" connectivity." foreach { nm={n} - {im} + {iM/100} 100%,100% = 1,${4-6} distance. 1,[0],{$7?4:3} k. channels. 1 i[0] 0 eval " x = round(ispercentage($1)?$1*(w - 1):$1); y = round(ispercentage($2)?$2*(h - 1):$2); z = round(ispercentage($3)?$3*(d - 1):$3); da_push(#0,[x,y,z]); do ( p = i(x,y,z); p&1?--x:p&2?++x; p&4?--y:p&8?++y; p&16?--z:p&32?++z; da_push(#0,[x,y,z]), _(while) p); resize(#0,1,da_size(#0),1,3,0)" rm. => $nm } #@cli mse : [reference] #@cli : Return the MSE (Mean-Squared Error) between selected images and specified reference image. #@cli : This command does not modify the images. It returns a value or a list of values in the status. mse : e[^-1] "Compute MSE between image$? and reference image $1." pass$1 1 +_mse[^-1] . rm. +mse : e[^-1] "Compute MSE between image$? and reference image $1." pass$1 1 +_mse[^-1] . rm. +_mse : check ${"is_image_arg $1"} rep,sep= pass$1 1 +foreach[^-1] { # Check image dimensions. pass. 1 if [w#0,h#0,d#0,s#0]!=[w#1,h#1,d#1,s#1] error[0--5] "Command 'mse': Image dimensions ("{0,[w,h,d,s]}") and ("{1,[w,h,d,s]}") do not match." fi # Compute MSE. - sqr res.=$sep{ia} sep=, rm } rm. u $res #@cli mse_matrix #@cli : Compute MSE (Mean-Squared Error) matrix between selected images. #@cli : $ image.jpg +noise 30 +noise[0] 35 +noise[0] 38 cut. 0,255 +mse_matrix mse_matrix : e[^-1] "Compute the "$!x$!" matrix of MSE values, for image$?." +mse_matrix k. +mse_matrix : e[^-1] "Compute the "$!x$!" matrix of MSE values, for images$?." N=$! $N,$N repeat $N { i=$> j:=$i+1 for $j<$N { +mse[$i] [$j] =. ${},$i,$j =. ${},$j,$i j+=1 } } #@cli patches2img : width>0,height>0,_overlap[%]>0,_overlap_std[%] #@cli : Recompose 2D images from their selected patch representations. #@cli : 'overlap' must be in range '[0,patch_size-1]' \ # where 'patch_size' is the width/height of the selected image. #@cli : 'overlap_std' is the standard deviation of the gaussian weights used for reconstructing overlapping patches. #@cli : If 'overlap_std' is set to '-1', uniform weights are used rather than gaussian. #@cli : Default value: 'overlap=0' and 'overlap_std=-1'. #@cli : See also: ''img2patches''. #@cli : $ image.jpg +img2patches 32,0,3 mirror[-1] xy patches2img[-1] {0,[w,h]} patches2img : check "isint($1,1) && isint($2,1) && ${3=0}>=0 && (${4=-1}==-1 || $4>=0)" e[^-1] "Recompose 2D images from patch-image$?, with size ($1,$2), overlap $3 and overlap_std $4." foreach { if w!=h error[0--4] "Command 'patches2img': Image "[$>]" is not a patch-image (size: "{[w,h,d,s]}")." fi nm={n} psiz:=w W,H:=${1,2} if ispercentage($3) overlap:=round(lerp(0,$psiz-1,$3)) else overlap:=int($3) fi if $overlap<0" || "$overlap>=$psiz error[0--4] "Command 'patches2img': Specified overlap $3 (value: "$overlap") is larger than "\ "patch size (value: "$psiz")." fi step:=$psiz-$overlap Nw,Nh:=round([$W,$H]/$step,1,1) # Draw patches. $W,$H,1,100% if $overlap # Overlapping patches $psiz,$psiz if $4==-1 f. 1 else gaussian. $4 n. 1e-8,1 fi store. weights # Create weights for overlapping patches $W,$H,1,1,1e-5 1,1,[0],1,"> begin(M = get('weights',$psiz*$psiz)); P = crop(#0,0,0,z,$psiz,$psiz,1); X = $step*(z%$Nw); Y = $step*int(z/$Nw); draw(#1,P,X,Y,0,0,$psiz,$psiz,1,s#0,-1,M); draw(#2,M,X,Y,0,0,$psiz,$psiz,1,1,-1)" /[-3,-2] else # Distinct patches 1,1,[0],1,"* P = crop(#0,0,0,z,w#0,h#0,1); X = $step*(z%$Nw); Y = $step*int(z/$Nw); draw(#1,P,X,Y,0,0,w#0,h#0,1,s#0)" fi k.. => $nm } #@cli patches : patch_width>0,patch_height>0,patch_depth>0,x0,y0,z0,_x1,_y1,_z1,...,_xN,_yN,_zN #@cli : Extract N+1 patches from selected images, centered at specified locations. #@cli : $ image.jpg +patches 64,64,1,153,124,0,184,240,0,217,126,0,275,38,0 patches : check "isint($1,1) && isint($2,1) && isint($3,1)" e[^-1] "Extract $1x$2x$3 patches from image$?, at locations (${4--1})." (${4--1}) r. 3,{w/3},1,1,-1 permute. yzcx N:=w H:=int(sqrt(w)) W:=round(w/$H,1,1) r. {$W*$H},1,1,3,0 r. $W,$H,1,3,-1 r. {w*$1},{h*$2},{d*$3} $1,$2,$3,1,x-{int($1/2)} +f. y-{int($2/2)} +f. z-{int($3/2)} a[-3--1] c ri. ..,0,2 +[-2,-1] repeat $!-1 { warp[$>] .,0,0,0 } rm. foreach { s y,$H s x,$W k[0-{$N-1}] } #@cli matchpatch : [patch_image],patch_width>=1,_patch_height>=1,_patch_depth>=1,_nb_iterations>=0,\ # _nb_randoms>=0,_patch_penalization,_output_score={ 0 | 1 },_[guide] : (+) #@cli : Estimate correspondence map between selected images and specified patch image, using #@cli : a patch-matching algorithm. #@cli : Each pixel of the returned correspondence map gives the location (p,q) of the closest patch in #@cli : the specified patch image. If 'output_score=1', the third channel also gives the corresponding #@cli : matching score for each patch as well. #@cli : If 'patch_penalization' is >=0, SSD is penalized with patch occurrences. #@cli : If 'patch_penalization' is <0, SSD is inf-penalized when distance between patches are less \ # than '-patch_penalization'. #@cli : Default values: 'patch_height=patch_width', 'patch_depth=1', 'nb_iterations=5', 'nb_randoms=5', \ # 'patch_penalization=0', 'output_score=0' and 'guide=(undefined)'. #@cli : $ image.jpg sample colorful +matchpatch[0] [1],3 +warp[-2] [-1],0 #@cli plot2value #@cli : Retrieve values from selected 2D graph plots. #@cli : $ 400,300,1,1,'y>300*abs(cos(x/10+2*u))' +plot2value +display_graph[-1] 400,300 plot2value : e[^-1] "Retrieve values from 2D graph plot$?." foreach { s c >= 50% foreach { (1,{w}) ri[1] [0],3 * histogram {w},1,{w} } a c } #@cli pointcloud : _type = { -X:-X-opacity | 0:binary | 1:cumulative | 2:label | 3:retrieve coordinates },\ # _width,_height>0,_depth>0 #@cli : Render a set of point coordinates, as a point cloud in a 1D/2D or 3D binary image #@cli : (or do the reverse, i.e. retrieve coordinates of non-zero points from a rendered point cloud). #@cli : Input point coordinates can be a NxMx1x1, Nx1x1xM or 1xNx1xM image, where 'N' is the number of points, #@cli : and M the point coordinates. #@cli : If 'M'>3, the 3-to-M components sets the (M-3)-dimensional color at each point. #@cli : Parameters 'width','height' and 'depth' are related to the size of the final image : #@cli : - If set to 0, the size is automatically set along the specified axis. #@cli : - If set to N>0, the size along the specified axis is N. #@cli : - If set to N<0, the size along the specified axis is at most N. #@cli : Points with coordinates that are negative or higher than specified ('width','height','depth') #@cli : are not plotted. #@cli : Default values: 'type=0' and 'max_width=max_height=max_depth=0'. #@cli : $ 3000,2 rand 0,400 +pointcloud 0 dilate[-1] 3 #@cli : $ 3000,2 rand 0,400 {w} {w},3 rand[-1] 0,255 append y +pointcloud 0 dilate[-1] 3 pointcloud : check "${1=0}<=3 && ${2=0}>=0 && ${3=0}>=0 && ${4=0}>=0" e[^-1] "Convert image$? to point clouds, in "${arg\ 2+($1>=0)*$1-($1<0),{-$1}-opacity,binary,cumulative,labeling}\ " mode, with dimensions $2x$3x$4." foreach { nm={n} if $1!=3 # Render point cloud image from set of point coordinates # Force input data to be of size Nx1x1xM. if "d>1 || (w>1 && h>1 && s>1)" error "Command '$0': Invalid input image "{w}x{h}x{d}x{s}". Should be NxMx1x1, Nx1x1xM or 1xNx1xM." fi if "w>1 && h>1 && s==1" r 100%,1,1,{h},-1 # NxMx1x1 -> Nx1x1xM elif "w==1 && h>1 && s>1" r {h},1,1,{s},-1 # 1xNx1xM -> Nx1x1xM fi # Retrieve coordinates and color info. if s<3 channels 0,2 fi if s<4 100%,1,1,1,1 a[-2,-1] c fi sh. 0 round. siz_x:=!$2?iM+1:$2 sh.. 1 round. siz_y:=!$3?iM+1:$3 sh... 2 round. siz_z:=!$4?iM+1:$4 rm[-3--1] # Draw point cloud. $siz_x,$siz_y,$siz_z,{$1!=2?s-3:1} if $1<0 # -X-opacity f.. ">V = I; P = V[0,3]; C = V[3,size(V) - 3]; I(#-1,P) = (1+$1)*I(#-1,P) - $1*C; V" elif !$1 # Binary f.. ">V = I; P = V[0,3]; C = V[3,size(V) - 3]; I(#-1,P) = C; V" elif $1==1 # Cumulative f.. ">V = I; P = V[0,3]; C = V[3,size(V) - 3]; I(#-1,P) += C; V" else # Label f.. ">begin(l = 0); V = I; P = V[0,3]; C = V[3,size(V) - 3]; I(#-1,P) = ++l; V" fi else # Retrieve set of point coordinates from rendered point cloud image 16,1,1,{s+3} f.. "> begin(N = 0; zero = vectors(0)); I!=zero?I[#-1,N++] = [ x,y,z,I ]; N>=w(#-1)?resize(#-1,1.5*w(#-1),1,1,s#-1,0); end(resize(#-1,N,1,1,s#-1,0)); I" fi k. => $nm } #@cli psnr : [reference],_max_value>0 #@cli : Return PSNR (Peak Signal-to-Noise Ratio) between selected images and specified reference image. #@cli : This command does not modify the images. It returns a value or a list of values in the status. #@cli : Default value: 'max_value=255'. psnr : check ${"is_image_arg $1"}" && ${2=255}>0" e[^-1] "Compute PSNR between image$? and reference image $1, with max value $2." pass$1 1 +_psnr[^-1] .,$2 rm. +psnr : check ${"is_image_arg $1"}" && ${2=255}>0" e[^-1] "Compute PSNR between image$? and reference image $1, with max value $2." pass$1 1 +_psnr[^-1] .,$2 rm. +_psnr : check ${"is_image_arg $1"}" && ${2=255}>0" rep,sep= pass$1 1 +foreach[^-1] { # Check image dimensions. pass. 1 if [w#0,h#0,d#0,s#0]!=[w#1,h#1,d#1,s#1] error[0--5] "Command 'psnr': Image dimensions ("{0,[w,h,d,s]}") and ("{1,[w,h,d,s]}") do not match." fi # Compute PSNR. - sqr res.=$sep{"const m2 = ($2)^2; 10*log10(m2/ia)"} sep=, rm } rm. u $res #@cli psnr_matrix : _max_value>0 #@cli : Compute PSNR (Peak Signal-to-Noise Ratio) matrix between selected images. #@cli : Default value: 'max_value=255'. #@cli : $ image.jpg +noise 30 +noise[0] 35 +noise[0] 38 cut. 0,255 +psnr_matrix psnr_matrix : check "${1=255}>0" e[^-1] "Compute the "$!x$!" matrix of PSNR values, for image$?, with max value $1." +psnr_matrix $1 k. +psnr_matrix : check "${1=255}>0" e[^-1] "Compute the "$!x$!" matrix of PSNR values, for images$?, with max value $1." N=$! $N,$N repeat $N { i=$> j:=$i+1 for $j<$N { +psnr[$i] [$j],$1 =. ${},$i,$j =. ${},$j,$i j+=1 } =. inf,$i,$i } #@cli segment_watershed : _threshold>=0 #@cli : Apply watershed segmentation on selected images. #@cli : Default values: 'threshold=2'. #@cli : $ image.jpg segment_watershed 2 segment_watershed : check "${1=2}>=0" e[^-1] "Apply watershed segmentation on image$?, with edge threshold $1." foreach { min:=im + {1+$min} +gradient_norm +f. "i<$1 && i=0,0<=_weight_std_max_avg<=1,_dilation,_smoothness>=0 #@cli : Estimate bumpmap from binary shape in selected images. #@cli : Default value: 'resolution=256', 'weight_std_max=0.75', 'dilation=0' and 'smoothness=100'. shape2bump : check "isint(${1=256},0) && ${2=0.75}>=0 && $2<=1 && isnum(${3=0}) && ${4=100}>=0" e[^-1] "Estimate bumpmap from binary shape in image$?, using "\ ${"if $1 u \"resolution $1\" else u \"full resolution\" fi"}", avg/max weight $2, dilation $3 and smoothness $4." foreach { slices 0 channels 100% > 0 siz:=w,h # Generate skeleton (fast method). distance 0 + $3 +f. "const boundary = 1; (i>j(-1)&&i>j(1)) || (i>j(0,-1)&&i>j(0,1)) || (i>j(-1,-1)&&i>j(1,1)) || (i>j(-1,1)&&i>j(1,-1))" # Downsize for faster computation. is_resized=0 if $1" && "max(w,h)>$1 rs $1,$1,2 gt. 0 thinning. 1 *.. {$1/max([$siz])} is_resized=1 fi rmax={0,iM} # Generate z-map (max method). [0],[0] if $2>0 eval[1] ":i?( r = i(#0,x,y); ir = floor(r); r2 = r^2; for (q = -ir, q<=ir, ++q, Y = y + q; for (p = -ir, p<=ir, ++p, X = x + p; dist = norm(p,q); dist=0 && $1<=1)" if isnum($1) bc=$1 else bc=1 noarg fi e[^-1] "Compute skeleton of binary image$? with "${"arg0 "$bc",dirichlet,neumann"}" boundary conditions." foreach { s c foreach { # [0] = 2D binary shape 1,16,1,2 # [1] = List of boundary pixels 1,16,1,2 # [2] = List of boundary pixels (next iteration) # [3] = Pixels on median axis. +distance[0] 0 f. "const boundary = 1; (i>j(-1)&&i>j(1)) || (i>j(0,-1)&&i>j(0,1)) || (i>j(-1,-1)&&i>j(1,1)) || (i>j(-1,1)&&i>j(1,-1))" # Extract boundary pixels. f[0] ">const boundary = "$bc"; i && (!j(-1) || !j(1) || !j(0,-1) || !j(0,1))?da_push(#1,[x,y]); i;" # Run thinning algorithm. eval " const boundary = "$bc"; # Lookup tables for detecting the simple points. is_removable = [ 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1,1,1,1,0,1,1,1,0, 1,1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1,1,1,1,0,1,1, 1,0,1,1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,0,0,0,0, 0,0,0,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,1,1,0, 1,1,1,0,1,1,1,0,0,1,1,1,1,1,1,0,1,1,1,1,0 ]; dotm = [ 128,64,32,16,0,8,4,2,1 ]; is_removed = 1; # Start thinning iterations. while (is_removed, is_removed = 0; N = da_size(#1); repeat (N,n, xc = i(#1,0,n,0,0); yc = i(#1,0,n,0,1); icc = i(#0,xc,yc); (icc && !i(#3,xc,yc))?( xp = xc - 1; yp = yc - 1; xn = xc + 1; yn = yc + 1; V = crop(#0,xp,yp,3,3); val = dot(dotm,V>0); is_removable[val]?( i(#0,xc,yc) = 0; is_removed = 1; V[3]==1?(da_push(#2,[xp,yc]); i(#0,xp,yc) = 2); V[5]==1?(da_push(#2,[xn,yc]); i(#0,xn,yc) = 2); V[1]==1?(da_push(#2,[xc,yp]); i(#0,xc,yp) = 2); V[7]==1?(da_push(#2,[xc,yn]); i(#0,xc,yn) = 2); ):(da_push(#2,[xc,yc]); i(#0,xc,yc) = 2); ) ); resize(#1,1,h(#2),1,2,0); copy(i(#1),i(#2),2*h(#2)); i[#2,h(#2)-1] = 0; );" k[0] > 0 thinning } a c } #@cli slic : size>0,_regularity>=0,_nb_iterations>0 #@cli : Segment selected 2D images with superpixels, using the SLIC algorithm (Simple Linear Iterative Clustering). #@cli : Scalar images of increasingly labeled pixels are returned. #@cli : Reference paper: Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P., & Susstrunk, S. (2010). \ # SLIC Superpixels (No. EPFL-REPORT-149300). #@cli : Default values: 'size=16', 'regularity=10' and 'nb_iterations=10'. #@cli : $ image.jpg +srgb2lab slic[-1] 16 +blend shapeaverage f[-2] "j(1,0)==i && j(0,1)==i" *[-1] [-2] slic : check "${1=16}>0 && ${2=10}>=0 && ${3=10}>0" e[^-1] "Segment image$? using SLIC superpixels, with size $1, regularity $2 and $3 iterations." S,m,nb_iter=${1-3} foreach { slices 50% # Initialize superpixel centers Ck. {[max(1,round(w/$S)),max(1,round(h/$S))]},1,2,"round(([x,y]+=0.5)*="$S")" # Perturb the Ck towards low gradient positions. if $S>=3 +b[0] 0.7 g. xy,1 a[-2,-1] c norm. # Gradient norm with forward differences f.. " const n = round("$S"/3); const n1 = int(n/2); pos = argmin(crop(#-1,i0 - n1,i1 - n1,n,n,1)); dxy = [pos%n,int(pos/n)] - n1; [ cut(i0 + dxy[0],0,w#0-1), cut(i1 + dxy[1],0,h#0-1) ]" rm. fi r. {wh},1,1,2,-1 100%,1,1,{0,s},"I(#0,I#1)" a[-2,-1] c # Add superpixels colors [0],[0],1,2 eval.. "I(#-1,i0,i1) = [ x + 1,1 ]; I" s. c distance. 1 *. -1 watershed.. . rm. channels. 0,1 # Start iteration loop. repeat $nb_iter { # Assign best superpixel to each pixel. sh[2] 1 f. inf rm. eval[1] " const m = "$m"; const S = "$S"; k = x; xk = i0; yk = i1; Ik = (I)[2,s#0]; x0 = max(xk - S,0); x1 = min(xk + S,w#0 - 1); y0 = max(yk - S,0); y1 = min(yk + S,h#0 - 1); for (y = y0, y<=y1, ++y, for (x = x0, x<=x1, ++x, delta_c = norm(I(#0,x,y) - Ik); delta_s = norm([x - xk, y - yk]); delta = delta_c + m/S*delta_s; if (delta const boundary = 1; if (i[#-1,i]>=0, N = [ j(-1,0),j(0,-1),j(1,0),j(0,1) ]; repeat (size(N),k, if (N[k]!=i, i[#-1,i] = i[#-1,i]==i || i[#-1,i]==N[k]?N[k]:-1 ); ); ); i" f. "i>=0" +map[0] . rm.. or[-2,-1] +[0] 1 100%,100% j[0] .,0,0,0,0,1,.. rm. distance. 0 *. -1 watershed.. . rm. label. 0,0 } #@cli ssd_patch : [patch],_use_fourier={ 0 | 1 },_boundary_conditions #@cli : Compute fields of SSD between selected images and specified patch. #@cli : Argument 'boundary_conditions' is valid only when 'use_fourier=0'. #@cli : 'boundary_conditions' can be { 0:dirichlet | 1:neumann | 2:periodic | 3:mirror }. #@cli : Default value: 'use_fourier=0' and 'boundary_conditions=0'. #@cli : $ image.jpg +crop 20%,20%,35%,35% +ssd_patch[0] [1],0,0 ssd_patch : check ${is_image_arg\ $1} skip ${2=0},${3=0} e[^-1] "Compute field of SSD between image$? and patch $1 using "${arg0\ !$2,fourier,spatial}" mode." repeat $! { pass$1 0 l[$>,-1] { r 100%,100%,100%,${-max_s} s c repeat $!/2 { l[$>,{-1-$<}] { +sqr[1] val:=is rm. # Sum J(p,q)^2 +sqr[0] +f[1] 1 if $2 convolve_fft.. . rm. # Sum I(x+p,y+q)^2 mirror[1] xyz convolve_fft[0] [1] rm[1] # Sum I(x+p,y+q).J(p,q) else correlate.. .,$3 rm. # Sum I(x+p,y+q)^2 correlate[0] [1],$3 rm[1] # Sum I(x+p,y+q).J(p,q) fi *[0] -2 +[0,1] + $val } } + } } #@cli ssim : [reference],_patch_size>0,_max_value>0 #@cli : Compute the Structural Similarity Index Measure (SSIM) between selected images and specified reference image. #@cli : This command does not modify the images, it just returns a value or a list of values in the status. #@cli : When 'downsampling_factor' is specified with a ending '%', its value is equal to \ # '1+(patch_size-1)*spatial_factor%'. #@cli : \nSSIM is a measure introduced int the following paper: #@cli : *Wang, Zhou, et al.*, "Image quality assessment: from error visibility to structural similarity.", #@cli : in IEEE transactions on image processing 13.4 (2004): 600-612. #@cli : \nThe implementation of this command is a direct translation of the reference code (in Matlab), found at : #@cli : https://ece.uwaterloo.ca/~z70wang/research/ssim/ #@cli : Default values: 'patch_size=11', and 'max_value=255'. +ssim : check ${"is_image_arg $1"}" && isint(${2=11},1) && ${3=255}>0" e[^-1] "Compute the SSIM between image$? and reference image $1, with patch size $2 and max_value $3." C1,C2:=([0.01,0.03]*$3)^2 $2,$2 gaussian. 1.5 /. {is} store. gaussian_weights res,sep= pass$1 +foreach[^-1] { # Check image dimensions. pass. 0 if [w#0,h#0,d#0,s#0]!=[w#1,h#1,d#1,s#1] error[0--4] "Command 'ssim': Image dimensions ("{0,[w,h,d,s]}") and ("{1,[w,h,d,s]}") do not match." fi # Automatic downsampling. f:=max(1,round(min(w,h)/256)); if $f>1 r {round([w,h,d]/$f,1,1)},100%,2 fi => A,B # Compute SSIM map. $gaussian_weights => W p0,p1:="p0 = round(w/2,1,-1); [ p0,w-1-p0 ]" +correlate[A,B] [W],0,0,1,$p0,$p0,0,$p0,$p0,0,{A,[w,h]-1-$p1},0 +mul[-2,-1] sqr[-3,-2] => muA2,muB2,muAB +mul[A,B] sqr[A,B] =>[A,B,-1] A2,B2,AB correlate[A2,B2,AB] [W],0,0,1,$p0,$p0,0,$p0,$p0,0,{A2,[w,h]-1-$p1},0 -[A2] [muA2] -[B2] [muB2] -[AB] [muAB] =>[A2,B2,AB] sigA2,sigB2,sigAB *[muAB] 2 +[muAB] $C1 *[sigAB] 2 +[sigAB] $C2 *[muAB,sigAB] =>[muAB] num +[muA2,muB2] +[muA2] $C1 +[sigA2,sigB2] +[sigA2] $C2 *[sigA2,muA2] =>[sigA2] den /[num] [den] # Return average value of SSIM map. res.=$sep{ia} sep=, rm } rm. u $res #@cli ssim_matrix : _patch_size>0,_max_value>0 #@cli : Compute SSIM (Structural Similarity Index Measure) matrix between selected images. #@cli : Default values: 'patch_size=11', and 'max_value=255'. #@cli : $ image.jpg +noise 30 +noise[0] 35 +noise[0] 38 cut. 0,255 +ssim_matrix ssim_matrix : check "isint(${1=11},1) && ${2=255}>0" e[^-1] "Compute the "$!x$!" matrix of SSIM values, for image$?, with patch size $1 and max value $2." +ssim_matrix ${1,2} k. +ssim_matrix : check "isint(${1=11},1) && ${2=255}>0" e[^-1] "Compute the "$!x$!" matrix of SSIM values, for image$?, with patch size $1 and max value $2." N=$! $N,$N repeat $N { i=$> j:=$i+1 for $j<$N { +ssim[$i] [$j],${1,2} =. ${},$i,$j =. ${},$j,$i j+=1 } =. 1,$i,$i } #@cli thinning : _boundary_conditions={ 0:dirichlet | 1:neumann } #@cli : Compute skeleton of binary shapes using morphological thinning #@cli : (beware, this is a quite slow iterative process) #@cli : Default value: 'boundary_conditions=1'. #@cli : $ shape_cupid 320 +thinning thinning : check "!isnum(${1=1}) || ($1>=0 && $1<=1)" if isnum($1) bc=$1 else bc=1 noarg fi e[^-1] "Apply morphological thinning to binary image$? with "\ ${"arg0 "$bc",dirichlet,neumann"}" boundary conditions." foreach { s c foreach { # [0] = 2D binary shape (current iteration) 1,16,1,2 # [1] = List of boundary pixels (current iteration) 1,16,1,2 # [2] = List of pixels to remove (current iteration) 1,16,1,2 # [3] = List of boundary pixels (for next iteration) # Extract boundary pixels. f[0] ">const boundary = "$bc"; i && (!j(-1) || !j(1) || !j(0,-1) || !j(0,1))?da_push(#1,[x,y]); i;" # Run thinning algorithm. eval " const boundary = "$bc"; # Lookup tables for detecting the 8 3x3 hit&miss. hm_and = [ 231,189,231,189,122,91,94,218 ]; hm_eq = [ 7,148,224,41,18,80,72,10 ]; # Start thinning iterations. dotm = [ 128,64,32,16,0,8,4,2,1 ]; is_removed = vector8(0); ind = 1; nind = 3; it = 0; do ( N = da_size(#ind); it8 = it%8; is_removed[it8] = 0; # Find removable contour points. repeat (N,n, xc = i(#ind,0,n,0,0); yc = i(#ind,0,n,0,1); icc = i(#0,xc,yc); icc?( xp = xc - 1; yp = yc - 1; xn = xc + 1; yn = yc + 1; V = crop(#0,xp,yp,3,3); val = dot(dotm,V>0); (val & hm_and[it8])==hm_eq[it8]?( da_push(#2,[xc,yc]); is_removed[it8] = 1; V[3]==1?(da_push(#nind,[xp,yc]); i(#0,xp,yc) = 2); V[5]==1?(da_push(#nind,[xn,yc]); i(#0,xn,yc) = 2); V[1]==1?(da_push(#nind,[xc,yp]); i(#0,xc,yp) = 2); V[7]==1?(da_push(#nind,[xc,yn]); i(#0,xc,yn) = 2); ):(da_push(#nind,[xc,yc]); i(#0,xc,yc) = 2); ); ); # Re-assign value '1' to contour points. N = da_size(#nind); repeat (N,n, i(#0,i[#nind,n],i[#nind,n + h(#nind)]) = 1); _tmp = ind; ind = nind; nind = _tmp; i[#nind,h(#nind)-1] = 0; # Remove matching contour points. N = da_size(#2); repeat (N,n, i(#0,I[#2,n]) = 0); i[#2,h(#2)-1] = 0; ++it; _(while), max(is_removed) );" k[0] } a c } #@cli tones : N>0 #@cli : Get N tones masks from selected images. #@cli : $ image.jpg +tones 3 tones : check $1>0 e[^-1] "Get $1 tones masks from image$?." norm n 0,{$1-1} round 1 foreach { repeat $1-1 { +==[0] {1+$>} } ==[0] 0 } #@cli topographic_map : _nb_levels>0,_smoothness #@cli : Render selected images as topographic maps. #@cli : Default values: 'nb_levels=16' and 'smoothness=2'. #@cli : $ image.jpg topographic_map 10 topographic_map : check "isint(${1=16},1)" skip ${2=2} e[^-1] "Render topographic maps from image$?, with $1 levels and smoothness $2." foreach { +b $2 isophotes. $1 compose_channels. + ==. 0 blend shapeaverage0 } #@cli tsp : _precision>=0 #@cli : Try to solve the 'travelling salesman' problem, using a combination of greedy search and 2-opt algorithms. #@cli : Selected images must have dimensions Nx1x1xC to represent N cities each with C-dimensional coordinates. #@cli : This command re-order the selected data along the x-axis so that the point sequence becomes a shortest path. #@cli : Default values: 'precision=256'. #@cli : $ 256,1,1,2 rand 0,512 tsp , 512,512,1,3 repeat w#0 circle[-1] {0,I[$>]},2,1,255,255,255 \ # line[-1] {0,boundary=2;[I[$>],I[$>+1]]},1,255,128,0 done keep[-1] tsp : check "${1=256}>=0" e[^-1] "Try to solve the 'travelling salesman' problem for pointcloud$?, with precision $1." foreach { nm={n} if h>1" || "d>1 error[0--4] "Selected image '"{n}"' has invalid dimensions ("{[w,h,d,s]}")." fi # Find initial estimate, using greedy nearest neighbor algorithm. eval " is_used = vectorw(0); next = vectorw(-1); n_initial = n_current = v(w - 1); do ( is_used[n_current] = 1; P_current = I[n_current]; n_next = -1; dmin = inf; repeat (w,n, if (!is_used[n], d = norm(I[n] - P_current); if (dbegin(ind = 0); val = I[#0,ind]; ind = val[s]; val" rm.. # Improving initial estimate iteratively by 2-opt algorithm. eval " is_improved = 1; while (is_improved, is_improved = 0; nb_try = $1*w; repeat (nb_try,try, r = round(max(8,0.5*w*(try/nb_try)^0.25)); i = v(w - 1); ni = (i+1)%w; pi = (i-1)%w; do (j = (i + v(-r,r))%w; _(while), j==i || j==ni || j==pi); nj = (j+1)%w; P_i = I[i]; P_ni = I[ni]; P_j = I[j]; P_nj = I[nj]; dist_ini = norm(P_ni - P_i); dist_jnj = norm(P_nj - P_j); dist_ij = norm(P_j - P_i); dist_ninj = norm(P_nj - P_ni); if (dist_ij + dist_ninj $nm } #@cli variance_patch : _patch_size>=1 #@cli : Compute variance of each images patch centered at (x,y), in selected images. #@cli : Default value: 'patch_size=16' #@cli : $ image.jpg +variance_patch variance_patch : check "isint(${1=16},1)" e[^-1] "Compute variance of image patches in image$?, with patch size $1." $1,$1,1,1,1 normalize_sum. repeat $!-1 { l[$>,-1] { +sqr[0] convolve[0,2] [1] sqr[0] rv[0,2] -[0,2] max[0] 0 } } rm. #--------------------------------- # #@cli :: Image Drawing # #--------------------------------- #@cli arrow : x0[%],y0[%],x1[%],y1[%],_thickness[%]>=0,_head_length[%]>=0,_head_thickness[%]>=0,_opacity,\ # _pattern,_color1,... #@cli : Draw specified arrow on selected images. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. If a pattern is specified, the arrow is #@cli : drawn outlined instead of filled. #@cli : Default values: 'thickness=1%', 'head_length=10%', 'head_thickness=3%', 'opacity=1', 'pattern=(undefined)' \ # and 'color1=0'. #@cli : $ 400,400,1,3 repeat 100 arrow 50%,50%,{u(100)}%,{u(100)}%,3,20,10,0.3,${-rgb} done arrow : check "${5=1%}>=0 && ${6=10%}>=0 && ${7=3%}" skip ${8=1} e[^-1] "Draw arrow in image$?, from ($1,$2) to ($3,$4), with thickness $5, head length $6, head_thickness $7 and opacity $8." foreach { polygon. 7,{" x0 = ispercentage($1)?(w-1)*$1:$1; y0 = ispercentage($2)?(h-1)*$2:$2; x1 = ispercentage($3)?(w-1)*$3:$3; y1 = ispercentage($4)?(h-1)*$4:$4; p0 = [x0,y0]; dp = [x1,y1]-=p0; l = norm(dp); # arrow length t = ispercentage($5)?l*$5:$5; # thickness hl = ispercentage($6)?l*$6:$6; # head length ht = ispercentage($7)?l*$7:$7; # head thickness lmhl = l - hl; X = mul([0,-t,lmhl,-t,lmhl,-ht,l,0,lmhl,ht,lmhl,t,0,t],rot(-atan2(dp[1],dp[0])),2); X+=[p0,p0,p0,p0,p0,p0,p0]"},${8--1} } #@cli axes : x0,x1,y0,y1,_font_height>=0,_opacity,_pattern,_color1,... #@cli : Draw xy-axes on selected images. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. #@cli : To draw only one x-axis at row Y, set both 'y0' and 'y1' to Y. #@cli : To draw only one y-axis at column X, set both 'x0' and 'x1' to X. #@cli : Default values: 'font_height=14', 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ 400,400,1,3,255 axes -1,1,1,-1 axes : check "isint(${5=14},0) && ${6=1}>=0" skip ${7=0},${8=0} if ${"is_pattern \"$7\""} e[0--3] "Draw xy-axes on image$?, with x-range ($1,$2), y-range ($3,$4), font height $5, opacity $6, pattern $7 and color (${8--1})." pattern=$7 color=${8--1} else e[0--3] "Draw xy-axes on image$?, with x-range ($1,$2), y-range ($3,$4), font height $5, opacity $6 and color (${7--1})." pattern=0xFFFFFFFF color=${7--1} fi if !$5" || "!$6 return fi mx:=min($1,$2) Mx:=max($1,$2) my:=min($3,$4) My:=max($3,$4) # Start drawing axes on selected images. foreach { w1,h1={0,[w,h]-1} # Determine number of axes tick marks. if $1!=$2 u=${"_axes[] $1,$2,{0.3*w/$5}"} offx:=arg(1,$u) deltax:=arg(2,$u) fi if $3!=$4 u=${"_axes[] $3,$4,{0.3*h/$5}"} offy:=arg(1,$u) deltay:=arg(2,$u) fi # Draw x-axis. is_0x=0 if $3==$4 y0=$3 else y0:=v=-($my)*$h1/($My-$my);$4>=$3?v:$h1-v fi sty:=$y0>$h1-$5?-1:1 if $1!=$2" && "$y0>=0" && "$y0<=$h1 line 0,$y0,$w1,$y0,$6,$pattern,$color 4,4,1,1,x<=y +mirror. y rows. 1,3 a[-2,-1] y .,.,1,[0] fc. $color if $2>=$1 j[0] .,{$w1-3},{$y0-3},0,0,$6,.. else mirror.. x j[0] .,0,{$y0-3},0,0,$6,.. fi rm[-2,-1] i=0 do val:=_$offx+$i*$deltax i+=1 if $val>=$mx" && "$val<=$Mx x:=v=($val-$mx)*$w1/($Mx-$mx);$2>=$1?v:$w1-v line $x,{$y0-1},$x,{$y0+1},$6,$pattern,$color if $val 0 t. $val,0,0,$5,1,1 100%,100%,1,[0] fc. $color j[0] .,{max(0,min($w1-w,$x-w/2))},{$sty>0?$y0+3:$y0-h-3},0,0,$6,.. rm[-2,-1] else is_0x=1 fi fi while $val<$Mx fi # Draw y-axis. is_0y=0 if $1==$2 x0=$1 else x0:=v=-($mx)*$w1/($Mx-$mx);$2>=$1?v:$w1-v fi stx:=$x0>$w1-$5?-1:1 if $3!=$4" && "$x0>=0" && "$x0<=$w1 line $x0,0,$x0,$h1,$6,$pattern,$color 4,4,1,1,x>=y +mirror. x z. 1,3 a[-2,-1] x .,.,1,[0] fc. $color if $4>=$3 j[0] .,{$x0-3},{$h1-3},0,0,$6,.. else mirror.. y j[0] .,{$x0-3},0,0,0,$6,.. fi rm[-2,-1] i=0 do val:=_$offy+$i*$deltay i+=1 if $val>=$my" && "$val<=$My y:=v=($val-$my)*$h1/($My-$my);$4>=$3?v:$h1-v line {$x0-1},$y,{$x0+1},$y,$6,$pattern,$color if $val 0 t. $val,0,0,$5,1,1 100%,100%,1,[0] fc. $color j[0] .,{$stx>0?$x0+6:$x0-w-6},{max(0,min($h1-h,$y-h/2))},0,0,$6,.. rm[-2,-1] else is_0y=1 fi fi while $val<$My fi # Draw origin, if necessary. if $is_0x" || "$is_0y 0 t. 0,0,0,$5,1,1 100%,100%,1,[0] fc. $color j[0] .,{$stx>0?$x0+6:$x0-w-6},{$sty>0?$y0+3:$y0-h-3},0,0,$6,.. rm[-2,-1] fi } # Return optimal (offset0,scale) to display input (min,max) values. # $1 = min value, $2 = max value, $3 = max number of tags(>=1). _axes : n:=max(1,round($3)) d:=abs($2-$1)/($n-1) s:=10^round(log10($d)) m:=round(min($1,$2),$s,-1) M:=round(max($1,$2),$s,1) do N:=1+round(($M-$m)/$s,1,1) s:=2*$s while $N>$n u $m,{$s/2} #@cli ball : _size>0, _R,_G,_B,0<=_specular_light<=8,0<=_specular_size<=8,_shadow>=0 #@cli : Input a 2D RGBA colored ball sprite. #@cli : Default values: 'size=64', 'R=255', 'G=R', 'B=R', 'specular_light=0.8', 'specular_size=1' and 'shading=1.5'. #@cli : $ repeat 9 { ball {1.5^($>+2)},${-rgb} } append x +ball : check "${1=64}>0 && ${5=0.8}>=0 && $5<=8 && ${6=1}>=0 && $6<=8 && ${7=1.5}>=0" skip ${2=255},${3=$2},${4=$3} e[^-1] "Input $1x$1 ball with color (${2-4}), specular light $5, specular size $6 and shadow factor $7." l[] { {2*$1},{2*$1} = 1,65%,30% distance 1 * -1 +n 0,1 ^[1] $7 *[1] 1.4 +*[1] $3 +*[1] $4 *[1] $2 a[^0] c >=[0] {100-10*$6}% b[0] {3*$6}% n[0] 0,{$5*255} rv + c 0,255 100%,100% circle[1] 50%,50%,34%,1,1 *[0] [1] *. 255 a c r $1,$1,1,4,2 } # chromeball64x64 : Output a 64x64 GreyA chrome ball sprite. # ${1-3} = R,G,B chromeball64x64 : base642img[] \ "MiB1bnNpZ25lZF9jaGFyIGxpdHRsZV9lbmRpYW4KNjQgNjQgMSAyICMzMzY0CnicxZl5VJNnvsfT0mM7ntPp9N7paefe6ZzOmWlnemfGmTpzer0dO1W"\ "RnQCBQPZ9TwhhDQkYwyKQAAlbEjZBthgRRBahFamAggp1KS7QIgKyk7DIpoCQd54XtComyPLH/f7xvn/k/Xyf3/N7n+X35EUgLOqN9z/DHcqtaW69cq"\ "EmK8z7s3csP2ZZO/7ILGzpHhwaHBzoHxgA9/vt55M8fvPaxuh37NOu9AyNjBpNY+PjE+OmkYGuOzfb73XURu1589X0zx2PXu8ZMo0Dbnh41DQxMT5mH"\ "O7vam/v7O5sUH35Cgebvyiaf+g3jRuHB/oGjeMPpqamHkyMDvb3g3iG+7tvnjm8a71e/Af+5PXOYdPo4MCwaWr24cO5ubnZ2ekHY6PDgwNDRtNQT9u3"\ "mRirqXztt5Laa10jo0N9fcaZ+UWghflHwGBudnpywjTUNzjcd/faN3kBH1vGX98VWdd6b2B4oKt3fH7JbF5+vDC/ADQPm0xPTRr779/vuNZUdUzyT0t"\ "9eON/5Webb3f8eOdqW9/Eo2XI/Gh6auYxbAMMHsIO48OD/TcvnCvNltjbvIz/I/RMXVPTlaa6xrauwbG55eU5wAObpZmJialpoJnpSZCZ63WV+rRgxz"\ "fW9n0Xr/RsZVX1mdOV9a237o3OLT+amJw3Q9DS3IRxdNQ4NmYcn4YN7l8+U5ybIDq4pgsf4fPrjhcWFhScqL54vWt0Zn5++sHcotkMLc1OjptGR4yjw"\ "6MPZqbBu+yoLyvURfjueQF/z1V98YRGo8vWV128PTTzeGlpeWkZtA5B5sVHczNTD4CmZ+Zmp8b6Oy7XFB9NDOV+8hz+1j7Jt9+kKVIyC0433DItmiHz"\ "CvtEZvPSit/j+bnpsYGbF6tL8tIiRfRn4+C1PzGKWwuVCRm5xWevDS5CVmRemp0Y6m1rqCrOT1eKBS4/peA/XZKu1mkVKVn5ZQ0/TJmt8dDSeO8PNy6"\ "fK9fnZqllIu4fnuA2u4VnO0rU8WmZheUN9x5ZxaHFwatN9bVnSgtzstKOBAkIO1b595007W25CQkpmUUwb7V981T3pXM1VWXFBUcztcpQIWfX6rj9G6"\ "nqXotGmZCUDvjO2cWFhWXL/OTdK9/WlJ8yFORkalXhIt5qAL/4l/hSz8UUhVKlKyiv7xibmRibsxzDo77vL56tKDXk52TqUuQiAetTmP/USXm1pzFZo"\ "VADvuHOkGlocGzJIr881tlaV1kC+KwMXZS/gI0Er+DNL7zUV+9d1CgUqvTC8gsdIyO9vabHljMwP3S7sbr0eN7R7KN5sf4CLu2XIHv7MXFX7rZmg/jT"\ "CysutBuNvT2j81Yy+OBea22ZIe9YblFpPOAZuxGIXQd9ws/fbTMoFIm6/NPfXu8Z6e8bsZIAEMCtxqoSvf54WbUK5pEIm332nsJTP7RXqRSJaTkl3zS"\ "13e3pG5mxnABoebKrpe5MWXllXXUc4JnEn73tZo+kpH9/u+FofGJSpr6ytvlGR49VHloytrfW19bVXy6RifgcJuXXv8E6OKIP1d9oOa1JUGtyT5yuvX"\ "yza8gqDz0e6/zuUvOVCxkhfjwOk/znv1Id7d0Z+ZcvNeiTEsEELK6qb7kzMG2Vh5ZMP95ovXIqOsCXx2aQ93zFdbZz8ZKX19bX5CSqtbmG01+f/+6u0"\ "eokBKNo4PZ3X6cG+/G5LDrZ1t3fzdbBjZqgLz97KkOdmlVUUl7deKN7fNHqNFgauX0+O9zfl8dh0chIfCjazs4ZxY07WlxWlKXRZBcYSmsab9wzPlxc"\ "smzxeKhFHx0oFHDZTBoJxZARHA46ItG86IxCg/5Yhi47/3hpdX1L293eQdP0/Mse5ocdlUlhAQKQPTqV6MmNYSDt7J3d0dzIjAKDoSA3J6/wRGlV9df"\ "nGi5dvdU1MDa9ZjbO99bnxAGey2bQyHiUUCVEO9k5unp4sw9r8/XHiwoLi/T6E8XFJWUV1bUXWm/c6TbNLS4/DWN5prvpZDrMc1gMKgmHZGskZHdHey"\ "ekhxctVJVTdBzIcBy20RtOlFR8Xdf43Z2eQePk9KPHy0vzk73f158u0MaGBwhA78lErAMxK5qHQdo7OiM90ARfeVoewE+A5oEMer3hZFnVuebrt37s6"\ "ukb6Ou80VRbebIgKzkmPIDPolNJeMy/XHJVEgba2cEJGHhhqP6RqcdAwydLTpVXlJWcMBiKSyrONjRdam48X1dzSl+QBy8d8VHSAB6LRiHhvP/+xVHN"\ "kQCKm5PjioEPjuYXrtTmFukNJWWnSk8CA8PJU2WlhsLcrAxtilqVmJAQHxd9ONSfx6BRyVj0xx+mZKkihBgXJydnFzcPT28snswSSaMTkjOOFR4HPSg"\ "qKsjNTE2Ii4mOlB+WyQ7LwfWQNNiPS6fRKViv99+JyNaqZCwvV8C7url7on2weBKFwRUGS+VH4hRKpTI2SiYVBwXCCgoODgkJDg4KFAk4DCabhkHtQH"\ "DS0zPigyhebq6urkg3d5SXNwZHIJKpNPAAl8fj8/krFz5fIPAV+MISCAQ8LpvN51N99iMQX4J9QxslJPt4uiNh3tMLhIDDE0kUKpVGpzOYLBaLvSIOE"\ "HxnsZhMFs8vwI/s80cE4r9CFGk6VbiAjPP2RHl4oFBeXmhvDHAgEElkMmxCg22eiQ7E5PmFSPwIaHgPJUmOJCVGifl08Dp90F5eIABvHwwWi3tqQQEm"\ "K6I9EZ3FFYllMi7uILz+/40TEBkXJfXns8GIIOJxGB9v72cGRBKRBExgARtgRaMzuQJ/SUS0mIb9aGXzxzOE4TIJWE/4YErCrxW4AAEcTyAQSLBWcAq"\ "VQgX54Pj6B4VHxcoFBORqFfQ5nswPDPL35cLpFfBXXOg0Gtzac4JRFofL9w2UhMuiFTEhVPyTQvAtNAbDEAj5bI6vn5/IPyAwQOQH+/B4XM5q5tnwKw"\ "Ao+DU4TB4dHaOIk7II7k+LsD97uLmTWWwGnSMQ+geGhIaKxWCYgMESFBTgD8tPKBSKwA8SqVQep1QqYqOlXCLuw6f1h42DowMSR6WQaWy+UBQUKpWAB"\ "8ElFHYSiyVhEnCXyCIjo6JiVMlqRbRczCUTv3pWP/3C1fagiw/INoXJEwiD4KclUglMAwupTA4GvTwyVhGfqErVpSnB7GGTSd4/e64A+/jg/v0O7j44"\ "YMDhCQNDACgGAx0oRCyVRUYdiYlVqlN1uvRMTZxUxKURiPj/fqEA3PPVflsndx8Chc7h8EXB0jAgqXjFAPBH4hLUKdrMLF1aYmQwj0HCk6l/ebH+tLH"\ "be8DBHUOiMUCeRSHhh+UR0dERMqBDkXHxquRUjS5dl5oYLRawqAQsjbMXsUav79t7wBlNoIM3xfUNORyjUCYmJ6mTU5ISkmBUk5qckhgVyqdRiBgMU2"\ "T7cgX/+sEvv3JGE+nwdBNKY1O06ZnpWm16SoIqJS01Me5IpCyYSyHgfbywfMna6nlV/2e31xlLptEoZDpfHJWoyy3ILyjIzdKoosOCgvxYVCyYFyhPi"\ "lj+hSUa6BOXvUgKk4rxROMYAWFH1FpNSnJ8ZFiwL5OC9/ZCodAoT4JYEfuJFRyBeNfDwZnEpHq7ubhj6Twhn8nkMYk+aA83Dx8Sm0OlsMK12ohPreJA"\ "n5FcPMg0vJu9vbMXDuPh5unuam9r6+BJF/n5R6Tk5aei3loPRyB22vliUHgqztV23wEHuwOObo62SAyJzpfEZxflpBI/WJ+G9ZZdmIxL9EG5OhzYb4c"\ "kEJnCUHmMUp2uVXj/6tX0iv6Hmp6nUYaHHkrUKmLjlOq0rOwU6u4dG6RX9ME+ukydW1FZXl6kCafs+91mWKCdu7Hqijvj8JY9dadajf3w1cgzvUutWV"\ "hbdnQl/XOD9J9y5iwXTp2itzdAF1s+PKxoTPoKh7eTXwr8RZlo6+H7e9anYTX82hptE7FO6M80ibKM7yzfCA1kllvC32veIA5U+PL5/722jeMWDHY2b"\ "gaHoGNr+JLN4RAU/gIeulkcMiOfwz9/xaixJNOzcfDmzc3jEHTmJ/7QVnAI8n6CfzC7Nb7vyZ9pmVvDIUi4OnK2kLwnAaysiJFbxSEInsw2A1vnLwF+"\ "39ZxCALLcvp2eAkCsY3wIagJ8fvt4NDCTuy2eGifent8YMX2eN2mlq2XdXZwe/w2m4d6/r/5DW1Z6/DbDKClZXt8RfH2eHXE9nim2/b43e9tC596HbG"\ "lveepKhGIbSWACgq2beAL74IF+PrW+TJ4/2BtnbeH+R19W8VbVvfPgK3yzk/Kh+6t4c1P6wfUlvCF3T8VIKVb4ROe1U+/7N88fu3549CXm64hJv+AeF"\ "6CTeILTmsK0NhN4WYmYq02Y2DmvIQjEHLrH37WBk+1gCMQRCtfHdZq0s4ijkD8tXMj+P2PrODg+JXx6u3kzLpH8C+urU93e69HA9ng2q3TfbwNfIFGO"\ "Fda7sUF0kZP8L8S1K05Ay80hP52g/CqdnzOTa1o6QFqqdb57dlp7bl/A5UFagkxIDQ1IDEgMSAjNTIKeJxz941i0M/Iz03VTykpTs4ozS3Qd0ktzi7J"\ "L9BPzigCiscXF2SkFqXqFeSlMwAAcSYQbQ==" s. c +apply_gamma.. 0.05 b. 3 n. 0,150 n... 0,1 i[-4] 100%,100%,1,3 fc[-4] ${1-3} *[-4,-3] +[-3,-1] c.. 0,255 a[-2,-1] c #@cli chessboard : size1>0,_size2>0,_offset1,_offset2,_angle,_opacity,_color1,...,_color2,... #@cli : Draw chessboard on selected images. #@cli : Default values: 'size2=size1', 'offset1=offset2=0', 'angle=0', 'opacity=1', 'color1=0' and 'color2=255'. #@cli : $ image.jpg chessboard 32,32,0,0,25,0.3,255,128,0,0,128,255 chessboard : check "$1>0 && ${2=$1}>0" skip ${3=0},${4=0},${5=0},${6=1},${7=0},${8=255} e[^-1] "Draw chessboard on image$?, with sizes ($1,$2), offsets ($3,$4), angle $5 deg., opacity $6 and colors (${7--1})." i[0] (${7--1}) r[0] {{0,w}/2},1,1,2,-1 permute[0] cyzx repeat $!-1 { w,h={w},{h} theta:=$5*pi/180 ($3,{$3+$w-1};$3,{$3+$w-1}^$4,$4;{$4+$h-1},{$4+$h-1}) r. $w,$h,1,2,3 r. {$w*$h},2,1,1,-1 i.. ({cos($theta)},{-sin($theta)};{sin($theta)},{cos($theta)}) m*[-2,-1] r. $w,$h,1,2,-1 %. {$1+$2} >=. $1 s. c xor[-2,-1] map. [0] r. 100%,100%,1,.. j.. .,0,0,0,0,$6 rm. mv. 1 } rm[0] #@cli cie1931 #@cli : Draw CIE-1931 chromaticity diagram on selected images. #@cli : $ 500,400,1,3 cie1931 cie1931 : e[^-1] "Draw CIE-1931 chromaticity diagram on image$?." # Generate convex hull of visible colors, as a 3D object. (67.5;73.5;109.5;103.5;51.5;100.5;37;36) # Header (280,420,0;171,829,0;158,820,0;153,816,0;147,811,0;140,804,0;132,794,0;121,776,0;106,747,0;88,701,0;\ # Vertices. 65,633,0;42,539,0;20,421,0;5,295,0;0,179,0;4,115,0;10,83,0;16,61,0;25,38,0;35,21,0;47,10,0;58,3,0;\ 71,0,0;92,1,0;111,7,0;151,28,0;189,52,0;226,79,0;262,109,0;298,141,0;334,175,0;370,209,0;405,244,0;\ 441,279,0;475,313,0;509,347,0;731,568,0) xM=731 yM=829 2,{h-1},1,1,3,0 1,{h},1,1,'y' ++. 1 %. {h} +[-2,-1] 1 a[-3--1] x # Primitives. 3,{h},1,1,160 1,{h},1,1,1 # Colors + opacities. y[-4--2] a[-5--1] y mv. 0 # Generate RGB triangle of displayable colors. xR=636 yR=504 xG=297 yG=234 xB=147 yB=774 512,512,1,3 triangle_shade. 0,0,{w-1},0,0,{h-1},""255,0,0,""0,255,0,""0,0,255 rgb2srgb. +compose_channels. max +. 1e-8 /[-2,-1] *. 255 i.. (67.5;73.5;109.5;103.5;51.5;100.5;3;1;$xR;$yR;-0.01;$xG;$yG;-0.01;$xB;$yB;-0.01;\ 9;0;1;2;0;0;511;0;0;511;-128;512;512;3) y. (1) a[-3--1] y mv. 1 # Draw chroma diagram. repeat $!-2 { to_rgb. fc. 255,255,255 grid. 10%,10%,0,0,0.3,0xCCCCCCCC,1,0 100%,100%,1,3 +*3d[0,1] {(w-8)/$xM},{(h-32)/$yM} j3d... .,2,30,0,1,2 +!=... 0 distance. 1 *. -1 watershed[-4] . rm. /... 1.5 j3d... .,2,30,0,1,2 p3d. 1 p3d. 2 col3d. 128 j3d... .,2,30,0,1,1 rm. {-2,w},{-2,h} j3d. ..,2,30,0,1,2 rm.. +erode. 4 -. .. ==. 0 *[-3,-1] a[-2,-1] c blend[-2,-1] alpha 100%,100%,1,1,255 axes. 0,0.75,0.85,0,14,1 +erode. 3 negate. to_rgb.. j... ..,0,0,0,0,1,.,400 rm[-2,-1] mv. 2 } rm[0,1] #@cli circle : x[%],y[%],R[%],_opacity,_pattern,_color1,... #@cli : Draw specified colored circle on selected images. #@cli : A radius of '100%' stands for 'sqrt(width^2+height^2)'. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. If a pattern is specified, the circle is #@cli : drawn outlined instead of filled. #@cli : Default values: 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ image.jpg repeat 300 circle {u(100)}%,{u(100)}%,{u(30)},0.3,${-rgb} done circle 50%,50%,100,0.7,255 circle : skip ${4=1},${5=0},${6=0} if ${"is_pattern \"$5\""} e[0--3] "Draw outlined circle at ($1,$2) with radius $3 on image$?, with opacity $4, pattern $5 and color (${6--1})." else e[0--3] "Draw filled circle at ($1,$2) with radius $3 on image$?, with opacity $4 and color (${5--1})." fi ellipse $1,$2,$3,$3,0,${4--1} #@cli close_binary : 0<=_endpoint_rate<=100,_endpoint_connectivity>=0,_spline_distmax>=0,_segment_distmax>=0,\ # 0<=_spline_anglemax<=180,_spline_roundness>=0,_area_min>=0,_allow_self_intersection={ 0 | 1 } #@cli : Automatically close open shapes in binary images (defining white strokes on black background). #@cli : Default values: 'endpoint_rate=75', 'endpoint_connectivity=2', 'spline_distmax=80', 'segment_distmax=20', \ # 'spline_anglemax=90', 'spline_roundness=1','area_min=100', 'allow_self_intersection=1'. close_binary : check "${1=75}>=0 && $1<=100 && ${2=2}>=0 && ${3=80}>=0 && ${4=20}>=0 && ${5=90}>=0 && $5<=180 && ${6=1}>=0 && ${7=100}>=0 && isnum(${8=1})" e[^-1] "Close open shapes in binary image$?, with endpoint rate $1, endpoint connectivity $2, spline max distance $3, segment max distance $4, spline max angle $5, spline roundness $6, area min $7 and self intersections "\ ${"arg0 !$8,allowed,\"not allowed\""}"." # Set algorithm parameters. endpoint_threshold:=100-$1 # in [0,100] endpoint_connectivity:=round($2) spline_distmax=$3 # in px segment_distmax=$4 # in px spline_anglemax=$5 # in deg spline_roundness=$6 area_min=$7 # in px allow_self_intersections:=!!$8 # 0 or 1 # Define useful functions to navigate through edgels. _edgel_lib=" begin( pn = [ 0,1,1,1,-1,0,-1,1,0,-1,-1,-1,1,0,1,-1 ]; pp = [ 0,-1,1,-1,1,0,1,1,0,1,-1,1,-1,0,-1,-1 ]; ); next(img,p) = ( # Used when shape is made of '!=0' (in 8-connexity) p0 = p#[0,2] + pn[4*p#[2],2]; p1 = p#[0,2] + pn[4*p#[2] + 2,2]; case = !!i(#img,p0[0],p0[1],0,0) + 2*!!i(#img,p1[0],p1[1],0,0); !case?[ p[0],p[1],(p[2]+1)%4 ]:case==1?[ p0[0],p0[1],p[2] ]:[ p1[0],p1[1],(p[2]-1)%4 ]; ); previous(img,p) = ( # Used when shape is made of '!=0' (in 8-connexity) p0 = p#[0,2] + pp[4*p#[2],2]; p1 = p#[0,2] + pp[4*p#[2] + 2,2]; case = !!i(#img,p0[0],p0[1],0,0) + 2*!!i(#img,p1[0],p1[1],0,0); !case?[ p[0],p[1],(p[2]-1)%4 ]:case==1?[ p0[0],p0[1],p[2] ]:[ p1[0],p1[1],(p[2]+1)%4 ]; ); next2(img,p) = ( # Used when shape is made of '{ 0 | 4 }' (in 4-connexity) p0 = p#[0,2] + pn[4*p#[2],2]; p1 = p#[0,2] + pn[4*p#[2] + 2,2]; case = !(i(#img,p0[0],p0[1],0,0)&3) + 2*!(i(#img,p1[0],p1[1],0,0)&3); case==1?[ p0[0],p0[1],p[2] ]:case==3?[ p1[0],p1[1],(p[2]-1)%4 ]:[ p[0],p[1],(p[2]+1)%4 ]; ); next3(img,p) = ( # Used when shape is made of '{ 1 | 2 }' (in 4-connexity) p0 = p#[0,2] + pn[4*p#[2],2]; p1 = p#[0,2] + pn[4*p#[2] + 2,2]; val1 = i(#img,p0[0],p0[1],0,0); val2 = i(#img,p1[0],p1[1],0,0); case = (val1==1 || val1==2) + 2*(val2==1 || val2==2); case==1?[ p0[0],p0[1],p[2] ]:case==3?[ p1[0],p1[1],(p[2]-1)%4 ]:[ p[0],p[1],(p[2]+1)%4 ]; ); vec(p) = ( ang = i(p[0],p[1],0,p[2])*pi/180; [ cos(ang), sin(ang) ]; );" foreach { nm={n} # Ensure input is a 2D binary image. slices 50% channels 0 > 0 => strokes # Extract primary edgel normals. 100%,100%,1,4," N = crop(#"$strokes",x - 1,y - 1,0,0,3,3,1,1,1); N[4]?[ N[5]?-2:0, N[7]?-2:90, N[3]?-2:180, N[1]?-2:270 ]:[-2,-2,-2,-2]; " => e_normals # Smooth edgel normals. f[e_normals] $_edgel_lib" const boundary = 1; i<-1?-2:( ppos = npos = pos = [ x,y,c ]; u = vec(pos); for (t = 1, t<=5, ++t, ppos = previous(#"$strokes",ppos); npos = next(#"$strokes",npos); if (ppos==npos, break()); w = exp(-t^2/30); u+=w*vec(ppos); u+=w*vec(npos); ); ang = (atan2(u[1],u[0])*180/pi)%360; )" # Estimate edgel curvatures. +f[e_normals] $_edgel_lib" const boundary = 1; i<-1?-2:( pos = [ x,y,c ]; ppos = previous(#"$strokes",pos); npos = next(#"$strokes",pos); pu = vec(ppos); nu = vec(npos); du = (nu - pu)/2; cr = cross([vec(pos),0],[du,0]); norm(du)*sign(cr[2]); )" => e_curvatures # Smooth edgel curvatures. +f[e_curvatures] $_edgel_lib" const boundary = 1; i<-1?-2:( ppos = npos = pos = [ x,y,c ]; val = i; sumw = 1; for (t = 1, t<=5, ++t, ppos = previous(#"$strokes",ppos); npos = next(#"$strokes",npos); if (ppos==npos, break()); w = exp(-t^2/30); val+=w*i(ppos[0],ppos[1],0,ppos[2]); val+=w*i(npos[0],npos[1],0,npos[2]); sumw+=2*w; ); val/sumw; )" => e_smooth_curvatures # Estimate stroke radius. +distance[strokes] 0 f. " i<1-0.01||i>1+0.01?0:( p = x; q = y; d = 1; for (is_better = 1, is_better && d<32, next_p = p; next_q = q; is_better = 0; pp = p - 1; np = p + 1; pq = q - 1; nq = q + 1; (nd = i(pp,pq))>d?(d = nd; next_p = pp; next_q = pq; is_better = 1); (nd = i(p, pq))>d?(d = nd; next_p = p; next_q = pq; is_better = 1); (nd = i(np,pq))>d?(d = nd; next_p = np; next_q = pq; is_better = 1); (nd = i(pp,q))>d?(d = nd; next_p = pp; next_q = q; is_better = 1); (nd = i(np,q))>d?(d = nd; next_p = np; next_q = q; is_better = 1); (nd = i(pp,nq))>d?(d = nd; next_p = pp; next_q = nq; is_better = 1); (nd = i(p, nq))>d?(d = nd; next_p = p; next_q = nq; is_better = 1); (nd = i(np,nq))>d?(d = nd; next_p = np; next_q = nq; is_better = 1); p = next_p; q = next_q; ); d)" => stroke_radii # Detect end points. compose_channels[e_smooth_curvatures] max =>[e_smooth_curvatures] smooth_curvatures +compose_channels[e_curvatures] max => curvatures f. "i(#"$smooth_curvatures")>=("$endpoint_threshold"%)/(max(1,i(#"$stroke_radii"))) || i>=max(0.25,"$endpoint_threshold"%)" label_fg. 0,1 => keypoints if iM>0 {iM},1,1,3,-1 => keycoords f[keypoints] "> # Keep only a single point on connected regions of high curvatures. ret = 0; if (i, j = i - 1; old_max = I[#"$keycoords",j]; kappa = i(#"$smooth_curvatures"); if (kappa>old_max[2], I[#"$keycoords",j] = [ x,y,kappa ]; i(#"$keypoints",old_max[0],old_max[1]) = 0; ret = 1; ); ); ret" channels[keycoords] 0,3 # Channels 2,3 = number of connexions and normal angle for each keypoint. fi rm[smooth_curvatures,stroke_radii,keypoints] # Estimate point normals. if !narg($keycoords) rm[e_normals,e_curvatures] [strokes] => new_strokes else f[keycoords] " P = (I)[0,2]; angles = I(#"$e_normals",P); U = [ 0,0 ]; repeat (size(angles),k, if (angles[k]>=0, w = max(1e-8,i(#"$e_curvatures",P[0],P[1],0,k))^2; ang = angles[k]*pi/180; U += w*[ cos(ang),sin(ang) ]; ); ); [ P,0,(atan2(U[1],U[0])*180/pi)%360 ]" rm[e_normals,e_curvatures] # Detect pairs of keypoints preserving the spline constraints. 256,1,1,3 => keypairs f[keycoords] "> begin(ind = 0); for (nx = x + 1, nx0, if (ind>=w(#"$keypairs"), resize(#"$keypairs",2*ind,1,1,3,0)); I[#"$keypairs",ind++] = [ quality,x,nx ]; ); ); end(resize(#"$keypairs",ind,1,1,3,0)); I" if {keypairs,w} sort[keypairs] -,x else rm[keypairs] fi # Find and connect splines. [strokes] => new_strokes ind_strokes:=$allow_self_intersections?$strokes:$new_strokes if narg($keypairs) f[keypairs] ">"$_edgel_lib" for_spline(code) = for (t = 0, t<=1, t+=dt, t3 = t*(t2 = t*t); P = round(mul([t3,t2,t,1],C,2)); code#; dP = abs(mul([3*t2,2*t,1,0],C,2)) + 1e-8; dt = min(dtmin,0.75/max(dP)); ); Ic = I; indS = Ic[1]; indT = Ic[2]; if (i(#"$keycoords",indS,0,0,2)<"$endpoint_connectivity" && i(#"$keycoords",indT,0,0,2)<"$endpoint_connectivity", S = I(#"$keycoords",indS)[0,2]; T = I(#"$keycoords",indT)[0,2]; angS = i(#"$keycoords",indS,0,0,3)*pi/180; angT = i(#"$keycoords",indT,0,0,3)*pi/180; ST = T - S; dist = "$spline_roundness"*norm(ST); NS = [ cos(angS),sin(angS) ]; NT = [ cos(angT),sin(angT) ]; tmax = max(abs(ST)); is_cond = 1; C = mul([ 2,-2,1,1,-3,3,-2,-1,0,0,1,0,1,0,0,0 ],[ S,T,dist*NS,-dist*NT ],2); dt = dtmin = 1/max(abs(T - S)); current_val = 1; nb_switches = 0; for_spline( if (i(#"$ind_strokes",P,0,0)!=current_val, ++nb_switches; current_val=!current_val); if (nb_switches>2, is_cond = 0; break()) ); if (is_cond, # Check that the new spline did not create small closed regions. const area_max_threshold = "$area_min"; const area_min_threshold = 5; const max_edgels = 2*(area_max_threshold + 1); if (area_max_threshold<=0, for_spline(i(#"$new_strokes",P)=1); , #else for_spline(i(#"$new_strokes",P)|=2); for_spline( if (!i(#"$new_strokes",P[0] + 1,P[1]), edgels = area = 0; Q0 = Q = [ P[0] + 1,P[1],2 ]; do ( i(#"$new_strokes",Q[0],Q[1]) = 4; area+=(Q[2]&1?0:1-Q[2])*(Q[0]+!Q[2]); Q = next2(#"$new_strokes",Q), Q!=Q0 && ++edgels<=max_edgels); if (edgels<=max_edgels && area>=area_min_threshold && area=area_min_threshold && area=area_min_threshold && area=area_min_threshold && area"$_edgel_lib" for_segment(code) = for (t = 0, t<=tmax, ++t, P = round(S + t*ST); code#; ); if (i(#"$keycoords",x,0,0,2)<"$endpoint_connectivity", S = I(#"$keycoords",x)[0,2]; angS = i(#"$keycoords",x,0,0,3)*pi/180; NS = [ cos(angS),sin(angS) ]; ST = round(NS*"$segment_distmax"); tmax = max(abs(ST)); ST/=tmax; is_cond = 0; current_val = 0; nb_switches = 0; for_segment( if (i(#"$new_strokes",P,0,0)!=current_val, ++nb_switches; current_val=1 - current_val); if (nb_switches==2,is_cond = 1; break();); ); tmax = t; if (is_cond, # Check that the new segment did not create small closed regions. const area_max_threshold = "$area_min"; const area_min_threshold = 5; const max_edgels = 2*(area_max_threshold + 1); if (area_max_threshold<=0, for_segment(i(#"$new_strokes",P)=0); , #else for_segment(i(#"$new_strokes",P)|=4); for_segment( if (i(#"$new_strokes",P[0] + 1,P[1])==1, edgels = area = 0; Q0 = Q = [ P[0] + 1,P[1],2 ]; do ( i(#"$new_strokes",Q[0],Q[1]) = 2; area+=(Q[2]&1?0:1-Q[2])*(Q[0]+!Q[2]); Q = next3(#"$new_strokes",Q), Q!=Q0 && ++edgels<=max_edgels); if (edgels<=max_edgels && area>=area_min_threshold && area=area_min_threshold && area=area_min_threshold && area=area_min_threshold && area[new_strokes] $nm } #@cli curve : [xy_coordinates],_thickness>0,_tilt,_tilt_strength[%],_is_closed={ 0:no | 1:yes },_opacity,_color1,... #@cli : Draw specified parameterized curve on selected images. #@cli : Arguments are: #@cli : * '[xy_coordinates]' is the set of XY-coordinates of the curve, specified as a 2-channels image. #@cli : * 'thickness' is the thickness of the drawing, specified in pixels. #@cli : * 'tilt' is an angle, specified in degrees. #@cli : * 'tilt_strength' must be a float value in [0,1] (or in [0,100] if specified as a percentage). #@cli : * 'is_closed' is a boolean which tells if the curve is closed or not. #@cli : Default values: 'thickness=0', 'tilt=45' #@cli : $ image.jpg srand 3 16,1,1,4,u s. c,2 rbf[-2,-1] 1000,0,1 n[-2] 10,{w#0-10} n[-1] 10,{h#0-10} a[-2,-1] c \ # curve[-2] [-1],6,0,0,0,1,0,128,0 curve : check ${"is_image_arg $1"}" && ${2=0}>=0 && isnum(${3=45}) && inrange(${4=0},0,1) && isbool(${5=0}) && "\ "isnum(${6=1}) && isnum(${7=0})" s0,s1=open,closed e[^-1] "Draw parameterized "${s$5}" curve $1 on image$?, with thickness $2, tilt $3, tilt strength $4, "\ "opacity $6 and color (${7--1})." pass$1 0 if !w rm. return fi r. 1,{whds/2},1,2,-1 if $2>=1 # Estimate pointwise (tilted) normals for thick curves # Ensure consecutive curve points are at a minimal distance from each others. 1,1,1,2 eval.. "> begin(ret = [ 0,0 ]; pP = I); P = I; norm(P - pP)>1e-3?(da_push(pP); pP = P); end( da_size()?(norm(P - da_back())>1e-3?da_push(P)):da_push(I[0]); da_freeze(); ); ret" rm.. # Estimate (tilted) normals. +f. " begin( const boundary = $5?2:1; const thickness = $2/2; U = cexp([ 0,$3° ]); ); T = unitnorm(J[1] - J[-1]); N = [ -T[1],T[0] ]; nU = dot(N,U)<0?-U:U; thickness*abs(dot(N,lerp(N,nU,$4)))*N" fi # Draw curve on selected images. if $2<1 repeat $!-1 { # W/o thickness 1,{h-1+$5},1,1,"const boundary = 2; P0 = I(#-1); P1 = J[#-1,1]; polygon(#"$>",2,P0[0],P0[1],P1[0],P1[1],${6--1})" rm. } rm. else repeat $!-2 { # W/ thickness 1,{h-1+$5},1,1,"const boundary = 2; P0 = I(#-2); N0 = I(#-1); P1 = J[#-2,1]; N1 = J[#-1,1]; A = round(P0 - N0); B = round(P0 + N0); C = round(P1 - N1); D = round(P1 + N1); polygon(#"$>",3,A[0],A[1],B[0],B[1],C[0],C[1],${6--1}); polygon(#"$>",3,D[0],D[1],B[0],B[1],C[0],C[1],${6--1})" rm. } rm[-2,-1] fi #@cli ellipse : x[%],y[%],R[%],r[%],_angle,_opacity,_pattern,_color1,... : (+) #@cli : Draw specified colored ellipse on selected images. #@cli : A radius of '100%' stands for 'sqrt(width^2+height^2)'. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. If a pattern is specified, the ellipse is #@cli : drawn outlined instead of filled. #@cli : Default values: 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ image.jpg repeat 300 ellipse {u(100)}%,{u(100)}%,{u(30)},{u(30)},{u(180)},0.3,${-rgb} done \ # ellipse 50%,50%,100,100,0,0.7,255 #@cli flood : x[%],_y[%],_z[%],_tolerance>=0,_is_high_connectivity={ 0 | 1 },_opacity,_color1,... : (+) #@cli : Flood-fill selected images using specified value and tolerance. #@cli : Default values: 'y=z=0', 'tolerance=0', 'is_high_connectivity=0', 'opacity=1' and 'color1=0'. #@cli : $ image.jpg repeat 1000 flood {u(100)}%,{u(100)}%,0,20,0,1,${-rgb} done #@cli gaussian : _sigma1[%],_sigma2[%],_angle #@cli : Draw a centered gaussian on selected images, with specified standard deviations and orientation. #@cli : Default values: 'sigma1=3', 'sigma2=sigma1' and 'angle=0'. #@cli : $ 400,400 gaussian 100,30,45 #@cli : $$ https://gmic.eu/oldtutorial/_gaussian gaussian : skip ${1=15%},${2=$1},${3=0} e[^-1] "Draw centered gaussian on image$? with standard deviations ($1,$2) and angle $3 deg." u:=cos($3*pi/180) v:=sin($3*pi/180) dmax:=max(w,h) if isnum($1) l1=$1 else l1:=${1}10000*$dmax/100 fi if isnum($2) l2=$2 else l2:=${2}10000*$dmax/100 fi l1:=1/(2*max(1/3,$l1)^2) l2:=1/(2*max(1/3,$l2)^2) A:=$l1*$u*$u+$l2*$v*$v B:=($l1-$l2)*$u*$v C:=$l1*$v*$v+$l2*$u*$u foreach { nm={n} w,h={w},{h} ds={d},{s} rm $w,$h,1,1,'X=x-{($w-1)/2};Y=y-{($h-1)/2};$A*X*X+2*$B*X*Y+$C*Y*Y' * -1 exp r $w,$h,$ds => $nm } #@cli graph : [function_image],_plot_type,_vertex_type,_ytop,_ybottom,_opacity,_pattern,_color1,... #@cli : Draw specified function graph on selected images. #@cli : 'plot_type' can be { 0:none | 1:lines | 2:splines | 3:bar }. #@cli : 'vertex_type' can be { 0:none | 1:points | 2,3:crosses | 4,5:circles | 6,7:squares }. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. #@cli : Default values: 'plot_type=1', 'vertex_type=1', 'ytop=ybottom=0 (auto)', 'opacity=1', 'pattern=(undefined)' #@cli : and 'color1=0'. #@cli : $ image.jpg +rows 50% blur[-1] 3 split[-1] c div[0] 1.5 graph[0] [1],2,0,0,0,1,255,0,0 \ # graph[0] [2],2,0,0,0,1,0,255,0 graph[0] [3],2,0,0,0,1,0,0,255 keep[0] graph : check ${"is_image_arg $1"}" && isint(${2=1},0,3) && isint(${3=1},0,7)" skip ${4=0},${5=0},${6=1},${7=0},${8=0} p_type,v_type,ytop,ybottom,opacity=${2-6} if ${"is_pattern $7"} pattern=$7 color=${8--1} else pattern=0xFFFFFFFF color=${7--1} fi pass$1 1 if !$ytop" && "!$ybottom ytop,ybottom:=iM,im fi header="begin(const ytop = "$ytop"; const ybottom = "$ybottom"; const delta = ybottom - ytop);" foreach[^-1] { pass. 1 y. x # Plot graph. if $p_type if w>w#0 +r. {0,w},1,1,1,2 elif $p_type==2 +r. {0,w},1,1,1,5 fi 100%,1,1,1x2 eval... $header" # Convert input data as 2D graph coordinates X = $p_type==3?round(lerp(0,w#0,x/w)):round(lerp(0,w#0 - 1,x/(w - 1))); Y = isinf(i)?(i<0?h#0:-1): isnan(i)?h#0: round(lerp(0,h#0 - 1,(i - ytop)/delta)); i(#-2) = X; i(#-1) = Y" a[-2,-1] c if $p_type==3 # Bar plot eval. " begin( const opacity = $opacity; color = resize([ "$color" ],s#0,0,2); d_color = color*0.75; const h1 = h#0 - 1; ret = I ); x0 = i0; y0 = i1; x1 = x1?polygon(#0,-3,x0,h1,x0,y0,x1,y0,opacity,-0xFFFFFFFF,d_color); ret" else permute. cxyz polygon[0] .,$opacity,-$pattern,$color # Curve plot fi fi rm[^0,1] # Plot vertices. if $v_type eval. $header" begin( const v_type = $v_type; const opacity = $opacity; const pattern = $pattern; color = resize([ "$color" ],s#0,0,2); const siz = max(3,min(w#0,h#0)/128); ); X = $p_type==3?round(lerp(0,w#0,(x + 0.5)/w)):round(lerp(0,w#0 - 1,x/(w - 1))); Y = round(lerp(0,h#0 - 1,(i - ytop)/delta)); !isinf(Y) && !isnan(Y)?( v_type==1?polygon(#0,1,X,Y,opacity,color): v_type==4?ellipse(#0,X,Y,siz,siz,0,opacity,color): v_type==5?ellipse(#0,X,Y,-siz,-siz,0,opacity,0xFFFFFFFF,color):( X0 = X - siz; Y0 = Y - siz; X1 = X + siz; Y1 = Y + siz; v_type==2?(polygon(#0,2,X,Y0,X,Y1,opacity,color); polygon(#0,2,X0,Y,X1,Y,opacity,color)): v_type==3?(polygon(#0,2,X0,Y0,X1,Y1,opacity,color); polygon(#0,2,X1,Y0,X0,Y1,opacity,color)): v_type==6?polygon(#0,-4,X0,Y0,X1,Y0,X1,Y1,X0,Y1,opacity,0xFFFFFFFF,color): polygon(#0,-4,X,Y0,X1,Y,X,Y1,X0,Y,opacity,0xFFFFFFFF,color) ); )" fi k[0] } rm. #@cli grid : size_x[%]>=0,size_y[%]>=0,_offset_x[%],_offset_y[%],_opacity,_pattern,_color1,... #@cli : Draw xy-grid on selected images. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. #@cli : Default values: 'offset_x=offset_y=0', 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ image.jpg grid 10%,10%,0,0,0.5,255 #@cli : $ 400,400,1,3,255 grid 10%,10%,0,0,0.3,0xCCCCCCCC,128,32,16 grid : check "$1>=0 && $2>=0" skip ${3=0},${4=0},${5=1},${6=0},${7=$6} if ${"is_pattern \"$6\""} e[0--3] "Draw xy-grid on image$?, with sizes ($1,$2), offsets ($3,$4), opacity $5, pattern $6 and color (${7--1})." pattern=$6 color=${7--1} else e[0--3] "Draw xy-grid on image$?, with sizes ($1,$2), offsets ($3,$4), opacity $5, and color (${6--1})." pattern=0xFFFFFFFF color=${6--1} fi eval " repeat (l,k, # Horizontal lines. size = ispercentage($1)?max(1,w#k*$1):$1; size>=1?( off = (ispercentage($3)?size*$3:$3)%size; for (x = off, x=1?( off = (ispercentage($4)?size*$4:$4)%size; for (y = off, y] ..,$2,$3,$4,$5,$6,.,255 } rm[-3--1] else s. c,{1-s} repeat $!-2 { j[$>] ..,$2,$3,$4,$5,$6,.,255 } rm[-2,-1] fi #@cli line : x0[%],y0[%],x1[%],y1[%],_opacity,_pattern,_color1,... : (+) #@cli : Draw specified colored line on selected images. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. #@cli : Default values: 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ image.jpg repeat 500 line 50%,50%,{u(w)},{u(h)},0.5,${-rgb} done line 0,0,100%,100%,1,0xCCCCCCCC,255 \ # line 100%,0,0,100%,1,0xCCCCCCCC,255 #@cli line_aa : x0[%],y0[%],x1[%],y1[%],_opacity,_color1,... #@cli : Draw specified antialiased colored line on selected images. #@cli : Default values: 'opacity=1' and 'color1=0'. #@cli : $ 512,512,1,3 repeat 100 line_aa {v([w,h,w,h])-1},1,${-RGB} done line_aa : check "isnum(${5=1}) && isnum(${6=0})" e[^-1] "Draw antialiased line ($1,$2) - ($3,$4) on image$?, with opacity $5 and color (${6--1})." if !$5 return fi foreach { x0:=ispercentage($1)?(w-1)*$1:$1 y0:=ispercentage($2)?(h-1)*$2:$2 x1:=ispercentage($3)?(w-1)*$3:$3 y1:=ispercentage($4)?(h-1)*$4:$4 if $#>6 eval ${-math_lib}"line_aa($x0,$y0,$x1,$y1,$5,[${6--1}])" else eval ${-math_lib}"line_aa($x0,$y0,$x1,$y1,$5,$6)" fi } #@cli spline : x0[%],y0[%],u0[%],v0[%],x1[%],y1[%],u1[%],v1[%],_opacity,_color1,... #@cli : Draw specified colored spline curve on selected images (cubic hermite spline). #@cli : Default values: 'opacity=1' and 'color1=0'. #@cli : $ image.jpg repeat 30 { spline {u(100)}%,{u(100)}%,{u(-600,600)},{u(-600,600)},{u(100)}%,{u(100)}%,\ # {u(-600,600)},{u(-600,600)},1,${-RGB} } spline : skip ${9=1},${10=0} e[^-1] "Draw spline from ($1,$2) [$3,$4] to ($5,$6) [$7,$8] on image$?, with opacity $9 and color (${10--1})." foreach { x0:=ispercentage($1)?$1*(w-1):$1 y0:=ispercentage($2)?$2*(h-1):$2 u0:=ispercentage($3)?$3*(w-1):$3 v0:=ispercentage($4)?$4*(h-1):$4 x1:=ispercentage($5)?$5*(w-1):$5 y1:=ispercentage($6)?$6*(h-1):$6 u1:=ispercentage($7)?$7*(w-1):$7 v1:=ispercentage($8)?$8*(h-1):$8 eval ${-math_lib}"spline(#0,["$x0","$y0"],["$u0","$v0"],["$x1","$y1"],["$u1","$v1"],$9,[${10--1}])" } #@cli thickline : x0[%],y0[%],x1[%],y1[%],_thickness,_opacity,_color1 #@cli : Draw specified colored thick line on selected images. #@cli : Default values: 'thickness=2', 'opacity=1' and 'color1=0'. #@cli : $ 400,400,1,3 repeat 100 thickline {u([w,h,w,h,5])},0.5,${-rgb} done thickline : check "${5=2}>=0 && isnum(${6=1}) && isnum(${7=0})" e[^-1] "Draw thick line ($1,$2) - ($3,$4) on image$?, with thickness $5, opacity $6 and color (${7--1})." if !$5 line ${1-4},${6--1} else foreach { x0:=ispercentage($1)?(w-1)*$1:$1 y0:=ispercentage($2)?(h-1)*$2:$2 x1:=ispercentage($3)?(w-1)*$3:$3 y1:=ispercentage($4)?(h-1)*$4:$4 coords:=" const th = "$5"; P0 = [ "$x0","$y0" ]; P1 = [ "$x1","$y1" ]; dP = P1 - P0; n = [ -dP[1],dP[0] ]/max(1e-8,norm(dP))*th/2; round([ P0 - n, P0 + n, P1 + n, P1 - n ]); " polygon 4,$coords,${6--1} } fi #@cli thickspline : x0[%],y0[%],u0[%],v0[%],x1[%],y1[%],u1[%],v1[%],_thickness,_opacity,_color1,... #@cli : Draw specified colored thick spline curve on selected images (cubic hermite spline). #@cli : Default values: 'thickness=3', 'opacity=1' and 'color1=0'. #@cli : $ image.jpg repeat 30 { thickspline {u(100)}%,{u(100)}%,{u(-600,600)},{u(-600,600)},{u(100)}%,{u(100)}%,\ # {u(-600,600)},{u(-600,600)},3,1,${-RGB} } thickspline : skip ${9=3},${10=1},${11=0} e[^-1] "Draw spline from ($1,$2) [$3,$4] to ($5,$6) [$7,$8] on image$?, "\ "with thickness $9, opacity $10 and color (${11--1})." foreach { x0:=ispercentage($1)?$1*(w-1):$1 y0:=ispercentage($2)?$2*(h-1):$2 u0:=ispercentage($3)?$3*(w-1):$3 v0:=ispercentage($4)?$4*(h-1):$4 x1:=ispercentage($5)?$5*(w-1):$5 y1:=ispercentage($6)?$6*(h-1):$6 u1:=ispercentage($7)?$7*(w-1):$7 v1:=ispercentage($8)?$8*(h-1):$8 eval ${-math_lib}"thickspline(#0,["$x0","$y0"],["$u0","$v0"],["$x1","$y1"],["$u1","$v1"],$9,$10,[${11--1}])" } #@cli mandelbrot : z0r,z0i,z1r,z1i,_iteration_max>=0,_is_julia={ 0 | 1 },_c0r,_c0i,_opacity #@cli : Draw mandelbrot/julia fractal on selected images. #@cli : Default values: 'iteration_max=100', 'is_julia=0', 'c0r=c0i=0' and 'opacity=1'. #@cli : $ 400,400 mandelbrot -2.5,-2,2,2,1024 map 0 +blur 2 elevation3d[-1] -0.2 mandelbrot : check "isnum($1) && isnum($2) && isnum($3) && isnum($4) && isint(${5=100},0) && isbool(${6=0}) && "\ "isnum(${7=0}) && isnum(${8=0}) && isnum(${9=1})" e[^-1] "Draw "${"arg0 $6,mandelbrot,julia"}" fractal on image$?, from complex area ($1,$2)-($3,$4), "\ "with $5 iterations, c0 = ($7,$8) and opacity $9." foreach { if w 100%,100%,1,1,"begin(z0 = [ $1,$2 ]; z1 = [ $3,$4 ]; c0 = [ $7,$8 ]; zero = [ 0,0 ]; const log2 = log(2)); c = [ lerp(z0[0],z1[0],x/(w-1)), lerp(z0[1],z1[1],y/(h-1)) ]; $6?(z = c; c = c0):(z = zero); for (it = 0, it<$5 && cabs(z)<2, ++it, z = z^^2 + c); it>=$5?0:(it + 1 - log(log(cabs(z)))/log(2))" to_colormode. {0,s} j.. .,0,0,0,0,$9 rm. fi } #@cli marble : _image_weight,_pattern_weight,_angle,_amplitude,_sharpness>=0,_anisotropy>=0,_alpha,_sigma,\ # _cut_low>=0,_cut_high>=0 #@cli : Render marble like pattern on selected images. #@cli : Default values: 'image_weight=0.2', 'pattern_weight=0.1', 'angle=45', 'amplitude=0', 'sharpness=0.4' \ # and 'anisotropy=0.8', #@cli : 'alpha=0.6', 'sigma=1.1' and 'cut_low=cut_high=0'. #@cli : $ image.jpg +marble , marble : skip ${1=0.2},${2=0.1},${3=45},${4=0},${5=0.4},${6=0.8},${7=0.6},${8=1.1},${9=0%},${10=100%} e[^-1] "Render marble like pattern on image$?, with image weight $1, pattern weight $2, angle $3 deg., amplitude $4, sharpness $5, anisotropy $6, alpha $7, sigma $8, and cut ($9,$10)." sx:=$2*sin($3*pi/180) sy:=$2*cos($3*pi/180) f sin(x*$sx+y*$sy+i*$1) if $4 smooth $4,$5,$6,$7,$8 fi c $9,$10 n 0,255 #@cli maze : _width>0,_height>0,_cell_size>0 #@cli : Input maze with specified size. #@cli : $ maze 30,20 negate normalize 0,255 +maze : check "isint(${1=15},1) && isint(${2=$1},1) && isint(${3=24},1)" e[^-1] "Input $1x$2 maze." ({v($1-1)},{v($2-1)}) # Starting cell. $1,$2,1,1,15 +f. 0 a[-2,-1] c # Starting maze data. _generate_maze $1,$2 _render_maze. $3 => [maze] _generate_maze : # Start opening walls. do x={-2,@-2} y={-2,@-1} # Coords of the current cell. =. 1,$x,$y,0,1 # Mark current cell as visited. # Check for neighboring cells that are candidate for opening wall, and select one random. is_candidate=0 up=-1 if i($x,$y)&8" && "$y>0" && "!i($x,$y-1,0,1) up=$x,{$y-1},8 is_candidate=1 fi # Up. down=-1 if i($x,$y)&4" && "$y<$2-1" && "!i($x,$y+1,0,1) down=$x,{$y+1},4 is_candidate=1 fi # Down. left=-1 if i($x,$y)&2" && "$x>0" && "!i($x-1,$y,0,1) left={$x-1},$y,2 is_candidate=1 fi # Left. right=-1 if i($x,$y)&1" && "$x<$1-1" && "!i($x+1,$y,0,1) right={$x+1},$y,1 is_candidate=1 fi # Right. if $is_candidate ($up,$down,$left,$right) y. discard. -1 r. 3,{h/3},1,1,-1 shift. 0,{v(4)},0,0,2 rows. 0,0 mv. -2 fi # Remove wall between the current and chosen neighboring cells. if $is_candidate if {-2,@-1}==8 =. {i($x,$y)&7},$x,$y =. {i($x,$y-1)&11},$x,{$y-1} # Remove up wall. elif {-2,@-1}==4 =. {i($x,$y)&11},$x,$y =. {i($x,$y+1)&7},$x,{$y+1} # Remove down wall. elif {-2,@-1}==2 =. {i($x,$y)&13},$x,$y =. {i($x-1,$y)&14},{$x-1},$y # Remove left wall. else =. {i($x,$y)&14},$x,$y =. {i($x+1,$y)&13},{$x+1},$y # Remove right wall. fi z.. 0,1 a[-3,-2] y # Add neighboring cell to stack of cells to explore. else # No candidate : remove current cell from cells to explore. if h#-2==1 break fi rows.. 0,{{-2,h}-2} fi while 1 rm.. channels. 0 _render_maze : # Create the 16 configurations of walls. i[0] $1,$1 i[1] [0]x15 line[8-15] 0,0,100%,0,1,1 line[4-7,12-15] 0,100%,100%,100%,1,1 line[2-3,6-7,10-11,14-15] 0,0,0,100%,1,1 line[1-15:2] 100%,0,100%,100%,1,1 # Map the wall data with them. a[0-15] x r. {w*$1},{h*$1} *. $1 channels. 0,1 $1,$1,1,1,x $1,$1,1,1,y a[-2,-1] c r. ..,..,1,2,0,2 +[-2,-1] warp.. .,0,0,0 rm. #@cli maze_mask : _cellsize>0 #@cli : Input maze according to size and shape of selected mask images. #@cli : Mask may contain disconnected shapes. #@cli : $ 0 text "G'MIC",0,0,53,1,1 dilate 3 autocrop 0 frame xy,1,0 maze_mask 8 dilate 3 negate mul 255 maze_mask : check "isint(${1=24},1)" e[^-1] "Input masked maze from image$? with cell size $1." compose_channels + >= 50% foreach { do +rand[0] 0,1 *. [0] ({[xM,yM]}) rm.. # Select one starting point in the mask. +flood[0] {^},0,0,0,1,2 >=. 2 +negate. *.. 15 a[-2,-1] c flood[0] {-2,^},0,0,0,1,0 _generate_maze {w},{h} while iM#0 rm[0] + _render_maze. $1 => [maze] } #@cli newton_fractal : z0r,z0i,z1r,z1i,_angle,0<=_descent_method<=2,_iteration_max>=0,_convergence_precision>0,\ # _expr_p(z),_expr_dp(z),_expr_d2p(z) #@cli : Draw newton fractal on selected images, for complex numbers in range (z0r,z0i) - (z1r,z1i). #@cli : Resulting images have 3 channels whose meaning is [ last_zr, last_zi, nb_iter_used_for_convergence ]. #@cli : 'descent_method' can be { 0:secant | 1:newton | 2:householder }. #@cli : Default values: 'angle=0', 'descent_method=1', 'iteration_max=200', 'convergence_precision=0.01', \ # 'expr_p(z)=z^^3-1', 'expr_dp(z)=3*z^^2' and 'expr_d2z(z)=6*z'. #@cli : $ 400,400 newton_fractal -1.5,-1.5,1.5,1.5,0,2,200,0.01,"z^^6 + z^^3 - 1","6*z^^5 + 3*z^^2","30*z^^4 + 6*z" \ # f "[ atan2(i1,i0)*90+20,1,cut(i2/30,0.2,0.7) ]" hsl2rgb newton_fractal : check "isin(${6=1},0,1,2) && ${7=200}>=0 && ${8=0.01}>0" skip "${4=0},${9=z^^3-1},${10=3*z^^2},${11=6*z}" m0,m1,m2=secant,newton,householder e[^-1] "Draw newton fractal on image$?, for complex range ($1,$2)-($3,$4), with angle $5, $7 max "${m$6}" "\ "iterations, precision $8, and expressions 'p(z)=$9', 'dp(z)=$10' and 'd2p(z)=$11'." channels 0,2 f "* begin( const dx = abs($3 - $1); const dy = abs($4 - $2); const angle = $5; const method = $6; const itermax = $7; const precision = $8; zc = [ $1 + $3, $2 + $4 ]/2; R = rot(-angle°); ); p(z) = ($9); dp(z) = ($10); d2p(z) = ($11); zn = [ $1 + x*dx/(w-1), $2 + y*dy/(h-1) ]; angle?(zn = (R*(zn-=zc)+=zc)); !method?(znm1 = zn + [ precision,0 ]); repeat (itermax,iter, pzn = p(zn); !method?( znp1 = zn - pzn**(zn - znm1)//(pzn - p(znm1)); # secant znm1 = zn; ):method==1?( dpzn = dp(zn); znp1 = zn - pzn//dpzn; # newton ):( # householder dpzn = dp(zn); d2pzn = d2p(zn); hn = (pzn**d2pzn)//(2*dpzn^^2); znp1 = zn - pzn//dpzn**([1,0] + hn); ); norm(znp1 - zn)=0,0<=_min_scale<=100,_allow_rotation={ 0:0 deg. | 1:180 deg. | 2:90 deg. | 3:any },\ # _spacing,_precision>=0,max_iterations>=0 #@cli : Try to randomly pack as many sprites as possible onto the 'empty' areas of an image. #@cli : Sprites can be eventually rotated and scaled during the packing process. #@cli : First selected image is the canvas that will be filled with the sprites. #@cli : Its last channel must be a binary mask whose zero values represent potential locations for drawing the sprites. #@cli : All other selected images represent the sprites considered for packing. #@cli : Their last channel must be a binary mask that represents the sprite shape (i.e. a 8-connected component). #@cli : The order of sprite packing follows the order of specified sprites in the image list. #@cli : Sprite packing is done on random locations and iteratively with decreasing scales. #@cli : 'nb_scales' sets the number of decreasing scales considered for all specified sprites to be packed. #@cli : 'min_scale' (in %) sets the minimal size considered for packing (specified as a percentage of the #@cli : original sprite size). #@cli : 'spacing' can be positive or negative. #@cli : 'precision' tells about the desired number of failed trials before ending the filling process. #@cli : Default values: 'nb_scales=5', 'min_scale=25', 'allow_rotation=3', 'spacing=1', 'precision=7' \ # and 'max_iterations=256'. #@cli : $ 512,512,1,3,"min(255,y*c/2)" 100%,100% circle 50%,50%,100,1,255 append c image.jpg rescale2d[-1] ,24 \ # to_rgba pack_sprites 3,25 pack_sprites : check "isint(${1=5},0) && ${2=25}>=0 && $2<=100 && isint(${3=3},0,3) && isint(${4=1}) && isint(${5=7},0) && isint(${6=256},0)" e[^-1] "Randomly pack image$? with $1 scales, minimum scale $2%, "${arg0\ $3,no,180\"\ \"deg.,90\"\ \"deg.,any}\ " rotation, spacing $4, precision $5 and $6 maximum iterations." N:=$!-1 is_first_time=1 foreach { r 100%,100%,1,{max(2,s)} } # Ensure all images have a binary shape mask. # Start iterations over scales. repeat $1 { rprogress {$>*100/$1} nb_attempts=0 # Generate all sprites for current scale. ratio={$1>1?$2+(100-$2)*$}] { w,h:=[w,h]*$ratio if $w<1||$h<1 rm else r $w,$h,1,100%,2 sh. 100% !=. 0 area{1+$>}:=is rm. fi } } # Pack rescaled sprites together. l[0,{$N+1}--1] { repeat $6 { # Compute reference sprite. ind:=1+($>%$N) area=${area$ind} if !$3 [$ind] elif $3==1 +rotate[$ind] {v()*180} elif $3==2 +rotate[$ind] {v(3)*90} else +rotate[$ind] {u*360} sh. 100% !=. 0 area:=is rm. fi # Get binary map of possible locations. +channels[0] 100% ==. 0 if $4>1 erode. {2*$4-1} elif $4<1 dilate. {-2*$4+3} fi # Generate random skeleton-oriented point cloud. +rectangle. 0,0,100%,100%,1,0xFFFFFFFF,0 if $is_first_time noise. 0.1,2 ==. 1 fi distance. 0 noise. 1,1 max_patch. {$ind,round(1.5*max(w,h))} *. .. pointcloud3d. # Subdivide point cloud if multiple sprites. if $N>1 l. { s3d /[1] $N round[1] max[1] 1 n={1,@0} r[2] 3,{{2,h}/3},1,1,-1 i[2] 1,{2,h} rand[2] 0,1 a[2,3] x sort[2] +,y z[2] 1,3 r[2] 3,$n,1,1 y[2] r[3] 1,{2*$n},1,1,0 r[4] 1,{3*$n},1,1,0 r[5] 1,$n,1,1,0 a y } fi # Create 3D cloud of sprites. n={@7} if $n s3d. rm[-2,-1] if !$3 # No rotation allowed. [-6] i.. (-128;{w};{h};{s}) if $n>1 4,{$n-1},1,1,-128,0,0,0 fi +channels.. 100% i.. (-128;{w};{h};{s}) if $n>1 ... fi elif $3==1 # 180 deg. rotation allowed. +rotate[-6] {v()*180} i.. (-128;{w};{h};{s}) if $n>1 +rotate. 180 i.. (-128;{w};{h};{s}) fi if $n>2 4,{$n-2},1,1,-128,0,0,0 1,100% rand. 0,1 round. 1 j.. .,1 rm. fi +channels[-4] 100% i.. (-128;{w};{h};{s}) if $n>1 +channels[-4] 100% i.. (-128;{w};{h};{s}) fi if $n>2 [-5] fi else # 90 deg. rotation (or more) allowed. +rotate[-6] {v(3)*90} i.. (-128;{w};{h};{s}) if $n>1 +rotate. 90 i.. (-128;{w};{h};{s}) fi if $n>2 +rotate. 90 i.. (-128;{w};{h};{s}) fi if $n>3 +rotate. 90 i.. (-128;{w};{h};{s}) fi if $n>4 4,{$n-4},1,1,-128,0,0,0 1,100% rand. 0,3 round. 1 j.. .,1 rm. fi +channels[-8] 100% i.. (-128;{w};{h};{s}) if $n>1 +channels[-8] 100% i.. (-128;{w};{h};{s}) fi if $n>2 +channels[-8] 100% i.. (-128;{w};{h};{s}) fi if $n>3 +channels[-8] 100% i.. (-128;{w};{h};{s}) fi if $n>4 [-9] fi fi y[{$N+3}--1] a[{$N+3}--1] y fi rm... # Delete reference sprite. # Draw cloud and detect non-intersecting sprites. [0] sh. 100% f. 1 -. [-4] j3d.. ...,0,0,0,1,2,0,0 rm[-3,-1] sh. 100% area_fg. 0,1 ==. $area *. ... rm... sh.. 0,{-2,s-2} *. .. rm. # Draw selected sprites on rendering image. if iM j[0] ..,0,0,0,0,1,. rm[-2,-1] else rm[-2,-1] nb_attempts+=1 if $nb_attempts>$5 break else continue fi fi } k[0] } } k[0] #@cli piechart : label_height>=0,label_R,label_G,label_B,"label1",value1,R1,G1,B1,...,"labelN",valueN,RN,GN,BN #@cli : Draw pie chart on selected (RGB) images. #@cli : $ image.jpg piechart 25,0,0,0,"Red",55,255,0,0,"Green",40,0,255,0,"Blue",30,128,128,255,"Other",5,128,128,128 piechart : check $1>=0 e[^-1] "Draw pie chart on image$?, with label height $1 and color ($2,$3,$4)." $=arg foreach { ellipse 50%,50%,{w/2-1},{h/2-1},0,1,1 ellipse 50%,50%,{w/2-1},{h/2-1},0,1,0xFFFFFFFF (${6--1:5}) normalize_sum. theta=0 if w>1 repeat w { xe:=0.5*{-2,w}*(1+cos($theta)) ye:=0.5*{-2,h}*(1+sin($theta)) line.. 50%,50%,$xe,$ye theta-=2*pi*i($>) } fi theta=0 repeat w { if i($>) ntheta:=$theta-2*pi*i($>) xc:=0.5*{-2,w}*(1+0.5*cos(0.5*($ntheta+$theta))) yc:=0.5*{-2,h}*(1+0.5*sin(0.5*($ntheta+$theta))) xf:=0.5*{-2,w}*(1+0.8*cos(0.5*($ntheta+$theta))) yf:=0.5*{-2,h}*(1+0.8*sin(0.5*($ntheta+$theta))) flood.. $xf,$yf,0,0,0,1,${arg{7+5*$>}},${arg{8+5*$>}},${arg{9+5*$>}} if abs($ntheta-$theta)>0.1 0 t. ${arg{5+5*$>}},0,0,$1,1,1 ($2^$3^$4) r. ..,..,1,3 *. .. j[-4] .,{$xc-w/2},{$yc-h/2},0,0,1,.. rm[-2,-1] fi theta=$ntheta fi } rm. } #@cli plasma : _alpha,_beta,_scale>=0 #@cli : Draw a random colored plasma fractal on selected images. #@cli : This command implements the so-called 'Diamond-Square' algorithm. #@cli : Default values: 'alpha=1', 'beta=1' and 'scale=8'. #@cli : $ 400,400,1,3 plasma 1 #@cli : $$ https://gmic.eu/oldtutorial/_plasma plasma : check "isnum(${1=1}) && isnum(${2=1}) && isint(${3=8},0)" e[^-1] "Draw plasma fractal on image$?, with alpha $1, beta $2 and scale $3." foreach { nm={n} scale:="1<1 { # Square step. {ceil([w,h]/$scale)},100%,100%,": const scale = $scale; const scale2 = scale/2; const noise = $1*scale + $2; const boundary = 2; Xt = x*scale + scale2; Yt = y*scale + scale2; X0 = Xt - scale2; Y0 = Yt - scale2; X1 = Xt + scale2; Y1 = Yt + scale2; i(#0,Xt,Yt) = avg(i(#0,X0,Y0),i(#0,X1,Y0),i(#0,X1,Y1),i(#0,X0,Y1)) + u(-noise,noise); " rm. # Diamond step. {ceil([w,h]/$scale)*[1,2]},100%,100%,": const scale = $scale; const scale2 = scale/2; const noise = $1*scale + $2; const boundary = 2; Xt = x*scale + (y%2?0:scale2); Yt = y*scale2; i(#0,Xt,Yt) = avg(i(#0,Xt - scale2,Yt),i(#0,Xt,Yt - scale2), i(#0,Xt + scale2,Yt),i(#0,Xt,Yt + scale2)) + u(-noise,noise); " rm. scale>>=1; } r $w,$h,100%,100%,0,0,0.5,0.5 => $nm } #@cli point : x[%],_y[%],_z[%],_opacity,_color1,... : (+) #@cli : Set specified colored pixel on selected images. #@cli : Default values: 'z=0', 'opacity=1' and 'color1=0'. #@cli : $ image.jpg repeat 10000 point {u(100)}%,{u(100)}%,0,1,${-rgb} done #@cli polka_dots : diameter>=0,_density,_offset1,_offset2,_angle,_aliasing,_shading,_opacity,_color,... #@cli : Draw dots pattern on selected images. #@cli : Default values: 'density=20', 'offset1=offset2=50', 'angle=0', 'aliasing=10', 'shading=1', 'opacity=1' \ # and 'color=255'. #@cli : $ image.jpg polka_dots 10,15,0,0,20,10,1,0.5,0,128,255 polka_dots : check $1>=0 skip ${2=20},${3=50},${4=50},${5=0},${6=10},${7=1},${8=1},${9=255} e[^-1] "Draw polka dots on image$?, with diameter $1, density $2, angle $3 deg., shift ($4,$5), aliasing $6 and shading $7." theta:=$5*pi/180 ct:=cos($theta) st:=sin($theta) mid1:=$1/2 mid2:=$2/2 i[0] (${9--1}) y[0] c repeat $!-1 { WH:=max(w,h) 100%,100%,100%,1,"xn = 100*x/"$WH"-$3; yn = 100*y/"$WH"-$4; \ xr = xn*"$ct"-yn*"$st"; yr = xn*"$st"+yn*"$ct"; \ xc = xr%$2-"$mid2"; yc = yr%$2-"$mid2"; \ "$mid1"-sqrt(xc*xc+yc*yc)" *. $6 c. 0,$7 n. 0,$8 (${9--1}) y. c r. ..,..,.. j... .,0,0,0,0,1,.. rm[-2,-1] mv. 1 } rm[0] #@cli polygon : N>=1,x1[%],y1[%],...,xN[%],yN[%],_opacity,_pattern,_color1,... : \ # [coords],_opacity,_pattern,_color1,... : (+) #@cli : Draw specified colored N-vertices polygon on selected images. #@cli : 'pattern' is an hexadecimal number starting with '0x' which can be omitted #@cli : even if a color is specified. If a pattern is specified, the polygon is #@cli : drawn outlined instead of filled. #@cli : Adding a '-' sign before 'pattern' makes the command draw an open polyline rather than a closed polygon. #@cli : Default values: 'opacity=1', 'pattern=(undefined)' and 'color1=0'. #@cli : $ image.jpg polygon 4,20%,20%,80%,30%,80%,70%,20%,80%,0.3,0,255,0 \ # polygon 4,20%,20%,80%,30%,80%,70%,20%,80%,1,0xCCCCCCCC,255 #@cli : $ image.jpg 2,16,1,1,'u(x?h#0:w#0)' polygon[-2] [-1],0.6,255,0,255 remove[-1] #@cli quiver : [function_image],_sampling[%]>0,_factor>=0,_is_arrow={ 0 | 1 },_opacity,_color1,... #@cli : Draw specified 2D vector/orientation field on selected images. #@cli : Default values: 'sampling=5%', 'factor=1', 'is_arrow=1', 'opacity=1', 'pattern=(undefined)' #@cli : and 'color1=0'. #@cli : $ 100,100,1,2,'!c?x-w/2:y-h/2' 500,500,1,3,255 quiver[-1] [-2],10 #@cli : $ image.jpg +rescale2d ,600 luminance[0] gradient[0] mul[1] -1 reverse[0,1] append[0,1] c \ # blur[0] 8 orientation[0] quiver[1] [0],20,1,1,0.8,255 quiver : check ${"is_image_arg $1"}" && ${2=5%}>0 && ${3=1}>=0 && isbool(${4=1})" skip "${5=1},${6=0}" e[^-1] "Draw 2D vector field $1 on image$?, with sampling $2, factor $3, arrows "${"arg0 $4,disabled,enabled"}", opacity $5 and color (${6--1})." pass$1 repeat $!-1 { l[$>,-1] { eval ${-math_lib}" s_sampling = ['$2']; sampling = s_sampling[size(s_sampling) - 1 ]==_'%'?min(w#0,h#0)*$2:$2; vmax = max(abs(im),abs(iM)); vmax = vmax?vmax:1; fact = $3*sampling/vmax; for (y = sampling/2, y=0','mirroring={ 0:none | 1:x | 2:y | 3:xy } #@cli : Render rorschach-like inkblots on selected images. #@cli : Default values: 'smoothness=5%' and 'mirroring=1'. #@cli : $ 400,400 rorschach 3% rorschach : check "${1=5%}>=0 && isint(${2=1},0,3)" e[^-1] "Render rorschach-like inkblots on image$?, with smoothness $1 and "${arg0\ $2,no,x,y,xy}"-mirroring." if !$2 # No mirroring. rand -1,1 b $1 >= 0 elif $2==1 # X-mirroring. foreach { w:=w columns 0,{w/2-1} rand -1,1 b $1 >= 0 +mirror x if $w%2 columns. 1,100% fi a x } elif $2==2 # Y-mirroring. foreach { h:=h rows 0,{h/2-1} rand -1,1 b $1 >= 0 +mirror y if $h%2 rows. 1,100% fi a y } elif $2==3 # XY-mirroring. foreach { w,h={w},{h} z 0,0,{w/2-1},{h/2-1} rand -1,1 b $1 >= 0 +mirror x if $w%2 columns. 1,100% fi a x +mirror y if $h%2 rows. 1,100% fi a y } fi #@cli sierpinski : recursion_level>=0 #@cli : Draw Sierpinski triangle on selected images. #@cli : Default value: 'recursion_level=7'. #@cli : $ image.jpg sierpinski 7 sierpinski : check ${1=7}>=0 skip ${2=50},${3=0},${4=0},${5=100},${6=100},${7=100} e[^-1] "Draw Sierpinski triangle of degree $1 on image$?." _sierpinski ${2-7},$1 _sierpinski : if $7<=0 polygon 3,$1%,$2%,$3%,$4%,$5%,$6%,1,255 return fi _sierpinski $1,$2,{($1+$3)/2},{($2+$4)/2},{($1+$5)/2},{($2+$6)/2},{$7-1} _sierpinski {($1+$3)/2},{($2+$4)/2},$3,$4,{($3+$5)/2},{($4+$6)/2},{$7-1} _sierpinski {($1+$5)/2},{($2+$6)/2},$5,$6,{($3+$5)/2},{($4+$6)/2},{$7-1} #@cli spiralbw : width>0,_height>0,_is_2dcoords={ 0 | 1 } #@cli : Input a 2D rectangular spiral image with specified size. #@cli : Default values: 'height=width' and 'is_2dcoords=0'. #@cli : $ spiralbw 16 #@cli : $ image.jpg spiralbw {[w,h]},1 +warp[0] [1],0,1,1 +warp[2] [1],2,1,1 +spiralbw : check "$1>=1 && ${2=$1}>=1 && isbool(${3=0})" e[^-1] "Input 2D rectangular spiral image of size $1x$2." main="alpha = min(x,y,w - 1 - x,h - 1 - y); t0 = alpha*2*(w + h) - 4*alpha^2; X = x - alpha; Y = y - alpha; W = w - 2*alpha; H = h - 2*alpha; t = t0 + (!Y?X: X==W - 1?W - 1 + Y: Y==H - 1?2*W + H - 3 - X: 2*(W + H - 2) - Y);" if $3 $1,$2,1,2,$main"[ t%w, int(t/w) ]" else $1,$2,1,1,$main fi #@cli tetraedron_shade : x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3,R0,G0,B0,...,R1,G1,B1,...,R2,G2,B2,...,R3,G3,B3,... #@cli : Draw tetraedron with interpolated colors on selected (volumetric) images. tetraedron_shade : e[^-1] "Draw tetraderon ($1,$2,$3)-($4,$5,$6)-($7,$8,$9)-($10,$11,$12) with interpolated colors in image$?." # Find bounding box. xm:=round(min($1,$4,$7,$10),1,-1) xM:=round(max($1,$4,$7,$10),1,1) ym:=round(min($2,$5,$8,$11),1,-1) yM:=round(max($2,$5,$8,$11),1,1) zm:=round(min($3,$6,$9,$12),1,-1) zM:=round(max($3,$6,$9,$12),1,1) # Find color mapping coefficients for each vertex. l[] { (${1-3},1;${4-6},1;${7-9},1;${10-12},1) (${13--1}) r. {w/4},4,1,1,-1 s. x solve[^0] [0] rm[0] a c } # Draw tetraedron on selected images. f[^-1] "* begin( x0 = $1; y0 = $2; z0 = $3; x1 = $4; y1 = $5; z1 = $6; x2 = $7; y2 = $8; z2 = $9; x3 = $10; y3 = $11; z3 = $12; u01 = x1 - x0; v01 = y1 - y0; w01 = z1 - z0; u02 = x2 - x0; v02 = y2 - y0; w02 = z2 - z0; u03 = x3 - x0; v03 = y3 - y0; w03 = z3 - z0; u12 = x2 - x1; v12 = y2 - y1; w12 = z2 - z1; u13 = x3 - x1; v13 = y3 - y1; w13 = z3 - z1; u23 = x3 - x2; v23 = y3 - y2; w23 = z3 - z2; nx012 = v01*w02 - w01*v02; ny012 = w01*u02 - u01*w02; nz012 = u01*v02 - v01*u02; if (nx012*u03 + ny012*v03 + nz012*w03<0, nx012*=-1; ny012*=-1; nz012*=-1); nx013 = v01*w03 - w01*v03; ny013 = w01*u03 - u01*w03; nz013 = u01*v03 - v01*u03; if (nx013*u02 + ny013*v02 + nz013*w02<0, nx013*=-1; ny013*=-1; nz013*=-1); nx023 = v02*w03 - w02*v03; ny023 = w02*u03 - u02*w03; nz023 = u02*v03 - v02*u03; if (nx023*u01 + ny023*v01 + nz023*w01<0, nx023*=-1; ny023*=-1; nz023*=-1); nx123 = v12*w13 - w12*v13; ny123 = w12*u13 - u12*w13; nz123 = u12*v13 - v12*u13; if (-nx123*u01 - ny123*v01 - nz123*w01<0, nx123*=-1; ny123*=-1; nz123*=-1); ); if (x<"$xm" || x>"$xM" || y<"$ym" || y>"$yM" || z<"$zm" || z>"$zM",i, dx0 = x - x0; dy0 = y - y0; dz0 = z - z0; dx1 = x - x1; dy1 = y - y1; dz1 = z - z1; is_in = dx0*nx012 + dy0*ny012 + dz0*nz012>=0 && dx0*nx013 + dy0*ny013 + dz0*nz013>=0 && dx0*nx023 + dy0*ny023 + dz0*nz023>=0 && dx1*nx123 + dy1*ny123 + dz1*nz123>=0; is_in? i(#-1,0,0,0)*x + i(#-1,0,1,0)*y + i(#-1,0,2,0)*z + i(#-1,0,3,0) :i )" rm. #@cli t : eq. to 'text'. : (+) #@cli text : text,_x[%|~],_y[%|~],_{ font_height[%]>=0 | custom_font },_opacity,_color1,... : (+) #@cli : Draw specified colored text string on selected images. #@cli : (eq. to 't').\n #@cli : If one of the x or y argument ends with a '~', its value is expected to be \ # a centering ratio (in [0,1]) rather than a position. #@cli : Usual centering ratio are { 0:left-justified | 0.5:centered | 1:right-justified }. #@cli : Sizes '13' and '128' are special and correspond to binary fonts (no-antialiasing). \ # Any other font size is rendered with anti-aliasing. #@cli : Specifying an empty target image resizes it to new dimensions such that the image contains \ # the entire text string. #@cli : A custom font can be specified as a variable name that stores an image list of 256 or 512 items \ # (512 for 256 character sprites + 256 associated opacities), or as an image selection that is a serialized \ # version of such an image list. #@cli : Default values: 'x=y=0.01~', 'font_height=16', 'opacity=1' and 'color1=0'. #@cli : $ image.jpg rescale2d ,600 div 2 y=0 repeat 30 { text {2*$>}" : This is a nice text!",10,$y,{2*$>},0.9,255 \ # y+={2*$>} } #@cli : $ 0 text "G'MIC",0,0,23,1,255 #@cli to : eq. to 'text_outline'. to : skip "${1=}",${2=0.01~},${3=0.01~},${4=7.5%} check "${5=2}>=0 && isnum(${6=1}) && isnum(${7=255}) && "\ "isnum(${8=$7}) && isnum(${9=$7}) && isnum(${10=255})" _text_outline $"*" #@cli text_outline : text,_x[%|~],_y[%|~],{ _font_height[%]>0 | custom_font },_outline>=0,_opacity,_color1,... #@cli : Draw specified colored and outlined text string on selected images. #@cli : If one of the x or y argument ends with a '~', its value is expected to be #@cli : a centering ratio (in [0,1]) rather than a position. #@cli : Usual centering ratio are { 0:left-justified | 0.5:centered | 1:right-justified }. #@cli : Default values: 'x=y=0.01~', 'font_height=7.5%', 'outline=2', 'opacity=1', 'color1=color2=color3=255' \ # and 'color4=255'. #@cli : $ image.jpg text_outline "Hi there!",10,10,63,3 text_outline : skip "${1=}",${2=0.01~},${3=0.01~},${4=7.5%} check "${5=2}>=0 && isnum(${6=1}) && isnum(${7=255}) && "\ "isnum(${8=$7}) && isnum(${9=$7}) && isnum(${10=255})" _text_outline $"*" _text_outline : skip "${1=}" if "isexpr($4)" str="height $4" is_custom_font=0 else str="'$4'" is_custom_font=1 fi e[0--3] "Draw outlined text '$1' at position ($2,$3) on image$?, with font "$str", outline $5, opacity $6 and color ${7--1}." if ['"$1"']==0 return fi sepx,sepy:="sx=['$2']; sy=['$3']; [sx[size(sx)-1], sy[size(sy)-1]]" if !$is_custom_font is_fontpercent:=ispercentage($4) fi xpos={`s=['"$2"'];$sepx==_'~'||$sepx==_'%'?s[0,size(s)-1]:s`} ypos={`s=['"$3"'];$sepy==_'~'||$sepy==_'%'?s[0,size(s)-1]:s`} foreach { 0 if $is_custom_font t. "$1",0,0,$4,1,1 else t. "$1",0,0,{-2,$is_fontpercent?h*$4:$4},1,1 fi expand. xy,{1+$5} +dilate. {2*$5+1} i[-3] (${7--1}) r... {s#0},1,1,1,0,2 y... c r... .,.,1,100% if $5 *[-3,-2] else rm.. fi if w#0 j... ..,{[($sepx==_'~'?(w#0-1-w):$sepx==_'%'?(w#0-1)%:1)*$xpos,\ ($sepy==_'~'?(h#0-1-h):$sepy==_'%'?(h#0-1)%:1)*$ypos]},0,0,$6,. k[0] else k[1] fi } #@cli triangle_shade : x0,y0,x1,y1,x2,y2,R0,G0,B0,...,R1,G1,B1,...,R2,G2,B2,... #@cli : Draw triangle with interpolated colors on selected images. #@cli : $ image.jpg triangle_shade 20,20,400,100,120,200,255,0,0,0,255,0,0,0,255 triangle_shade : e[^-1] "Draw triangle ($1,$2)-($3,$4)-($5,$6) with interpolated colors on image$?." # Find color mapping coefficients for each vertex. l[] { ($1,$2,1;$3,$4,1;$5,$6,1) (${7--1}) r. {w/3},3,1,1,-1 s. x solve[^0] [0] rm[0] a c } # Pre-compute coefs to test point inside triangle. invarea:=(-$4*$5+$2*(-$3+$5)+$1*($2-$6)+$3*$6)^-1 s1:=$2*$5-$1*$6 s2:=$6-$2 s3:=$1-$5 t1:=$1*$4-$2*$3 t2:=$2-$4 t3:=$3-$1 # Begin drawing on each selected image. repeat $!-1 { l[$>,-1] { repeat s#0 { a:=i(0,0,0,$>) b:=i(0,1,0,$>) c:=i(0,2,0,$>) sh[0] $> f. "s = "$invarea"*("$s1" + "$s2"*x + "$s3"*y); t = "$invarea"*("$t1" + "$t2"*x + "$t3"*y); s>=0 && t>=0 && t+s<=1 ? "$a"*x+"$b"*y+"$c":i" rm. } } } rm. #@cli truchet : _scale>0,_radius>=0,_pattern_type={ 0:straight | 1:curved } #@cli : Fill selected images with random truchet patterns. #@cli : Default values: 'scale=32', 'radius=5' and 'pattern_type=1'. #@cli : $ 400,300 truchet , truchet : check "isint(${1=32},1) && ${2=3}>=0" skip ${3=1} e[^-1] "Render "${arg0\ !$3,curved,straight}" truchet patterns in image$?, with scale $1 and radius $2." foreach { nm={n} w,h,s={w},{h},{s} rm $1,$1 = 1,0,0 = 1,100%,100% distance 1,{1+$3} M:=int(iM/2) # Generate truchet pattern and its mirrored version. ir {$M-$2/2-($1%2)},{$M+$2/2} +mirror y a x {round($w/$1,1,1)},{round($h/$1,1,1)} rand. 0,1 >=. 50% r. {w*$1},{h*$1} *. $1 channels. 0,1 (0,{$1-1}) r. $1,$1,1,1,3 +transpose. a[-2,-1] c ri. ..,0,2 +[-2,-1] warp.. . rm. >= 50% r $w,$h,1,1,0 r 100%,100%,1,$s => $nm } #@cli turbulence : _radius>0,_octaves={1,2,3...,12},_alpha>0,_difference={-10,10},_mode={0,1,2,3} #@cli : Render fractal noise or turbulence on selected images. #@cli : Default values: 'radius=32', 'octaves=6', 'alpha=3', 'difference=0' and 'mode=0'. #@cli : $ 400,400,1,3 turbulence 16 #@cli : $$ https://gmic.eu/oldtutorial/_turbulence turbulence : check "${1=32}>0 && ${2=6}>0" skip ${3=3},${4=0},${5=0} e[^-1] "Render fractal noise or turbulence on image$?, with radius $1, octaves $2, damping per octave $3, difference $4 and mode $5." foreach { nm={n} if $4 . fi f. 0 +noise. 10,0 b. $1,0 if isbool($5) -. {ia} abs. elif $5==3||$5==4 ^. 2 elif $5==5 ^. 3 fi repeat $2-1 { +noise.. 10,0 b. {$1/2^$>},0 if !$5 -. {ia} abs. elif $5==4 ^. 2 elif $5==5 ^. 3 fi *.. $3 +[-2--1] } n. 0,255 rm.. if $4 *. $4 mv.. 2 - n. 0,255 fi => $nm } #@cli yinyang #@cli : Draw a yin-yang symbol on selected images. #@cli : $ 400,400 yinyang yinyang : e[^-1] "Draw yin-yang symbol on image$?." f 0 foreach { s:=s channels 0 r:=round(0.95*min(w,h)/4) +line 50%,0,50%,50%,1,2 ellipse. 50%,{h/2-$r},$r,$r,0,1,2 line. 50%,50%,50%,100%,1,1 ellipse. 50%,{h/2+$r},$r,$r,0,1,1 flood. {w/2-$r},50%,0,0,0,1,2 flood. {w/2+$r},50%,0,0,0,1,1 ellipse.. 50%,50%,{2*$r},{2*$r},0,1,1 * ellipse. 50%,{h/2-$r},{$r/3},{$r/3},0,1,1 ellipse. 50%,{h/2+$r},{$r/3},{$r/3},0,1,2 r 100%,100%,1,$s } #--------------------------------- # #@cli :: Matrix Computation # #--------------------------------- #@cli dijkstra : starting_vertex>=0,_ending_vertex={ -1:none | >=0 } #@cli : Compute minimal distances/paths in selected graphs, from specified 'starting_vertex' to all other vertices \ # (opt. only until 'ending_vertex' has been reached). #@cli : A graph of 'N' vertices is specified as a 'NxN' adjacency matrix giving the weights of all edges \ # connecting vertices (set to 'inf' when two vertices are not connected). #@cli : This command return a '1xNx1x2' image containing the '[distance,parent]' information : #@cli : - 'distance' is the minimal distance from vertex '#y' to the 'starting_vertex' (i.e. the sum \ # of edge weights composing the minimal path between these two vertices). #@cli : - 'parent' is the index of the next vertex that must be followed to reaches the 'starting_vertex' \ # through the minimal path. #@cli : Default value: 'ending_vertex=-1' +dijkstra : check "isint($1,0) && isint(${2=-1},-1)" s0,s1=x,ces t0,t1=," to vertex \#$2" e[^-1] "Compute minimal distances/path from vertex \#$1"${t{$2>=0}}", for adjacency matri"${s{$!>1}}" [$[]]." if $2!=-1 str_check_end_vertex="C[1]==$2?break();" fi foreach { nm={n} if !w" || "w!=h" || "d>1" || "s>1 error[0--4] "Command 'dijkstra': Image ["{arg0($>,$[])}"]: '"$nm"', of size ("{[w,h,d,s]}") "\ "is not an adjacency matrix." fi N:=w # Number of nodes 1,$N,1,2,[inf,-1] =. 0,0,$1 # [1]: Resulting image [ dist,parent ] 1,{$N+1},1,2,[inf,y] eval "swap(I[0],I[$1]); i[0] = 0; I[h - 1] = [$N,0]" # [2]: Heap [ dist,index ] eval " while (da_size(), C = da_pop_heap(); "$str_check_end_vertex" repeat (da_size(),q, N = I[q]; edge = i(#0,C[1],N[1]); !isinf(edge)?( # Neighbor found dist = C[0] + edge; dist0, # Update heap parent = (index - 1)>>1; N[0] $nm } if $! mv[1--1:2] $! add_copymark[50%--1] fi dijkstra : foreach { nm={n} +dijkstra $* k. => $nm } #@cli eigen : (+) #@cli : Compute the eigenvalues and eigenvectors of selected symmetric matrices or matrix fields. #@cli : If one selected image has 3 or 6 channels, it is regarded as a field of 2x2 or 3x3 symmetric matrices, #@cli : whose eigen elements are computed at each point of the field. #@cli : $ (1,0,0;0,2,0;0,0,3) +eigen #@cli : $ image.jpg structuretensors blur 2 eigen split[0] c #@cli : $$ https://gmic.eu/oldtutorial/_eigen #@cli eye : _size>0 #@cli : Insert an identity matrix of given size at the end of the image list. #@cli : $ eye 3 eye 7 eye 10 +eye : check "isint($1,0)" e[^-1] "Input $1x$1 identity matrix." if $1 $1,$1,1,1,x==y else 0 fi #@cli fitsamples : nb_samples>0,_relevant_dimension[%]>0,\ # _average_vector_varname,_dilation_vector_varname,_orientation_matrix_varname #@cli : Generate 'nb_samples' vectors having the same multivariate gaussian distribution as the vectors of the \ # selected images. #@cli : Each input represents a set of 'M' vectors of dimension 'N' (with M>1) (specified as an image with size \ # 'MxNx1x1', 'Mx1xNx1', 'Mx1x1xN', '1xMxNx1', '1xMx1xN' or '1x1xMxN'). #@cli : The command returns a new set of random vectors with similar geometry. #@cli : Default values: 'relevant_dimension=100%', and \ # 'average_vector_varname=orientation_matrix_varname=dilation_matrix_varname=(undefined)'. fitsamples : check "isint($1,1) && ${2=100%}>0" skip "${3=},${4=},${5=}" e[^-1] "Generate $1 new vectors for input vector set$? with $2 relevant dimensions." fwd0,fwd1,fwd2,fwd3,fwd4,fwd5=xzcy,xycz,,yxcz,yxzc,zxyc bwd0,bwd1,bwd2,bwd3,bwd4,bwd5=xcyz,xycz,,yxcz,yxzc,yzxc foreach { nm={n} # Determine input format: 'MxNx1x1', 'Mx1xNx1', 'Mx1x1xN', '1xMxNx1', '1xMx1xN' or '1x1xMxN'. M,N,type:=w>1?[w,h>1?[h,0]:d>1?[d,1]:[s,2]]:h>1?[h,d>1?[d,3]:[s,4]]:d>1?[d,s>1?[s,5]:[-1,-1]] if $M*$N!=whds error[0--4] "Command 'fitsamples': Selected image ("{[w,h,d,s]}") does not represent a set of vectors." fi permute ${fwd$type} # Force it to be a 'Mx1x1xN' matrix. # Compute average vector. if !narg("$3") +l { s x + / $M } else $$3 fi => avg # Compute dilation vector and orientation matrix. if !narg("$4")" || "!narg("$5") $N,$N eval... "* begin(avg = crop(#$avg); C = vector(#$N^2,0)); mI = I - avg; C+=mul(mI,mI,s); end(merge(C,+); C/=$M; draw(#-1,C,0,0,0,0,$N,$N,1,1))" P:=round(ispercentage($2)?$N*$2:$2) poweriteration. $P sqrt.. else $$4 $$5 P:=w fi => dilation,orientation # Generate new random samples with same gaussian distribution. $1,1,1,$N,"* begin(avg = crop(#$avg); L = crop(#$dilation); R = crop(#$orientation)); U = vector(#$P); fill(U,k,g*L[k]); mul(R,U)+=avg" k. permute ${bwd$type} => $nm } #@cli invert : _use_LU={ 0:SVD | 1:LU },_lambda>=0 : (+) #@cli : Inverse selected matrices (or compute Moore-Penrose pseudoinverse for non-square matrices). #@cli : SVD solver is slower but more precise than LU. #@cli : 'lambda' is used only in the Moore-Penrose pseudoinverse, by estimating A^t.(A^t.A + lambda.Id)^-1. #@cli : Default value: 'use_LU=0' and 'lambda=0'. #@cli : $ (0,1,0;0,0,1;1,0,0) +invert #@cli meigen : m>=1 #@cli : Compute an approximation of the 'm' largest eigenvalues and eigenvectors of selected symmetric matrices, #@cli : using the Arnoldi iteration method (https://en.wikipedia.org/wiki/Arnoldi_iteration). #@cli : A larger 'm' goes with better numerical precision. #@cli : $ (1,0,0;0,2,0;0,0,3) +meigen 3 meigen : check "isint($1,1)" if $!!=1 s="ce" else s="x" fi e[^-1] "Compute $1 largest eigen-values of matri"$s"$?." foreach { nm={n} if w!=h" || "d!=1" || "s!=1 v 1 error[0--5] "Command 'meigen': Image '"$nm"' is not a square matrix." fi eval ${-math_lib}" store('val',meig(crop(),$1,h),1,min($1,h))" $val k. => $nm } #@cli mproj : [dictionary],_method,_max_iter={ 0:auto | >0 },_max_residual>=0 : (+) #@cli : Find best matching projection of selected matrices onto the span of an over-complete #@cli : dictionary D, using the orthogonal projection or Matching Pursuit algorithm. #@cli : Selected images are 2D-matrices in which each column represent a signal to project. #@cli : '[dictionary]' is a matrix in which each column is an element of the dictionary D. #@cli : 'method' tells what projection algorithm must be applied. It can be: #@cli : \ - 0 = orthogonal projection (least-squares solution using LU-based solver). #@cli : \ - 1 = matching pursuit. #@cli : \ - 2 = matching pursuit, with a single orthogonal projection step at the end. #@cli : \ - >=3 = orthogonal matching pursuit where an orthogonal projection step is performed #@cli : \ every 'method-2' iterations. #@cli : 'max_iter' sets the max number of iterations processed for each signal. #@cli : If set to '0' (default), 'max_iter' is equal to the number of columns in D. #@cli : (only meaningful for matching pursuit and its variants). #@cli : 'max_residual' gives a stopping criterion on signal reconstruction accuracy. #@cli : (only meaningful for matching pursuit and its variants). #@cli : For each selected image, the result is returned as a matrix W #@cli : whose columns correspond to the weights associated to each column of D, #@cli : such that the matrix product D*W is an approximation of the input matrix. #@cli : Default values: 'method=0', 'max_iter=0' and 'max_residual=1e-6'. #@cli orthogonalize : _mode = { 0:orthogonalize | 1:orthonormalize } #@cli : Orthogonalize or orthonormalize selected matrices, using Modified Gram-Schmidt process. #@cli : Default value: 'mode=0'. orthogonalize : if isbool($1) mode=$1 else mode=0 noarg fi u0,u1,v0,v1=Orthogonalize,Orthonormalize,x,ce e[^-1] ${u$mode}" matri"${v{$!!=1}}"$?, using Modified Gram-Schmidt process." foreach { eval "> proj(u,v) = (dot(u,v)/dot(u,u)*u); for (p = 1, p0,_epsilon>0,_max_iter>0 #@cli : Compute the 'nb_eigenvectors' largest eigenvectors of the selected symmetric matrices, #@cli : using the power iteration algorithm. #@cli : Default values: 'nb_eigenvectors=1', 'epsilon=1e-5' and 'max_iter=100'. poweriteration : check "isint(${1=1},1) && ${2=1e-6}>0 && isint(${3=1000},1)" s0,s1=,s if $!>1 c=ces else c=x fi e[^-1] "Compute the $1 first eigenvector"${s{$1>1}}" of matri"$c" [$[]] with epsilon $2." foreach { nm={n} if !w rm 0 0 => $nm[val] $nm[vec] continue fi if w!=h" || "d>1" || "s>1 error[0--3] "Command '$0': Image "[{[$[]][$>]}]" ("{[w,h,d,s]}") is not a square matrix." fi 1,$1 $1,.. eval " const N = w#0; A = crop(#0); b = expr('u(-1,1)',N); b/=norm(b); repeat ($1,k, p_lambda = inf; repeat ($3, c = mul(A,b); lambda = norm(c); c/=lambda; delta = abs(lambda - p_lambda); b = c; p_lambda = lambda; delta<$2?break(); ); lambda = mul(b,mul(A,b,1))[0]; # Get eigenvalue with correct sign i[#-2,k] = lambda; draw(#-1,b,k,1); A-=(mul(b,b,N)*=lambda); )" rm[0] } #@cli solve : [image],_use_LU={ 0:SVD | 1:LU } : (+) #@cli : Solve linear system AX = B for selected B-matrices and specified A-matrix. #@cli : If the system is under- or over-determined, the least squares solution is returned. #@cli : Default value: 'use_LU=0'. #@cli : $ (0,1,0;1,0,0;0,0,1) (1;2;3) +solve[-1] [-2] #@cli svd : (+) #@cli : Compute SVD decomposition of selected matrices. #@cli : $ 10,10,1,1,'x==y?x+u(-0.2,0.2):0' +svd #@cli transpose #@cli : Transpose selected matrices. #@cli : $ image.jpg +transpose transpose : e[^-1] "Transpose image$?." permute yxzc #@cli trisolve : [image] #@cli : Solve tridiagonal system AX = B for selected B-vectors and specified tridiagonal A-matrix. #@cli : Tridiagonal matrix must be stored as a 3 column vector, where 2nd column contains the #@cli : diagonal coefficients, while 1st and 3rd columns contain the left and right coefficients. #@cli : $ (0,0,1;1,0,0;0,1,0) (1;2;3) +trisolve[-1] [-2] trisolve : check ${"is_image_arg $1"} e[^-1] "Solve tridiagonal system AX = B, with B-vector$? and tridiagonal A-matrix $1." pass$1 foreach[^-1] { pass. if "w!=3 || h!=h#0 || d>1 || s>1" error[0--4] "Command '$0': Selected vector ("{0,[w,h,d,s]}") and tridiagonal matrix ("{[w,h,d,s]}") "\ "have incompatible dimensions." fi eval " const eps = 1e-4; const hm1 = h - 1; B = crop(1,0,1,h); V = crop(#0); for (i = 1, i=0, --i, i[#0,i] = (V[i] - i(2,i)*i[#0,i + 1])/(B[i]?B[i]:eps); )" rm. } rm. #--------------------------------- # #@cli :: 3D Meshes # #--------------------------------- #@cli +3d : eq. to 'add3d'. : (+) #@cli add3d : tx,_ty,_tz : [object3d] : (no arg) : (+) #@cli : Shift selected 3D objects with specified displacement vector, or merge them with specified #@cli : 3D object, or merge all selected 3D objects together. #@cli : (eq. to '+3d'). #@cli : Default values: 'ty=tz=0'. #@cli : $ sphere3d 10 repeat 5 { +add3d[-1] 10,{u(-10,10)},0 color3d[-1] ${-rgb} } add3d #@cli : $ repeat 20 { torus3d 15,2 color3d[-1] ${-rgb} mul3d[-1] 0.5,1 if $>%2 rotate3d[-1] 0,1,0,90 fi add3d[-1] 70 \ # add3d rotate3d[-1] 0,0,1,18 } double3d 0 #@cli animate3d : nb_frames>0,_step_angle_x,_step_angle_y,_step_angle_z,_zoom_factor,0<=_fake_shadow_level<=100,\ # _[background] #@cli : Generate 3D animation frames of rotating 3D objects. #@cli : Frames are stacked along the z-axis (volumetric image). #@cli : Frame size is the same as the size of the '[background]' image (or 800x800 if no background specified). #@cli : Default values: 'step_angle_x=0', 'step_angle_y=5', 'step_angle_z=0', 'zoom_factor=1', 'fake_shadow_level=50' \ # and 'background=(undefined)'. animate3d : check "isint($1,1) && isnum(${2=0}) && isnum(${3=5}) && isnum(${4=0}) && ${5=1}>0 && "\ "inrange(${6=50},0,100)" skip "${7=}" e[^-1] "Generate 3D animation frames from 3D object$?, with $1 frames, angle steps (${2-4}), zoom factor $5 "\ "and $6% fake shadow." if ${"is_image_arg $7"} pass$7 else 3,2,1,1,"32,32,64,64,116,96" permute. cyzx r. 800,800,1,3,3 round. fi => anim3d_bg repeat {$!-1} { l[$>,-1] { check ${-is_mesh3d..} nm={0,n} bn={0,b} e " * Object '"$bn"': 0/$1" c3d[0] n3d[0] *3d[0] {1,$5*min(w,h)/2} repeat $1 { e "\r * Object '"$bn"': "{1+$>}"/$1" +r3d[0] 0,0,1,{$>*$4} r3d. 0,1,0,{$>*$3} r3d. 1,0,0,{$>*$2} {1,[w,h,d,4]},-1 j3d. ..,50%,50%,0,1 sh. 0,{s-2} +l.. { s c max } !=. -1 mul.. . mul. 255 j... .,0,0,0,{-2,s} rm[-2,-1] if $6 sh. 100% +b. 2% shift. {m=min(w,h)*2%;[m,m]} -. {255-$6*255%} c. 0,255 max[-2,-1] rm. fi # Add fake shadow +blend[1,-1] alpha rm[-3,-2] if {*} w. -1,-1,0 fi } a[2--1] z rv[0,-1] rm. } =>[$>] $nm } rm. #@cli apply_camera3d : pos_x,pos_y,pos_z,target_x,target_y,target_z,up_x,up_y,up_z #@cli : Apply 3D camera matrix to selected 3D objects. #@cli : Default values: 'target_x=0', 'target_y=0', 'target_z=0', 'up_x=0', 'up_y=-1' and 'up_z=0'. apply_camera3d : skip ${4=0},${5=0},${6=0},${7=0},${8=-1},${9=0} e[^-1] "Apply 3D camera matrix to 3D object$?, with camera position ($1,$2,$3), target position ($4,$5,$6) and up-vector ($7,$8,$9)." ({$4-$1}^{$5-$2}^{$6-$3}) # f. ($7^$8^$9) # up. orientation[-2,-1] # f/|f| and up/|up|. _cross3d {-2,^},{^} # s = f x up _cross3d {^},{-3,^} # u = s x f rm... y[-3--1] x mv[-2,-1] -3 a[-3--1] y z. 0,3 # Rotation matrix R. -3d[^-1] $1,$2,$3 pose3d[^-1] {^} rm. -3d 0,0,800 _cross3d : ({$2*$6-$3*$5}^{$3*$4-$1*$6}^{$1*$5-$2*$4}) orientation. y. #@cli apply_matrix3d : a11,a12,a13,...,a31,a32,a33 #@cli : Apply specified 3D rotation matrix to selected 3D objects. #@cli : $ torus3d 10,1 +apply_matrix3d {mul(rot(1,0,1,-15°),[1,0,0,0,2,0,0,0,8],3)} double3d 0 apply_matrix3d : e[^-1] "Apply 3x3 matrix (${1-3};${4-6};${7-9}) to 3D object$?." foreach { nbp:=i[6] sh 8,{8+3*$nbp-1},0,0 r. 3,$nbp,1,1,-1 3,3,1,1,$* transpose. m*[-2,-1] rm. } #@cli array3d : size_x>=1,_size_y>=1,_size_z>=1,_offset_x[%],_offset_y[%],_offset_y[%] #@cli : Duplicate a 3D object along the X,Y and Z axes. #@cli : Default values: 'size_y=1', 'size_z=1' and 'offset_x=offset_y=offset_z=100%'. #@cli : $ torus3d 10,1 +array3d 5,5,5,110%,110%,300% array3d : check "isint($1,1) && isint(${2=1},1) && isint(${3=1},1)" skip ${4=100%},${5=100%},${6=100%} e[^-1] "Duplicate 3D object$? along X,Y,Z axes with factors ($1,$2,$3) and offsets ($4,$5,$6)." foreach { # Retrieve object dimensions. +rows 8,{8+3*i[6]} r. 3,{h/3},1,1,-1 s. x,3 dx={-3,ispercentage($4)?$4*(iM-im):$4} dy={-2,ispercentage($5)?$5*(iM-im):$5} dz={-1,ispercentage($6)?$6*(iM-im):$6} rm[-3--1] # Duplicate along X. off=0 repeat int(log2($1)) { ++3d. {2^$>*$dx} +3d. .. if !($1&(2^$>)) rm.. else +3d.. $off off+=2^$>*$dx fi } +3d. $off +3d # Duplicate along Y. off=0 repeat int(log2($2)) { ++3d. 0,{2^$>*$dy} +3d. .. if !($2&(2^$>)) rm.. else +3d.. 0,$off off+=2^$>*$dy fi } +3d. 0,$off +3d # Duplicate along Z. off=0 repeat int(log2($3)) { ++3d. 0,0,{2^$>*$dz} +3d. .. if !($3&(2^$>)) rm.. else +3d.. 0,0,$off off+=2^$>*$dz fi } +3d. 0,0,$off +3d } #@cli arrow3d : x0,y0,z0,x1,y1,z1,_radius[%]>=0,_head_length[%]>=0,_head_radius[%]>=0 #@cli : Input 3D arrow with specified starting and ending 3D points. #@cli : Default values: 'radius=5%', 'head_length=25%' and 'head_radius=15%'. #@cli : $ repeat 10 { a:=$>*2*pi/10 arrow3d 0,0,0,{cos($a)},{sin($a)},-0.5 } +3d +arrow3d : check "${7=5%}>=0 && ${8=25%}>=0 && ${9=15%}>=0" e[^-1] "Input 3D arrow, from (${1-3}) to (${4-6}), with radius $7, head length $8 and head radius $9." # Create 3D object. L:=sqrt(($4-$1)^2+($5-$2)^2+($6-$3)^2) R:=ispercentage($7)?$7*$L:$7 l:=ispercentage($8)?$8*$L:$8 r:=ispercentage($9)?$9*$L:$9 L-=$l cylinder3d $R,$L cone3d $r,$l +3d. 0,0,$L +3d[-2,-1] # Compute rotation matrix for arrow orientation. ({$4-$1}^{$5-$2}^{$6-$3}) (0.01^-0.02^0.03) orientation[-2,-1] _cross3d {-2,^},{^} _cross3d {^},{-3,^} rm... y[-3--1] x mv[-2,-1] -3 a[-3--1] y # Rotate and translate the arrow at specified coordinates. s3d.. r[-5] 3,{-5,h/3},1,1,-1 m*[-5,-1] y[-4] a[-6--1] y +3d. ${1-3} rv3d. #@cli axes3d : _size_x,_size_y,_size_z,_font_size>0,_label_x,_label_y,_label_z,_is_origin={ 0:no | 1:yes } #@cli : Input 3D axes with specified sizes along the x,y and z orientations. #@cli : Default values: 'size_x=size_y=size_z=1', 'font_size=23', 'label_x=X', 'label_y=Y', 'label_z=Z' and \ # 'is_origin=1' #@cli : $ axes3d , +axes3d : check "${4=23}>0 && isbool(${8=1})" skip ${1=1},${2=$1},${3=$2},"${5=X},${6=Y},${7=Z}" e[^-1] "Input 3D axes with sizes ($1,$2,$3)." l[] { m:=max(abs($1),abs($2),abs($3))/40 m2:=2*$m m3:=1.2*$m2 if $1 line3d 0,0,0,$1,0,0 fi if $2 line3d 0,0,0,0,$2,0 fi if $3 line3d 0,0,0,0,0,$3 fi if $1 cone3d $m,{2*$m},16 r3d. 0,1,0,90 +3d. {$1-$m2},0,0 _axes3d "$5",$4 +3d. {$1+$m3},0,0 fi if $2 cone3d $m,{2*$m},16 r3d. 1,0,0,-90 +3d. 0,{$2-$m2},0 _axes3d "$6",$4 +3d. 0,{$2+$m3},0 fi if $3 cone3d $m,{2*$m},16 +3d. 0,0,{$3-$m2} _axes3d "$7",$4 +3d. 0,0,{$3+$m3} fi if $8 _axes3d "O",$4 -3d. $m3,$m3,$m3 fi +3d => [3d\ axes] } _axes3d : 0 t. "$1",2,0,$2,1,1 +dilate. 3 *.. 255 r.. 100%,100%,1,3 i... (67.5;73.5;109.5;103.5;51.5;100.5;1;1;0;0;0;1;0;-128;{w};{h};3) i.. (-128;{w};{h};1) y[-3,-1] a[-4--1] y #@cli boundingbox3d #@cli : Replace selected 3D objects by their 3D bounding boxes. #@cli : $ torus3d 100,30 +boundingbox3d +3d[-1] [-2] boundingbox3d : e[^-1] "Replace 3D object$? by their 3D bounding boxes." foreach { s3d nbv,nbp={1,f2ui([i[0],i[1]])} k[2,3] => pts,prims r[pts] 3,{pts,h/3},1,1,-1 permute[pts] zycx ($nbv) a[pts,-1] y # Convert vertices to dynamic array $nbp eval. ">begin(p = 0); # Manage 3D spheres by adding virtual vertices N = i[#$prims,p++]; N==5?( i0 = i[#$prims,p++]; i1 = i[#$prims,p++]; p+=3; P0 = I[#$pts,i0]; P1 = I[#$pts,i1]; Pc = (P0 + P1)/2; U = (P1 - P0)/2; V = rot(-U[1],U[0],U[2],pi/2)*U; W = cross(U,V)/norm(U); da_push(#$pts,Pc + V,Pc - V,Pc + W,Pc - W); ):(p+=N); end(da_freeze(#$pts))" rm[prims,-1] s c xm,dx={0,[im,iM-im]} ym,dy={1,[im,iM-im]} zm,dz={2,[im,iM-im]} rm box3d $dx,$dy,$dz +3d $xm,$ym,$zm p3d 1 } #@cli box3d : _size_x,_size_y,_size_z #@cli : Input 3D box at (0,0,0), with specified geometry. #@cli : Default values: 'size_x=1' and 'size_z=size_y=size_x'. #@cli : $ box3d 100,40,30 +primitives3d 1 color3d[-2] ${-rgb} +box3d : skip ${1=1},${2=$1},${3=$2} e[^-1] "Input 3D box, with size ($1,$2,$3)." 1,86,1,1,\ 67.5,73.5,109.5,103.5,51.5,100.5,8,6,\ 0,0,0,$1,0,0,$1,$2,0,0,$2,0,\ 0,0,$3,$1,0,$3,$1,$2,$3,0,$2,$3,\ 4,0,3,2,1,4,4,5,6,7,4,0,1,5,4,4,3,7,6,2,4,0,4,7,3,4,1,2,6,5,\ 200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,\ 1,1,1,1,1,1 => [3D\ box] #@cli c3d : eq. to 'center3d'. c3d : _center3d #@cli center3d #@cli : Center selected 3D objects at (0,0,0). #@cli : (eq. to 'c3d'). #@cli : $ repeat 100 { circle3d {u(100)},{u(100)},{u(100)},2 } add3d color3d[-1] 255,0,0 +center3d \ # color3d[-1] 0,255,0 add3d center3d : _$0 _center3d : e[0--3] "Center 3D object$?." check3d foreach { if i[6] s3d r[2] 3,{2,h/3},1,1,-1 s[2] x -[2] {2,(iM+im)/2} -[3] {3,(iM+im)/2} -[4] {4,(iM+im)/2} a[2-4] x y[2] a y fi } #@cli chainring3d : _nb_links>=3,_x_scale>0,_y_scale>0,_z_scale>0 #@cli : Input 3D chain ring with specified geometry. #@cli : 'nb_links' should be preferably even. #@cli : Default values: 'nb_links=16', 'x_scale=0.5', 'y_scale=1' and 'z_scale=1'. #@cli : $ chainring3d chainring3d : check "isint(${1=16},3) && ${2=0.5}>0 && ${3=1}>0 && ${4=1}>0" e[^-1] "Input 3D chain ring, with $1 links and scales (${2-4})." l[] { torus3d 1,0.1 *3d ${2-4} shift:=0.75*$1/pi repeat $1 { ang:=$>*360/$1 ($ang^1^0.784) hsv2rgb. +col3d[0] {^} rm.. if $>%2 r3d. 0,1,0,90 fi +3d. $shift r3d. 0,0,1,$ang } rm[0] +3d /3d $shift => [3D\ chainring] } #@cli circle3d : _x0,_y0,_z0,_radius>=0 #@cli : Input 3D circle at specified coordinates. #@cli : Default values: 'x0=y0=z0=0' and 'radius=1'. #@cli : $ repeat 500 { a:=$>*pi/250 circle3d {cos(3*$a)},{sin(2*$a)},0,{$a/50} color3d[-1] ${-rgb},0.4 } add3d +circle3d : skip ${1=0},${2=0},${3=0},${4=1} e[^-1] "Input 3D circle at position ($1,$2,$3) with radius $4." r:=$4/sqrt(3) 1,24,1,1,\ 67.5,73.5,109.5,103.5,51.5,100.5,2,1,\ {$1-$r},{$2-$r},{$3-$r},\ {$1+$r},{$2+$r},{$3+$r},\ 5,0,1,0,0,0,200,200,200,1 => [3D\ circle] #@cli circles3d : _radius>=0,_is_outlined={ 0 | 1 } #@cli : Convert specified 3D objects to sets of 3D circles with specified radius. #@cli : Default values: 'radius=1' and 'is_outlined=1'. #@cli : $ image.jpg luminance rescale2d ,40 threshold 50% * 255 pointcloud3d color3d[-1] 255,255,255 circles3d 0.7 circles3d : check "${1=1}>=0 && isbool(${2=0})" e[^-1] "Convert 3D object$? to sets of 3D "${arg0\ $2,filled,outlined}" circles with radius $1." p3d 0 foreach { -3d {$1/2},0,0 ++3d $1,0,0 +3d[1] [0] s3d # Two 3D objects decomposed here! rows[7] 0 j[1] [7] # Number of points rv[2,8] # Points rv[3,9] l[3] { r 2,{h/2},1,1,-1 z 1,1 s y,2 i[0] 1,100%,1,1,5 1,100%,1,1,$2 2,100% a x y } # Primitives k[0-5] a y } #@cli col3d : eq. to 'color3d'. col3d : _gmic_s="$?" v + _color3d $* #@cli color3d : R,_G,_B,_opacity #@cli : Set color (and optionally opacity) of selected 3D objects. #@cli : (eq. to 'col3d'). #@cli : Default value: 'B=G=R' and 'opacity=(undefined)'. #@cli : $ torus3d 100,10 double3d 0 repeat 7 { +rotate3d[-1] 1,0,0,20 color3d[-1] ${-rgb} } add3d color3d : _gmic_s="$?" v + _$0 $* _color3d : $=arg col:=$#==1?[$arg1,$arg1,$arg1]:$#==2?[$arg1,$arg2,0]:[$arg1,$arg2,$arg3] if $#<4 e[0--4] "Set color of 3D object"$_gmic_s" to ("$col")." else e[0--4] "Set color of 3D object"$_gmic_s" to ("$col"), with opacity "$arg4"." fi foreach { nbv,nbp:=f2ui([i[6],i[7]]) $nbp eval. ">begin(p = 8 + 3*$nbv); p+=i[#0,p] + 1; end(set('p',p))" # Find beginning of color data eval. ">begin(p = $p; col = ["$col"]); i[#0,p]==-128?(p+=prod(crop(#0,0,++p,1,3))):copy(i[#0,p],col); p+=3; end(set('p',p))" if $#>3 eval. ">begin(p = $p; const o = $arg4); i[#0,p]==-128?(p+=3 + prod(crop(#0,0,++p,1,3))):(i[#0,p++] = o)" fi k[0] } #@cli colorcube3d : _is_wireframe={ 0 | 1 } #@cli : Input 3D color cube. #@cli : Default value: 'is_wireframe=0'. #@cli : $ colorcube3d mode3d 2 +primitives3d 1 +colorcube3d : l[] { check "isbool(${1=0})" is_wireframe=$1 onfail is_wireframe=1 noarg } s0,s1="face","wireframe" e[^-1] "Input 3D RGB-color "$s$1" cube." if $is_wireframe l[] { # Wireframe version ({'CImg3d'},8,12) # Header (0,0,0;255,0,0;255,255,0;0,255,0;0,0,255;255,0,255;255,255,255;0,255,255) # Vertices (6,0,1,0,0,63,0;6,1,2,0,1,63,1;6,2,3,0,2,63,2;6,3,0,0,3,63,3;\ # Primitives 6,0,4,0,4,63,4;6,1,5,0,5,63,5;6,2,6,0,6,63,6;6,3,7,0,7,63,7;\ 6,4,5,0,8,63,8;6,5,6,0,9,63,9;6,6,7,0,10,63,10;6,7,4,0,11,63,11) (-128,64,12,3) (0,0,0;255,0,0/255,0,0;255,255,0/255,255,0;0,255,0/0,255,0;0,0,0/\ # First texture 0,0,0;0,0,255/255,0,0;255,0,255/255,255,0;255,255,255/0,255,0;0,255,255/\ 0,0,255;255,0,255/255,0,255;255,255,255/255,255,255;0,255,255/0,255,255;0,0,255) permute. yzcx r. 64,100%,1,3,3 (-128,0,0,0)x11 # 11 other shared textures 1,12,1,1,1 # Opacities y a y } else l[] { # Face version ({'CImg3d'},8,6) # Header (0,0,0;255,0,0;255,255,0;0,255,0;0,0,255;255,0,255;255,255,255;0,255,255) # Vertices (12,0,3,2,1,0,0,0,63,63,63,63,0;12,1,2,6,5,0,0,0,63,63,63,63,0;\ # Primitives 12,0,4,7,3,0,0,63,0,63,63,0,63;12,4,5,6,7,0,0,63,0,63,63,0,63;\ 12,0,1,5,4,0,0,63,0,63,63,0,63;12,3,7,6,2,0,0,0,63,63,63,63,0) (0,255;0,255^0,0;255,255^0,0;0,0) # Generate 6 textures (255,255;255,255^0,0;255,255^0,255;0,255) (0,0;0,0^0,0;255,255^0,255;0,255) (0,255;0,255^0,0;255,255^255,255;255,255) (0,255;0,255^0,0;0,0^0,0;255,255) (0,255;0,255^255,255;255,255^0,0;255,255) r[-6--1] 64,64,1,3,3 round[-6--1] y[-6--1] i[-7--2] (-128;64;64;3) 1,6,1,1,1 # Opacities y a y } fi => [3D\ colorcube] #@cli colorize3d : _color_function,_passed_images_for_color_function #@cli : Colorize primitives of selected 3D objects, according to a specified function. #@cli : - 'color_function' returns a G,GA,RGB or RGBA vector that can depend on variables 'x','y' and 'z', which are \ # defined as the barycenter coordinates for each primitive. #@cli : - 'passed_images_for_color_function' can be specified as a selection (e.g. '[0,2]') of images that will be \ # inserted at the end of the image list while modifying 3D objects, so that the 'color_function' can have access to \ # their content. #@cli : Default values: 'color_function=[x,y,z]' and 'passed_images_for_color_function='. #@cli : $ torus3d 100,40,640,100 c3d n3d mul3d 256 +3d 128,128,128 sample colorful,257 colorize3d[0] "I(#-1,x,y,0)",[1] colorize3d : skip "${1=[x,y,z]},${2=}" e[^-1] "Colorize 3D objects$?, according to color function '$1'." check3d 0 p3d 2 N=$! if narg($2) is_passed=1 pass$2 1 fi foreach[0-{$N-1}] { s3d nbp={1,@1} if $is_passed pass[$N--1] 1 fi eval " begin( fn() = ($1); x = y = z = 0; col = fn(); P = [ 0,0,0 ]; ); for (p = off = 0, off=3); size(col)==2 || size(col)>3?(i[#5,p] = col[size(col)-1]/255); )" k[0-5] a y } if $is_passed rm[$N--1] fi #@cli cone3d : _radius,_height,_nb_subdivisions>0 #@cli : Input 3D cone at (0,0,0), with specified geometry. #@cli : Default value: 'radius=1','height=1' and 'nb_subdivisions=24'. #@cli : $ cone3d 10,40 +primitives3d 1 color3d[-2] ${-rgb} +cone3d : check ${3=24}>0 skip ${1=1},${2=1} e[^-1] "Input 3D cone, with radius $1, height $2 and $3 subdivisions." # Header. (67.5;73.5;109.5;103.5;51.5;100.5) ({$3+2};{2*$3}) # Vertices. (0,0,0;0,0,$2) (0;{2*pi}) r. 1,{$3+1},1,1,3 rows. 0,{$3-1} +sin. cos.. *[-2,-1] $1 a[-2,-1] x z. 0,2 a[-2,-1] y # Primitives. 1,$3,1,1,'y' +shift. 0,-1 +[-2,-1] 2 2,$3,1,1,3,0 .. [-4] a[-3--1] x i[-4] 2,$3,1,1,3,1 a[-4--2] x a[-2,-1] y # Colors / opacities. 3,{h},1,1,200 1,{h},1,1,1 y[-4--2] a[-6--1] y => [3D\ cone] #@cli cubes3d : _size>=0 #@cli : Convert specified 3D objects to sets of 3D cubes with specified size. #@cli : Default value: 'size=1'. #@cli : $ image.jpg luminance rescale2d ,40 threshold 50% * 255 pointcloud3d color3d[-1] 255,255,255 cubes3d 1 cubes3d : check ${1=1}>=0 e[^-1] "Convert 3D object$? to sets of 3D cubes with size $1." p3d 0 foreach { nbv={@6} nbp={@7} if $nbv&&$nbp s3d l[1] { = {8*i[0]} = {6*i[1]},0,1 } # Header l[2] { # Vertices r 3,{h/3},1,1,-1 half:=$1/2 - '$half,0,0' ++ '$1,0,0' a x - '0,$half,0' ++ '0,$1,0' a x - '0,0,$half' ++ '0,0,$1' a x } l[3] { # Primitives r 2,{h/2},1,1,-1 z 1,1 * 8 r 4,100% i[0] 1,100%,1,1,4 a x [-1]x5 a x + '"0,0,2,3,1, 0,4,5,7,6, 0,0,1,5,4, 0,2,6,7,3, 0,0,4,6,2, 0,1,3,7,5"' } l[4] { r 3,{h/3},1,1,-1 r 18,100%,1,1,0,2 } r[5] 6,100% # Colors & opacities. y a y fi } #@cli cup3d : _resolution>0 #@cli : Input 3D cup object. #@cli : Default value: 'resolution=128'. #@cli : $ cup3d , +cup3d : check ${1=128}>0 e[^-1] "Input 3D cup, with resolution $1." 100,200 ellipse. 0%,0%,40%,40%,0,1,1 ellipse. 0,0,35%,35%,0,1,0 polygon. 4,0,45%,8%,45%,20%,90%,0,90%,1,1 ellipse. 0%,100%,30%,10%,0,1,1 b. 0.1% lathe3d. $1,2 => [3D\ cup] #@cli curve3d : _"x(t)",_"y(t)",_"z(t)",_"r(t)",_resolution>1,_tmin,_tmax,_nb_sides>=0,_is_closed_curve={ 0 | 1 } #@cli : Input 3D curve with specified parameterization. #@cli : If 'r(t)==0' or 'nb_sides<3', the generated 3D object is composed of segments only. #@cli : Default values: 'x(t)=cos(2*pi*t)', 'y(t)=sin(2*pi*t)', 'z(t)=t', 'r(t)=0.025', 'resolution=128', \ # 'tmin=0', 'tmax=1', 'nb_sides=16' and 'is_closed_curve=0'. #@cli : $ curve3d , +curve3d : check "isint(${5=128},2) && isbool(${9=0}) && isint(${8=16},0)" skip "${1=0.25*cos(4*pi*t)},${2=0.25*sin(4*pi*t)},${3=t},${4=0.025},${6=0},${7=1}" xt,yt,zt,rt,resolution,tmin,tmax,nb_sides,is_closed=$"*" nb_sides:="$nb_sides<3 || ['"$rt"']=='0'?1:$nb_sides" s0,s1="open","closed" e[^-1] "Input 3D "${s$is_closed}" curve with parameterization ("$xt,$yt,$zt"), radius '"$rt"', "\ "resolution "$resolution", t-range ["$tmin,$tmax"], radius and "$nb_sides" sides." ('CImg3d') (0,0) 1,$resolution,1,4,"t = lerp($tmin,$tmax,y/(h - !$is_closed)); [ ("$xt"),("$yt"),("$zt"),("$rt") ]" s. c,-3 if $nb_sides==1 # Curve made of segments rm. 1,{$resolution-1+$is_closed},1,3,"[ 2,y,(y + 1)%$resolution ]" nbv=$resolution nbp:=h permute[-2,-1] cyzx else # Curve made of quadrangles +g.. y,0,{$is_closed?2:1} orientation. # Unit tangent vector 0 $nb_sides,$resolution,1,1," !x?( P = I(#-4,0,y); R = i(#-3,0,y); W = I(#-2,0,y); !norm(W)?(W = [ 0,0,1 ]); !y?( P0 = P; do (ref = unitnorm([ u([-1,-1,-1],[1,1,1]) ]); crossWref = cross(W,ref), norm(crossWref)<1e-5); ); U = cross(W,ref); U/=norm(U); V = cross(W,U); V/=norm(V); M = transpose([ U,V,W ],3); ref = -V; ); ang = x*2*pi/w; Q = P + R*mul(M,[ c