diff --git a/.snapcraft.yaml b/.snapcraft.yaml
deleted file mode 100644
index dcf3af94..00000000
--- a/.snapcraft.yaml
+++ /dev/null
@@ -1,83 +0,0 @@
-name: vips-nip2
-icon: share/nip2/data/vips-128.png
-version: 'master'
-summary: An image-processing spreadsheet.
-description: |
- nip2 is half-way between a spreadsheet and a paint program.
- It's terrible for retouching photographs, but very handy for automating image
- processing tasks.
-
- It can process multi-gigabyte images efficiently. It has good support for
- scientific image formats. It's much faster and more flexible than tools like
- ImageJ.
-
- This snap is git master nip2 built with git master libvips.
-
-grade: devel
-confinement: strict
-
-apps:
- nip2:
- command: desktop-launch nip2
- desktop: share/applications/nip2.desktop
- plugs: [x11, home, gsettings]
-
-parts:
- vips-part:
- source: https://github.com/jcupitt/libvips
- source-branch: master
- source-type: git
- plugin: autotools
- build-packages:
- - gtk-doc-tools
- - gobject-introspection
- - intltool
- - libgirepository1.0-dev
- - libglib2.0-dev
- - libfftw3-dev
- - libexpat1-dev
- - libxml2-dev
- - libjpeg-turbo8-dev
- - libtiff5-dev
- - libopenslide-dev
- - libopenexr-dev
- - libcfitsio-dev
- - libmatio-dev
- - libgif-dev
- - libpoppler-dev
- - librsvg2-dev
- - libmagickcore-6.q16-dev
- - libexif-dev
- - libgsf-1-dev
- - liblcms2-dev
- - libpng12-dev
- - libfontconfig1-dev
- - libgdk-pixbuf2.0-dev
- - libfreetype6-dev
- - libxft-dev
- - libcairo2-dev
- - libpango1.0-dev
- - liborc-0.4-dev
- - libwebp-dev
-
- nip2-part:
- source: https://github.com/jcupitt/nip2
- source-branch: master
- source-type: git
- plugin: autotools
- build-packages:
- - gobject-introspection
- - intltool
- - libgirepository1.0-dev
- - libglib2.0-dev
- - libgtk2.0-dev
- - libx11-dev
- - libxml2-dev
- - flex
- - bison
- - libfftw3-dev
- - libgoffice-0.8-dev
- - libgsl-dev
- - libgvc6
- after: [desktop-gtk2, vips-part]
-
diff --git a/ChangeLog b/ChangeLog
index efb7090a..87c5bebd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,35 @@
+started 8.9.2 22/11/23
+- fix a lockup with comment characters in REPL [MvGulik]
+
+started 8.9.1 15/2/23
+- fix build with --std=c99 [Schamschula]
+
+started 8.9.0 10/4/20
+- add find_trim
+
+started 8.7.1 13/11/18
+- fix uint status bar pixels >2**31 [Rob Erdmann]
+- fix crash on redhat [bgilbert]
+
+started 8.7.0 22/5/18
+- added vips7compat.h include for libvips 8.7
+- more output for -V to help debugging CLI mode
+- revised Text widget
+- added Canny
+- Sobel uses the new vips_sobel() operator
+- add mitchell kernel
+- add 8.6 compat
+
+started 8.6.1 1/5/18
+- better enum display in header
+
started 8.6.0 16/8/17
- add scRGB support
- improve radiance support
- add composite to alpha menu
+- add Image / Select / Fill
+- add combine mode to indexed histogram
+- better compat handling
started 8.5.1 22/1/17
- fix a crash bug
diff --git a/Makefile.am b/Makefile.am
index 5b5b4bbc..4c8aa931 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,7 @@ EXTRA_DIST = \
doc \
proj \
m4 \
+ screenshot.png \
nip2.desktop.in \
nip2.xml \
nip2.appdata.xml
diff --git a/README.md b/README.md
index ce2ec3d1..3e57bffb 100644
--- a/README.md
+++ b/README.md
@@ -1,87 +1,70 @@
-# nip2 - a user interface for the VIPS image processing library
+# nip2 --- a user interface for libvips
-[](https://build.snapcraft.io/user/jcupitt/nip2)
+We now have a first public test release of nip4, a rewrite of nip2 for the
+gtk4 UI toolkit. If you have some time to try it, any feedback would
+be very welcome:
-nip2 is a GUI for the [VIPS image processing
-library](https://jcupitt.github.io/libvips). It's a little like a spreadsheet:
-you create a set of formula connecting your objects together, and on a change
-nip2 recalculates.
-
-You can probably install nip2 via your package manager. For
-Windows and OS X, you can download a binary from the [nip2 releases
-area](https://github.com/jcupitt/nip2/releases). Only read on if you want to
-compile yourself from source.
-
-# Building nip2 from source
-
-In the nip2 directory you should just be able to do:
-
- $ ./configure
- $ make
- $ sudo make install
-
-By default this will install files to `/usr/local`.
-
-See the Dependencies section below for a list of the things that
-nip2 needs in order to be able to build.
-
-If you downloaded from GIT you'll need:
+https://github.com/jcupitt/nip4/releases
- $ ./autogen.sh
+## nip2
-first to build the configure system.
-
-# Dependencies
-
-nip2 needs vips, gtk2 and libxml2 at runtime and flex/bison at compile time.
-
-If you have fftw3, gsl, goffice, libgvc you get extra optional, but useful,
-features.
+nip2 is a GUI for the [libvips image processing
+library](https://libvips.github.io/libvips). It's a little like a spreadsheet:
+you create a set of formula connecting your objects together, and on a change
+nip2 will recalculate. This makes it convenient for developing image processing
+systems since you can watch pixels change as you adjust your equations.
-# Tips
+Because nip2 uses libvips as the image processing engine it can handle very
+large images and only needs a little memory. It scales to fairly complex
+workflows: I've used it to develop systems with more than 10,000 cells,
+analyzing images of many tens of gigabytes. It has a batch mode, so you
+can run any image processing system you develop from the command-line and
+without a GUI.
-production build with
+
- ./configure --prefix=/home/john/vips
+## Installing
-debug build with
+You can probably install nip2 via your package manager. For
+Windows and OS X, you can download a binary from the [nip2 releases
+page](https://github.com/libvips/nip2/releases). If you have to build from
+source, see the section below.
- CFLAGS="-g -Wall" ./configure --prefix=/home/john/vips
+## Documentation
-(--enable-debug turns on and off automatically with development / production
-minor version numbers)
+nip2 comes with a 50-page manual --- press F1 or Help / Contents in the
+program to view it.
-leak check
+## Building nip2 from source
- export G_DEBUG=gc-friendly
- export G_SLICE=always-malloc
- valgrind --suppressions=/home/john/nip2.supp \
- --leak-check=yes \
- nip2 ... > nip2-vg.log 2>&1
+In the nip2 directory you should just be able to do the usual:
-memory access check
+```
+./configure
+make
+sudo make install
+```
- valgrind --suppressions=/home/john/nip2.supp \
- --leak-check=no --db-attach=yes \
- nip2 ... > nip2-vg.log 2>&1
+By default this will install files to `/usr/local`. Check the summary at the
+end of `configure` and make sure you have all of the features you want.
-or put "--suppressions=/home/john/nip2.supp" into ~/.valgrindrc
+If you downloaded from GIT you'll need:
-profiling
+```
+./autogen.sh
+```
- valgrind --tool=callgrind \
- --suppressions=/home/john/nip2.supp \
- nip2 ... > nip2-vg.log 2>&1
+first to build the configure system.
-Disclaimer: No guarantees of performance accompany this software, nor is any
-responsibility assumed on the part of the authors. Please read the licence
-agreement.
+nip2 needs vips, gtk2 and libxml2 at runtime and flex/bison at compile time.
+If you have fftw3, gsl, goffice, libgvc you get extra features.
-# snapcraft
+### snapcraft
Rebuild snap with:
- snapcraft cleanbuild
+```
+snapcraft cleanbuild
+```
Though it's done automatically on a push.
diff --git a/configure.ac b/configure.ac
index cfe129ee..ac6a03b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
# Process this file with autoconf to produce a configure script.
-AC_INIT([nip2], [8.6.0], [vipsip@jiscmail.ac.uk])
+AC_INIT([nip2], [8.9.2], [vipsip@jiscmail.ac.uk])
# foreign stops complaints about a missing README (we use README.md instead)
# and missing INSTALL (the standard Gnu INSTALL is not very useful)
@@ -16,8 +16,8 @@ dnl of them.
dnl
m4_define([nip_major_version], [8])
-m4_define([nip_minor_version], [6])
-m4_define([nip_micro_version], [0])
+m4_define([nip_minor_version], [9])
+m4_define([nip_micro_version], [2])
m4_define([nip_version],
[nip_major_version.nip_minor_version.nip_micro_version])
@@ -380,6 +380,8 @@ AC_OUTPUT([
share/nip2/compat/8.2/Makefile
share/nip2/compat/8.3/Makefile
share/nip2/compat/8.4/Makefile
+ share/nip2/compat/8.5/Makefile
+ share/nip2/compat/8.6/Makefile
src/BITMAPS/Makefile
src/Makefile
test/Makefile
diff --git a/doc/src/nipguide.tex b/doc/src/nipguide.tex
index 17abcb2e..194710bb 100644
--- a/doc/src/nipguide.tex
+++ b/doc/src/nipguide.tex
@@ -17,7 +17,7 @@
\fancyhead[LE,RO]{\leftmark} % left-even, right-odd
\fancyhead[RE,LO]{\nip{} Manual} % right-even, left-odd
\fancyfoot[LE,RO]{\thepage} % left-even, right-odd
-\fancyfoot[RE,LO]{March 2017}
+\fancyfoot[RE,LO]{December 2017}
\begin{document}
@@ -28,7 +28,7 @@
\begin{center}
\huge
\nip{} Manual\\[0.2em]
-\large Version 8.5\\
+\large Version 8.6\\
\vspace{0.5in}
\large
John Cupitt,
diff --git a/doc/src/program.tex b/doc/src/program.tex
index 3ff4a811..5fa53827 100644
--- a/doc/src/program.tex
+++ b/doc/src/program.tex
@@ -821,7 +821,8 @@ \section{Lists and recursion}
\ct{map3 fn l1 l2 l3} & map 3-ary function \ct{fn} over lists \ct{l1},
\ct{l2} and \ct{l3} \\
\ct{member l x} & true if \ct{x} is a member of list \ct{l} \\
-\ct{mkset l} & remove duplicates from list \ct{l} \\
+\ct{mkset eq l} & remove duplicates from list \ct{l} with equality
+ function \ct{eq} \\
\ct{postfix l r} & add element \ct{r} to the end of list \ct{l} \\
\ct{product l} & product of list l \\
\ct{repeat x} & make an infinite list of \ct{x}es \\
@@ -1510,7 +1511,7 @@ \subsection{Workspaces}
\subsection{The \ct{Image} class}
\mylabel{sec:Image}.
-say supports mioxed ops with real, vector and complex constants
+say supports mixed ops with real, vector and complex constants
\subsection{The \ct{Colour} class}
\mylabel{sec:colour}
diff --git a/nip2.appdata.xml b/nip2.appdata.xml
index 22736a9b..c5025b9d 100644
--- a/nip2.appdata.xml
+++ b/nip2.appdata.xml
@@ -22,8 +22,8 @@
- http://www.vips.ecs.soton.ac.uk/images/thumb/Nip2-7.36.png/800px-Nip2-7.36.png
+ https://raw.githubusercontent.com/libvips/nip2/master/doc/src/figs/snap1.jpg
- http://www.vips.ecs.soton.ac.uk
- https://github.com/jcupitt/nip2
+ https://libvips.github.io/libvips
+ https://github.com/libvips/nip2
diff --git a/nip2.spec.in b/nip2.spec.in
index 2511364a..c35132b5 100644
--- a/nip2.spec.in
+++ b/nip2.spec.in
@@ -5,8 +5,8 @@ Summary: Interactive tool for working with large images
Group: Applications/Multimedia
License: GPLv2+
-URL: http://www.vips.ecs.soton.ac.uk/
-Source0: http://www.vips.ecs.soton.ac.uk/supported/7.14/%{name}-%{version}.tar.gz
+URL: https://github.com/jcupitt/nip2
+Source0: https://github.com/jcupitt/nip2/releases
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
@@ -33,9 +33,6 @@ GIMP should be used instead.
%prep
%setup -q
-# CVS dirs
-find . -name CVS -type d -print0 | xargs -0 rm -rf
-
%build
%configure
@@ -120,6 +117,9 @@ rm -rf $RPM_BUILD_ROOT
%changelog
+* Sat Jun 10 2008 John Cupitt - 8.7.0
+- Update URLs
+
* Sat Jul 19 2008 Jesper Friis - 7.15.0-1
- Added this spec file from the Fedora source rpm
diff --git a/po/en_GB.gmo b/po/en_GB.gmo
index 32fea4e6..c6734198 100644
Binary files a/po/en_GB.gmo and b/po/en_GB.gmo differ
diff --git a/share/nip2/compat/8.5/Colour.def b/share/nip2/compat/8.5/Colour.def
new file mode 100644
index 00000000..bb2b7906
--- /dev/null
+++ b/share/nip2/compat/8.5/Colour.def
@@ -0,0 +1,680 @@
+
+Colour_new_item = class
+ Menupullright (_ "_New") (_ "make a patch of colour") {
+ Widget_colour_item = class
+ Menuaction (_ "_Colour") (_ "make a patch of colour") {
+ action = Colour_picker "Lab" [50,0,0];
+ }
+
+ LAB_colour = class
+ Menuaction (_ "CIE Lab _Picker") (_ "pick colour in CIE Lab space") {
+ action = widget "Lab" [50, 0, 0];
+
+ // ab_slice size
+ size = 512;
+
+ // range of values ... +/- 128 for ab
+ range = 256;
+
+ // map xy in slice image to ab and back
+ xy2ab x = x / (size / range) - 128;
+ ab2xy a = (a + 128) * (size / range);
+
+ widget space default_value = class
+ Colour space _result {
+ _vislevel = 3;
+
+ [_L, _a, _b] = default_value;
+ L = Scale "Lightness" 0 100 _L;
+ ab_slice = Image (lab_slice size L.value);
+ point = Mark ab_slice (ab2xy _a) (ab2xy _b);
+
+ _result = [L.value, xy2ab point.left, xy2ab point.top];
+
+ Colour_edit colour_space value = widget colour_space value;
+ }
+ }
+
+ CCT_colour = class
+ Menuaction (_ "Colour from CCT") (_ "pick colour by CCT") {
+ action = widget 6500;
+
+ widget x = class
+ _result {
+ _vislevel = 3;
+
+ T = Scale "CCT" 1800 25000 x;
+
+ _result = colour_from_temp (to_real T);
+
+ Colour_edit space value
+ = widget (temp_from_colour (Colour space value));
+ }
+ }
+}
+
+Colour_to_colour_item = class
+ Menuaction (_ "Con_vert to Colour") (_ "convert anything to a colour") {
+ action x = to_colour x;
+}
+
+#separator
+
+Colour_convert_item = class
+ Menupullright (_ "_Colourspace") (_ "convert to various colour spaces") {
+ spaces = Image_type.image_colour_spaces;
+
+ conv dest x = class
+ _result {
+ _vislevel = 3;
+
+ to = Option_enum (_ "Convert to") spaces (spaces.get_name dest);
+
+ _result = map_unary (colour_transform_to to.value_thing) x;
+ }
+
+ Mono_item = class
+ Menuaction (_ "_Monochrome") (_ "convert to mono colourspace") {
+ action x = conv Image_type.B_W x;
+ }
+
+ sRGB_item = class
+ Menuaction (_ "_sRGB") (_ "convert to sRGB colourspace") {
+ action x = conv Image_type.sRGB x;
+ }
+
+ GREY16_item = class
+ Menuaction (_ "_GREY16") (_ "convert to GREY16 colourspace") {
+ action x = conv Image_type.GREY16 x;
+ }
+
+ RGB16_item = class
+ Menuaction (_ "_RGB16") (_ "convert to RGB16 colourspace") {
+ action x = conv Image_type.RGB16 x;
+ }
+
+ Lab_item = class
+ Menuaction (_ "_Lab") (_ "convert to Lab colourspace (float Lab)") {
+ action x = conv Image_type.LAB x;
+ }
+
+ LabQ_item = class
+ Menuaction (_ "Lab_Q") (_ "convert to LabQ colourspace (32-bit Lab)") {
+ action x = conv Image_type.LABQ x;
+ }
+
+ LabS_item = class
+ Menuaction (_ "Lab_S") (_ "convert to LabS colourspace (48-bit Lab)") {
+ action x = conv Image_type.LABS x;
+ }
+
+ LCh_item = class
+ Menuaction (_ "L_Ch") (_ "convert to LCh colourspace") {
+ action x = conv Image_type.LCH x;
+ }
+
+ XYZ_item = class
+ Menuaction (_ "_XYZ") (_ "convert to XYZ colourspace") {
+ action x = conv Image_type.XYZ x;
+ }
+
+ Yxy_item = class
+ Menuaction (_ "_Yxy") (_ "convert to Yxy colourspace") {
+ action x = conv Image_type.YXY x;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_UCS") (_ "convert to UCS colourspace") {
+ action x = conv Image_type.UCS x;
+ }
+}
+
+/* mark objects as being in various colourspaces
+ */
+Colour_tag_item = class
+ Menupullright (_ "_Tag As")
+ (_ "tag object as being in various colour spaces") {
+ spaces = Image_type.image_colour_spaces;
+
+ tag dest x = class
+ _result {
+ _vislevel = 3;
+
+ to = Option_enum (_ "Tag as") spaces (spaces.get_name dest);
+
+ _result = map_unary (image_set_type to.value_thing) x;
+ }
+
+ Mono_item = class
+ Menuaction (_ "_Monochrome") (_ "tag as being in mono colourspace") {
+ action x = tag Image_type.B_W x;
+ }
+
+ sRGB_item = class
+ Menuaction (_ "_sRGB") (_ "tag as being in sRGB colourspace") {
+ action x = tag Image_type.sRGB x;
+ }
+
+ RGB16_item = class
+ Menuaction (_ "_RGB16") (_ "tag as being in RGB16 colourspace") {
+ action x = tag Image_type.RGB16 x;
+ }
+
+ GREY16_item = class
+ Menuaction (_ "_GREY16") (_ "tag as being in GREY16 colourspace") {
+ action x = tag Image_type.GREY16 x;
+ }
+
+ Lab_item = class
+ Menuaction (_ "_Lab")
+ (_ "tag as being in Lab colourspace (float Lab)") {
+ action x = tag Image_type.LAB x;
+ }
+
+ LabQ_item = class
+ Menuaction (_ "Lab_Q")
+ (_ "tag as being in LabQ colourspace (32-bit Lab)") {
+ action x = tag Image_type.LABQ x;
+ }
+
+ LabS_item = class
+ Menuaction (_ "Lab_S")
+ (_ "tag as being in LabS colourspace (48-bit Lab)") {
+ action x = tag Image_type.LABS x;
+ }
+
+ LCh_item = class
+ Menuaction (_ "L_Ch") (_ "tag as being in LCh colourspace") {
+ action x = tag Image_type.LCH x;
+ }
+
+ XYZ_item = class
+ Menuaction (_ "_XYZ") (_ "tag as being in XYZ colourspace") {
+ action x = tag Image_type.XYZ x;
+ }
+
+ Yxy_item = class
+ Menuaction (_ "_Yxy") (_ "tag as being in Yxy colourspace") {
+ action x = tag Image_type.YXY x;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_UCS") (_ "tag as being in UCS colourspace") {
+ action x = tag Image_type.UCS x;
+ }
+}
+
+Colour_temperature_item = class
+ Menupullright (_ "Te_mperature")
+ (_ "colour temperature conversions") {
+ Whitepoint_item = class
+ Menuaction (_ "_Move Whitepoint") (_ "change whitepoint") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ old_white = Option_enum (_ "Old whitepoint") Whitepoints "D65";
+ new_white = Option_enum (_ "New whitepoint") Whitepoints "D50";
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im' *
+ (new_white.value_thing / old_white.value_thing);
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ D65_to_D50_item = class
+ Menupullright (_ "D_65 to D50") (_ "complex conversion") {
+ XYZ_minimal_item = class
+ Menuaction (_ "_Minimal")
+ (_ "D65 to D50 using the minimal 3x3 matrix in XYZ") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = recomb D652D50_direct im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+
+ Bradford_item = class
+ Menuaction (_ "_Bradford") (_ "D65 to D50 in Bradford cone space") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im_D652D50 im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ D50_to_D65_item = class
+ Menupullright (_ "D_50 to D65") (_ "complex conversion") {
+ XYZ_minimal_item = class
+ Menuaction (_ "_Minimal")
+ (_ "D50 to D65 using the minimal 3x3 matrix in XYZ") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = recomb D502D65_direct im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+
+ Bradford_item = class
+ Menuaction (_ "_Bradford") (_ "D60 to D65 in Bradford cone space") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im_D502D65 im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ Lab_to_D50XYZ_item = class
+ Menuaction (_ "_Lab to D50 XYZ")
+ (_ "Lab to XYZ with a D50 whitepoint") {
+ action x = map_unary (colour_unary im_D50Lab2XYZ) x;
+ }
+
+ D50XYZ_to_Lab_item = class
+ Menuaction (_ "D50 _XYZ to Lab")
+ (_ "XYZ to Lab with a D50 whitepoint") {
+ action x = map_unary (colour_unary im_D50XYZ2Lab) x;
+ }
+
+ sep1 = Menuseparator;
+
+ CCT_item = class
+ Menuaction (_ "Calculate temperature")
+ (_ "estimate CCT using the McCamy approximation") {
+ action z = map_unary temp_from_colour z;
+ }
+
+ Colour_item = Colour_new_item.CCT_colour;
+}
+
+Colour_icc_item = class
+ Menupullright (_ "_ICC") (_ "transform with ICC profiles") {
+ print_profile =
+ "$VIPSHOME/share/$PACKAGE/data/cmyk.icm";
+ monitor_profile =
+ "$VIPSHOME/share/$PACKAGE/data/sRGB.icm";
+ guess_profile image
+ = print_profile,
+ has_type image &&
+ get_type image == Image_type.CMYK &&
+ has_bands image &&
+ get_bands image >= 4
+ = monitor_profile;
+ render_intents = Option_enum (_ "Render intent") Render_intent.names
+ (_ "Absolute");
+
+ Export_item = class
+ Menuaction (_ "_Export") (_ "export from PCS to device space") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ profile = Pathname (_ "Output profile") print_profile;
+ intent = render_intents;
+ depth = Option (_ "Output depth") [_ "8 bit", _ "16 bit"] 0;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_export [8, 16]?depth profile.value
+ intent.value_thing lab
+ {
+ lab = colour_transform_to Image_type.LABQ image;
+ }
+ }
+ }
+ }
+
+ Import_item = class
+ Menuaction (_ "_Import") (_ "import from device space to PCS") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ embedded = Toggle (_ "Use embedded profile if possible") false;
+ profile = Pathname (_ "Default input profile") (guess_profile x);
+ intent = render_intents;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_import_embedded intent.value_thing image,
+ get_header_type "icc-profile-data" image != 0 &&
+ embedded
+ = icc_import profile.value intent.value_thing image;
+ }
+ }
+ }
+
+ Transform_item = class
+ Menuaction (_ "_Transform") (_ "transform between two device spaces") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ in_profile = Pathname (_ "Input profile") (guess_profile x);
+ out_profile = Pathname (_ "Output profile") print_profile;
+ intent = render_intents;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_transform in_profile.value out_profile.value
+ intent.value_thing image;
+ }
+ }
+ }
+
+ AC2RC_item = class
+ Menuaction (_ "_Absolute to Relative")
+ (_ "absolute to relative colorimetry using device profile") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ profile = Pathname (_ "Pick a profile") (guess_profile x);
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_ac2rc profile.value lab
+ {
+ lab = colour_transform_to Image_type.LAB image;
+ }
+ }
+ }
+ }
+}
+
+Colour_rad_item = class
+ Menupullright (_ "_Radiance") (_ "convert to and from Radiance packed format") {
+ Unpack_item = class
+ Menuaction (_ "Unpack")
+ (_ "unpack Radiance format to float") {
+ action x = map_unary rad2float x;
+ }
+
+ Pack_item = class
+ Menuaction (_ "Pack")
+ (_ "pack 3-band float to Radiance format") {
+ action x = map_unary float2rad x;
+ }
+}
+
+#separator
+
+Colour_dE_item = class
+ Menupullright (_ "_Difference") (_ "calculate colour difference") {
+ /* Apply a converter to an object ... convert image or colour (since
+ * we can guess the colour space we're converting from), don't convert
+ * matrix or vector (since we can't tell ... assume it's in the right
+ * space already).
+ */
+ apply_cvt cvt x
+ = cvt x,
+ is_Image x || is_Colour x || is_image x
+ = x;
+
+ diff cvt in1 in2 = abs_vec (apply_cvt cvt in1 - apply_cvt cvt in2);
+
+ /* Converter to LAB.
+ */
+ lab_cvt = colour_transform_to Image_type.LAB;
+
+ /* Converter to UCS ... plain UCS is Ch form, so we go LAB again after
+ * to make sure we get a rectangular coord system.
+ */
+ ucs_cvt = colour_transform Image_type.LCH Image_type.LAB @
+ colour_transform_to Image_type.UCS;
+
+ CIEdE76_item = class
+ Menuaction (_ "CIE dE _76")
+ (_ "calculate CIE dE 1976 for two objects") {
+ action a b = map_binary (diff lab_cvt) a b;
+ }
+
+ CIEdE00_item = class
+ Menuaction (_ "CIE dE _00")
+ (_ "calculate CIE dE 2000 for two objects") {
+ action a b = map_binary
+ (colour_binary (_ "im_dE00_fromLab") im_dE00_fromLab) a b;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_CMC(l:l)") (_ "calculate CMC(l:l) for two objects") {
+ action a b = map_binary (diff ucs_cvt) a b;
+ }
+}
+
+Colour_adjust_item = class
+ Menupullright (_ "_Adjust") (_ "alter colours in various ways") {
+ Recombination_item = class
+ Menuaction (_ "_Recombination")
+ (_ "recombine colour with an editable matrix") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ matrix
+ = Matrix_rec (identity_matrix (bands x))
+ {
+ // try to guess a sensible value for the size of the
+ // matrix
+ bands x
+ = x.bands, is_Image x || is_Colour x
+ = x.width, is_Matrix x
+ = bands x.value?0, is_Group x
+ = x.bands, has_member "bands" x
+ = 3;
+ }
+
+ _result = map_unary (recomb matrix) x;
+ }
+ }
+
+ Cast_item = class
+ Menuaction (_ "_Cast") (_ "displace neutral axis in CIE Lab") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gr = Scale "Green-red" (-20) 20 0;
+ by = Scale "Blue-yellow" (-20) 20 0;
+
+ _result
+ = map_unary adjust_cast x
+ {
+ adjust_cast in
+ = colour_transform_to (get_type in) in''
+ {
+ in' = colour_transform_to Image_type.LAB in;
+ in'' = in' +
+ Vector [0, gr.value, by.value];
+ }
+ }
+ }
+ }
+
+ HSB_item = class
+ Menuaction (_ "_HSB") (_ "adjust hue-saturation-brightness in LCh") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ h = Scale "Hue" 0 360 0;
+ s = Scale "Saturation" 0.01 5 1;
+ b = Scale "Brightness" 0.01 5 1;
+
+ _result
+ = map_unary adjust_hsb x
+ {
+ adjust_hsb in
+ = colour_transform_to (get_type in) in''
+ {
+ in' = colour_transform_to Image_type.LCH in;
+ in'' = in' * Vector [b.value, s.value, 1] +
+ Vector [0, 0, h.value];
+ }
+ }
+ }
+ }
+}
+
+Colour_similar_item = class
+ Menuaction (_ "_Similar Colour") (_ "find pixels with a similar colour") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ target_colour = Colour_picker "Lab" [50, 0, 0];
+ t = Scale "dE threshold" 0 100 10;
+
+ _result
+ = map_unary match x
+ {
+ match in
+ = abs_vec (in' - target) < t
+ {
+ target = colour_transform_to Image_type.LAB target_colour;
+ in' = colour_transform_to Image_type.LAB in;
+ }
+ }
+ }
+}
+
+#separator
+
+Colour_chart_to_matrix_item = class
+ Menuaction (_ "_Measure Colour Chart")
+ (_ "measure average pixel values for a colour chart image") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pacross = Expression (_ "Patches across chart") 6;
+ pdown = Expression (_ "Patches down chart") 4;
+ measure = Scale (_ "Measure area (%)") 1 100 50;
+
+ // get a representative image from an arg
+ get_image x
+ = get_image x.value?0, is_Group x
+ = x;
+
+ _im = get_image x;
+ sample = measure_draw (to_real pacross) (to_real pdown)
+ (to_real measure) _im;
+
+ _result
+ = map_unary chart x
+ {
+ chart in
+ = measure_sample (to_real pacross) (to_real pdown)
+ (to_real measure) in;
+ }
+ }
+}
+
+Colour_matrix_to_chart_item = class
+ Menuaction (_ "Make Synth_etic Colour Chart")
+ (_ "make a colour chart image from a matrix of measurements") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pacross = Expression (_ "Patches across chart") 6;
+ pdown = Expression (_ "Patches down chart") 4;
+ pwidth = Expression (_ "Patch width in pixels") 50;
+ pheight = Expression (_ "Patch height in pixels") 50;
+ bwidth = Expression (_ "Border between patches") 0;
+
+ _result
+ = map_unary build_chart x
+ {
+ build_chart in
+ = Image (imagearray_assemble
+ (to_real bwidth) (to_real bwidth) patch_table)
+ {
+ // patch numbers for row starts
+ rowstart = map (multiply (to_real pacross))
+ [0 .. to_real pdown - 1];
+
+ // assemble patches ... each one a pixel value
+ patches = map (take (to_real pacross))
+ (map (converse drop in.value) rowstart);
+
+ // make an n-band constant image from eg. [1,2,3]
+ // we don't know the format .. use sRGB (well, why not?)
+ patch v = image_new (to_real pwidth) (to_real pheight) (len v)
+ Image_format.FLOAT Image_coding.NOCODING
+ Image_type.sRGB (Vector v) 0 0;
+
+ // make an image for each patch
+ patch_table = map (map patch) patches;
+ }
+ }
+ }
+}
+
+Colour_plot_ab_scatter_item = class
+ Menuaction (_ "_Plot ab Scatter") (_ "plot an ab scatter histogram") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bins = Expression (_ "Number of bins on each axis") 8;
+
+ _result
+ = map_unary plot_scatter x
+ {
+ plot_scatter in
+ = Image (bg * (((90 / mx) * hist) ++ blk))
+ {
+ lab = colour_transform_to Image_type.LAB in.value;
+ ab = (unsigned char) ((lab?1 ++ lab?2) + 128);
+ hist = hist_find_nD bins.expr ab;
+ mx = max hist;
+ bg = lab_slice bins.expr 1;
+ blk = 1 + im_black (to_real bins) (to_real bins) 2;
+ }
+ }
+ }
+}
diff --git a/share/nip2/compat/8.5/Filter.def b/share/nip2/compat/8.5/Filter.def
new file mode 100644
index 00000000..a8ac20c0
--- /dev/null
+++ b/share/nip2/compat/8.5/Filter.def
@@ -0,0 +1,1694 @@
+Filter_conv_item = class
+ Menupullright "_Convolution" "various spatial convolution filters" {
+ /* Some useful masks.
+ */
+ filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
+ filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]];
+ filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]];
+ filter_laplacian = Matrix_con 1 128
+ [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]];
+ filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]];
+ filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]];
+
+ Blur_item = class
+ Menuaction "_Blur" "3x3 blur of image" {
+ action x = map_unary (conv filter_blur) x;
+ }
+
+ Sharpen_item = class
+ Menuaction "_Sharpen" "3x3 sharpen of image" {
+ action x = map_unary (conv filter_sharp) x;
+ }
+
+ Emboss_item = class
+ Menuaction "_Emboss" "1 pixel displace emboss" {
+ action x = map_unary (conv filter_emboss) x;
+ }
+
+ Laplacian_item = class
+ Menuaction "_Laplacian" "3x3 laplacian edge detect" {
+ action x = map_unary (conv filter_laplacian) x;
+ }
+
+ Sobel_item = class
+ Menuaction "So_bel" "3x3 Sobel edge detect" {
+ action x
+ = map_unary sobel x
+ {
+ sobel im
+ = abs (a - 128) + abs (b - 128)
+ {
+ a = conv filter_sobel im;
+ b = conv (rot270 filter_sobel) im;
+ }
+ }
+ }
+
+/* 3x3 line detect of image
+diagonals should be scaled down by root(2) I guess
+Kirk
+*/
+ Linedet_item = class
+ Menuaction "Li_ne Detect" "3x3 line detect" {
+ action x
+ = map_unary lindet x
+ {
+ lindet im
+ = foldr1 max_pair images
+ {
+ masks = take 4 (iterate rot45 filter_lindet);
+ images = map (converse conv im) masks;
+ }
+ }
+ }
+
+ Usharp_item = class
+ Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ size = Option "Radius" [
+ "3 pixels",
+ "5 pixels",
+ "7 pixels",
+ "9 pixels",
+ "11 pixels",
+ "51 pixels"
+ ] 0;
+
+ st = Scale "Smoothness threshold" 0 5 2;
+ bm = Scale "Brighten by at most" 1 50 10;
+ dm = Scale "Darken by at most" 1 50 20;
+ fs = Scale "Sharpen flat areas by" 0 5 0.5;
+ js = Scale "Sharpen jaggy areas by" 0 5 1;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = Image in'''
+ {
+ in' = colour_transform_to Image_type.LABS in.value;
+ in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
+ in''' = colour_transform_to (get_type in) in'';
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Custom_blur_item = class
+ Menuaction "Custom B_lur / Sharpen"
+ "blur or sharpen with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ type = Option "Type" ["Blur", "Sharpen"] 0;
+ r = Scale "Radius" 1 100 1;
+ fac = Scale "Amount" 0 1 1;
+ layers = Scale "Layers" 1 100 10;
+ shape = Option "Mask shape" [
+ "Square",
+ "Gaussian"
+ ] 0;
+ prec = Option "Precision" ["Int", "Float", "Approximate"] 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = clip2fmt blur.format proc
+ {
+ mask
+ = matrix_blur r.value, shape.value == 0
+ = matrix_gaussian_blur r.value;
+ blur = [convsep, convsepf, aconvsep layers]?prec mask in;
+ proc
+ = in + fac * (in - blur), type == 1
+ = blur * fac + in * (1 - fac);
+ }
+ }
+ }
+ }
+
+ Custom_conv_item = class
+ Menuaction "Custom C_onvolution"
+ "convolution filter with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
+ separable
+ = Toggle "Seperable convolution" false,
+ matrix.width == 1 || matrix.height == 1
+ = false;
+ type = Option "Convolution type" ["Int", "Float"] 0;
+ rotate = Option "Rotate" [
+ "Don't rotate",
+ "4 x 45 degrees",
+ "8 x 45 degrees",
+ "2 x 90 degrees"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = in.Image in'
+ {
+ conv_fn
+ = im_lindetect, !separable && type == 0 && rotate == 1
+ = im_compass, !separable && type == 0 && rotate == 2
+ = im_gradient, !separable && type == 0 && rotate == 3
+ = im_conv, !separable && type == 0
+ = im_convsep, separable && type == 0
+ = im_conv_f, !separable && type == 1
+ = im_convsep_f, separable && type == 1
+ = error "boink!";
+ in' = conv_fn in.value matrix;
+ }
+ }
+ }
+ }
+}
+
+Filter_rank_item = class
+ Menupullright "_Rank" "various rank filters" {
+ Median_item = class
+ Menuaction "_Median" "3x3 median rank filter" {
+ action x = map_unary (rank 3 3 4) x;
+ }
+
+ Image_rank_item = class
+ Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ select
+ = Expression "Rank" ((int) (guess_size / 2))
+ {
+ guess_size
+ = len x, is_list x
+ = len x.value, is_Group x
+ = 0;
+ }
+
+ // can't really iterate over groups ... since we allow a group
+ // argument
+ _result = rank_image select x;
+ }
+ }
+
+ Custom_rank_item = class
+ Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ window_width = Expression "Window width" 3;
+ window_height = Expression "Window height" 3;
+ select = Expression "Rank"
+ ((int) ((to_real window_width * to_real window_height) / 2));
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = rank window_width window_height select in;
+ }
+ }
+ }
+}
+
+Filter_morphology_item = class
+ Menupullright "_Morphology" "various morphological filters" {
+ /* Some useful masks.
+ */
+ mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
+ mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
+ mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
+ thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];
+
+ Threshold_item = Select_item.Threshold_item;
+
+ sep1 = Menuseparator;
+
+ Dilate_item = class
+ Menupullright "_Dilate" "morphological dilate" {
+ Dilate8_item = class
+ Menuaction "_8-connected" "dilate with an 8-connected mask" {
+ action x = map_unary (dilate mask8) x;
+ }
+
+ Dilate4_item = class
+ Menuaction "_4-connected" "dilate with a 4-connected mask" {
+ action x = map_unary (dilate mask4) x;
+ }
+ }
+
+ Erode_item = class
+ Menupullright "_Erode" "morphological erode" {
+ Erode8_item = class
+ Menuaction "_8-connected" "erode with an 8-connected mask" {
+ action x = map_unary (erode mask8) x;
+ }
+
+ Erode4_item = class
+ Menuaction "_4-connected" "erode with a 4-connected mask" {
+ action x = map_unary (erode mask4) x;
+ }
+ }
+
+ Custom_morph_item = class
+ Menuaction "Custom _Morphology"
+ "convolution morphological operator" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ mask = mask4;
+ type = Option "Operation" ["Erode", "Dilate"] 1;
+ apply = Expression "Number of times to apply mask" 1;
+
+ _result
+ = map_unary morph x
+ {
+ morph image
+ = Image value'
+ {
+ fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);
+
+ value'
+ = im_erode image.value fatmask, type.value == 0
+ = im_dilate image.value fatmask;
+ }
+ }
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Open_item = class
+ Menuaction "_Open" "open with an 8-connected mask" {
+ action x = map_unary (dilate mask8 @ erode mask8) x;
+ }
+
+ Close_item = class
+ Menuaction "_Close" "close with an 8-connected mask" {
+ action x = map_unary (erode mask8 @ dilate mask8) x;
+ }
+
+ Clean_item = class
+ Menuaction "C_lean" "remove 8-connected isolated points" {
+ action x
+ = map_unary clean x
+ {
+ clean x = x ^ erode mask1 x;
+ }
+ }
+
+ Thin_item = class
+ Menuaction "_Thin" "thin once" {
+ action x
+ = map_unary thinall x
+ {
+ masks = take 8 (iterate rot45 thin);
+ thin1 m x = x ^ erode m x;
+ thinall x = foldr thin1 x masks;
+ }
+ }
+
+}
+
+Filter_fourier_item = class
+ Menupullright "_Fourier" "various Fourier filters" {
+ preview_size = 64;
+
+ sense_option = Option "Sense" [
+ "Pass",
+ "Reject"
+ ] 0;
+
+ // make a visualisation image
+ make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
+ (im_create_fmask preview_size preview_size);
+
+ // make the process function
+ process fn in
+ = (Image @ fn) (im_flt_image_freq in.value);
+
+ New_ideal_item = class
+ Menupullright "_Ideal" "various ideal Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f sense.value fc.value 0 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 6) fc.value
+ rw.value 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 12) fcx.value fcy.value
+ r.value 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+
+ New_gaussian_item = class
+ Menupullright "_Gaussian" "various Gaussian Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 4) fc.value
+ ac.value 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 10) fc.value
+ rw.value ac.value 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 16) fcx.value fcy.value
+ r.value ac.value 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+
+ New_butterworth_item = class
+ Menupullright "_Butterworth"
+ "various Butterworth Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 2) o.value fc.value ac.value
+ 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 8) o.value fc.value rw.value
+ ac.value 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 14) o.value fcx.value fcy.value
+ r.value ac.value;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+}
+
+Filter_enhance_item = class
+ Menupullright "_Enhance" "various enhancement filters" {
+ Falsecolour_item = class
+ Menuaction "_False Colour" "false colour a mono image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ o = Scale "Offset" (-255) 255 0;
+ clip = Toggle "Clip colour range" false;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = falsecolour mono''
+ {
+ mono = colour_transform_to Image_type.B_W im;
+ mono' = mono + o;
+ mono''
+ = (unsigned char) mono', clip
+ = (unsigned char) (mono' & 0xff);
+ }
+ }
+ }
+ }
+
+ Statistical_diff_item = class
+ Menuaction "_Statistical Difference"
+ "statistical difference of an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ wsize = Expression "Window size" 11;
+ tmean = Expression "Target mean" 128;
+ mean_weight = Scale "Mean weight" 0 1 0.8;
+ tdev = Expression "Target deviation" 50;
+ dev_weight = Scale "Deviation weight" 0 1 0.8;
+ border = Toggle "Output image matches input image in size" true;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = Image in''
+ {
+ in' = colour_transform_to Image_type.B_W in.value;
+ fn
+ = im_stdif, border
+ = im_stdif_raw;
+ in'' = fn in'
+ mean_weight.value tmean.expr
+ dev_weight.value tdev.expr
+ wsize.expr wsize.expr;
+ }
+ }
+ }
+ }
+
+ Hist_equal_item = class
+ Menupullright "_Equalise Histogram" "equalise contrast" {
+ Global_item = class
+ Menuaction "_Global" "equalise contrast globally" {
+ action x = map_unary hist_equalize x;
+ }
+
+ Local_item = class
+ Menuaction "_Local" "equalise contrast within a roving window" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ window_width = Expression "Window width" 20;
+ window_height = Expression "Window height" 20;
+ max_slope = Scale "Maxium slope" 0 10 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = hist_equalize_local
+ window_width
+ window_height
+ max_slope in;
+ }
+ }
+ }
+ }
+}
+
+Filter_correlate_item = class
+ Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
+ Correlate_item = class
+ Menuaction "_Correlate" "calculate correlation coefficient" {
+ action a b
+ = map_binary corr a b
+ {
+ corr a b
+ = correlate a b,
+ a.width <= b.width && a.height <= b.height
+ = correlate b a;
+ }
+ }
+
+ Correlate_fast_item = class
+ Menuaction "_Simple Difference"
+ "calculate sum of squares of differences" {
+ action a b
+ = map_binary corr a b
+ {
+ corr a b
+ = correlate_fast a b,
+ a.width <= b.width && a.height <= b.height
+ = correlate_fast b a;
+ }
+ }
+}
+
+Filter_hough_item = class
+ Menupullright "_Hough Transform" "transform to parameter space" {
+ Line_item = class
+ Menuaction "_Line" "find straight line Hough transform" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ pspace_width = Expression "Parameter space width" 64;
+ pspace_height = Expression "Parameter space height" 64;
+
+ _result
+ = map_unary line a
+ {
+ line a
+ = hough_line
+ (to_real pspace_width) (to_real pspace_height) a;
+ }
+ }
+ }
+
+ Circle_item = class
+ Menuaction "_Circle" "find circle Hough transform" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ scale = Expression "Scale down parameter space by" 10;
+ min_radius = Expression "Minimum radius" 10;
+ max_radius = Expression "Maximum radius" 30;
+
+ _result
+ = map_unary circle a
+ {
+ circle a
+ = hough_circle (to_real scale) (to_real min_radius)
+ (to_real max_radius) a;
+ }
+ }
+ }
+}
+
+Filter_coordinate_item = class
+ Menupullright "_Coordinate Transform" "various coordinate transforms" {
+ // run a function which wants a complex arg on a non-complex two-band
+ // image
+ run_cmplx fn x
+ = re x' ++ im x'
+ {
+ x' = fn (x?0, x?1);
+ }
+
+ Polar_item = class
+ Menuaction "_Polar" "transform to polar coordinates" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary to_polar a
+ {
+ to_polar im
+ = mapim interp.value map' im
+ {
+ // xy image, origin in the centre, scaled to fit image to
+ // a circle
+ xy = make_xy im.width im.height;
+ xy' = xy - Vector [im.width / 2, im.height / 2];
+ scale = min [im.width, im.height] / im.width;
+ xy'' = 2 * xy' / scale;
+
+ // to polar, scale vertical axis to 360 degrees
+ map = run_cmplx polar xy'';
+ map' = map * Vector [1, im.height / 360];
+ }
+ }
+ }
+ }
+
+ Rectangular_item = class
+ Menuaction "_Rectangular" "transform to rectangular coordinates" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary to_rect a
+ {
+ to_rect im
+ = mapim interp.value map'' im
+ {
+ // xy image, vertical scaled to 360 degrees
+ xy = make_xy im.width im.height;
+ xy' = xy * Vector [1, 360 / im.height];
+
+ // to rect, scale to image rect
+ map = run_cmplx rectangular xy';
+ scale = min [im.width, im.height] / im.width;
+ map' = map * scale / 2;
+
+ map'' = map' + Vector [im.width / 2, im.height / 2];
+ }
+ }
+ }
+ }
+}
+
+#separator
+
+Filter_tilt_item = class
+ Menupullright "Ti_lt Brightness" "tilt brightness" {
+ Left_right_item = class
+ Menuaction "_Left to Right" "linear left-right brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Left-right tilt" (-1) 1 0;
+
+ _result
+ = map_unary tilt_lr x
+ {
+ tilt_lr image
+ = image * scale
+ {
+ ramp = im_fgrey image.width image.height;
+ scale = (ramp - 0.5) * tilt + 1;
+ }
+ }
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "linear top-bottom brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Top-bottom tilt" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ ramp = rot90
+ (im_fgrey image.height image.width);
+ scale = (ramp - 0.5) * tilt + 1;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Left_right_cos_item = class
+ Menuaction "Cosine Left-_right" "cosine left-right brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Left-right tilt" (-1) 1 0;
+ shift = Scale "Shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_lr x
+ {
+ tilt_lr image
+ = image * scale
+ {
+ ramp = im_fgrey image.width image.height - 0.5 -
+ shift.value;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+
+ Top_bottom_cos_item = class
+ Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Top-bottom tilt" (-1) 1 0;
+ shift = Scale "Shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
+ shift.value;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Circular_item = class
+ Menuaction "_Circular" "circular brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Tilt" (-1) 1 0;
+ hshift = Scale "Horizontal shift by" (-1) 1 0;
+ vshift = Scale "Vertical shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ hramp = im_fgrey image.width image.height - 0.5 -
+ hshift.value;
+ vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
+ vshift.value;
+ ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+}
+
+Filter_blend_item = class
+ Menupullright "_Blend" "blend objects together" {
+ Scale_blend_item = class
+ Menuaction "_Scale" "blend two objects together with a scale" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ p = Scale "Blend position" 0 1 0.5;
+
+ _result
+ = map_binary process a b
+ {
+ process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
+ }
+ }
+ }
+
+ Image_blend_item = class
+ Menuaction "_Image" "use an image to blend two objects" {
+ action a b c = class
+ _result {
+ _vislevel = 3;
+
+ i = Toggle "Invert mask" false;
+
+ _result
+ = map_trinary process a b c
+ {
+ process a b c
+ = blend condition in1 in2, !i
+ = blend (invert condition) in1 in2
+ {
+ compare a b
+ // prefer image as the condition
+ = false,
+ !has_image a && has_image b
+ // prefer mono images as the condition
+ = false,
+ has_bands a && has_bands b &&
+ get_bands a > 1 && get_bands b == 1
+ // prefer uchar as the condition
+ = false,
+ has_format a && has_format b &&
+ get_format a > Image_format.UCHAR &&
+ get_format b == Image_format.UCHAR
+ = true;
+ [condition, in1, in2] = sortc compare [a, b, c];
+ }
+ }
+ }
+ }
+
+ Line_blend_item = class
+ Menuaction "_Along Line"
+ "blend between image a and image b along a line" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Left to Right",
+ "Top to Bottom"
+ ] 0;
+ blend_position = Scale "Blend position" 0 1 0.5;
+ blend_width = Scale "Blend width" 0 1 0.05;
+
+ _result
+ = map_binary process a b
+ {
+ process a b
+ = blend (Image condition) b a
+ {
+ output_width = max_pair a.width b.width;
+ output_height = max_pair a.height b.height;
+ range
+ = output_width, orientation == 0
+ = output_height;
+ blend_position'
+ = floor (range * blend_position.value);
+ blend_width'
+ = 1, blend_width.value == 0
+ = floor (range * blend_width.value);
+ start = blend_position' - blend_width' / 2;
+
+ background = (make_xy output_width output_height) >=
+ blend_position';
+ ramp
+ = im_grey blend_width' output_height, orientation == 0
+ = rot90 (im_grey blend_width' output_width);
+ condition
+ = insert_noexpand start 0 ramp background?0,
+ orientation == 0
+ = insert_noexpand 0 start ramp background?1;
+ }
+ }
+ }
+ }
+
+ Blend_alpha_item = class
+ Menuaction "Blend _Alpha" "blend images with optional alpha channels" {
+ // usage: layerit foreground background
+ // input images must be either 1 or 3 bands, optionally + 1 band
+ // which is used as the alpha channel
+ // rich lott
+
+ scale_mask im opacity
+ = (unsigned char) (to_real opacity / 255 * im);
+
+ // to mono
+ intensity = colour_transform_to Image_type.B_W;
+
+ // All the blend functions
+ // I am grateful to this page
+ // http://www.pegtop.net/delphi/blendmodes/
+ // for most of the formulae.
+
+ blend_normal mask opacity fg bg
+ = blend (scale_mask mask opacity) fg bg;
+
+ blend_iflighter mask opacity fg bg
+ = blend (if fg' > bg' then mask' else 0) fg bg
+ {
+ fg' = intensity fg;
+ bg' = intensity bg;
+ mask' = scale_mask mask opacity ;
+ }
+
+ blend_ifdarker mask opacity fg bg
+ = blend (if fg' < bg' then mask' else 0) fg bg
+ {
+ fg' = intensity fg ;
+ bg' = intensity bg ;
+ mask' = scale_mask mask opacity ;
+ }
+
+ blend_multiply mask opacity fg bg
+ = blend (scale_mask mask opacity) fg' bg
+ {
+ fg' = fg / 255 * bg;
+ }
+
+ blend_add mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = opacity / 255 * fg + bg;
+ }
+
+ blend_subtract mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = bg - opacity / 255 * fg;
+ }
+
+ blend_screen mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
+ }
+
+ blend_burn mask opacity fg bg
+ = blend mask fg'' bg
+ {
+ // fades to white which has no effect.
+ fg' = (255 - opacity) + opacity * fg / 255;
+ fg'' = 255 - 255 * (255 - bg) / fg';
+ }
+
+ blend_softlight mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
+ }
+
+ blend_hardlight mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg'
+ = 2 / 255 * fg * bg, bg < 129
+ = 255 - 2 * (255 - bg) * (255 - fg) / 255;
+ }
+
+ blend_lighten mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = if bg < fg then fg else bg;
+ }
+
+ blend_darken mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = if bg > fg then fg else bg;
+ }
+
+ blend_dodge mask opacity fg bg
+ = blend mask fg'' bg
+ {
+ // one added to avoid divide by zero
+ fg' = 1 + 255 - (opacity / 255 * fg);
+ fg'' = bg * 255 / fg';
+ }
+
+ blend_reflect mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = bg * bg / (255 - fg);
+ }
+
+ blend_freeze mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
+ }
+
+ blend_or mask opacity fg bg
+ = bg | (unsigned char) fg'
+ {
+ mask' = scale_mask mask opacity;
+ fg' = fg * mask' / 255;
+ }
+
+ blend_and mask opacity fg bg
+ = bg & (unsigned char) fg'
+ {
+ mask' = scale_mask mask opacity;
+ fg' = fg * mask' / 255;
+ }
+
+ // blend types
+ NORMAL = 0;
+ IFLIGHTER = 1;
+ IFDARKER = 2;
+ MULTIPLY = 3;
+ ADD = 4;
+ SUBTRACT = 5;
+ SCREEN = 6;
+ BURN = 7;
+ DODGE = 8;
+ HARDLIGHT = 9;
+ SOFTLIGHT = 10;
+ LIGHTEN = 11;
+ DARKEN = 12;
+ REFLECT = 13;
+ FREEZE = 14;
+ OR = 15;
+ AND = 16;
+
+ // names we show the user for blend types
+ names = Enum [
+ _ "Normal" => NORMAL,
+ _ "If Lighter" => IFLIGHTER,
+ _ "If Darker" => IFDARKER,
+ _ "Multiply" => MULTIPLY,
+ _ "Add" => ADD,
+ _ "Subtract" => SUBTRACT,
+ _ "Screen" => SCREEN,
+ _ "Burn" => BURN,
+ _ "Soft Light" => SOFTLIGHT,
+ _ "Hard Light" => HARDLIGHT,
+ _ "Lighten" => LIGHTEN,
+ _ "Darken" => DARKEN,
+ _ "Dodge" => DODGE,
+ _ "Reflect" => REFLECT,
+ _ "Freeze" => FREEZE,
+ _ "Bitwise OR" => OR,
+ _ "Bitwise AND" => AND
+ ];
+
+ // functions we call for each blend type
+ actions = Table [
+ [NORMAL, blend_normal],
+ [IFLIGHTER, blend_iflighter],
+ [IFDARKER, blend_ifdarker],
+ [MULTIPLY, blend_multiply],
+ [ADD, blend_add],
+ [SUBTRACT, blend_subtract],
+ [SCREEN, blend_screen],
+ [BURN, blend_burn],
+ [SOFTLIGHT, blend_softlight],
+ [HARDLIGHT, blend_hardlight],
+ [LIGHTEN, blend_lighten],
+ [DARKEN, blend_darken],
+ [DODGE, blend_dodge],
+ [REFLECT, blend_reflect],
+ [FREEZE, blend_freeze],
+ [OR, blend_or],
+ [AND, blend_and]
+ ];
+
+ // make sure im has an alpha channel (set opaque if it hasn't)
+ put_alpha im
+ = im, im.bands == 4 || im.bands == 2
+ = im ++ 255;
+
+ // make sure im has no alpha channel
+ lose_alpha im
+ = extract_bands 0 3 im, im.bands == 4
+ = im?0, im.bands == 2
+ = im;
+
+ // does im have al alpha channel?
+ has_alpha im = im.bands == 2 || im.bands == 4;
+
+ // get the alpha (set opaque if no alpha)
+ get_alpha img
+ = img'?3, img.bands == 4
+ = img'?1
+ {
+ img' = put_alpha img;
+ }
+
+ // add an alpha ... cast the alpha image to match the main image
+ append_alpha im alpha
+ = im ++ clip2fmt im.format alpha;
+
+ // makes fg the same size as bg, displaced with u, v pixel offset
+ moveit fg bg u v
+ = insert_noexpand u v fg bg'
+ {
+ bg' = image_new bg.width bg.height
+ fg.bands fg.format fg.coding fg.type 0 0 0;
+ }
+
+ action bg fg = class
+ _value {
+ _vislevel = 3;
+
+ method = Option_enum "Blend mode" names "Normal";
+ opacity = Scale "Opacity" 0 255 255;
+ hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0;
+ vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0;
+
+ _value
+ = append_alpha blended merged_alpha, has_alpha bg
+ = blended
+ {
+ // displace and resize fg (need to displace alpha too)
+ fg' = moveit (put_alpha fg) bg hmove vmove;
+
+ // transform to sRGB
+ fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg');
+ bg' = colour_transform_to Image_type.sRGB (lose_alpha bg);
+
+ // alphas merged
+ merged_alpha = get_alpha bg | get_alpha fg';
+
+ // blend together
+ blended = (actions.lookup 0 1 method.value_thing)
+ (get_alpha fg') opacity.value fg'' bg';
+ }
+ }
+ }
+}
+
+Filter_overlay_header_item = class
+ Menuaction "_Overlay"
+ "make a colour overlay of two monochrome images" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ colour = Option "Colour overlay as" [
+ _ "Green over Red",
+ _ "Blue over Red",
+ _ "Red over Green",
+ _ "Red over Blue",
+ _ "Blue over Green",
+ _ "Green over Blue"
+ ] 0;
+
+ _result
+ = map_binary overlay a b
+ {
+ overlay a b
+ = image_set_type Image_type.sRGB
+ [(a' ++ b' ++ 0),
+ (a' ++ 0 ++ b'),
+ (b' ++ a' ++ 0),
+ (b' ++ 0 ++ a'),
+ (0 ++ a' ++ b'),
+ (0 ++ b' ++ a')]?colour
+ {
+ a' = colour_transform_to Image_type.B_W a;
+ b' = colour_transform_to Image_type.B_W b;
+ }
+ }
+ }
+}
+
+Filter_colourize_item = class
+ Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ tint = Scale "Tint" 0 1 0.6;
+
+ _result
+ = map_binary tintit a b
+ {
+ tintit a b
+ = colour_transform_to (get_type colour) colourized'
+ {
+ // get the mono thing first
+ [mono, colour] =
+ sortc (const (is_colour_type @ get_type)) [a, b];
+
+ colour' = tint * colour_transform_to Image_type.LAB colour;
+ mono' = colour_transform_to Image_type.B_W mono;
+ colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
+ colourized' = image_set_type Image_type.LAB colourized;
+ }
+ }
+ }
+}
+
+Filter_browse_multiband_item = class
+ Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
+ Bandwise_item = class
+ Menuaction "B_andwise" "browse through the bands of a multiband image" {
+ action image = class
+ _result {
+ _vislevel = 3;
+
+ band = Scale "Band" 0 (image.bands - 1) 0;
+ display = Option "Display as" [
+ _ "Grey",
+ _ "Green over Red",
+ _ "Blue over Red",
+ _ "Red over Green",
+ _ "Red over Blue",
+ _ "Blue over Green",
+ _ "Green over Blue"
+ ] 0;
+
+ _result
+ = output
+ {
+ down = (int) band.value;
+ up = down + 1;
+ remainder = band.value - down;
+
+ fade x a
+ = Vector [0], x == 0
+ = a * x;
+
+ a = fade remainder image?up;
+ b = fade (1 - remainder) image?down;
+
+ output = [
+ a + b,
+ a ++ b ++ 0,
+ a ++ 0 ++ b,
+ b ++ a ++ 0,
+ b ++ 0 ++ a,
+ 0 ++ a ++ b,
+ 0 ++ b ++ a
+ ] ? display;
+ }
+ }
+ }
+
+ Bitwise_item = class
+ Menuaction "Bi_twise" "browse through the bits of an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bit
+ = Islider "Bit" 0 (nbits - 1) (nbits - 1)
+ {
+ nbits
+ = x.bits, is_Image x
+ = 8;
+ Islider c f t v = class
+ scope.Scale c f t ((int) v) {
+ Scale = Islider;
+ }
+ }
+
+ _result
+ = map_unary process x
+ {
+ process im = (im & (0x1 << bit.value)) != 0;
+ }
+ }
+ }
+}
+
+#separator
+
+Filter_negative_item = class
+ Menuaction "Photographic _Negative" "swap black and white" {
+ action x
+ = map_unary invert x
+ {
+ invert in
+ = clip2fmt in.format (colour_transform_to (get_type in) rgb')
+ {
+ rgb = colour_transform_to Image_type.sRGB in;
+ rgb' = 255 - rgb;
+ }
+ }
+}
+
+Filter_solarize_item = class
+ Menuaction "_Solarise" "invert colours above a threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ kink = Scale "Kink" 0 1 0.5;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = hist_map tab'''' image
+ {
+ // max pixel value for this format
+ mx = Image_format.maxval image.format;
+
+ // make a LUT ... just 8 and 16 bit
+ tab
+ = im_identity_ushort image.bands mx,
+ image.format ==
+ Image_format.USHORT
+ = im_identity image.bands;
+ tab' = Image tab;
+
+ // make basic ^ shape
+ tab''
+ = tab' * (1 / kink), tab' < mx * kink
+ = (mx - tab') / (1 - kink);
+ tab''' = clip2fmt image.format tab'';
+
+ // smooth a bit
+ mask = matrix_blur (tab'''.width / 8);
+ tab'''' = convsep mask tab''';
+ }
+ }
+ }
+}
+
+Filter_diffuse_glow_item = class
+ Menuaction "_Diffuse Glow" "add a halo to highlights" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ r = Scale "Radius" 0 50 5;
+ highlights = Scale "Highlights" 0 100 95;
+ glow = Scale "Glow" 0 1 0.5;
+ colour = Colour_new_item.Widget_colour_item.action;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = image'
+ {
+ mono = (unsigned char) (colour_transform_to
+ Image_type.B_W image);
+ thresh = hist_thresh (highlights.value / 100) mono;
+ mask = mono > thresh;
+ blur = convsep (matrix_gaussian_blur r.value) mask;
+ colour' = colour_transform_to image.type colour;
+ image' = image + colour' * glow * (blur / 255);
+ }
+ }
+ }
+}
+
+Filter_drop_shadow_item = class
+ Menuaction "Drop S_hadow" "add a drop shadow to an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sx = Scale "Horizontal shadow" (-50) 50 5;
+ sy = Scale "Vertical shadow" (-50) 50 5;
+ ss = Scale "Shadow softness" 0 20 5;
+ bg_colour = Expression "Background colour" 255;
+ sd_colour = Expression "Shadow colour" 128;
+ alpha = Toggle "Shadow in alpha channel" false;
+ transparent = Toggle "Zero pixels are transparent" false;
+
+ _result
+ = map_unary shadow x
+ {
+ shadow image
+ = Image final
+ {
+ blur_size = ss.value * 2 + 1;
+
+ // matrix we blur with to soften shadows
+ blur_matrix = matrix_gaussian_blur blur_size;
+ matrix_size = blur_matrix.width;
+ matrix_radius = (int) (matrix_size / 2) + 1;
+
+ // position and size of shadow image in input cods
+ // before and after fuzzing
+ shadow_rect = Rect sx.value sy.value
+ image.width image.height;
+ fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;
+
+ // size and pos of final image, in input cods
+ final_rect = image.rect.union fuzzy_shadow_rect;
+
+ // hard part of shadow in output cods
+ shadow_rect' = Rect
+ (shadow_rect.left - final_rect.left)
+ (shadow_rect.top - final_rect.top)
+ shadow_rect.width shadow_rect.height;
+
+ // make the shadow mask ... true for parts which cast
+ // a shadow
+ mask
+ = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
+ transparent
+ = image_new image.width image.height 1 Image_format.UCHAR
+ Image_coding.NOCODING Image_type.B_W 255 0 0;
+ mask' = embed 0 shadow_rect'.left shadow_rect'.top
+ final_rect.width final_rect.height mask;
+ mask'' = convsep blur_matrix mask';
+
+ // use mask to fade between bg and shadow colour
+ mk_background colour = image_new
+ final_rect.width final_rect.height
+ image.bands image.format image.coding image.type
+ colour 0 0;
+
+ bg_image = mk_background bg_colour.expr;
+ shadow_image = mk_background sd_colour.expr;
+ bg = blend mask'' shadow_image bg_image;
+
+ // make a full size mask
+ fg_mask = embed 0
+ (image.rect.left - final_rect.left)
+ (image.rect.top - final_rect.top)
+ final_rect.width final_rect.height mask;
+
+ // wrap up the input image ... put the shadow colour
+ // around it, so if we are outputting a separate
+ // alpha the shadow colour will be set correctly
+ fg = insert (image.rect.left - final_rect.left)
+ (image.rect.top - final_rect.top)
+ image.value shadow_image;
+
+ final
+ // make a separate alpha
+ = fg ++ mask'', alpha
+
+ // paste image over shadow
+ = if fg_mask then fg else bg;
+ }
+ }
+ }
+}
+
+Filter_paint_text_item = class
+ Menuaction "_Paint Text" "paint text into an image" {
+ action x
+ = paint_position, is_Group x
+ = paint_area
+ {
+ paint_area = class
+ _result {
+ _check_args = [
+ [x, "x", check_Image]
+ ];
+ _vislevel = 3;
+
+ text = String "Text to paint" "Hello world!";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ align = Option "Alignment" ["Left", "Centre", "Right"] 0;
+ dpi = Expression "DPI" 300;
+ colour = Expression "Text colour" 255;
+ place = Region x (x.width / 4) (x.height / 4)
+ (x.width / 2) (x.height / 2);
+
+ _result
+ = insert_noexpand place.left place.top (blend txt' fg place) x
+ {
+ fg = image_new place.width place.height x.bands x.format
+ x.coding x.type colour.expr 0 0;
+ txt = Image (im_text text.value font.value
+ place.width align.value (to_real dpi));
+ bg = im_black place.width place.height 1;
+ txt' = insert_noexpand 0 0 txt bg;
+ }
+ }
+
+ paint_position = class
+ _result {
+ _vislevel = 3;
+
+ text = Pattern_images_item.Text_item.action;
+ colour = Expression "Text colour" 255;
+ position = Option "Position" [
+ _ "North-west",
+ _ "North",
+ _ "North-east",
+ _ "West",
+ _ "Centre",
+ _ "East",
+ _ "South-west",
+ _ "South",
+ _ "South-east",
+ _ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_unary paint x
+ {
+ paint image
+ = insert_noexpand x' y' place' image
+ {
+ xr = image.width - text.width;
+ yr = image.height - text.height;
+ x
+ = left.expr, position == 9
+ = [0, xr / 2, xr]?(position % 3);
+ y
+ = top.expr, position == 9
+ = [0, yr / 2, yr]?(position / 3);
+ x' = range 0 x (image.width - 1);
+ y' = range 0 y (image.height - 1);
+ w' = range 1 text.width (image.width - x');
+ h' = range 1 text.height (image.height - y');
+
+ place = extract_area x' y' w' h' image;
+ text' = insert_noexpand 0 0 text (im_black w' h' 1);
+ fg = image_new w' h' image.bands image.format
+ image.coding image.type colour.expr 0 0;
+ place' = blend text' fg place;
+ }
+ }
+ }
+ }
+}
+
+Autotrace_item = class
+ Menuaction "_Trace" "convert a bitmap to an SVG file" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ despeckle = Scale "Despeckle level" 1 20 1;
+ line = Scale "Line threshold" 1 20 1;
+ center = Toggle "Trace centreline" false;
+ scale = Scale "SVG scale" 0.1 10 1;
+
+ command
+ = "autotrace %s " ++ join_sep " "
+ [ofmt, ofile, desp, lint, cent]
+ {
+ prog = search_for_error "autotrace";
+ ofmt = "-output-format svg";
+ ofile = "-output-file %s";
+ desp = "-despeckle-level " ++ print despeckle.value;
+ lint = "-line-threshold " ++ print line.value;
+ cent = if center then "-centerline " else "";
+ }
+
+ _result
+ = Image output
+ {
+ [output] = vips_call "system"
+ [command]
+ [$in => [x.value],
+ $in_format => "%s.ppm",
+ $out => true,
+ $out_format => "%s.svg[scale=" ++ print scale.value ++ "]"
+ ];
+ }
+ }
+}
+
diff --git a/share/nip2/compat/8.5/Histogram.def b/share/nip2/compat/8.5/Histogram.def
new file mode 100644
index 00000000..6280f4f9
--- /dev/null
+++ b/share/nip2/compat/8.5/Histogram.def
@@ -0,0 +1,334 @@
+Hist_new_item = class
+ Menupullright "_New" "new histogram" {
+ Hist_item = class
+ Menuaction "_Identity" "make an identity histogram" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ d = Option "Depth" ["8 bit", "16 bit"] 0;
+ _result = Plot [] ([im_identity 1, im_identity_ushort 1 65536]?d);
+ }
+ }
+
+ Hist_new_from_matrix = Matrix_buildlut_item;
+
+ Hist_from_image_item = class
+ Menuaction "Ta_g Image As Histogram" "set image Type to Histogram" {
+ action x = hist_tag x;
+ }
+
+ Tone_item = class
+ Menuaction "_Tone Curve" "make a new tone mapping curve" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ d = Option "Depth" ["8 bit", "16 bit"] 0;
+ b = Scale "Black point" 0 100 0;
+ w = Scale "White point" 0 100 100;
+
+ sp = Scale "Shadow point" 0.1 0.3 0.2;
+ mp = Scale "Mid-tone point" 0.4 0.6 0.5;
+ hp = Scale "Highlight point" 0.7 0.9 0.8;
+
+ sa = Scale "Shadow adjust" (-15) 15 0;
+ ma = Scale "Mid-tone adjust" (-30) 30 0;
+ ha = Scale "Highlight adjust" (-15) 15 0;
+
+ _result
+ = tone_build fmt b w sp mp hp sa ma ha
+ {
+ fmt = [Image_format.UCHAR, Image_format.USHORT]?d;
+ }
+ }
+ }
+}
+
+Hist_convert_to_hist_item = class
+ Menuaction "Con_vert to Histogram" "convert anything to a histogram" {
+ action x = hist_tag (to_image x);
+}
+
+Hist_find_item = class
+ Menupullright "_Find" "find a histogram" {
+ Oned_item = class
+ Menuaction "_One Dimension"
+ "for a n-band image, make an n-band 1D histogram" {
+ action x = map_unary hist_find x;
+ }
+
+ Nd_item = class
+ Menuaction "_Many Dimensions"
+ "for a n-band image, make an n-dimensional histogram" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // default to something small-ish
+ bins = Expression "Number of bins in each dimension" 8;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = hist_find_nD bins in;
+ }
+ }
+ }
+
+ Indexed_item = class
+ Menuaction "_Indexed"
+ "use a 1-band index image to pick bins for an n-band image" {
+ action x y
+ = map_binary map x y
+ {
+ map a b
+ = hist_find_indexed index im
+ {
+ [im, index] = sortc (const is_index) [a, b];
+
+ is_index x
+ = has_image x && b == 1 &&
+ (f == Image_format.UCHAR || f == Image_format.USHORT)
+ {
+ im = get_image x;
+ b = get_bands x;
+ f = get_format x;
+ }
+ }
+ }
+ }
+}
+
+Hist_map_item = class
+ Menuaction "_Map" "map an image through a histogram" {
+ action x y
+ = map_binary map x y
+ {
+ map a b
+ = hist_map hist im
+ {
+ [im, hist] = sortc (const is_hist) [a, b];
+ }
+ }
+}
+
+Hist_eq_item = Filter_enhance_item.Hist_equal_item;
+
+#separator
+
+Hist_cum_item = class
+ Menuaction "_Integrate"
+ "form cumulative histogram" {
+ action x = map_unary hist_cum x;
+}
+
+Hist_diff_item = class
+ Menuaction "_Differentiate"
+ "find point-to-point differences (inverse of Integrate)" {
+ action x = map_unary hist_diff x;
+}
+
+Hist_norm_item = class
+ Menuaction "N_ormalise" "normalise a histogram" {
+ action x = map_unary hist_norm x;
+}
+
+Hist_inv_item = class
+ Menuaction "In_vert" "invert a histogram" {
+ action x = map_unary hist_inv x;
+}
+
+Hist_match_item = class
+ Menuaction "Ma_tch"
+ "find LUT which will match first histogram to second" {
+ action in ref = map_binary hist_match in ref;
+}
+
+Hist_zerox_item = class
+ Menuaction "_Zero Crossings" "find zero crossings" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Direction" [
+ "Positive-going",
+ "Negative-going"
+ ] 0;
+
+ _result
+ = map_unary (zerox (if edge == 0 then -1 else 1)) x;
+ }
+}
+
+Hist_entropy_item = class Menuaction "Entropy" "calculate histogram entropy" {
+ action x = hist_entropy x;
+}
+
+#separator
+
+Hist_profile_item = class
+ Menuaction "Find _Profile"
+ "search from image edges for non-zero pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Search from" [
+ "Top edge down",
+ "Left edge to right",
+ "Bottom edge up",
+ "Right edge to left"
+ ] 2;
+
+ _result
+ = map_unary profile x
+ {
+ profile image
+ = (Plot_histogram @ hist_tag) [
+ profilemb 0 image.value,
+ profilemb 1 image.value,
+ profilemb 0 (fliptb image.value),
+ profilemb 1 (fliplr image.value)
+ ]?edge;
+
+ // im_profile only does 1 band images :-(
+ profilemb d = bandjoin @ map (converse im_profile d) @ bandsplit;
+ }
+ }
+}
+
+Hist_project_item = class
+ Menuaction "Find Pro_jections"
+ "find horizontal and vertical projections" {
+ action x = class {
+ _vislevel = 2;
+
+ _result = map_unary project x;
+
+ // extract the result ... could be a group
+ extr n
+ = Plot_histogram _result?n, is_list _result
+ = Group (map (Plot_histogram @ converse subscript n) _result.value);
+
+ horizontal = extr 0;
+ vertical = extr 1;
+ centre = (gravity horizontal, gravity vertical);
+ }
+}
+
+#separator
+
+Hist_graph_item = class
+ Menuaction "P_lot Slice" "plot a slice along a guide or arrow" {
+ action x = class
+ _value {
+ _vislevel = 3;
+
+ width = Scale "Width" 1 40 1;
+ displace = Scale "Horizontal displace" (-50) 50 0;
+ vdisplace = Scale "Vertical displace" (-50) 50 0;
+
+ _value
+ = map_unary graph x
+ {
+ graph arrow
+ = hist_tag area'
+ {
+ area = extract_arrow
+ displace.value vdisplace.value width.value arrow;
+
+ // squish vertically to get an average
+ area' = resize Kernel_linear 1 (1 / width.value) area;
+ }
+ }
+ }
+}
+
+Extract_arrow_item = class
+ Menuaction "Extract _Arrow" "extract the area around an arrow" {
+ action x = class
+ _value {
+ _vislevel = 3;
+
+ width = Scale "Width" 1 40 1;
+ displace = Scale "Horizontal displace" (-50) 50 0;
+ vdisplace = Scale "Vertical displace" (-50) 50 0;
+
+ _value
+ = map_unary (extract_arrow
+ displace.value vdisplace.value width.value) x;
+ }
+}
+
+Hist_plot_item = class
+ Menuaction "Plot _Object"
+ "plot an object as a bar, point or line graph" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ caption = Expression "Chart caption" "none";
+ format = Option_enum "Format" Plot_format.names "YYYY";
+ style = Option_enum "Style" Plot_style.names "Line";
+
+ auto = Toggle "Auto Range" true;
+ xmin = Expression "X range minimum" 0;
+ xmax = Expression "X range maximum" 1;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+ xcaption = Expression "X axis caption" "none";
+ ycaption = Expression "Y axis caption" "none";
+ series_captions = Expression "Series captions" ["Band 0"];
+
+ _result
+ = Plot options (image x)
+ {
+ options
+ = [$style => style.value, $format => format.value] ++
+ range ++ captions;
+ range
+ = [], auto
+ = [$xmin => xmin.expr, $xmax => xmax.expr,
+ $ymin => ymin.expr, $ymax => ymax.expr];
+
+ captions
+ = concat (map test caption_options) ++
+ [$series_captions => series_captions.expr]
+ {
+ caption_options = [
+ $caption => caption.expr,
+ $xcaption => xcaption.expr,
+ $ycaption => ycaption.expr
+ ];
+ test x
+ = [], value == "none"
+ = [option_name => value]
+ {
+ [option_name, value] = x;
+ }
+ }
+
+ image x
+ = image (extract_arrow 0 0 1 x), is_Arrow x
+ = get_image x, has_image x
+ = x2b im, b == 1
+ = im
+ {
+ im = get_image (to_image x);
+ w = get_width im;
+ h = get_height im;
+ b = get_bands im;
+
+ // matrix to image makes a 1-band mxn image
+ // we need to put columns into bands
+ x2b im
+ = bandjoin (map extract_col [0 .. w - 1])
+ {
+ extract_col x = extract_area x 0 1 h im;
+ }
+ }
+ }
+ }
+}
diff --git a/share/nip2/compat/8.5/Image.def b/share/nip2/compat/8.5/Image.def
new file mode 100644
index 00000000..59cec551
--- /dev/null
+++ b/share/nip2/compat/8.5/Image.def
@@ -0,0 +1,2272 @@
+Image_new_item = class Menupullright "_New" "make new things" {
+ Image_black_item = class Menuaction "_Image" "make a new image" {
+ format_names = [
+ "8-bit unsigned int - UCHAR", // 0
+ "8-bit signed int - CHAR", // 1
+ "16-bit unsigned int - USHORT", // 2
+ "16-bit signed int - SHORT", // 3
+ "32-bit unsigned int - UINT", // 4
+ "32-bit signed int - INT", // 5
+ "32-bit float - FLOAT", // 6
+ "64-bit complex - COMPLEX", // 7
+ "64-bit float - DOUBLE", // 8
+ "128-bit complex - DPCOMPLEX" // 9
+ ];
+
+ action = class
+ Image _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ nbands = Expression "Image bands" 1;
+ format_option = Option "Image format" format_names 0;
+ type_option = Option_enum "Image type"
+ Image_type.type_names "B_W";
+ pixel = Expression "Pixel value" 0;
+
+ _result
+ = image_new (to_real nwidth) (to_real nheight) (to_real nbands)
+ (to_real format_option) Image_coding.NOCODING
+ type_option.value_thing pixel.expr 0 0;
+ }
+ }
+
+ Image_new_from_image_item = class
+ Menuaction "_From Image" "make a new image based on image x" {
+ action x = class
+ Image _result {
+ _vislevel = 3;
+
+ pixel = Expression "Pixel value" 0;
+
+ _result
+ = image_new x.width x.height x.bands
+ x.format x.coding x.type pixel.expr x.xoffset x.yoffset;
+ }
+ }
+
+ Image_region_item = class
+ Menupullright "_Region on Image" "make a new region on an image" {
+ Region_item = class
+ Menuaction "_Region" "make a region on an image" {
+ action image = scope.Region_relative image 0.25 0.25 0.5 0.5;
+ }
+
+ Mark_item = class
+ Menuaction "_Point" "make a point on an image" {
+ action image = scope.Mark_relative image 0.5 0.5;
+ }
+
+ Arrow_item = class
+ Menuaction "_Arrow" "make an arrow on an image" {
+ action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5;
+ }
+
+ HGuide_item = class
+ Menuaction "_Horizontal Guide"
+ "make a horizontal guide on an image" {
+ action image = scope.HGuide image 0.5;
+ }
+
+ VGuide_item = class
+ Menuaction "_Vertical Guide" "make a vertical guide on an image" {
+ action image = scope.VGuide image 0.5;
+ }
+
+ sep1 = Menuseparator;
+
+ Move_item = class
+ Menuaction "From Region"
+ "new region on image using existing region as a guide" {
+ action a b
+ = map_binary process a b
+ {
+ process a b
+ = x.Region target x.left x.top x.width x.height,
+ is_Region x
+ = x.Arrow target x.left x.top x.width x.height,
+ is_Arrow x
+ = error "bad arguments to region-from-region"
+ {
+ // prefer image then region
+ compare a b
+ = false,
+ !is_Image a && is_Image b
+ = false,
+ is_Region a && !is_Region b
+ = true;
+
+ [target, x] = sortc compare [a, b];
+ }
+ }
+ }
+ }
+}
+
+Image_convert_to_image_item = class
+ Menuaction "Con_vert to Image" "convert anything to an image" {
+ action x = to_image x;
+}
+
+Image_number_format_item = class
+ Menupullright "_Format" "convert numeric format" {
+
+ U8_item = class
+ Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" {
+ action x = map_unary cast_unsigned_char x;
+ }
+
+ U16_item = class
+ Menuaction "1_6 bit unsigned"
+ "convert to unsigned 16 bit [0, 65535]" {
+ action x = map_unary cast_unsigned_short x;
+ }
+
+ U32_item = class
+ Menuaction "_32 bit unsigned"
+ "convert to unsigned 32 bit [0, 4294967295]" {
+ action x = map_unary cast_unsigned_int x;
+ }
+
+ sep1 = Menuseparator;
+
+ S8_item = class
+ Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" {
+ action x = map_unary cast_signed_char x;
+ }
+
+ S16_item = class
+ Menuaction "16 b_it signed"
+ "convert to signed 16 bit [-32768, 32767]" {
+ action x = map_unary cast_signed_short x;
+ }
+
+ S32_item = class
+ Menuaction "32 bi_t signed"
+ "convert to signed 32 bit [-2147483648, 2147483647]" {
+ action x = map_unary cast_signed_int x;
+ }
+
+ sep2 = Menuseparator;
+
+ Float_item = class
+ Menuaction "_Single precision float"
+ "convert to IEEE 32 bit float" {
+ action x = map_unary cast_float x;
+ }
+
+ Double_item = class
+ Menuaction "_Double precision float"
+ "convert to IEEE 64 bit float" {
+ action x = map_unary cast_double x;
+ }
+
+ sep3 = Menuseparator;
+
+ Scmplxitem = class
+ Menuaction "Single _precision complex"
+ "convert to 2 x IEEE 32 bit float" {
+ action x = map_unary cast_complex x;
+ }
+
+ Dcmplx_item = class
+ Menuaction "Double p_recision complex"
+ "convert to 2 x IEEE 64 bit float" {
+ action x = map_unary cast_double_complex x;
+ }
+}
+
+Image_header_item = class
+ Menupullright "_Header" "do stuff to the image header" {
+
+ Image_get_item = class
+ Menupullright "_Get" "get header fields" {
+
+ // the header fields we can get
+ fields = class {
+ type = 0;
+ width = 1;
+ height = 2;
+ format = 3;
+ bands = 4;
+ xres = 5;
+ yres = 6;
+ xoffset = 7;
+ yoffset = 8;
+ coding = 9;
+
+ field_names = Enum [
+ $width => width,
+ $height => height,
+ $bands => bands,
+ $format => format,
+ $type => type,
+ $xres => xres,
+ $yres => yres,
+ $xoffset => xoffset,
+ $yoffset => yoffset,
+ $coding => coding
+ ];
+
+ field_option name = Option_enum (_ "Field") field_names name;
+
+ field_funcs = Table [
+ [type, get_type],
+ [width, get_width],
+ [height, get_height],
+ [format, get_format],
+ [bands, get_bands],
+ [xres, get_xres],
+ [yres, get_yres],
+ [xoffset, get_xoffset],
+ [yoffset, get_yoffset],
+ [coding, get_coding]
+ ];
+ }
+
+ get_field field_name x = class
+ _result {
+ _vislevel = 3;
+
+ field = fields.field_option field_name;
+
+ _result
+ = map_unary (Real @
+ fields.field_funcs.lookup 0 1 field.value_thing) x;
+ }
+
+ Width_item = class
+ Menuaction "_Width" "get width" {
+ action x = get_field "width" x;
+ }
+
+ Height_item = class
+ Menuaction "_Height" "get height" {
+ action x = get_field "height" x;
+ }
+
+ Bands_item = class
+ Menuaction "_Bands" "get bands" {
+ action x = get_field "bands" x;
+ }
+
+ Format_item = class
+ Menuaction "_Format" "get format" {
+ action x = get_field "format" x;
+ }
+
+ Type_item = class
+ Menuaction "_Type" "get type" {
+ action x = get_field "type" x;
+ }
+
+ Xres_item = class
+ Menuaction "_Xres" "get X resolution" {
+ action x = get_field "xres" x;
+ }
+
+ Yres_item = class
+ Menuaction "_Yres" "get Y resolution" {
+ action x = get_field "yres" x;
+ }
+
+ Xoffset_item = class
+ Menuaction "X_offset" "get X offset" {
+ action x = get_field "xoffset" x;
+ }
+
+ Yoffset_item = class
+ Menuaction "Yo_ffset" "get Y offset" {
+ action x = get_field "yoffset" x;
+ }
+
+ Coding_item = class
+ Menuaction "_Coding" "get coding" {
+ action x = get_field "coding" x;
+ }
+
+ sep1 = Menuseparator;
+
+ Custom_item = class
+ Menuaction "C_ustom" "get any header field" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ field = String "Field" "Xsize";
+ parse = Option "Parse" [
+ "No parsing",
+ "Parse string as integer",
+ "Parse string as real",
+ "Parse string as hh:mm:ss"
+ ] 0;
+
+ _result
+ = map_unary (wrap @ process @ get_header field.value) x
+ {
+ parse_str parse str = parse (split is_space str)?0;
+
+ parse_field name cast parse x
+ = cast x, is_number x
+ = parse_str parse x, is_string x
+ = error ("not " ++ name);
+
+ get_int = parse_field "int"
+ cast_signed_int parse_int;
+ get_float = parse_field "float"
+ cast_float parse_float;
+ get_time = parse_field "hh:mm:ss"
+ cast_signed_int parse_time;
+
+ wrap x
+ = Real x, is_real x
+ = Vector x, is_real_list x
+ = Image x, is_image x
+ = Bool x, is_bool x
+ = Matrix x, is_matrix x
+ = String "String" x, is_string x
+ = List x, is_list x
+ = x;
+
+ process = [
+ id,
+ get_int,
+ get_float,
+ get_time
+ ]?parse;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Image_set_meta_item = class
+ Menuaction "_Set" "set image metadata" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ fname = String "Field" "field-name";
+ val = Expression "Value" 42;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = set_header fname.value val.expr image;
+ }
+ }
+ }
+
+ Image_edit_header_item = class
+ Menuaction "_Edit" "change advisory header fields of image" {
+ type_names = Image_type.type_names;
+ all_names = sort (map (extract 0) type_names.value);
+
+ get_prop has get def x
+ = get x, has x
+ = def;
+
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ nxres = Expression "Xres" (get_prop has_xres get_xres 1 x);
+ nyres = Expression "Yres" (get_prop has_yres get_yres 1 x);
+ nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x);
+ nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x);
+
+ type_option
+ = Option_enum "Image type" Image_type.type_names
+ (Image_type.type_names.get_name type)
+ {
+ type
+ = x.type, is_Image x
+ = Image_type.MULTIBAND;
+ }
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = Image (im_copy_set image.value type_option.value_thing
+ (to_real nxres) (to_real nyres)
+ (to_real nxoff) (to_real nyoff));
+ }
+ }
+ }
+}
+
+Image_cache_item = class
+ Menuaction "C_ache" "cache calculated image pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tile_width = Number "Tile width" 128;
+ tile_height = Number "Tile height" 128;
+ max_tiles = Number "Maximum number of tiles to cache" (-1);
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = cache (to_real tile_width) (to_real tile_height)
+ (to_real max_tiles) image;
+ }
+ }
+}
+
+#separator
+
+Image_levels_item = class
+ Menupullright "_Levels" "change image levels" {
+ Scale_item = class
+ Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" {
+ action x = map_unary scale x;
+ }
+
+ Linear_item = class
+ Menuaction "_Linear" "linear transform of image levels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ scale = Scale "Scale" 0.001 3 1;
+ offset = Scale "Offset" (-128) 128 0;
+
+ _result
+ = map_unary adj x
+ {
+ adj x
+ // only force back to input type if this is a thing
+ // with a type ... so we work for Colour / Matrix etc.
+ = clip2fmt x.format x', has_member "format" x
+ = x'
+ {
+ x' = x * scale + offset;
+ }
+ }
+ }
+ }
+
+ Gamma_item = class
+ Menuaction "_Power" "power transform of image levels (gamma)" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gamma = Scale "Gamma" 0.001 4 1;
+ image_maximum_hint = "You may need to change image_maximum if " ++
+ "this is not an 8 bit image";
+ im_mx
+ = Expression "Image maximum" mx
+ {
+ mx
+ = Image_format.maxval x.format, has_format x
+ = 255;
+ }
+
+ _result
+ = map_unary gam x
+ {
+ gam x
+ = clip2fmt (get_format x) x', has_format x
+ = x'
+ {
+ x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma;
+ }
+ }
+ }
+ }
+
+ Tone_item = class
+ Menuaction "_Tone Curve" "adjust tone curve" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ b = Scale "Black point" 0 100 0;
+ w = Scale "White point" 0 100 100;
+
+ sp = Scale "Shadow point" 0.1 0.3 0.2;
+ mp = Scale "Mid-tone point" 0.4 0.6 0.5;
+ hp = Scale "Highlight point" 0.7 0.9 0.8;
+
+ sa = Scale "Shadow adjust" (-15) 15 0;
+ ma = Scale "Mid-tone adjust" (-30) 30 0;
+ ha = Scale "Highlight adjust" (-15) 15 0;
+
+ curve = tone_build x.format b w sp mp hp sa ma ha;
+
+ _result = map_unary (hist_map curve) x;
+ }
+ }
+}
+
+Image_transform_item = class
+ Menupullright "_Transform" "transform images" {
+ Rotate_item = class
+ Menupullright "Ro_tate" "rotate image" {
+ Fixed_item = class
+ Menupullright "_Fixed" "clockwise rotation by fixed angles" {
+ rotate_widget default x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Option "Rotate by" [
+ "Don't rotate",
+ "90 degrees clockwise",
+ "180 degrees",
+ "90 degrees anticlockwise"
+ ] default;
+
+ _result
+ = map_unary process x
+ {
+ process = [
+ // we can't use id here since we want to "declass"
+ // the members of x ... consider if x is a crop class,
+ // for example, we don't want to inherit from crop, we
+ // want to make a new image class
+ rot180 @ rot180,
+ rot90,
+ rot180,
+ rot270
+ ] ? angle;
+ }
+ }
+
+ Rot90_item = class
+ Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" {
+ action x = rotate_widget 1 x;
+ }
+
+ Rot180_item = class
+ Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" {
+ action x = rotate_widget 2 x;
+ }
+
+ Rot270_item = class
+ Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" {
+ action x = rotate_widget 3 x;
+ }
+ }
+
+ Free_item = class
+ Menuaction "_Free" "clockwise rotation by any angle" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "Angle" (-180) 180 0;
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = rotate interp angle image;
+ }
+ }
+ }
+
+ Straighten_item = class
+ Menuaction "_Straighten"
+ ("smallest rotation that makes an arrow either horizontal " ++
+ "or vertical") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary straighten x
+ {
+ straighten arrow
+ = rotate interp angle'' arrow.image
+ {
+ x = arrow.width;
+ y = arrow.height;
+
+ angle = im (polar (x, y));
+
+ angle'
+ = angle - 360, angle > 315
+ = angle - 180, angle > 135
+ = angle;
+
+ angle''
+ = -angle', angle' >= (-45) && angle' < 45
+ = 90 - angle';
+ }
+ }
+ }
+ }
+ }
+
+ Flip_item = class
+ Menupullright "_Flip" "mirror left/right or up/down" {
+ Left_right_item = class
+ Menuaction "_Left Right" "mirror object left/right" {
+ action x = map_unary fliplr x;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top Bottom" "mirror object top/bottom" {
+ action x = map_unary fliptb x;
+ }
+ }
+
+ Resize_item = class
+ Menupullright "_Resize" "change image size" {
+ Scale_item = class
+ Menuaction "_Scale" "scale image size by a factor" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ xfactor = Expression "Horizontal scale factor" 1;
+ yfactor = Expression "Vertical scale factor" 1;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = resize kernel xfactor yfactor image;
+ }
+ }
+ }
+
+ Size_item = class
+ Menuaction "_Size To" "resize to a fixed size" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ which = Option "Resize axis" [
+ "Shortest",
+ "Longest",
+ "Horizontal",
+ "Vertical"
+ ] 0;
+ size = Expression "Resize to (pixels)" 128;
+ aspect = Toggle "Break aspect ratio" false;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = resize kernel h v image, aspect
+ = resize kernel fac fac image
+ {
+ xfac = to_real size / image.width;
+ yfac = to_real size / image.height;
+ max_factor
+ = [xfac, 1], xfac > yfac
+ = [1, yfac];
+ min_factor
+ = [xfac, 1], xfac < yfac
+ = [1, yfac];
+ [h, v] = [
+ max_factor,
+ min_factor,
+ [xfac, 1],
+ [1, yfac]]?which;
+
+ fac
+ = h, v == 1
+ = v;
+ }
+ }
+ }
+ }
+
+ Size_within_item = class
+ Menuaction "Size _Within" "size to fit within a rectangle" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // the rects we size to fit within
+ _rects = [
+ [2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050],
+ [1280, 1024], [1024, 768], [800, 600], [640, 480]
+ ];
+
+ within = Option "Fit within (pixels)" (
+ [print w ++ " x " ++ print h :: [w, h] <- _rects] ++
+ ["Custom"]
+ ) 4;
+ custom_width = Expression "Custom width" 1000;
+ custom_height = Expression "Custom height" 1000;
+ size = Option "Page size" [
+ "Full page", "Half page", "Quarter page"
+ ] 0;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ xdiv = [1, 2, 2]?size;
+ ydiv = [1, 1, 2]?size;
+ allrect = _rects ++ [
+ [custom_width.expr, custom_height.expr]
+ ];
+ [width, height] = allrect?within;
+
+ process x
+ = resize kernel fac fac x, fac < 1
+ = x
+ {
+ xfac = (width / xdiv) / x.width;
+ yfac = (height / ydiv) / x.height;
+ fac = min_pair xfac yfac;
+ }
+ }
+ }
+ }
+
+ Resize_canvas_item = class
+ Menuaction "_Canvas" "change size of surrounding image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // try to guess a sensible size for the new image
+ _guess_size
+ = x.rect, is_Image x
+ = Rect 0 0 100 100;
+
+ nwidth = Expression "New width (pixels)" _guess_size.width;
+ nheight = Expression "New height (pixels)" _guess_size.height;
+ bgcolour = Expression "Background colour" 0;
+
+ position = Option "Position" [
+ "North-west",
+ "North",
+ "North-east",
+ "West",
+ "Centre",
+ "East",
+ "South-west",
+ "South",
+ "South-east",
+ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = insert_noexpand xp yp image background
+ {
+ width = image.width;
+ height = image.height;
+ coding = image.coding;
+ bands
+ = 3, coding == Image_coding.LABPACK
+ = image.bands;
+ format
+ = Image_format.FLOAT, coding == Image_coding.LABPACK
+ = image.format;
+ type = image.type;
+
+ // placement vectors ... left, centre, right
+ xposv = [0, to_real nwidth / 2 - width / 2,
+ to_real nwidth - width];
+ yposv = [0, to_real nheight / 2 - height / 2,
+ to_real nheight - height];
+ xp
+ = left, position == 9
+ = xposv?((int) (position % 3));
+ yp
+ = top, position == 9
+ = yposv?((int) (position / 3));
+
+ background = image_new nwidth nheight
+ bands format coding type bgcolour.expr 0 0;
+ }
+ }
+ }
+ }
+ }
+
+ Image_map_item = class
+ Menuaction "_Map" "map an image through a 2D transform image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_binary trans a b
+ {
+ trans a b
+ = mapim interp.value in index
+ {
+ // get the index image first
+ [index, in] = sortc (const is_twocomponent) [a, b];
+
+ // is a two-component image, ie. one band complex, or
+ // two-band non-complex
+ is_twocomponent x
+ = is_nonc x || is_c x;
+ is_nonc x
+ = has_bands x && get_bands x == 2 &&
+ has_format x && !is_complex_format (get_format x);
+ is_c x
+ = has_bands x && get_bands x == 1 &&
+ has_format x && is_complex_format (get_format x);
+ is_complex_format f
+ = f == Image_format.COMPLEX ||
+ f == Image_format.DPCOMPLEX;
+ }
+ }
+ }
+ }
+
+ Image_perspective_item = Perspective_item;
+
+ Image_rubber_item = class
+ Menupullright "Ru_bber Sheet"
+ "automatically warp images to superposition" {
+ rubber_interp = Option "Interpolation" ["Nearest", "Bilinear"] 1;
+ rubber_order = Option "Order" ["0", "1", "2", "3"] 1;
+ rubber_wrap = Toggle "Wrap image edges" false;
+
+ // a transform ... a matrix, plus the size of the image the
+ // matrix was made for
+ Transform matrix image_width image_height = class
+ matrix {
+ // scale a transform ... if it worked for a m by n image, make
+ // it work for a (m * xfac) by (y * yfac) image
+ rescale xfac yfac
+ = Transform (Matrix (map2 (map2 multiply) matrix.value facs))
+ (image_width * xfac) (image_height * yfac)
+ {
+ facs = [
+ [xfac, yfac],
+ [1, 1],
+ [1, 1],
+ [1 / xfac, 1 / yfac],
+ [1 / xfac, 1 / yfac],
+ [1 / xfac, 1 / yfac]
+ ];
+ }
+ }
+
+ // yuk!!!! fix is_instanceof to not need absolute names
+ is_Transform = is_instanceof
+ "Image_transform_item.Image_rubber_item.Transform";
+
+ Find_item = class
+ Menuaction "_Find"
+ ("find a transform which will map sample image onto " ++
+ "reference") {
+ action reference sample = class
+ _trn {
+ _vislevel = 3;
+
+ // controls
+ order = rubber_order;
+ interp = rubber_interp;
+ wrap = rubber_wrap;
+ max_err = Expression "Maximum error" 0.3;
+ max_iter = Expression "Maximum iterations" 10;
+
+ // transform
+ [sample', trn, err] = transform_search
+ max_err max_iter order interp wrap
+ sample reference;
+ transformed_image = Image sample';
+ _trn = Transform trn reference.width reference.height;
+ final_error = err;
+ }
+ }
+
+ Apply_item = class
+ Menuaction "_Apply" "apply a transform to an image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ // controls
+ interp = rubber_interp;
+ wrap = rubber_wrap;
+
+ _result
+ = map_binary trans a b
+ {
+ trans a b
+ = transform interp wrap t' i
+ {
+ // get the transform arg first
+ [i, t] = sortc (const is_Transform) [a, b];
+ t' = t.rescale (i.width / t.image_width)
+ (i.height / t.image_height);
+ }
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Match_item = class
+ Menuaction "_Linear Match"
+ "rotate and scale one image to match another" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+ _b = find_image y;
+
+ ap1 = Mark_relative _a 0.5 0.25;
+ bp1 = Mark_relative _b 0.5 0.25;
+ ap2 = Mark_relative _a 0.5 0.75;
+ bp2 = Mark_relative _b 0.5 0.75;
+
+ refine = Toggle "Refine selected tie-points" false;
+ lock = Toggle "No resize" false;
+
+ _result
+ = map_binary process x y
+ {
+ process a b
+ = Image b'''
+ {
+ _prefs = Workspaces.Preferences;
+ window = _prefs.MOSAIC_WINDOW_SIZE;
+ object = _prefs.MOSAIC_OBJECT_SIZE;
+
+ a' = a.value;
+ b' = b.value;
+
+ b'' = clip2fmt a.format b';
+
+ // return p2 ... if lock is set, return a p2 a standard
+ // distance along the vector joining p1 and p2
+ norm p1 p2
+ = Rect left' top' 0 0, lock
+ = p2
+ {
+ v = (p2.left - p1.left, p2.top - p1.top);
+ // 100000 to give precision since we pass points as
+ // ints to match
+ n = 100000 * sign v;
+ left' = p1.left + re n;
+ top' = p1.top + im n;
+ }
+
+ ap2'' = norm ap1 ap2;
+ bp2'' = norm bp1 bp2;
+
+ b'''
+ = im_match_linear_search a' b''
+ ap1.left ap1.top bp1.left bp1.top
+ ap2''.left ap2''.top bp2''.left bp2''.top
+ object window,
+ // we can't search if lock is on
+ refine && !lock
+ = im_match_linear a' b''
+ ap1.left ap1.top bp1.left bp1.top
+ ap2''.left ap2''.top bp2''.left bp2''.top;
+ }
+ }
+ }
+ }
+
+ Image_perspective_match_item = Perspective_match_item;
+}
+
+Image_band_item = class
+ Menupullright "_Band" "manipulate image bands" {
+ // like extract_bands, but return [] for zero band image
+ // makes compose a bit simpler
+ exb b n x
+ = [], to_real n == 0
+ = extract_bands b n x;
+
+ Extract_item = class Menuaction "_Extract" "extract bands from image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from band" 0;
+ number = Expression "Extract this many bands" 1;
+
+ _result = map_unary (exb first number) x;
+ }
+ }
+
+ Insert_item = class Menuaction "_Insert" "insert bands into image" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at position" 0;
+
+ _result
+ = map_binary process x y
+ {
+ process im1 im2
+ = exb 0 f im1 ++ im2 ++ exb f (b - f) im1
+ {
+ f = to_real first;
+ b = im1.bands;
+ }
+ }
+ }
+ }
+
+ Delete_item = class Menuaction "_Delete" "delete bands from image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from band" 0;
+ number = Expression "Delete this many bands" 1;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = exb 0 f im ++ exb (f + n) (b - (f + n)) im
+ {
+ f = to_real first;
+ n = to_real number;
+ b = im.bands;
+ }
+ }
+ }
+ }
+
+ Bandwise_item = Image_join_item.Bandwise_item;
+
+ sep1a = Menuseparator;
+
+ Bandand_item = class
+ Menuaction "Bitwise Band AND" "bitwise AND of image bands" {
+ action x = bandand x;
+ }
+
+ Bandor_item = class
+ Menuaction "Bitwise Band OR" "bitwise OR of image bands" {
+ action x = bandor x;
+ }
+
+ sep2 = Menuseparator;
+
+ To_dimension_item = class
+ Menuaction "To D_imension" "convert bands to width or height" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = foldl1 [join_lr, join_tb]?orientation (bandsplit im);
+ }
+ }
+ }
+
+ To_bands_item = class
+ Menuaction "To B_ands" "turn width or height to bands" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = bandjoin (map extract_column [0 .. im.width - 1]),
+ orientation == 0
+ = bandjoin (map extract_row [0 .. im.height - 1])
+ {
+ extract_column n
+ = extract_area n 0 1 im.height im;
+ extract_row n
+ = extract_area 0 n im.width 1 im;
+ }
+ }
+ }
+ }
+}
+
+Image_alpha_item = class
+ Menupullright "_Alpha" "manipulate image alpha" {
+
+ Add_item = class Menuaction "_Add" "add alpha" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ opacity = Expression "Opacity (255 == solid)" 255;
+
+ _result = x ++ to_real opacity;
+ }
+ }
+
+ Flatten_item = class Menuaction "_Flatten" "flatten alpha out of image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bg = Expression "Background" 0;
+
+ _result = map_unary (flattenimage bg) x;
+ }
+ }
+
+ Extract_item = class Menuaction "_Extract" "extract alpha" {
+ action x
+ = map_unary exb x
+ {
+ exb x = extract_bands (x.bands - 1) 1 x;
+ }
+ }
+
+ Drop_item = class Menuaction "_Drop" "drop alpha" {
+ action x
+ = map_unary exb x
+ {
+ exb x = extract_bands 0 (x.bands - 1) x;
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Premultiply_item = class Menuaction "_Premultiply" "premultiply alpha" {
+ action x = premultiply x;
+ }
+
+ Unpremultiply_item = class
+ Menuaction "_Unpremultiply" "unpremultiply alpha" {
+ action x = unpremultiply x;
+ }
+
+ sep2 = Menuseparator;
+
+ Blend_alpha_item = Filter_blend_item.Blend_alpha_item;
+
+}
+
+Image_crop_item = class
+ Menuaction "_Crop" "extract a rectangular area from an image" {
+ action x
+ = crop x [l, t, w, h]
+ {
+ fields = [
+ [has_left, get_left, 0],
+ [has_top, get_top, 0],
+ [has_width, get_width, 100],
+ [has_height, get_height, 100]
+ ];
+
+ [l, t, w, h]
+ = map get_default fields
+ {
+ get_default line
+ = get x, has x
+ = default
+ {
+ [has, get, default] = line;
+ }
+ }
+ }
+
+ crop x geo = class
+ _result {
+ _vislevel = 3;
+
+ l = Expression "Crop left" ((int) (geo?0 + geo?2 / 4));
+ t = Expression "Crop top" ((int) (geo?1 + geo?3 / 4));
+ w = Expression "Crop width" (max_pair 1 ((int) (geo?2 / 2)));
+ h = Expression "Crop height" (max_pair 1 ((int) (geo?3 / 2)));
+
+ _result
+ = map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr]
+ {
+ extract im l t w h
+ = extract_area left' top' width' height' im
+ {
+ width' = min_pair (to_real w) im.width;
+ height' = min_pair (to_real h) im.height;
+ left' = range 0 (to_real l) (im.width - width');
+ top' = range 0 (to_real t) (im.height - height');
+ }
+ }
+ }
+}
+
+Image_insert_item = class
+ Menuaction "_Insert" "insert a small image into a large image" {
+ action a b
+ = insert_position, is_Group a || is_Group b
+ = insert_area
+ {
+ insert_area = class
+ _result {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _vislevel = 3;
+
+ // sort to get smallest first
+ _pred x y = x.width * x.height < y.width * y.height;
+ [_a', _b'] = sortc _pred [a, b];
+
+ place
+ = Area _b' left top width height
+ {
+ // be careful in case b is smaller than a
+ left = max_pair 0 ((_b'.width - _a'.width) / 2);
+ top = max_pair 0 ((_b'.height - _a'.height) / 2);
+ width = min_pair _a'.width _b'.width;
+ height = min_pair _a'.height _b'.height;
+ }
+
+ _result
+ = insert_noexpand place.left place.top
+ (clip2fmt _b'.format a'') _b'
+ {
+ a'' = extract_area 0 0 place.width place.height _a';
+ }
+ }
+
+ insert_position = class
+ _result {
+ _vislevel = 3;
+
+ position = Option "Position" [
+ "North-west",
+ "North",
+ "North-east",
+ "West",
+ "Centre",
+ "East",
+ "South-west",
+ "South",
+ "South-east",
+ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_binary insert a b
+ {
+ insert a b
+ = insert_noexpand left top (clip2fmt b.format a) b,
+ position == 9
+ = insert_noexpand xp yp (clip2fmt b.format a) b
+ {
+ xr = b.width - a.width;
+ yr = b.height - a.height;
+ xp = [0, xr / 2, xr]?((int) (position % 3));
+ yp = [0, yr / 2, yr]?((int) (position / 3));
+ }
+ }
+ }
+ }
+}
+
+Image_select_item = Select_item;
+
+Image_draw_item = class
+ Menupullright "_Draw" "draw lines, circles, rectangles, floods" {
+ Line_item = class Menuaction "_Line" "draw line on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ x1 = Expression "Start x" 0;
+ y1 = Expression "Start y" 0;
+ x2 = Expression "End x" 100;
+ y2 = Expression "End y" 100;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary line x
+ {
+ line im
+ = draw_line x1 y1 x2 y2 i.expr im;
+ }
+ }
+ }
+
+ Rect_item = class Menuaction "_Rectangle" "draw rectangle on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rx = Expression "Left" 50;
+ ry = Expression "Top" 50;
+ rw = Expression "Width" 100;
+ rh = Expression "Height" 100;
+
+ f = Toggle "Fill" true;
+
+ t = Scale "Line thickness" 1 50 3;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary rect x
+ {
+ rect im
+ = draw_rect_width rx ry rw rh f t i.expr im;
+ }
+ }
+ }
+
+ Circle_item = class Menuaction "_Circle" "draw circle on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ cx = Expression "Centre x" 100;
+ cy = Expression "Centre y" 100;
+ r = Expression "Radius" 50;
+
+ f = Toggle "Fill" true;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary circle x
+ {
+ circle im
+ = draw_circle cx cy r f i.expr im;
+ }
+ }
+ }
+
+ Flood_item = class Menuaction "_Flood" "flood bounded area of image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sx = Expression "Start x" 0;
+ sy = Expression "Start y" 0;
+
+ e = Option "Flood while" [
+ "Not equal to ink",
+ "Equal to start point"
+ ] 0;
+
+ // pick a default ink that won't flood, if we can
+ i
+ = Expression "Ink" default_ink
+ {
+ default_ink
+ = [0], ! has_image x
+ = pixel;
+ pixel = map mean (bandsplit (extract_area sx sy 1 1 im));
+ im = get_image x;
+ }
+
+ _result
+ = map_unary flood x
+ {
+ flood im
+ = draw_flood sx sy i.expr im, e == 0
+ = draw_flood_blob sx sy i.expr im;
+ }
+ }
+ }
+
+ Draw_scalebar_item = class Menuaction "_Scale" "draw scale bar" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ px = Expression "Left" 50;
+ py = Expression "Top" 50;
+ wid = Expression "Width" 100;
+ thick = Scale "Line thickness" 1 50 3;
+ text = String "Dimension text" "50μm";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ pos = Option "Position Text" ["Above", "Below"] 1;
+ vp = Option "Dimension by" [
+ "Inner Vertical Edge",
+ "Centre of Vertical",
+ "Outer Vertical Edge"
+ ] 1;
+ dpi = Expression "DPI" 100;
+ ink = Colour "Lab" [50,0,0];
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = blend (Image scale) ink' im
+ {
+ // make an ink compatible with the image
+ ink' = colour_transform_to (get_type im) ink;
+
+ x = to_real px;
+ y = to_real py;
+ w = to_real wid;
+ d = to_real dpi;
+
+ t = floor thick;
+
+ bg = image_new (get_width im) (get_height im) (get_bands im)
+ (get_format im) (get_coding im) (get_type im) 0 0 0;
+ draw_block x y w t im =
+ draw_rect_width x y w t true 1 [255] im;
+ label = im_text text.value font.value w 1 d;
+ lw = get_width label;
+ lh = get_height label;
+ ly = [y - lh - t, y + 2 * t]?pos;
+ vx = [
+ [x - t, x + w],
+ [x - t / 2, x + w - t / 2],
+ [x, x + w - t]
+ ]?vp;
+
+ scale = (draw_block x y w t @
+ draw_block vx?0 (y - 2 * t) t (t * 5) @
+ draw_block vx?1 (y - 2 * t) t (t * 5) @
+ insert_noexpand (x + w / 2 - lw / 2) ly label)
+ bg;
+ }
+ }
+ }
+ }
+}
+
+Image_join_item = class
+ Menupullright "_Join" "join two or more images together" {
+ Bandwise_item = class
+ Menuaction "_Bandwise Join" "join two images bandwise" {
+ action a b = join a b;
+ }
+
+ sep1 = Menuseparator;
+
+ join_lr shim bg align a b
+ = im2
+ {
+ w = a.width + b.width + shim;
+ h = max_pair a.height b.height;
+
+ back = image_new w h a.bands a.format a.coding a.type bg 0 0;
+
+ ya = [0, max_pair 0 ((b.height - a.height)/2),
+ max_pair 0 (b.height - a.height)];
+ yb = [0, max_pair 0 ((a.height - b.height)/2),
+ max_pair 0 (a.height - b.height)];
+
+ im1 = insert_noexpand 0 ya?align a back;
+ im2 = insert_noexpand (a.width + shim) yb?align b im1;
+ }
+
+ join_tb shim bg align a b
+ = im2
+ {
+ w = max_pair a.width b.width;
+ h = a.height + b.height + shim;
+
+ back = image_new w h a.bands a.format a.coding a.type bg 0 0;
+
+ xa = [0, max_pair 0 ((b.width - a.width)/2),
+ max_pair 0 (b.width - a.width)];
+ xb = [0, max_pair 0 ((a.width - b.width)/2),
+ max_pair 0 (a.width - b.width)];
+
+ im1 = insert_noexpand xa?align 0 a back;
+ im2 = insert_noexpand xb?align (a.height + shim) b im1;
+ }
+
+ halign_names = ["Top", "Centre", "Bottom"];
+ valign_names = ["Left", "Centre", "Right"];
+
+ Left_right_item = class
+ Menuaction "_Left to Right" "join two images left-right" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ shim = Scale "Spacing" 0 100 0;
+ bg_colour = Expression "Background colour" 0;
+ align = Option "Alignment" halign_names 1;
+
+ _result = map_binary
+ (join_lr shim.value bg_colour.expr align.value) a b;
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "join two images top-bottom" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ shim = Scale "Spacing" 0 100 0;
+ bg_colour = Expression "Background colour" 0;
+ align = Option "Alignment" valign_names 1;
+
+ _result = map_binary
+ (join_tb shim.value bg_colour.expr align.value) a b;
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Array_item = class
+ Menuaction "_Array"
+ "join a list of lists of images into a single image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ hshim = Scale "Horizontal spacing" (-100) (100) 0;
+ vshim = Scale "Vertical spacing" (-100) (100) 0;
+ bg_colour = Expression "Background colour" 0;
+ halign = Option "Horizontal alignment" valign_names 1;
+ valign = Option "Vertical alignment" halign_names 1;
+
+ // we can't use map_unary since chop-into-tiles returns a group of
+ // groups and we want to be able to reassemble that
+ // TODO: chop-into-tiles should return an array class which
+ // displays as group but does not have the looping behaviour?
+ _result
+ = (image_set_origin 0 0 @
+ foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
+ map (foldl1 (join_lr hshim.value
+ bg_colour.expr valign.value))) (to_list (to_list x));
+ }
+ }
+
+ ArrayFL_item = class
+ Menuaction "_Array from List"
+ "join a list of images into a single image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ ncol = Number "Max. Number of Columns" 1;
+ hshim = Scale "Horizontal spacing" (-100) (100) 0;
+ vshim = Scale "Vertical spacing" (-100) (100) 0;
+ bg_colour = Expression "Background colour" 0;
+ halign = Option "Horizontal alignment" valign_names 1;
+ valign = Option "Vertical alignment" halign_names 1;
+ snake = Toggle "Reverse the order of every other row" false;
+
+ _l
+ = split_lines ncol.value x.value, is_Group x
+ = split_lines ncol.value x;
+
+ _l'
+ = map2 reverse_if_odd [0..] _l, snake
+ = _l
+ {
+ reverse_if_odd n x
+ = reverse x, n % 2 == 1
+ = x;
+ }
+
+ _result
+ = (image_set_origin 0 0 @
+ foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
+ map (foldl1 (join_lr hshim.value
+ bg_colour.expr valign.value))) (to_list (to_list _l'));
+ }
+ }
+}
+
+Image_tile_item = class
+ Menupullright "Til_e" "tile an image across and down" {
+ tile_widget default_type x = class
+ _result {
+ _vislevel = 3;
+
+ across = Expression "Tiles across" 2;
+ down = Expression "Tiles down" 2;
+ repeat = Option "Tile type"
+ ["Replicate", "Four-way mirror"] default_type;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = tile across down image, repeat == 0
+ = tile across down image''
+ {
+ image' = insert image.width 0 (fliplr image) image;
+ image'' = insert 0 image.height (fliptb image') image';
+ }
+ }
+ }
+
+ Replicate_item = class
+ Menuaction "_Replicate" "replicate image across and down" {
+ action x = tile_widget 0 x;
+ }
+
+ Fourway_item = class
+ Menuaction "_Four-way Mirror" "four-way mirror across and down" {
+ action x = tile_widget 1 x;
+ }
+
+ Chop_item = class
+ Menuaction "_Chop Into Tiles" "slice an image into tiles" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tile_width = Expression "Tile width" 100;
+ tile_height = Expression "Tile height" 100;
+ hoverlap = Expression "Horizontal overlap" 0;
+ voverlap = Expression "Vertical overlap" 0;
+
+ _result
+ = map_unary (Group @ map Group @ process) x
+ {
+ process x
+ = imagearray_chop tile_width tile_height
+ hoverlap voverlap x;
+ }
+ }
+ }
+}
+
+#separator
+
+Pattern_images_item = class
+ Menupullright "_Patterns" "make a variety of useful patterns" {
+ Grey_item = class
+ Menuaction "Grey _Ramp" "make a smooth grey ramp" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+ foption = Option "Format" ["8 bit", "float"] 0;
+
+ _result
+ = Image im
+ {
+ gen
+ = im_grey, foption == 0
+ = im_fgrey;
+ w = to_real nwidth;
+ h = to_real nheight;
+ im
+ = gen w h, orientation == 0
+ = rot90 (gen h w);
+ }
+ }
+ }
+
+ Xy_item = class
+ Menuaction "_XY Image"
+ "make a two band image whose pixel values are their coordinates" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+
+ _result = Image (make_xy nwidth nheight);
+ }
+ }
+
+ Noise_item = class
+ Menupullright "_Noise" "various noise generators" {
+ Gaussian_item = class
+ Menuaction "_Gaussian" "make an image of gaussian noise" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ mean = Scale "Mean" 0 255 128;
+ deviation = Scale "Deviation" 0 128 50;
+
+ _result = Image (gaussnoise nwidth nheight
+ mean.value deviation.value);
+ }
+ }
+
+ Fractal_item = class
+ Menuaction "_Fractal" "make a fractal noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ dimension = Scale "Dimension" 2.001 2.999 2.001;
+
+ _result = Image (im_fractsurf (to_real nsize) dimension.value);
+ }
+ }
+
+ Perlin_item = class
+ Menuaction "_Perlin" "Perlin noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ cell_size = Expression "Cell size (pixels)" 8;
+ eight = Toggle "Eight bit output" true;
+
+ _result
+ = 128 * im + 128, eight
+ = im
+ {
+ im = perlin cell_size nwidth nheight;
+ }
+ }
+ }
+
+ Worley_item = class
+ Menuaction "_Worley" "Worley noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 512;
+ nheight = Expression "Image height (pixels)" 512;
+ cell_size = Expression "Cell size (pixels)" 256;
+
+ _result
+ = worley cell_size nwidth nheight;
+ }
+ }
+
+ }
+
+ Checkerboard_item = class
+ Menuaction "_Checkerboard" "make a checkerboard image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ hpsize = Expression "Horizontal patch size" 8;
+ vpsize = Expression "Vertical patch size" 8;
+ hpoffset = Expression "Horizontal patch offset" 0;
+ vpoffset = Expression "Vertical patch offset" 0;
+
+ _result
+ = Image (xstripes ^ ystripes)
+ {
+ pixels = make_xy nwidth nheight;
+ xpixels = pixels?0 + to_real hpoffset;
+ ypixels = pixels?1 + to_real vpoffset;
+
+ make_stripe pix swidth = pix % (swidth * 2) >= swidth;
+
+ xstripes = make_stripe xpixels (to_real hpsize);
+ ystripes = make_stripe ypixels (to_real vpsize);
+ }
+ }
+ }
+
+ Grid_item = class
+ Menuaction "Gri_d" "make a grid" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ hspace = Expression "Horizontal line spacing" 8;
+ vspace = Expression "Vertical line spacing" 8;
+ thick = Expression "Line thickness" 1;
+ hoff = Expression "Horizontal grid offset" 4;
+ voff = Expression "Vertical grid offset" 4;
+
+ _result
+ = Image (xstripes | ystripes)
+ {
+ pixels = make_xy nwidth nheight;
+ xpixels = pixels?0 + to_real hoff;
+ ypixels = pixels?1 + to_real voff;
+
+ make_stripe pix swidth = pix % swidth < to_real thick;
+
+ xstripes = make_stripe xpixels (to_real hspace);
+ ystripes = make_stripe ypixels (to_real vspace);
+ }
+ }
+ }
+
+ Text_item = class
+ Menuaction "_Text" "make a bitmap of some text" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ text = String "Text to paint" "Hello world!";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ wrap = Expression "Wrap text at" 500;
+ align = Option "Alignment" [
+ "Left",
+ "Centre",
+ "Right"
+ ] 0;
+ dpi = Expression "DPI" 300;
+
+ _result = Image (im_text text.value font.value
+ (to_real wrap) align.value (to_real dpi));
+ }
+ }
+
+ New_CIELAB_slice_item = class
+ Menuaction "CIELAB _Slice" "make a slice through CIELAB space" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ L = Scale "L value" 0 100 50;
+
+ _result = Image (lab_slice (to_real nsize) L.value);
+ }
+ }
+
+ sense_option = Option "Sense" [
+ "Pass",
+ "Reject"
+ ] 0;
+
+ build fn size
+ = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
+ (im_create_fmask size size);
+
+ New_ideal_item = class
+ Menupullright "_Ideal Fourier Mask"
+ "make various ideal Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f sense.value fc.value 0 0 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 6) fc.value rw.value 0 0 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 12) fcx.value fcy.value
+ r.value 0 0;
+ }
+ }
+ }
+ }
+
+ New_gaussian_item = class
+ Menupullright "_Gaussian Fourier Mask"
+ "make various Gaussian Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 4) fc.value ac.value 0 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 10) fc.value rw.value
+ ac.value 0 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 16) fcx.value fcy.value
+ r.value ac.value 0;
+ }
+ }
+ }
+ }
+
+ New_butterworth_item = class
+ Menupullright "_Butterworth Fourier Mask"
+ "make various Butterworth Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 2) order.value fc.value
+ ac.value 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 8) order.value fc.value
+ rw.value ac.value 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 14) order.value fcx.value
+ fcy.value r.value ac.value;
+ }
+ }
+ }
+ }
+}
+
+Test_images_item = class
+ Menupullright "Test I_mages" "make a variety of test images" {
+ Eye_item = class
+ Menuaction "_Spatial Response"
+ "image for testing the eye's spatial response" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ factor = Scale "Factor" 0.001 1 0.2;
+
+ _result = Image (im_eye (to_real nwidth) (to_real nheight)
+ factor.value);
+ }
+ }
+
+ Zone_plate = class
+ Menuaction "_Zone Plate" "make a zone plate" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+
+ _result = Image (im_zone (to_real nsize));
+ }
+ }
+
+ Frequency_test_chart_item = class
+ Menuaction "_Frequency Testchart"
+ "make a black/white frequency test pattern" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ sheight = Expression "Strip height (pixels)" 10;
+ waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2];
+
+ _result
+ = imagearray_assemble 0 0 (transpose [strips])
+ {
+ freq_slice wave = Image (sin (grey / wave) > 0);
+ strips = map freq_slice waves.expr;
+ grey = im_fgrey (to_real nwidth) (to_real sheight) *
+ 360 * (to_real nwidth);
+ }
+ }
+ }
+
+ CRT_test_chart_item = class
+ Menuaction "CRT _Phosphor Chart"
+ "make an image for measuring phosphor colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ brightness = Scale "Brightness" 0 255 200;
+ psize = Expression "Patch size (pixels)" 32;
+
+ _result
+ = Image (imagearray_assemble 0 0 [[green, red], [blue, white]])
+ {
+
+ black = image_new (to_real psize) (to_real psize) 1
+ Image_format.FLOAT Image_coding.NOCODING
+ Image_type.B_W 0 0 0;
+ notblack = black + brightness;
+
+ green = black ++ notblack ++ black;
+ red = notblack ++ black ++ black;
+ blue = black ++ black ++ notblack;
+ white = notblack ++ notblack ++ notblack;
+ }
+ }
+ }
+
+ Greyscale_chart_item = class
+ Menuaction "_Greyscale" "make a greyscale" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ pwidth = Expression "Patch width" 8;
+ pheight = Expression "Patch height" 8;
+ npatches = Expression "Number of patches" 16;
+
+ _result
+ = Image (image_set_type Image_type.B_W
+ (clip2fmt Image_format.UCHAR wedge))
+ {
+ wedge
+ = 255 / (to_real npatches - 1) *
+ (int) (strip?0 / to_real pwidth)
+ {
+ strip = make_xy (to_real pwidth * to_real npatches) pheight;
+ }
+ }
+ }
+ }
+
+ CMYK_test_chart_item = class
+ Menuaction "_CMYK Wedges" "make a set of CMYK wedges" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ pwidth = Expression "Patch width" 8;
+ pheight = Expression "Patch height" 8;
+ npatches = Expression "Number of patches" 16;
+
+ _result
+ = Image (image_set_type Image_type.CMYK
+ (clip2fmt Image_format.UCHAR strips))
+ {
+ wedge
+ = 255 / (to_real npatches - 1) *
+ (int) (strip?0 / to_real pwidth)
+ {
+ strip = make_xy (to_real pwidth * to_real npatches) pheight;
+ }
+
+ black = wedge * 0;
+
+ C = wedge ++ black ++ black ++ black;
+ M = black ++ wedge ++ black ++ black;
+ Y = black ++ black ++ wedge ++ black;
+ K = black ++ black ++ black ++ wedge;
+
+ strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]];
+ }
+ }
+ }
+
+ Colour_atlas_item = class
+ Menuaction "_Colour Atlas"
+ "make a grid of patches grouped around a colour" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ start = Colour_picker "Lab" [50,0,0];
+ nstep = Expression "Number of steps" 9;
+ ssize = Expression "Step size" 10;
+ psize = Expression "Patch size" 32;
+ sepsize = Expression "Separator size" 4;
+
+ _result
+ = colour_transform_to (get_type start) blocks'''
+ {
+ size = (to_real nstep * 2 + 1) * to_real psize -
+ to_real sepsize;
+ xy = make_xy size size;
+
+ xy_grid = (xy % to_real psize) <
+ (to_real psize - to_real sepsize);
+ grid = xy_grid?0 & xy_grid?1;
+
+ blocks = (int) (to_real ssize * ((int) (xy / to_real psize)));
+ lab_start = colour_transform_to Image_type.LAB start;
+ blocks' = blocks - to_real nstep * to_real ssize +
+ Vector (tl lab_start.value);
+ blocks'' = hd lab_start.value ++ Image blocks';
+ blocks'''
+ = image_set_type Image_type.LAB blocks'', Image grid
+ = 0;
+ }
+ }
+ }
+}
+
diff --git a/share/nip2/compat/8.5/Magick.def b/share/nip2/compat/8.5/Magick.def
new file mode 100644
index 00000000..ab6cdb35
--- /dev/null
+++ b/share/nip2/compat/8.5/Magick.def
@@ -0,0 +1,2423 @@
+/*
+
+ ImageMagick operations edited by Alan Gibson (aka "snibgo"; snibgo at earthling dot net).
+ 1-Apr-2014
+ Minor corrections to Geometry_widget and Alpha.
+ Added loads of widgets and Menuactions.
+ Not fully tested.
+ 5-Apr-2014
+ Many more menu actions.
+ Reorganised Magick menu.
+ 10-Apr-2014
+ Many more menu actions.
+ 11-Apr-2014 jcupitt
+ Split to separate Magick.def
+ 13-Apr-2014 snibgo
+ Put "new image" items into sub-menu.
+ New class VirtualPixlBack.
+ 17-Apr-2014 snibgo
+ Many small changes.
+ A few new menu options.
+ Created sub-menu for multi-input operations.
+ 3-May-2014 jcupitt
+ Put quotes around ( in shadow to help unix
+
+ Last update: 17-Apr-2014.
+
+ For details of ImageMagick operations, see http://www.imagemagick.org/script/command-line-options.php etc.
+*/
+
+// We don't need Noop.
+/*===
+Magick_noop_item = class
+ Menuaction "_Noop" "no operation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+===*/
+
+Magick_testPar_item = class
+ Menuaction "_TestPar" "no operation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "( +clone ) +append ",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+/* Removed Read_item and Write_item, much better to use nip2 load/save image.
+ * Plus they can load all libMagick formats anyway.
+ */
+
+
+// Put "new image" items into sub-menu
+Magick_NewImageMenu_item = class
+ Menupullright "_New image" "make a new image" {
+
+ Magick_newcanvas_item = class
+ Menuaction "_Solid colour" "make image of solid colour" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ colour = Magick.generalcol_widget;
+ command = Magick.command [
+ size._flag,
+ "\"canvas:" ++ colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_builtin_item = class
+ Menuaction "_Built-in image" "create a built-in image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ builtin = Magick.builtin_widget;
+ command = Magick.command [
+ builtin._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_gradient_item = class
+ Menuaction "_Gradient" "make a linear gradient between two colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ topColour = Magick.generalcol_widget;
+ bottomColour = Magick.generalcol_widget;
+
+ command = Magick.command [
+ size._flag,
+ concat ["\"gradient:",
+ topColour._flag, "-", bottomColour._flag, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_hald_item = class
+ Menuaction "_Hald-clut image" "create an identity hald-clut image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ order = Expression "order" 8;
+ command = Magick.command [
+ "hald:" ++ print order.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_pattern_item = class
+ Menuaction "_Pattern" "create pattern" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ pattern = Magick.pattern_widget;
+ command = Magick.command [
+ size._flag,
+ pattern._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_plasma_item = class
+ Menuaction "_Plasma image" "create plasma image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ // FIXME? ColourA-ColourB.
+ // FIXME? Allow plasma:fractal?
+
+ command = Magick.command [
+ size._flag,
+ "plasma:",
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_radialgradient_item = class
+ Menuaction "_Radial gradient"
+ "make a radial gradient between two colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ innerColour = Magick.generalcol_widget;
+ outerColour = Magick.generalcol_widget;
+
+ command = Magick.command [
+ size._flag,
+ concat ["\"radial-gradient:",
+ innerColour._flag, "-", outerColour._flag, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+} // end Magick_NewImageMenu_item
+
+
+Magick_MultiMenu_item = class
+ Menupullright "_Multiple inputs" "make an image from multiple images" {
+
+ Magick_composite_item = class
+ Menuaction "_Composite" "composite two images (without mask)" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.compose_widget;
+ offsets = Magick.OffsetGeometry_widget;
+ gravity = Magick.gravity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-geometry", offsets._flag,
+ gravity._flag,
+ method._flag,
+ "-composite",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+ Magick_compositeMask_item = class
+ Menuaction "_Composite masked" "composite two images (with mask)" {
+ action x y z = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.compose_widget;
+ offsets = Magick.OffsetGeometry_widget;
+ gravity = Magick.gravity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "\"%s\"",
+ "-geometry", offsets._flag,
+ gravity._flag,
+ method._flag,
+ "-composite",
+ "\"%s\""
+ ];
+
+ _result = Magick.system3 command x y z;
+ }
+ }
+
+ // FIXME: other operations like remap that take another image as arguments are:
+ // mask (pointless?), texture, tile (pointless?)
+
+ // FIXME: operations that take a filename that isn't an image:
+ // cdl, profile
+
+ Magick_clut_item = class
+ Menuaction "_Clut" "replace values using second image as colour look-up table" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // FIXME: uses -intensity "when mapping greyscale CLUT image to alpha channel if set by -channels"
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-clut",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+ Magick_haldclut_item = class
+ Menuaction "_Hald clut" "replace values using second image as Hald colour look-up table" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-hald-clut",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+
+ // Encipher and decipher: key files can be text or image files.
+
+ Magick_encipher_item = class
+ Menuaction "_Encipher/Decipher" "encipher or decipher an image image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pathname = Pathname "Read key file" "";
+ isDecipher = Toggle "Decipher" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ if pathname.value == "" then "" else (
+ ( if isDecipher then "-decipher " else "-encipher ") ++
+ ( "\"" ++ pathname.value ++ "\"" )
+ ),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_profile_item = class
+ Menuaction "_Profile" "assigns/applies an ICC profile" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pathname1 = Pathname "Read profile file" "";
+ pathname2 = Pathname "Read profile file" "";
+
+ command = Magick.command [
+ "\"%s\"",
+ if pathname1.value == "" then "" else (
+ "-profile " ++
+ "\"" ++ pathname1.value ++ "\""),
+ if pathname2.value == "" then "" else (
+ "-profile " ++
+ "\"" ++ pathname2.value ++ "\""),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_remap_item = class
+ Menuaction "_Remap" "reduce colours to those in another image" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-remap",
+ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+} // end Magick_MultiMenu_item
+
+
+Magick_image_type_item = class
+ Menuaction "_Image Type" "change image type" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ imagetype = Magick.imagetype_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ imagetype._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+sep2 = Menuseparator;
+
+Magick_alpha_item = class
+ Menuaction "_Alpha" "add/remove alpha channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ alpha = Magick.alpha_widget;
+ command = Magick.command [
+ "\"%s\"",
+ alpha._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_annotate_item = class
+ Menuaction "_Annotate" "add text annotation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ text = Magick.text_widget;
+ font = Magick.Font_widget;
+ geometry = Magick.AnnotGeometry_widget;
+ gravity = Magick.gravity_widget;
+ foreground = Magick.foreground_widget;
+ undercol = Magick.undercol_widget;
+ antialias = Magick.antialias_widget;
+ command = Magick.command [
+ "\"%s\"",
+ font._flag,
+ antialias._flag,
+ gravity._flag,
+ foreground._flag,
+ undercol._flag,
+ "-annotate",
+ geometry._flag,
+ "\"" ++ text.value ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_autoGamma_item = class
+ Menuaction "_AutoGamma" "automatic gamma" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-auto-gamma",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_autoLevel_item = class
+ Menuaction "_AutoLevel" "automatic level" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-auto-level",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_blurSharpMenu_item = class
+ Menupullright "_Blur/Sharpen" "blur and sharpen" {
+
+ Magick_adaptive_blur_item = class
+ Menuaction "_Adaptive Blur" "blur less near edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ // note: adaptive-blur doesn't regard VP.
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-adaptive-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_blur_item = class
+ Menuaction "_Blur" "blur" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_gaussianBlur_item = class
+ Menuaction "_Gaussian Blur" "blur with a Gaussian operator" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-gaussian-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_motionBlur_item = class
+ Menuaction "_Motion Blur" "simulate motion blur" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ angle = Scale "angle" (-360) 360 0;
+ channels = Magick.ch_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-motion-blur",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_rotationalBlur_item = class
+ Menuaction "_RotationalBlur" "blur around the centre" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ angle = Scale "angle (degrees)" (-360) 360 20;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-radial-blur",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_selectiveBlur_item = class
+ Menuaction "_Selective Blur" "blur where contrast is less than or equal to threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ virtpixback._flag,
+ channels._flag,
+ "-selective-blur",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print threshold.value ++ "%%",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Magick_adaptive_sharpen_item = class
+ Menuaction "_Adaptive Sharpen" "sharpen more near edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-adaptive-sharpen",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_sharpen_item = class
+ Menuaction "_Sharpen" "sharpen" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-sharpen",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_unsharpen_item = class
+ Menuaction "_Unsharp" "sharpen with unsharp mask" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ gain = Scale "Gain" (-10) 10 1;
+ threshold = Scale "Threshold" 0 1 0.05;
+ virtpixback = Magick.VirtualPixelBack_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-unsharp",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print gain.value ++ "+" ++
+ print threshold.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end BlurSharpMenu_item
+
+
+Magick_border_item = class
+ Menuaction "_Border" "add border of given colour" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ compose = Magick.compose_widget;
+ width = Expression "Width" 3;
+ bordercol = Magick.bordercol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ bordercol._flag,
+ "-border",
+ print width.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_brightCont_item = class
+ Menuaction "_Brightness-contrast" "adjust the brightness and/or contrast" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bri = Scale "brightness" (-100) 100 0;
+ con = Scale "contrast" (-100) 100 0;
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-brightness-contrast",
+ print bri.value ++ "x" ++ print con.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// Note: canny requires ImageMagick 6.8.9-0 or later.
+
+Magick_canny_item = class
+ Menuaction "_Canny" "detect a wide range of edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ lowPc = Scale "lower percent" 0 100 10;
+ highPc = Scale "lower percent" 0 100 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-canny",
+ concat ["\"",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print lowPc.value ++ "%%+" ++
+ print highPc.value ++ "%%" ++ "\""
+ ],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
+Magick_charcoal_item = class
+ Menuaction "_Charcoal" "simulate a charcoal drawing" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ factor = Scale "factor" 0 50 1;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-charcoal",
+ print factor.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_chop_item = class
+ Menuaction "_Chop" "remove pixels from the interior" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ gravity._flag,
+ "-chop",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colorize_item = class
+ Menuaction "_Colorize" "colorize by given amount" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ val = Scale "value" 0 100 100;
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-colorize",
+ print val.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colors_item = class
+ Menuaction "_Colors" "reduce number of colors" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ treedepth = Expression "Treedepth" 8;
+ dither = Magick.dither_widget;
+ quantize = Magick.colorspace_widget;
+ colors = Expression "Colours" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-quantize", quantize._flag,
+ "-treedepth",
+ print treedepth.expr,
+ dither._flag,
+ "-colors",
+ print colors.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: color-matrix?
+
+Magick_colorspace_item = class
+ Menuaction "_Colourspace" "convert to arbitrary colourspace" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colsp = Magick.colorspace_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-colorspace",
+ colsp._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colorspaceGray_item = class
+ Menuaction "_Colourspace gray" "convert to gray using given intensity method" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-colorspace gray",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_contrast_item = class
+ Menuaction "_Contrast" "increase or reduce the contrast" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ isReduce = Toggle "reduce contrast" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ (if isReduce then "+" else "-") ++ "contrast",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_contrastStretch_item = class
+ Menuaction "_Contrast stretch" "stretches tones, making some black/white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+ blk = Scale "percent to make black" 0 100 0;
+ wht = Scale "percent to make white" 0 100 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-contrast-stretch",
+ "\"" ++ print blk.value ++ "x" ++ print wht.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: convolve (bias, kernel)
+
+Magick_crop_item = class
+ Menuaction "_Crop" "cut out a rectangular region" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ gravity._flag,
+ "-crop",
+ geometry._flag,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_deskew_item = class
+ Menuaction "_Deskew" "straighten the image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ threshold = Scale "Threshold (percent)" 0 100 80;
+
+ // FIXME: toggle auto-crop?
+
+ command = Magick.command [
+ "\"%s\"",
+ "-deskew",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_despeckle_item = class
+ Menuaction "_Despeckle" "reduce the speckles" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-despeckle",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_distort_item = class
+ Menuaction "_Distort" "distort using a method and arguments" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ virtpixback = Magick.VirtualPixelBack_widget;
+ distort = Magick.distort_widget;
+ args = String "Arguments" "1,0";
+ isPlus = Toggle "Extend to show entire image" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ (if isPlus then "+" else "-") ++ "distort",
+ distort._flag,
+ args.value,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_draw_item = class
+ Menuaction "_Draw" "annotate with one or more graphic primitives" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ args = String "Arguments" "line 0,0 9,9 rectangle 10,10 20,20";
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-draw",
+ concat ["\"", args.value, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_edge_item = class
+ Menuaction "_Edge" "detect edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "Radius" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-edge",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_emboss_item = class
+ Menuaction "_Emboss" "emboss" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "Radius" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-emboss",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_enhance_item = class
+ Menuaction "_Enhance" "enhance a noisy image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-enhance",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_equalize_item = class
+ Menuaction "_Equalize" "equalize the histogram" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-equalize",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_evaluate_item = class
+ Menuaction "_Evaluate" "evaluate an expression on each pixel channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ operation = Magick.evaluate_widget;
+ val = Expression "value" 5;
+ isPc = Toggle "Value is percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ operation._flag,
+ print val.expr ++ if isPc then "%%" else "",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_extent_item = class
+ Menuaction "_Extent" "set the image size and offset" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ background = Magick.background_widget;
+ compose = Magick.compose_widget;
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ background._flag,
+ gravity._flag,
+ "-extent",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
+Magick_FlipFlopMenu_item = class
+ Menupullright "_Flip/flop" "flip/flop/transverse/transpose" {
+
+ Magick_flip_item = class
+ Menuaction "_Flip vertically" "mirror upside-down" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-flip",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_flop_item = class
+ Menuaction "_Flop horizontally" "mirror left-right" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-flop",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_transpose_item = class
+ Menuaction "_Transpose" "mirror along the top-left to bottom-right diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-transpose +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_transverse_item = class
+ Menuaction "_Transverse" "mirror along the bottom-left to top-right diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-transverse +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end Magick_FlipFlopMenu_item
+
+
+Magick_floodfill_item = class
+ Menuaction "_Floodfill" "recolour neighbours that match" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ fuzz = Magick.fuzz_widget;
+ coordinate = Magick.coordinate_widget;
+
+ // -draw "color x,y floodfill"
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-fuzz",
+ "\"" ++ print fuzz.value ++ "%%\"",
+ "-draw \" color",
+ coordinate._flag,
+ "floodfill \"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_frame_item = class
+ Menuaction "_Frame" "surround with border or beveled frame" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ border = Magick.bordercol_widget;
+ compose = Magick.compose_widget;
+ matte = Magick.mattecol_widget;
+ geometry = Magick.FrameGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ border._flag,
+ matte._flag,
+ "-frame",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_function_item = class
+ Menuaction "_Function" "evaluate a function on each pixel channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ function = Magick.function_widget;
+ // FIXME: explain values; use sensible defaults.
+ values = String "values" "0,0,0,0";
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-function",
+ function._flag,
+ values.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_fx_item = class
+ Menuaction "_Fx" "apply a mathematical expression" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ interpolate = Magick.interpolate_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ args = String "Expression" "u*1/2";
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ interpolate._flag,
+ virtpixback._flag,
+ "-fx",
+ concat ["\"", args.value, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gamma_item = class
+ Menuaction "_Gamma" "apply a gamma correction" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ gamma = Magick.gamma_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-gamma",
+ print gamma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gradient_item = class
+ Menuaction "_Gradient" "apply a linear gradient" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colourA = Magick.generalcol_widget;
+ colourB = Magick.generalcol_widget;
+
+ position = Option "colourA is at" [
+ "top", "bottom",
+ "left", "right",
+ "top-left", "top-right",
+ "bottom-left", "bottom-right"] 0;
+ _baryArg
+ = concat ["0,0,", colourA._flag, " 0,%%[fx:h-1],", colourB._flag], position.value == 0
+ = concat ["0,0,", colourB._flag, " 0,%%[fx:h-1],", colourA._flag], position.value == 1
+ = concat ["0,0,", colourA._flag, " %%[fx:w-1],0,", colourB._flag], position.value == 2
+ = concat ["0,0,", colourB._flag, " %%[fx:w-1],0,", colourA._flag], position.value == 3
+ = concat ["0,0,", colourA._flag, " %%[fx:w-1],%%[fx:h-1],", colourB._flag], position.value == 4
+ = concat ["%%[fx:w-1],0,", colourA._flag, " 0,%%[fx:h-1],", colourB._flag], position.value == 5
+ = concat ["%%[fx:w-1],0,", colourB._flag, " 0,%%[fx:h-1],", colourA._flag], position.value == 6
+ = concat ["0,0,", colourB._flag, " %%[fx:w-1],%%[fx:h-1],", colourA._flag], position.value == 7
+ = "dunno";
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sparse-color barycentric \"" ++ _baryArg ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gradientCorn_item = class
+ Menuaction "_Gradient corners"
+ "apply a bilinear gradient between the corners" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colour_top_left = Magick.generalcol_widget;
+ colour_top_right = Magick.generalcol_widget;
+ colour_bottom_left = Magick.generalcol_widget;
+ colour_bottom_right = Magick.generalcol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sparse-color bilinear \"" ++
+ "0,0," ++ colour_top_left._flag ++
+ ",%%[fx:w-1],0" ++ colour_top_right._flag ++
+ ",0,%%[fx:h-1]" ++ colour_bottom_left._flag ++
+ ",%%[fx:w-1],%%[fx:h-1]" ++ colour_bottom_right._flag ++ "\"",
+ "+depth",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_histogram_item = class
+ Menuaction "_Histogram" "make a histogram image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-define histogram:unique-colors=false histogram:" ++
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_implode_item = class
+ Menuaction "_Implode" "implode pixels about the center" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ factor = Scale "factor" 0 20 1;
+ interpolate = Magick.interpolate_widget;
+ // FIXME: virtual-pixel?
+
+ command = Magick.command [
+ "\"%s\"",
+ interpolate._flag,
+ "-implode",
+ print factor.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_level_item = class
+ Menuaction "_Level" "adjust the level of channels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ blk = Scale "black point" (-100) 200 0;
+ wht = Scale "white point" (-100) 200 100;
+ gam = Scale "gamma" 0 30 1;
+ isPc = Toggle "Levels are percent" true;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "level",
+ "\"" ++ print blk.value ++ "," ++
+ print wht.value ++ (if isPc then "%%" else "") ++ "," ++
+ print gam.value ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_levelCols_item = class
+ Menuaction "_Level colors" "adjust levels to given colours" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ colour_black = Magick.generalcol_widget;
+ colour_white = Magick.generalcol_widget;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "level-colors",
+ "\"" ++ colour_black._flag ++ "," ++ colour_white._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_linearStretch_item = class
+ Menuaction "_Linear stretch" "stretches tones, making some black/white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ blk = Scale "percent to make black" 0 100 0;
+ wht = Scale "percent to make white" 0 100 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-linear-stretch",
+ "\"" ++ print blk.value ++ "x" ++ print wht.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_magnify_item = class
+ Menuaction "_Magnify" "double the size of the image with pixel art scaling" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-magnify",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_modulate_item = class
+ Menuaction "_Modulate" "modulate brightness, saturation and hue" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ modcolsp = Magick.ModColSp_widget;
+ bright = Scale "brightness" 0 200 100;
+ sat = Scale "saturation" 0 200 100;
+ hue = Scale "hue" 0 200 100;
+
+ command = Magick.command [
+ "\"%s\"",
+ modcolsp._flag,
+ "-modulate",
+ print bright.value ++ "," ++ print sat.value ++ "," ++
+ print hue.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_monochrome_item = class
+ Menuaction "_Monochrome" "transform to black and white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // FIXME: also intensity?
+
+ intensity = Magick.intensity_widget;
+ treedepth = Expression "Treedepth" 8;
+ dither = Magick.dither_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ dither._flag,
+ "-treedepth",
+ print treedepth.expr,
+ "-monochrome",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_morphology_item = class
+ // See http://www.imagemagick.org/Usage/morphology/
+ Menuaction "_Morphology" "apply a morphological method" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.morphmeth_widget;
+ iter = Expression "Iterations (-1=repeat until done)" 1;
+
+ kernel = Magick.kernel_widget;
+ // FIXME: custom kernel eg "3x1+2+0:1,0,0"
+ // width x height + offsx + offsy : {w*h values}
+ // each value is 0.0 to 1.0 or "NaN" or "-"
+
+ // kernel args, mostly float radius,scale. radius=0=default. default scale = 1.0
+ // but
+ // ring takes: radius1, radius2, scale
+ // rectangle and comet take: width x height + offsx + offsy
+ // blur takes: radius x sigma
+ // FIXME: for now, simply allow any string input.
+ kernel_arg = String "Kernel arguments" "";
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-morphology",
+ method._flag ++ ":" ++ print iter.expr,
+ kernel._flag ++
+ (if kernel_arg.value == "" then "" else ":") ++ kernel_arg.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_negate_item = class
+ Menuaction "_Negate" "negate" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-negate",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_addNoise_item = class
+ Menuaction "_add Noise" "add noise" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ attenuate = Scale "attenuate" 0 1.0 1.0;
+ noise = Magick.noise_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-attenuate",
+ print attenuate.value,
+ noise._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_normalize_item = class
+ Menuaction "_Normalize" "normalize" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-normalize",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_opaque_item = class
+ Menuaction "_Opaque" "change this colour to the fill colour" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ fill = Magick.foreground_widget;
+ changeColour = Magick.changeCol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ fill._flag,
+ channels._flag,
+ "-fuzz",
+ "\"" ++ print changeColour.fuzz.value ++ "%%\"",
+ (if changeColour.nonMatch then "+" else "-") ++ "opaque",
+ "\"" ++ changeColour.colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_paint_item = class
+ Menuaction "_Paint" "simulate an oil painting" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "radius" 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-paint",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+/*=== FIXME Bug; remove for now.
+Polaroid_item = class
+ Menuaction "_Polaroid" "simulate a polaroid picture" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "angle" (-90) 90 20;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-polaroid",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+===*/
+
+Magick_posterize_item = class
+ Menuaction "_Posterize" "reduce to (n) levels per channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ levels = Expression "levels" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-posterize",
+ print levels.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_raise_item = class
+ Menuaction "_Raise" "lighten or darken image edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ thk = Expression "Thickness" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-raise",
+ print thk.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_resize_item = class
+ Menuaction "_Resize" "resize to given width and height" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ filter = Magick.filter_widget;
+ type = Magick.ResizeType_widget;
+ width = Scale "Width" 1 100 10;
+ height = Scale "Height" 1 100 10;
+ isPc = Toggle "Width and height are percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ filter._flag,
+ "-" ++ type._flag,
+ "\"" ++ print width.value ++ "x" ++ print height.value ++
+ (if isPc then "%%" else "!") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_roll_item = class
+ Menuaction "_Roll" "roll an image horizontally or vertically" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rollx = Expression "X" 3;
+ rolly = Expression "Y" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-roll",
+ (if rollx.expr >= 0 then "+" else "") ++ print rollx.expr ++
+ (if rolly.expr >= 0 then "+" else "") ++ print rolly.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_rotate_item = class
+ Menuaction "_Rotate" "rotate" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "angle (degrees)" (-360) 360 20;
+ virtpixback = Magick.VirtualPixelBack_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ "+distort",
+ "SRT",
+ print angle.value,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: -segment, but cluster-threshold should be percentage of image area.
+
+Magick_sepia_item = class
+ Menuaction "_Sepia tone" "simulate a sepia-toned photo" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ threshold = Scale "Threshold (percent)" 0 100 80;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sepia-tone",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shade_item = class
+ Menuaction "_Shade" "shade with a distant light source" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ azimuth = Scale "Azimuth (degrees)" (-360) 360 0;
+ elevation = Scale "Elevation (degrees)" 0 90 45;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-shade",
+ print azimuth.value ++ "x" ++ print elevation.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shadow_item = class
+ Menuaction "_Shadow" "simulate a shadow" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ shadowCol = Magick.generalcol_widget;
+ opacity = Scale "Opacity (percent)" 0 100 75;
+ sigma = Scale "Sigma" 0 30 2;
+ // FIXME: make offsets a single widget?
+ offsx = Scale "X-offset" (-20) 20 4;
+ offsy = Scale "Y-offset" (-20) 20 4;
+ arePc = Toggle "offsets are percentages" false;
+
+ // FIXME: raw operation creates page offset, which vips dislikes.
+ // So we take this futher, compositing with source.
+ command = Magick.command [
+ "\"%s\"",
+ "\"(\" +clone",
+ "-background", "\"" ++ shadowCol._flag ++ "\"",
+ "-shadow",
+ concat [
+ "\"",
+ print opacity.value, "x", print sigma.value,
+ (if offsx.value >= 0 then "+" else ""), print offsx.value,
+ (if offsy.value >= 0 then "+" else ""), print offsy.value,
+ (if arePc then "%%" else ""),
+ "\""
+ ],
+ "\")\" +swap -background None -layers merge",
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shave_item = class
+ Menuaction "_Shave" "shave pixels from the edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Scale "Width" 0 50 10;
+ height = Scale "Height" 0 50 10;
+ isPc = Toggle "Width and height are percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-shave",
+ "\"" ++ print width.value ++ "x" ++ print height.value ++
+ (if isPc then "%%" else "") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shear_item = class
+ Menuaction "_Shear" "shear along the x-axis and/or y-axis" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ shearX = Expression "shear X (degrees)" 0;
+ shearY = Expression "shear Y (degrees)" 0;
+ background = Magick.background_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ "-shear",
+ print shearX.expr ++ "x" ++ print shearY.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_sigmoid_item = class
+ Menuaction "_Sigmoid" "increase or decrease mid-tone contrast sigmoidally" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ contrast = Scale "contrast" 0 30 3;
+ midpoint = Scale "mid-point (percent)" 0 100 50;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "sigmoidal-contrast",
+ "\"" ++ print contrast.value ++ "x" ++
+ print midpoint.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_sketch_item = class
+ Menuaction "_Sketch" "simulate a pencil sketch" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ angle = Scale "angle" (-360) 360 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sketch",
+ print radius.value ++ "x" ++ print sigma.value ++
+ (if angle >= 0 then ("+" ++ print angle.value) else ""),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_solarize_item = class
+ Menuaction "_Solarize" "negate all pixels above a threshold level" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-solarize",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: -sparse-color needs abitrary list of {x,y,colour}.
+
+Magick_splice_item = class
+ Menuaction "_Splice" "splice a colour into the image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ background = Magick.background_widget;
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ gravity._flag,
+ "-splice",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_spread_item = class
+ Menuaction "_Spread" "displace pixels by random amount" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ virtpixback = Magick.VirtualPixelBack_widget;
+ amount = Expression "Amount (pixels)" 5;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ "-spread",
+ print amount.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_statistic_item = class
+ Menuaction "_Statistic" "replace each pixel with statistic from neighbourhood" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Expression "Width" 5;
+ height = Expression "Height" 5;
+ statisticType = Magick.StatType_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-statistic",
+ statisticType._flag,
+ print width.expr ++ "x" ++ print height.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_swirl_item = class
+ Menuaction "_Swirl" "swirl around the centre" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Magick.angle_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-swirl",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_thresholdMenu_item = class
+ Menupullright "_Threshold" "make black or white" {
+
+ Magick_threshold_item = class
+ Menuaction "_Threshold" "apply black/white threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_blackThreshold_item = class
+ Menuaction "_Black threshold" "where below threshold set to black" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-black-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_whiteThreshold_item = class
+ Menuaction "_White threshold" "where above threshold set to white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-white-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_latThreshold_item = class
+ Menuaction "_Local Adaptive Threshold" "where above average plus offset set to white, otherwise black" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Expression "Width" 10;
+ height = Expression "Height" 10;
+ offset = Scale "Offset (percent)" (-100) 100 0;
+ // note: "-lat" doesn't respond to channels
+
+ command = Magick.command [
+ "\"%s\"",
+ "-lat",
+ concat ["\"", print width.expr, "x", print height.expr,
+ (if offset.value >= 0 then "+" else ""), print offset.value,
+ "%%\""
+ ],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_randThreshold_item = class
+ Menuaction "_Random Threshold" "between specified limits, apply random threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ low = Scale "Low threshold" 0 100 10;
+ high = Scale "High threshold" 0 100 90;
+ isPc = Toggle "Thresholds are percent" true;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-random-threshold",
+ "\"" ++ print low.value ++ "x" ++ print high.value ++
+ (if isPc then "%%" else "") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end ThresholdMenu_item
+
+
+// Note: alternatives include:
+// convert in.tif -write mpr:TILE +delete -size 200x200 -background XXXX -tile-offset +30+30 tile:mpr:TILE out.tif
+
+Magick_tile_item = class
+ Menuaction "_Tile" "fill given size with tiled image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+
+ command = Magick.command [
+ size._flag,
+ "tile:" ++ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_tint_item = class
+ Menuaction "_Tint" "apply a tint" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ amount = Scale "amount (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-tint",
+ // snibgo note: although the amount is a percentage, it doesn't need "%" character.
+ print amount.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_transparent_item = class
+ Menuaction "_Transparent" "make this colour transparent" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ changeColour = Magick.changeCol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-fuzz",
+ "\"" ++ print changeColour.fuzz.value ++ "%%\"",
+ (if changeColour.nonMatch then "+" else "-") ++ "transparent",
+ "\"" ++ changeColour.colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_trim_item = class
+ Menuaction "_Trim" "trims away border" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ fuzz = Magick.fuzz_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-fuzz",
+ "\"" ++ print fuzz.value ++ "%%\"",
+ "-trim +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_uniqueCols_item = class
+ Menuaction "_Unique colours" "discard all but one of any pixel color" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-unique-colors",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_vignette_item = class
+ Menuaction "_Vignette" "soften the edges in vignette style" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ rx = Scale "Rolloff x (percent)" 0 100 10;
+ ry = Scale "Rolloff y (percent)" 0 100 10;
+ background = Magick.background_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ "-vignette",
+ print radius.value ++ "x" ++ print sigma.value ++
+ (if rx.value >= 0 then "+" else "") ++ print rx.value ++
+ (if ry.value >= 0 then "+" else "") ++ print ry.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_wave_item = class
+ Menuaction "_Wave" "shear the columns into a sine wave" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ amplitude = Scale "Amplitude (pixels)" 0 100 10;
+ wavelength = Scale "Wavelength (pixels)" 0 100 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-wave",
+ print amplitude.value ++ "x" ++ print wavelength.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
diff --git a/share/nip2/compat/8.5/Makefile.am b/share/nip2/compat/8.5/Makefile.am
new file mode 100644
index 00000000..5de2cdd9
--- /dev/null
+++ b/share/nip2/compat/8.5/Makefile.am
@@ -0,0 +1,27 @@
+startdir = $(pkgdatadir)/compat/8.5
+
+start_DATA = \
+ Math.def \
+ Image.def \
+ Magick.def \
+ Colour.def \
+ Tasks.def \
+ Object.def \
+ Filter.def \
+ Matrix.def \
+ Widgets.def \
+ Histogram.def \
+ Preferences.ws \
+ _joe_extra.def \
+ _joe_utilities.def \
+ _convert.def \
+ _generate.def \
+ _list.def \
+ _predicate.def \
+ _stdenv.def \
+ _Object.def \
+ _magick.def \
+ _types.def
+
+EXTRA_DIST = $(start_DATA)
+
diff --git a/share/nip2/compat/8.5/Math.def b/share/nip2/compat/8.5/Math.def
new file mode 100644
index 00000000..34a40a66
--- /dev/null
+++ b/share/nip2/compat/8.5/Math.def
@@ -0,0 +1,588 @@
+Math_arithmetic_item = class
+ Menupullright "_Arithmetic" "basic arithmetic for objects" {
+ Add_item = class
+ Menuaction "_Add" "add a and b" {
+ action a b = map_binary add a b;
+ }
+
+ Subtract_item = class
+ Menuaction "_Subtract" "subtract b from a" {
+ action a b = map_binary subtract a b;
+ }
+
+ Multiply_item = class
+ Menuaction "_Multiply" "multiply a by b" {
+ action a b = map_binary multiply a b;
+ }
+
+ Divide_item = class
+ Menuaction "_Divide" "divide a by b" {
+ action a b = map_binary divide a b;
+ }
+
+ Remainder_item = class
+ Menuaction "_Remainder"
+ "remainder after integer division of a by b" {
+ action a b = map_binary remainder a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Absolute_value_item = class
+ Menuaction "A_bsolute Value" "absolute value of x" {
+ action x = map_unary abs x;
+ }
+
+ Absolute_value_vector_item = class
+ Menuaction "Absolute Value _Vector"
+ "like Absolute Value, but treat pixels as vectors" {
+ action x = map_unary abs_vec x;
+ }
+
+ Sign_item = class
+ Menuaction "S_ign" "unit vector" {
+ action x = map_unary sign x;
+ }
+
+ Negate_item = class
+ Menuaction "_Negate" "multiply by -1" {
+ action x = map_unary unary_minus x;
+ }
+}
+
+Math_trig_item = class
+ Menupullright "_Trigonometry" "trigonometry operations (all in degrees)" {
+ Sin_item = class
+ Menuaction "_Sine" "calculate sine x" {
+ action x = map_unary sin x;
+ }
+
+ Cos_item = class
+ Menuaction "_Cosine" "calculate cosine x" {
+ action x = map_unary cos x;
+ }
+
+ Tan_item = class
+ Menuaction "_Tangent" "calculate tangent x" {
+ action x = map_unary tan x;
+ }
+
+ sep1 = Menuseparator;
+
+ Asin_item = class
+ Menuaction "Arc S_ine" "calculate arc sine x" {
+ action x = map_unary asin x;
+ }
+
+ Acos_item = class
+ Menuaction "Arc C_osine" "calculate arc cosine x" {
+ action x = map_unary acos x;
+ }
+
+ Atan_item = class
+ Menuaction "Arc T_angent" "calculate arc tangent x" {
+ action x = map_unary atan x;
+ }
+
+ sep2 = Menuseparator;
+
+ Rad_item = class
+ Menuaction "_Degrees to Radians" "convert degrees to radians" {
+ action x = map_unary rad x;
+ }
+
+ Deg_item = class
+ Menuaction "_Radians to Degrees" "convert radians to degrees" {
+ action x = map_unary deg x;
+ }
+
+ sep3 = Menuseparator;
+
+ Angle_range_item = class
+ Menuaction "Angle i_n Range"
+ "is angle within t degrees of r, mod 360" {
+ action t r angle
+ = clock (max - angle) < 2*r
+ {
+ max = clock (t + r);
+
+ clock a
+ = a + 360, a < 0;
+ = a - 360, a >= 360;
+ = a;
+ }
+ }
+}
+
+Math_log_item = class
+ Menupullright "_Log" "logarithms and anti-logs" {
+ Exponential_item = class
+ Menuaction "_Exponential" "calculate e ** x" {
+ action x = map_unary (power e) x;
+ }
+
+ Log_natural_item = class
+ Menuaction "Natural _Log" "log base e of x" {
+ action x = map_unary log x;
+ }
+
+ sep1 = Menuseparator;
+
+ Exponential10_item = class
+ Menuaction "E_xponential base 10" "calculate 10 ** x" {
+ action x = map_unary (power 10) x;
+ }
+
+ Log10_item = class
+ Menuaction "L_og Base 10" "log base 10 of x" {
+ action x = map_unary log10 x;
+ }
+
+ sep2 = Menuseparator;
+
+ Raise_to_power_item = class
+ Menuaction "_Raise to Power" "calculate x ** y" {
+ action x y = map_binary power x y;
+ }
+}
+
+Math_complex_item = class
+ Menupullright "_Complex" "operations on complex numbers and images" {
+ Complex_extract = class
+ Menupullright "_Extract" "extract fields from complex" {
+ Real_item = class
+ Menuaction "_Real"
+ "extract real part of complex" {
+ action in = map_unary re in;
+ }
+
+ Imaginary_item = class
+ Menuaction "_Imaginary"
+ "extract imaginary part of complex" {
+ action in = map_unary im in;
+ }
+ }
+
+ Complex_build_item = class
+ Menuaction "_Build" "join a and b to make a complex" {
+ action a b = map_binary comma a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Polar_item = class
+ Menuaction "_Polar"
+ "convert real and imag to amplitude and phase" {
+ action a = map_unary polar a;
+ }
+
+ Rectangular_item = class
+ Menuaction "_Rectagular"
+ ("convert (amplitude, phase) image to rectangular " ++
+ "coordinates") {
+ action x = map_unary rectangular x;
+ }
+
+ sep2 = Menuseparator;
+
+ Conjugate_item = class
+ Menuaction "_Conjugate" "invert imaginary part" {
+ action x = map_unary conj x;
+ }
+}
+
+Math_boolean_item = class
+ Menupullright "_Boolean" "bitwise boolean operations for integer objects" {
+ And_item = class
+ Menuaction "_AND" "bitwise AND of a and b" {
+ action a b = map_binary bitwise_and a b;
+ }
+
+ Or_item = class
+ Menuaction "_OR" "bitwise OR of a and b" {
+ action a b = map_binary bitwise_or a b;
+ }
+
+ Eor_item = class
+ Menuaction "_XOR" "bitwise exclusive or of a and b" {
+ action a b = map_binary eor a b;
+ }
+
+ Not_item = class
+ Menuaction "_NOT" "invert a" {
+ action a = map_unary not a;
+ }
+
+ sep1 = Menuseparator;
+
+ Right_shift_item = class
+ Menuaction "Shift _Right" "shift a right by b bits" {
+ action a b = map_binary right_shift a b;
+ }
+
+ Left_shift_item = class
+ Menuaction "Shift _Left" "shift a left by b bits" {
+ action a b = map_binary left_shift a b;
+ }
+
+ sep2 = Menuseparator;
+
+ If_then_else_item = class
+ Menuaction "_If Then Else"
+ "b where a is non-zero, c elsewhere" {
+ action a b c
+ = map_trinary ite a b c
+ {
+ // can't use if_then_else, we need a true trinary
+ ite a b c = if a then b else c;
+ }
+ }
+
+ Bandand_item = Image_band_item.Bandand_item;
+
+ Bandor_item = Image_band_item.Bandor_item;
+}
+
+Math_relational_item = class
+ Menupullright "R_elational" "comparison operations" {
+ Equal_item = class
+ Menuaction "_Equal to" "test a equal to b" {
+ action a b = map_binary equal a b;
+ }
+
+ Not_equal_item = class
+ Menuaction "_Not Equal to" "test a not equal to b" {
+ action a b = map_binary not_equal a b;
+ }
+
+ sep1 = Menuseparator;
+
+ More_item = class
+ Menuaction "_More Than" "test a strictly greater than b" {
+ action a b = map_binary more a b;
+ }
+
+ Less_item = class
+ Menuaction "_Less Than" "test a strictly less than b" {
+ action a b = map_binary less a b;
+ }
+
+ sep2 = Menuseparator;
+
+ More_equal_item = class
+ Menuaction "M_ore Than or Equal to"
+ "test a greater than or equal to b" {
+ action a b = map_binary more_equal a b;
+ }
+
+ Less_equal_item = class
+ Menuaction "L_ess Than or Equal to"
+ "test a less than or equal to b" {
+ action a b = map_binary less_equal a b;
+ }
+}
+
+Math_list_item = class
+ Menupullright "L_ist" "operations on lists" {
+ Head_item = class
+ Menuaction "_Head" "first element in list" {
+ action x = map_unary hd x;
+ }
+
+ Tail_item = class
+ Menuaction "_Tail" "list without the first element" {
+ action x = map_unary tl x;
+ }
+
+ Last_item = class
+ Menuaction "_Last" "last element in list" {
+ action x = map_unary last x;
+ }
+
+ Init_item = class
+ Menuaction "_Init" "list without the last element" {
+ action x = map_unary init x;
+ }
+
+ sep1 = Menuseparator;
+
+ Reverse_item = class
+ Menuaction "_Reverse" "reverse order of elements in list" {
+ action x = map_unary reverse x;
+ }
+
+ Sort_item = class
+ Menuaction "_Sort" "sort list into ascending order" {
+ action x = map_unary sort x;
+ }
+
+ Make_set_item = class
+ Menuaction "_Make Set" "remove duplicates from list" {
+ action x = map_unary mkset equal x;
+ }
+
+ Transpose_list_item = class
+ Menuaction "Tr_anspose"
+ "exchange rows and columns in a list of lists" {
+ action x = map_unary transpose x;
+ }
+
+ Concat_item = class
+ Menuaction "_Concat"
+ "flatten a list of lists into a single list" {
+ action l = map_unary concat l;
+ }
+
+ sep2 = Menuseparator;
+
+ Length_item = class
+ Menuaction "L_ength" "find the length of list" {
+ action x = map_unary len x;
+ }
+
+ Subscript_item = class
+ Menuaction "S_ubscript"
+ "return element n from list (index from zero)" {
+ action n x = map_binary subscript n x;
+ }
+
+ Take_item = class
+ Menuaction "_Take" "take the first n elements of list x" {
+ action n x = map_binary take n x;
+ }
+
+ Drop_item = class
+ Menuaction "_Drop" "drop the first n elements of list x" {
+ action n x = map_binary drop n x;
+ }
+
+ sep3 = Menuseparator;
+
+ Join_item = class
+ Menuaction "_Join" "join two lists end to end" {
+ action a b = map_binary join a b;
+ }
+
+ Difference_item = class
+ Menuaction "_Difference" "difference of two lists" {
+ action a b = map_binary difference a b;
+ }
+
+ Cons_item = class
+ Menuaction "C_ons" "put element a on the front of list x" {
+ action a x = map_binary cons a x;
+ }
+
+ Zip_item = class
+ Menuaction "_Zip" "join two lists, pairwise" {
+ action a b = map_binary zip2 a b;
+ }
+}
+
+Math_round_item = class
+ Menupullright "_Round" "various rounding operations" {
+ /* smallest integral value not less than x
+ */
+ Ceil_item = class
+ Menuaction "_Ceil" "smallest integral value not less than x" {
+ action x = map_unary ceil x;
+ }
+
+ Floor_item = class
+ Menuaction "_Floor"
+ "largest integral value not greater than x" {
+ action x = map_unary floor x;
+ }
+
+ Rint_item = class
+ Menuaction "_Round to Nearest" "round to nearest integer" {
+ action x = map_unary rint x;
+ }
+}
+
+Math_fourier_item = class
+ Menupullright "_Fourier" "Fourier transform" {
+ Forward_item = class
+ Menuaction "_Forward" "fourier transform of image" {
+ action a = map_unary (rotquad @ fwfft) a;
+ }
+
+ Reverse_item = class
+ Menuaction "_Reverse" "inverse fourier transform of image" {
+ action a = map_unary (invfft @ rotquad) a;
+ }
+
+ Rotate_quadrants_item = class
+ Menuaction "Rotate _Quadrants" "rotate quadrants" {
+ action a = map_unary rotquad a;
+ }
+}
+
+Math_stats_item = class
+ Menupullright "_Statistics" "measure various statistics of objects" {
+ Value_item = class
+ Menuaction "_Value" "value of point in object" {
+ action a = class _result {
+ _vislevel = 3;
+
+ position = Expression "Coordinate" (0, 0);
+
+ _result = map_binary point position.expr a;
+ }
+ }
+
+ Mean_item = class
+ Menuaction "_Mean" "arithmetic mean value" {
+ action a = map_unary mean a;
+ }
+
+ Gmean_item = class
+ Menuaction "_Geometric Mean" "geometric mean value" {
+ action a = map_unary meang a;
+ }
+
+ Zmean_item = class
+ Menuaction "_Zero-excluding Mean" "mean value of non-zero elements" {
+ action a = map_unary meanze a;
+ }
+
+ Deviation_item = class
+ Menuaction "_Standard Deviation" "standard deviation of object" {
+ action a = map_unary deviation a;
+ }
+
+ Zdeviation_item = class
+ Menuaction "Z_ero-excluding Standard Deviation"
+ "standard deviation of non-zero elements" {
+ action a = map_unary deviationze a;
+ }
+
+ Skew_item = class
+ Menuaction "S_kew" "skew of image or list or vector" {
+ action a = map_unary skew a;
+ }
+
+ Kurtosis_item = class
+ Menuaction "Kurtosis" "kurtosis of image or list or vector" {
+ action a = map_unary kurtosis a;
+ }
+
+ Stats_item = class
+ Menuaction "Ma_ny Stats" "calculate many stats in a single pass" {
+ action a = map_unary stats a;
+ }
+
+ sep1 = Menuseparator;
+
+ Max_item = class
+ Menuaction "M_aximum" "maximum of object" {
+ action a = map_unary max a;
+ }
+
+ Min_item = class
+ Menuaction "M_inimum" "minimum of object" {
+ action a = map_unary min a;
+ }
+
+ Maxpos_item = class
+ Menuaction "_Position of Maximum" "position of maximum in object" {
+ action a = map_unary maxpos a;
+ }
+
+ Minpos_item = class
+ Menuaction "P_osition of Minimum" "position of minimum in object" {
+ action a = map_unary minpos a;
+ }
+
+ Gravity_item = class
+ Menuaction "Centre of _Gravity"
+ "position of centre of gravity of histogram" {
+ action a = map_unary gravity a;
+ }
+
+ sep2 = Menuseparator;
+
+ Count_set_item = class
+ Menuaction "_Non-zeros" "number of non-zero elements in object" {
+ action a
+ = map_unary cset a
+ {
+ cset i = (mean (i != 0) * i.width * i.height) / 255;
+ }
+ }
+
+ Count_clear_item = class
+ Menuaction "_Zeros" "number of zero elements in object" {
+ action a
+ = map_unary cclear a
+ {
+ cclear i = (mean (i == 0) * i.width * i.height) / 255;
+ }
+ }
+
+ Count_edges_item = class
+ Menuaction "_Edges"
+ "count average edges across or down image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Count" [
+ "Horizontal lines",
+ "Vertical lines"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process image = Number (edge.labels?edge)
+ (im_cntlines image.value edge.value);
+ }
+ }
+ }
+
+ sep3 = Menuseparator;
+
+ Linear_regression_item = class
+ Menuaction "_Linear Regression" "fit a line to a set of points" {
+ action xes yes = linreg xes yes;
+ }
+
+ Weighted_linear_regression_item = class
+ Menuaction "_Weighted Linear Regression"
+ "fit a line to a set of points and deviations" {
+ action xes yes devs = linregw xes yes devs;
+ }
+
+ Cluster_item = class
+ Menuaction "_Cluster" "cluster a list of numbers" {
+ action l = class {
+ _vislevel = 3;
+
+ thresh = Expression "Threshold" 10;
+
+ [_r, _w] = cluster thresh.expr l;
+
+ result = _r;
+ weights = _w;
+ }
+ }
+}
+
+Math_base_item = class
+ Menupullright "Bas_e" "convert number bases" {
+ Hexadecimal_item = class
+ Menuaction "_Hexadecimal" "convert to hexadecimal (base 16)" {
+ action a = map_unary (print_base 16) a;
+ }
+
+ Binary_item = class
+ Menuaction "_Binary" "convert to binary (base 2)" {
+ action a = map_unary (print_base 2) a;
+ }
+
+ Octal_item = class
+ Menuaction "_Octal" "convert to octal (base 8)" {
+ action a = map_unary (print_base 8) a;
+ }
+}
diff --git a/share/nip2/compat/8.5/Matrix.def b/share/nip2/compat/8.5/Matrix.def
new file mode 100644
index 00000000..944e8d77
--- /dev/null
+++ b/share/nip2/compat/8.5/Matrix.def
@@ -0,0 +1,537 @@
+
+Matrix_build_item = class
+ Menupullright "_New" "make a new matrix of some sort" {
+
+ Plain_item = class
+ Menuaction "_Plain" "make a new plain matrix widget" {
+ action = Matrix (identity_matrix 3);
+ }
+
+ Convolution_item = class
+ Menuaction "_Convolution" "make a new convolution matrix widget" {
+ action = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
+ }
+
+ Recombination_item = class
+ Menuaction "_Recombination"
+ "make a new recombination matrix widget" {
+ action = Matrix_rec (identity_matrix 3);
+ }
+
+ Morphology_item = class
+ Menuaction "_Morphology" "make a new morphology matrix widget" {
+ action = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
+ }
+
+ sep1 = Menuseparator;
+
+ Matrix_identity_item = class
+ Menuaction "_Identity" "make an identity matrix" {
+ action = identity (identity_matrix 5);
+
+ identity v = class
+ _result {
+ _vislevel = 3;
+
+ s = Expression "Size" (len v);
+
+ _result
+ = Matrix (identity_matrix (to_real s)), to_real s != len v;
+ = Matrix v;
+
+ Matrix_vips value scale offset filename display = identity value;
+ }
+ }
+
+ Matrix_series_item = class
+ Menuaction "_Series" "make a series" {
+ action = series (mkseries 0 1 5);
+
+ mkseries s t e
+ = transpose [[to_real s, to_real s + to_real t .. to_real e]];
+
+ series v = class
+ _result {
+ _vislevel = 3;
+
+ _s = v?0?0;
+ _t = v?1?0 - v?0?0;
+ _e = (last v)?0;
+
+ s = Expression "Start value" _s;
+ t = Expression "Step by" _t;
+ e = Expression "End value" _e;
+
+ _result
+ = Matrix (mkseries s t e),
+ to_real s != _s || to_real t != _t || to_real e != _e
+ = Matrix v;
+
+ Matrix_vips value scale offset filename display = series value;
+ }
+ }
+
+ Matrix_square_item = class
+ Menuaction "_Square" "make a square matrix" {
+ action = square (mksquare 5);
+
+ mksquare s = replicate s (take s [1, 1 ..]);
+
+ square v = class
+ _result {
+ _vislevel = 3;
+
+ s = Expression "Size" (len v);
+
+ _result
+ = Matrix_con (sum v) 0 v, len v == to_real s
+ = Matrix_con (sum m) 0 m
+ {
+ m = mksquare (to_real s);
+ }
+
+ Matrix_vips value scale offset filename display = square value;
+ }
+ }
+
+ Matrix_circular_item = class
+ Menuaction "_Circular" "make a circular matrix" {
+ action = circle (mkcircle 3);
+
+ mkcircle r
+ = map2 (map2 pyth) xes yes
+ {
+ line = [-r .. r];
+ xes = replicate (2 * r + 1) line;
+ yes = transpose xes;
+ pyth a b
+ = 1, (a**2 + b**2) ** 0.5 <= r
+ = 0;
+ }
+
+ circle v = class
+ _result {
+ _vislevel = 3;
+
+ r = Expression "Radius" ((len v - 1) / 2);
+
+ _result
+ = Matrix_con (sum v) 0 v, len v == r.expr * 2 + 1
+ = Matrix_con (sum m) 0 m
+ {
+ m = mkcircle (to_real r);
+ }
+
+ Matrix_vips value scale offset filename display = circle value;
+ }
+ }
+
+ Matrix_gaussian_item = class
+ Menuaction "_Gaussian" "make a gaussian matrix" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ s = Scale "Sigma" 0.001 10 1;
+ ma = Scale "Minimum amplitude" 0 1 0.2;
+ integer = Toggle "Integer" false;
+
+ _result
+ = fn s.value ma.value
+ {
+ fn
+ = im_gauss_imask, integer
+ = im_gauss_dmask;
+ }
+ }
+ }
+
+ Matrix_laplacian_item = class
+ Menuaction "_Laplacian" "make the Laplacian of a Gaussian matrix" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ s = Scale "Sigma" 0.001 10 1.5;
+ ma = Scale "Minimum amplitude" 0 1 0.1;
+ integer = Toggle "Integer" false;
+
+ _result
+ = fn s.value ma.value
+ {
+ fn
+ = im_log_imask, integer
+ = im_log_dmask;
+ }
+ }
+ }
+
+}
+
+Matrix_to_matrix_item = class
+ Menuaction "Con_vert to Matrix" "convert anything to a matrix" {
+ action x = to_matrix x;
+}
+
+#separator
+
+Matrix_extract_item = class
+ Menupullright "_Extract" "extract rows or columns from a matrix" {
+ Rows_item = class
+ Menuaction "_Rows" "extract rows" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from row" 0;
+ number = Expression "Extract this many rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = extract_area 0 first (get_width x) number x;
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "extract columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from column" 0;
+ number = Expression "Extract this many columns" 1;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = extract_area first 0 number (get_height x) x;
+ }
+ }
+ }
+
+ Area_item = class
+ Menuaction "_Area" "extract area" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ left = Expression "First column" 0;
+ top = Expression "First row" 0;
+ width = Expression "Number of columns" 1;
+ height = Expression "Number of rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = extract_area left top width height x;
+ }
+ }
+ }
+
+ Diagonal_item = class
+ Menuaction "_Diagonal" "extract diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ which = Option "Extract" [
+ "Leading Diagonal",
+ "Trailing Diagonal"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = mat.Matrix_base (map2 extr [0..] mat.value),
+ which == 0
+ = mat.Matrix_base (map2 extr
+ [mat.width - 1, mat.width - 2 .. 0] mat.value);
+ extr n l = [l?n];
+ }
+ }
+ }
+}
+
+Matrix_insert_item = class
+ Menupullright "_Insert" "insert rows or columns into a matrix" {
+ // make a new 8-bit uchar image of wxh with pixels set to p
+ // use to generate new cells
+ newim w h p
+ = image_new w h 1
+ Image_format.UCHAR Image_coding.NOCODING Image_type.B_W p 0 0;
+
+ Rows_item = class
+ Menuaction "_Rows" "insert rows" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at row" 0;
+ number = Expression "Insert this many rows" 1;
+ item = Expression "Set new cells to" 0;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_tb (concat [top, new, bottom])
+ {
+ top
+ = [extract_area 0 0 w f x], f > 0
+ = [];
+ new = [(if is_Matrix x then to_matrix else id)
+ (newim w number item.expr)];
+ bottom
+ = [extract_area 0 f w (h - f) x], f < h
+ = [];
+
+ f = to_real first;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "insert columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at column" 0;
+ number = Expression "Insert this many columns" 1;
+ item = Expression "Set new cells to" 0;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_lr (concat [left, new, right])
+ {
+ left
+ = [extract_area 0 0 f h x], f > 0
+ = [];
+ new = [(if is_Matrix x then to_matrix else id)
+ (newim number h item.expr)];
+ right
+ = [extract_area f 0 (w - f) h x], f < w
+ = [];
+
+ f = to_real first;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+}
+
+Matrix_delete_item = class
+ Menupullright "_Delete" "delete rows or columns from a matrix" {
+ // remove number of items, starting at first
+ delete first number l = take (to_real first) l ++
+ drop (to_real first + to_real number) l;
+
+ Rows_item = class
+ Menuaction "_Rows" "delete rows" {
+
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from row" 0;
+ number = Expression "Delete this many rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_tb (concat [top, bottom])
+ {
+ top
+ = [extract_area 0 0 w f x], f > 0
+ = [];
+ bottom
+ = [extract_area 0 b w (h - b) x], b < h
+ = [];
+
+ f = to_real first;
+ n = to_real number;
+ b = f + n;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "delete columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from column" 0;
+ number = Expression "Delete this many columns" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_lr (concat [left, right])
+ {
+ left
+ = [extract_area 0 0 f h x], f > 0
+ = [];
+ right
+ = [extract_area r 0 (w - r) h x], r < w
+ = [];
+
+ f = to_real first;
+ n = to_real number;
+ r = f + n;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+}
+
+Matrix_join = class
+ Menupullright "_Join" "join two matricies" {
+ Left_right_item = class
+ Menuaction "_Left to Right" "join two matricies left-right" {
+ action a b = map_binary join_lr a b;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "joiin two matricies top-bottom" {
+ action a b = map_binary join_tb a b;
+ }
+}
+
+Matrix_rotate_item = class
+ Menupullright "_Rotate" "clockwise rotation by fixed angles" {
+
+ rot90 = Image_transform_item.Rotate_item.Fixed_item.Rot90_item;
+
+ rot180 = Image_transform_item.Rotate_item.Fixed_item.Rot180_item;
+
+ rot270 = Image_transform_item.Rotate_item.Fixed_item.Rot270_item;
+
+ Matrix_rot45_item = class
+ Menuaction "_45 Degrees"
+ "45 degree rotate (square, odd-length-sides only)" {
+ action x = map_unary rot45 x;
+ }
+}
+
+Matrix_flip_item = Image_transform_item.Flip_item;
+
+Matrix_sort_item = class
+ Menuaction "_Sort" "sort by column or row" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ o = Option (_ "Orientation") [
+ _ "Sort by column",
+ _ "Sort by row"
+ ] 0;
+ i = Expression (_ "Sort on index") 0;
+ d = Option (_ "Direction") [
+ _ "Ascending",
+ _ "Descending"
+ ] 0;
+
+ _result
+ = map_unary sort x
+ {
+ idx = to_real i;
+ pred a b
+ = a?idx <= b?idx, d == 0
+ = a?idx >= b?idx;
+ sort x
+ = (x.Matrix_base @ sortc pred) x.value,
+ o == 0
+ = (x.Matrix_base @ transpose @ sortc pred @ transpose) x.value;
+ }
+ }
+}
+
+#separator
+
+Matrix_invert_item = class
+ Menuaction "In_vert" "calculate inverse matrix" {
+ action x = map_unary (converse power (-1)) x;
+}
+
+Matrix_transpose_item = class
+ Menuaction "_Transpose" "swap rows and columns" {
+ action x = map_unary transpose x;
+}
+
+#separator
+
+Matrix_plot_scatter_item = class
+ Menuaction "_Plot Scatter"
+ "plot a scatter graph of a matrix of [x,y1,y2,..] coordinates" {
+ action x = class
+ _result {
+ _check_args = [
+ [x, "x", check_Matrix]
+ ];
+ _vislevel = 3;
+
+ auto = Toggle "Auto Range" true;
+ xmin = Expression "X range minimum" 0;
+ xmax = Expression "X range maximum" 1;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+
+ _result
+ = Plot options ((x2b @ get_image @ to_image) x)
+ {
+ options
+ = [$style => Plot_style.POINT, $format => Plot_format.XYYY] ++
+ range;
+ range
+ = [], auto
+ = [$xmin => xmin.expr, $xmax => xmax.expr,
+ $ymin => ymin.expr, $ymax => ymax.expr];
+
+ // matrix to image makes a 1-band mxn image
+ // we need to put columns into bands
+ x2b im
+ = bandjoin (map extract_col [0 .. w - 1])
+ {
+ w = get_width im;
+ h = get_height im;
+ b = get_bands im;
+ extract_col x = extract_area x 0 1 h im;
+ }
+ }
+ }
+}
+
+Matrix_plot_item = Hist_plot_item;
+
+Matrix_buildlut_item = class
+ Menuaction "_Build LUT From Scatter"
+ "make a lookup table from a matrix of [x,y1,y2..] coordinates" {
+ action x = class
+ _result {
+ _check_args = [
+ [x, "x", check_Matrix]
+ ];
+ _result = buildlut x;
+ }
+}
diff --git a/share/nip2/compat/8.5/Object.def b/share/nip2/compat/8.5/Object.def
new file mode 100644
index 00000000..b1585186
--- /dev/null
+++ b/share/nip2/compat/8.5/Object.def
@@ -0,0 +1,49 @@
+Object_duplicate_item = class
+ Menuaction "_Duplicate" "take a copy of an object" {
+ action x = map_unary copy x;
+}
+
+#separator
+
+Object_list_to_group_item = class
+ Menuaction "_List to Group" "turn a list of objects into a group" {
+ action x = to_group x;
+}
+
+Object_group_to_list_item = class
+ Menuaction "_Group to List" "turn a group into a list of objects" {
+ action x = to_list x;
+}
+
+#separator
+
+Object_break_item = class
+ Menuaction "_Break Up Object"
+ "break an object into a list of components" {
+ action x
+ = map_unary break x
+ {
+ break x
+ = bandsplit x, is_Image x
+ = map Vector x.value, is_Matrix x
+ = x.value, is_Vector x || is_Real x
+ = error "Breakup: not Image/Matrix/Vector/Real";
+ }
+}
+
+Object_assemble_item = class
+ Menuaction "_Assemble Objects"
+ "assemble a list of objects into a single object" {
+ action x
+ = map_unary ass x
+ {
+ ass x
+ = [], x == []
+ = Vector x, is_real_list x
+ = Matrix x, is_matrix x
+ = bandjoin x, is_listof is_Image x
+ = Vector (map get_value x), is_listof is_Real x
+ = Matrix (map get_value x), is_listof is_Vector x
+ = error "Assemble: not list of Image/Vector/Real/image/real";
+ }
+}
diff --git a/share/nip2/compat/8.5/Preferences.ws b/share/nip2/compat/8.5/Preferences.ws
new file mode 100644
index 00000000..37a24978
--- /dev/null
+++ b/share/nip2/compat/8.5/Preferences.ws
@@ -0,0 +1,919 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/share/nip2/compat/8.5/Tasks.def b/share/nip2/compat/8.5/Tasks.def
new file mode 100644
index 00000000..d0c6d711
--- /dev/null
+++ b/share/nip2/compat/8.5/Tasks.def
@@ -0,0 +1,952 @@
+Tasks_capture_item = class
+ Menupullright "_Capture"
+ "useful stuff for capturing and preprocessing images" {
+
+ Csv_import_item = class
+ Menuaction "_CSV Import" "read a file of comma-separated values" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ path = Pathname "File to load" "empty";
+ start_line = Expression "Start at line" 1;
+ rows = Expression "Lines to read (-1 for whole file)" (-1);
+ whitespace = String "Whitespace characters" " \"";
+ separator = String "Separator characters" ",;\t";
+
+ _result
+ = Image blank, path.value == "empty"
+ = Image (im_csv2vips filename)
+ {
+ filename = search (expand path.value) ++ ":" ++
+ "skip:" ++ print (start_line.expr - 1) ++ "," ++
+ "whi:" ++ escape whitespace.value ++ "," ++
+ "sep:" ++ escape separator.value ++ "," ++
+ "line:" ++ print rows.expr;
+
+ // prefix any ',' with a '\' in the separators line
+ escape x
+ = foldr prefix [] x
+ {
+ prefix x l
+ = '\\' : x : l, x == ','
+ = x : l;
+ }
+
+ blank = image_new 1 1 1
+ Image_format.DOUBLE Image_coding.NOCODING Image_type.B_W
+ 0 0 0;
+ }
+ }
+ }
+
+ Raw_import_item = class
+ Menuaction "_Raw Import" "read a file of binary values" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ path = Pathname "File to load" "empty";
+ across = Expression "Pixels across" 100;
+ down = Expression "Pixels down" 100;
+ bytes = Expression "Bytes per pixel" 3;
+ skip = Expression "Skip over initial bytes" 0;
+
+ _result
+ = Image blank, path.value == "empty"
+ = Image (im_binfile path.value
+ across.expr down.expr bytes.expr skip.expr)
+ {
+ blank = image_new 1 1 1
+ Image_format.DOUBLE Image_coding.NOCODING Image_type.B_W
+ 0 0 0;
+ }
+ }
+ }
+
+ // interpret Analyze header for layout and calibration
+ Analyze7_header_item = class
+ Menuaction "_Interpret Analyze 7 Header"
+ "examine the Analyze header and set layout and value" {
+ action x
+ = x'''
+ {
+ // read bits of header
+ dim n = get_header ("dsr-image_dimension.dim[" ++ print n ++ "]");
+ dim0 = dim 0 x;
+ dim1 = dim 1 x;
+ dim2 = dim 2 x;
+ dim3 = dim 3 x;
+ dim4 = dim 4 x;
+ dim5 = dim 5 x;
+ dim6 = dim 6 x;
+ dim7 = dim 7 x;
+ glmax = get_header "dsr-image_dimension.glmax" x;
+ cal_max = get_header "dsr-image_dimension.cal_max" x;
+
+ // oops, now a nop
+ x' = x;
+
+ // lay out higher dimensions width-ways
+ x''
+ = grid dim2 dim3 1 x', dim0 == 3
+ = grid dim2 dim3 dim4 x', dim0 == 4
+ = grid (dim2 * dim4) dim5 1 (grid dim2 dim3 dim4) x', dim0 == 5
+ = grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4) x', dim0 == 6
+ = grid (dim2 * dim4 * dim6) dim7 1
+ (grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4)) x',
+ dim0 == 7
+ = error (_ "unsupported dimension " ++ dim0);
+
+ // multiply by scale factor to get kBeq
+ x''' = x'' * (cal_max / glmax);
+ }
+ }
+
+ Video_item = class
+ Menuaction "Capture _Video Frame" "capture a frame of still video" {
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+
+ action = class
+ _result {
+ _vislevel = 3;
+
+ device = prefs.VIDEO_DEVICE;
+ channel = Option "Input channel" [
+ "TV",
+ "Composite 1",
+ "Composite 2",
+ "Composite 3"
+ ] prefs.VIDEO_CHANNEL;
+ b = Scale "Brightness" 0 32767 prefs.VIDEO_BRIGHTNESS;
+ col = Scale "Colour" 0 32767 prefs.VIDEO_COLOUR;
+ con = Scale "Contrast" 0 32767 prefs.VIDEO_CONTRAST;
+ hue = Scale "Hue" 0 32767 prefs.VIDEO_HUE;
+ frames = Scale "Frames to average" 0 100 prefs.VIDEO_FRAMES;
+ mono = Toggle "Monochrome grab" prefs.VIDEO_MONO;
+ crop = Toggle "Crop image" prefs.VIDEO_CROP;
+
+ // grab, but hide it ... if we let the crop edit
+ _raw_grab = Image (im_video_v4l1 device channel.value
+ b.value col.value con.value
+ hue.value frames.value);
+
+ edit_crop
+ = Region _raw_grab left top width height
+ {
+ left = prefs.VIDEO_CROP_LEFT;
+ top = prefs.VIDEO_CROP_TOP;
+ width = min_pair
+ prefs.VIDEO_CROP_WIDTH (_raw_grab.width + left);
+ height = min_pair
+ prefs.VIDEO_CROP_HEIGHT (_raw_grab.height + top);
+ }
+
+ aspect_ratio = Expression "Stretch vertically by"
+ prefs.VIDEO_ASPECT;
+
+ _result
+ = frame'
+ {
+ frame
+ = edit_crop, crop
+ = _raw_grab;
+
+ frame'
+ = colour_transform_to Image_type.B_W frame, mono
+ = frame;
+ }
+ }
+ }
+
+ Smooth_image_item = class
+ Menuaction "_Smooth" "remove small features from image" {
+ action in = class
+ _result {
+ _vislevel = 3;
+
+ feature = Scale "Minimum feature size" 1 50 20;
+
+ _result = map_unary (smooth feature.value) in;
+ }
+ }
+
+ Light_correct_item = class
+ Menuaction "_Flatfield" "use white image w to flatfield image i" {
+ action w i
+ = map_binary wc w i
+ {
+ wc w i
+ = clip2fmt i.format (w' * i)
+ {
+ fac = mean w / max w;
+ w' = fac * (max w / w);
+ }
+ }
+ }
+
+ Image_rank_item = Filter_rank_item.Image_rank_item;
+
+ Tilt_item = Filter_tilt_item;
+
+ sep1 = Menuseparator;
+
+ White_balance_item = class
+ Menuaction "_White Balance"
+ "use average of small image to set white of large image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ white_hint = "Set image white to:";
+ white = Colour_picker "Lab" [100, 0, 0];
+
+ _result
+ = map_binary wb a b
+ {
+ wb a b
+ = colour_transform_to (get_type image) image_xyz'
+ {
+ area x = x.width * x.height;
+ larger x y = area x > area y;
+ [image, patch] = sortc larger [a, b];
+ to_xyz = colour_transform_to Image_type.XYZ;
+
+ // white balance in XYZ
+ patch_xyz = to_colour (to_xyz patch);
+ white_xyz = to_xyz white;
+
+ facs = (mean patch_xyz / mean white_xyz) *
+ (white_xyz / patch_xyz);
+
+ image_xyz = to_xyz image;
+ image_xyz' = image_xyz * facs;
+ }
+ }
+ }
+ }
+
+ Gamma_item = Image_levels_item.Gamma_item;
+
+ Tone_item = Image_levels_item.Tone_item;
+
+ sep2 = Menuseparator;
+
+ Crop_item = Image_crop_item;
+
+ Rotate_item = Image_transform_item.Rotate_item;
+
+ Flip_item = Image_transform_item.Flip_item;
+
+ Resize_item = Image_transform_item.Resize_item;
+
+ Rubber_item = Image_transform_item.Image_rubber_item;
+
+ sep3 = Menuseparator;
+
+ ICC_item = Colour_icc_item;
+
+ Temp_item = Colour_temperature_item;
+
+ Find_calib_item = class
+ Menuaction "Find _Colour Calibration"
+ "find an RGB -> XYZ transform from an image of a colour chart" {
+ action image = class
+ _result {
+ _check_args = [
+ [image, "image", check_Image]
+ ];
+ _vislevel = 3;
+
+ measure = Scale (_ "Measure area (%)") 1 100 50;
+
+ sample = measure_draw 6 4 (to_real measure) image;
+
+ // get macbeth data file to use
+ macbeth = Pathname "Pick a Macbeth data file"
+ "$VIPSHOME/share/$PACKAGE/data/macbeth_lab_d65.mat";
+
+ mode = Option "Input LUT" [
+ "Linearize from chart greyscale",
+ "Fit intercept from chart greyscale",
+ "Linear input, set brightness from chart",
+ "Linear input"
+ ] 0;
+
+ // get max of input image
+ _max_value = Image_format.maxval image.format;
+
+ // measure chart image
+ _camera = measure_sample 6 4 (to_real measure) image;
+
+ // load true values
+ _true_Lab = Matrix_file macbeth.value;
+ _true_XYZ = colour_transform
+ Image_type.LAB Image_type.XYZ _true_Lab;
+
+ // get Ys of greyscale
+ _true_grey_Y = map (extract 1) (drop 18 _true_XYZ.value);
+
+ // camera greyscale (all bands)
+ _camera_grey = drop 18 _camera.value;
+
+ // normalise both to 0-1 and combine
+ _camera_grey' = map (map (multiply (1 / _max_value))) _camera_grey;
+ _true_grey_Y' = map (multiply (1 / 100)) _true_grey_Y;
+ _comb
+ = Matrix (map2 cons _true_grey_Y' _camera_grey'), mode == 0
+ = Matrix [0: intercepts, replicate (_camera.width + 1) 1],
+ mode == 1
+ = Matrix [[0, 0], [1, 1]]
+ {
+ intercepts = [(linreg _true_grey_Y' cam).intercept ::
+ cam <- transpose _camera_grey'];
+ }
+
+ // make a linearising lut ... zero on left
+ _linear_lut = im_invertlut _comb (_max_value + 1);
+
+ // and display it
+ // plot from 0 explicitly so we see the effect of mode1 (intercept
+ // from greyscale)
+ linearising_lut = Plot [$ymin => 0] _linear_lut;
+
+ // map an image though the lineariser
+ linear x
+ = hist_map linearising_lut.value x, mode == 0 || mode == 1
+ = x;
+
+ // map the chart measurements though the lineariser
+ _camera' = (to_matrix @ linear @ to_image) _camera;
+
+ // solve for RGB -> XYZ
+ // normalise: the 2nd row is what makes Y, so divide by that to
+ // get Y in 0-1.
+ _pinv = (transpose _camera' * _camera') ** -1;
+ _full_M = transpose (_pinv * (transpose _camera' * _true_XYZ));
+ M = _full_M / scale;
+ scale = sum _full_M.value?1;
+
+ // now turn the camera to LAB and calculate dE76
+ _camera'' = (to_matrix @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ recomb M @
+ multiply scale @
+ to_image) _camera';
+
+ _dEs = map abs_vec (_camera'' - _true_Lab).value;
+ avg_dE76 = mean _dEs;
+ _max_dE = foldr max_pair 0 _dEs;
+ _worst = index (equal _max_dE) _dEs;
+ worst_patch
+ = name _worst ++ " (patch " ++
+ print (_worst + 1) ++ ", " ++
+ print _max_dE ++ " dE)"
+ {
+ name i
+ = macbeth_names?i, i >= 0 && i < len macbeth_names
+ = "Unknown";
+ }
+
+ // normalise brightness ... in linear mode, we optionally don't
+ // set the brightness from the Macbeth chart
+ norm x
+ = x * scale, mode != 3
+ = x;
+
+ // convert RGB camera to Lab
+ _result = (Image @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ norm @
+ recomb M @
+ cast_float @
+ linear) image.value;
+ }
+ }
+
+ Apply_calib_item = class
+ Menuaction "_Apply Colour Calibration"
+ "apply an RGB -> LAB transform to an image" {
+ action a b = class
+ (map_binary process a b) {
+ process a b
+ = result, is_instanceof calib_name calib && is_Image image
+ = error (_ "bad arguments to " ++ "Calibrate_image")
+ {
+ // the name of the calib object we need
+ calib_name = "Tasks_capture_item.Find_calib_item.action";
+
+ // get the Calibrate_chart arg first
+ [image, calib] = sortc (const (is_instanceof calib_name))
+ [a, b];
+
+ result = (Image @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ calib.norm @
+ recomb calib.M @
+ cast_float @
+ calib.linear) image.value;
+ }
+ }
+ }
+
+ sep4 = Menuseparator;
+
+ Graph_hist_item = Hist_find_item;
+
+ Graph_bands_item = class
+ Menuaction "Plot _Bands" "show image bands as a graph" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ style = Option_enum "Style" Plot_style.names "Line";
+
+ auto = Toggle "Auto Range" true;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+
+ _result
+ = Plot options (to_image (bands (image x))).value
+ {
+ options
+ = [$style => style.value] ++
+ if auto then [] else
+ [$ymin => ymin.expr, $ymax => ymax.expr];
+
+ // try to make something image-like from it
+ image x
+ = extract_area x.left x.top 1 1 x.image, is_Mark x
+ = get_image x, has_image x
+ = get_image (to_image x);
+
+ // get as [[1],[2],[3]]
+ bands x
+ = transpose [map mean (bandsplit x)];
+ }
+ }
+ }
+}
+
+Tasks_mosaic_item = class
+ Menupullright "_Mosaic" "build image mosaics" {
+ /* Check and group a point list by image.
+ */
+ mosaic_sort_test l
+ = error "mosaic: not all points",
+ !is_listof is_Mark l
+ = error "mosaic: points not on two images",
+ !is_list_len 2 images
+ = error "mosaic: images do not match in format and coding",
+ !all_equal (map get_format l) || !all_equal (map get_coding l)
+ = error "mosaic: not same number of points on each image",
+ !foldr1 equal (map len l')
+ = l'
+ {
+ // test for all elements of a list equal
+ all_equal l = all (map (equal (hd l)) (tl l));
+
+ // all the different images
+ images = mkset pointer_equal (map get_image l);
+
+ // find all points defined on image
+ test_image image p = (get_image p) === image;
+ find l image = filter (test_image image) l;
+
+ // group point list by image
+ l' = map (find l) images;
+ }
+
+ /* Sort a point group to get right before left, and within each group to
+ * get above before below.
+ */
+ mosaic_sort_lr l
+ = l''
+ {
+ // sort to get upper point first
+ above a b = a.top < b.top;
+ l' = map (sortc above) l;
+
+ // sort to get right group before left group
+ right a b = a?0.left > b?0.left;
+ l'' = sortc right l';
+ }
+
+ /* Sort a point group to get top before bottom, and within each group to
+ * get left before right.
+ */
+ mosaic_sort_tb l
+ = l''
+ {
+ // sort to get upper point first
+ left a b = a.left < b.left;
+ l' = map (sortc left) l;
+
+ // sort to get right group before left group
+ below a b = a?0.top > b?0.top;
+ l'' = sortc below l';
+ }
+
+ /* Put 'em together! Group by image, sort vertically (or horizontally) with
+ * one of the above, transpose to get pairs matched up, and flatten again.
+ */
+ mosaic_sort fn = concat @ transpose @ fn @ mosaic_sort_test;
+
+ Mosaic_1point_item = class
+ Menupullright "_One Point" "join two images with a single tie point" {
+ check_ab_args a b = [
+ [a, "a", check_Mark],
+ [b, "b", check_Mark]
+ ];
+
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+ search_area = prefs.MOSAIC_WINDOW_SIZE;
+ object_size = prefs.MOSAIC_OBJECT_SIZE;
+ blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
+ refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
+
+ lr_mos _refine a b = class
+ Image _result {
+ _check_args = check_ab_args a b;
+
+ bw = blend_width_widget;
+ refine = _refine;
+
+ _result
+ = im_lrmosaic a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ (object_size / 2) (search_area / 2) 0 bw.value,
+ refine
+ = im_lrmerge a'.image.value b'.image.value
+ (b'.left - a'.left) (b'.top - a'.top) bw.value
+ {
+ [a', b'] = mosaic_sort mosaic_sort_lr [a, b];
+ }
+ }
+
+ tb_mos _refine a b = class
+ Image _result {
+ _check_args = check_ab_args a b;
+
+ bw = blend_width_widget;
+ refine = _refine;
+
+ _result
+ = im_tbmosaic a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ (object_size / 2) (search_area / 2) 0 bw.value,
+ refine
+ = im_tbmerge a'.image.value b'.image.value
+ (b'.left - a'.left) (b'.top - a'.top) bw.value
+ {
+ [a', b'] = mosaic_sort mosaic_sort_tb [a, b];
+ }
+ }
+
+ Left_right_item = class
+ Menuaction "_Left to Right"
+ "join two images left-right with a single tie point" {
+ action a b = lr_mos refine_widget a b;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom"
+ "join two images top-bottom with a single tie point" {
+ action a b = tb_mos refine_widget a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Left_right_manual_item = class
+ Menuaction "Manual L_eft to Right"
+ "join left-right, no auto-adjust of tie points" {
+ action a b = lr_mos false a b;
+ }
+
+ Top_bottom_manual_item = class
+ Menuaction "Manual T_op to Bottom"
+ "join top-bottom, no auto-adjust of tie points" {
+ action a b = tb_mos false a b;
+ }
+ }
+
+ Mosaic_2point_item = class
+ Menupullright "_Two Point" "join two images with two tie points" {
+ check_abcd_args a b c d = [
+ [a, "a", check_Mark],
+ [b, "b", check_Mark],
+ [c, "c", check_Mark],
+ [d, "d", check_Mark]
+ ];
+
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+ search_area = prefs.MOSAIC_WINDOW_SIZE;
+ object_size = prefs.MOSAIC_OBJECT_SIZE;
+ blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
+ refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
+
+ Left_right_item = class
+ Menuaction "_Left to Right"
+ "join two images left-right with a pair of tie points" {
+ action a b c d = class
+ Image _result {
+ _check_args = check_abcd_args a b c d;
+
+ bw = blend_width_widget;
+ refine = refine_widget;
+
+ _result
+ = im_lrmosaic1 a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ (object_size / 2) (search_area / 2)
+ 0 bw.value,
+ refine
+ = im_lrmerge1 a'.image.value b'.image.value
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ bw.value
+ {
+ [a', b', c', d'] = mosaic_sort mosaic_sort_lr [a, b, c, d];
+ }
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom"
+ "join two images top-bottom with a pair of tie points" {
+ action a b c d = class
+ Image _result {
+ _check_args = check_abcd_args a b c d;
+
+ bw = blend_width_widget;
+ refine = refine_widget;
+
+ _result
+ = im_tbmosaic1 a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ (object_size / 2) (search_area / 2)
+ 0 bw.value,
+ refine
+ = im_tbmerge1 a'.image.value b'.image.value
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ bw.value
+ {
+ [a', b', c', d'] = mosaic_sort mosaic_sort_tb [a, b, c, d];
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Balance_item = class
+ Menuaction "Mosaic _Balance"
+ "disassemble mosaic, scale brightness to match, reassemble" {
+ action x
+ = map_unary balance x
+ {
+ balance x
+ = oo_unary_function balance_op x, is_class x
+ = im_global_balancef x
+ Workspaces.Preferences.MOSAIC_BALANCE_GAMMA,
+ is_image x
+ = error (_ "bad arguments to " ++ "balance")
+ {
+ balance_op = Operator "balance" balance
+ Operator_type.COMPOUND_REWRAP false;
+ }
+ }
+ }
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Manual_balance_item = class
+ Menupullright "Manual B_alance" "balance tonality of user defined areas" {
+ prefs = Workspaces.Preferences;
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_find_item = class
+ Menuaction "_Find Values"
+ "calculates values required to scale and offset balance user defined areas in a given image"
+ /* Outputs a matrix of scale and offset values. Eg. Values required to balance the secondary
+ * structure in an X-ray image. Takes an X-ray image an 8-bit control mask and a list of
+ * 8-bit reference masks, where the masks are white on a black background.
+ */
+ {
+ action im_in m_control m_group = class
+ Matrix values{
+ _vislevel = 1;
+
+ _control_im = if m_control then im_in else 0;
+ _control_meanmax = so_meanmax _control_im;
+ _group_check = is_Group m_group;
+ _m_list = m_group.value, _group_check
+ = m_group;
+
+ process m_current mat_in = mat_out
+ {so_values = so_calculate _control_meanmax im_in m_current;
+ mat_out = join [so_values] mat_in;}
+
+ values = (foldr process [] _m_list);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_check_item = class
+ Menuaction "_Check Values"
+ "allows calculated set of scale and offset values to be checked and adjusted if required"
+ /* Outputs adjusted matrix of scale and offset values and scale and offset image maps.
+ * Eg. Check values required to balance the secondary structure in an X-ray image.
+ * Takes an X-ray image an 8-bit control mask and a list of 8-bit reference masks,
+ * where the masks are white on a black background.
+ */
+ {
+ action im_in m_matrix m_group = class
+ Image value
+ {
+ _vislevel = 3;
+
+ blur = Scale "Blur" 1 10 1;
+ _blur = (blur.value/2 + 0.5), blur.value > 1
+ = 1;
+
+ _group_check = is_Group m_group;
+ _m_list = m_group.value, _group_check
+ = m_group;
+
+ adjust = Matrix_rec mat_a
+ {
+ no_masks = len _m_list;
+ mat_a = replicate no_masks [0, 0];
+ }
+
+ // Apply the user defined adjustments to the inputted matrix of scale and offset values
+ _adjusted = map2 fn_adjust m_matrix.value adjust.value;
+ fn_adjust a b = [(a?0 + b?0), (a?1 + (a?1 * b?1))];
+
+ _scaled_ims = map (fn_so_apply im_in) _adjusted;
+ fn_so_apply im so = map_unary adj im
+ {adj im = im * (so?0) + (so?1);}
+ _im_pairs = zip2 _m_list _scaled_ims;
+
+ // Prepare black images as starting point. ////////////
+ _blank = image_new (_m_list?0).width (_m_list?0).height 1 6 Image_coding.NOCODING 1 0 0 0;
+ _pair_start = [(_blank + 1), _blank];
+
+ Build = Toggle "Build Scale and Offset Correction Images" false;
+
+ Output = class
+ {
+ _vislevel = 1;
+ scale_im = _build?0;
+ offset_im = _build?1;
+ so_values = Matrix _adjusted;
+
+ _build = [Image so_images?0, Image so_images?1], Build
+ = ["Scale image not built.", "Offset image not built."]
+ {
+ m_list' = transpose [_m_list];
+ m_all = map2 join m_list' _adjusted;
+ so_images = foldr process_2 _pair_start m_all;
+ }
+ }
+
+ value = (foldr process_1 im_in_b _im_pairs).value
+ {im_in_b = map_unary cast_float im_in;}
+
+ process_1 m_current im_start
+ = im_out
+ {
+ bl_mask = convsep (matrix_blur _blur) (get_image m_current?0);
+ blended_im = im_blend bl_mask (m_current?1).value im_start.value;
+ im_out = Image (clip2fmt im_start.format blended_im);
+ }
+
+ // Process for building scale and offset image.
+ process_2 current p_start
+ = p_out
+ {
+ im_s = if ((current?0) > 128) then current?1 else _blank;
+ im_o = if ((current?0) > 128) then current?2 else _blank;
+
+ im_s' = convsep (matrix_blur _blur) (im_s != 0);
+ im_o' = convsep (matrix_blur _blur) (im_o != 0);
+
+ im_s'' = im_blend im_s'.value im_s.value p_start?0;
+ im_o'' = im_blend im_o'.value im_o.value p_start?1;
+
+ p_out = [im_s'', im_o''];
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_apply_item = class
+ Menuaction "_Apply Values"
+ "apply scale and offset corrections, defined as image maps, to a given image"
+ /* Outputs the balanced image. Eg. Balance the secondary structure in an X-ray image. Takes an
+ * X-ray image an 32-bit float scale image and a 32-bit offset image.
+ */
+ {
+ action im_in scale_im offset_im = class
+ Image value
+ {
+ _vislevel = 1;
+
+ xfactor = im_in.width/scale_im.width;
+ yfactor = im_in.height/scale_im.height;
+
+ _scale_im = resize Kernel_linear xfactor yfactor scale_im;
+ _offset_im = resize Kernel_linear xfactor yfactor offset_im;
+
+ value = get_image ( clip2fmt im_in.format ( ( im_in * _scale_im ) + _offset_im ) );
+ }
+ }
+}
+
+ Tilt_item = Filter_tilt_item;
+
+ sep2 = Menuseparator;
+
+ Rebuild_item = class
+ Menuaction "_Rebuild"
+ "disassemble mosaic, substitute image files and reassemble" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ old = String "In each filename, replace" "foo";
+ new = String "With" "bar";
+
+ _result
+ = map_unary remosaic x
+ {
+ remosaic image
+ = Image (im_remosaic image.value old.value new.value);
+ }
+ }
+ }
+
+ sep3 = Menuseparator;
+
+Clone_area_item = class
+ Menuaction "_Clone Area"
+ "replace dark or light section of im1 with pixels from im2"
+ {
+ action im1 im2 = class
+ _result {
+ _check_args = [
+ [im1, "im1", check_Image],
+ [im2, "im2", check_Image]
+ ];
+ _vislevel = 3; /* Region on first
+image placed in the top left hand corner,
+ * positioned and size relative to the height and width of im1.
+ */
+ r1 = Region_relative im1 0.05 0.05 0.05 0.05;
+ /* Mark on second image placed in the top left hand corner,
+ * positioned relative to the height and width of im2. Used to
+ * define _r2, the region from which the section of image is cloned
+ * from.
+ */
+ p2 = Mark_relative im2 0.05 0.05; _r2 = Region im2 p2.left
+p2.top r1.width r1.height;
+ mask = [r1 <= Options.sc, r1 >=
+Options.sc]?(Options.replace);
+ Options = class
+ {
+ _vislevel = 3;
+ pause = Toggle "Pause process" true;
+ /* Option toggle used to define whether the user is
+ * replacing a dark or a light area.
+ */
+ replace = Option "Replace" [ "A Dark Area", "A Light Area" ] 1;
+
+ // Used to select the area to be replaced.
+ sc = Scale "Scale cutoff" 0.01 mx (mx / 2)
+ {mx = Image_format.maxval im1.format;}
+ //Allows replacement with scale&offset balanced gaussian noise.
+ balance = Toggle "Balance cloned data to match surroundings." true;
+ //Allows replacement with scale&offset balanced
+ //gaussian noise.
+ process = Toggle "Replace area with Gaussian noise." false;
+ }
+ _result = im1, Options.pause
+ = Image (im_insert im1.value patch r1.left r1.top)
+ { r2 = Region im2 p2.left p2.top r1.width r1.height;
+ ref_meanmax = so_meanmax (if mask then 0 else r1);
+ mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255],
+[255, 255, 255]];
+ mask_a = map_unary (dilate mask8) mask;
+ mask_b = convsep (matrix_blur 2) mask_a;
+ patch = so_balance ref_meanmax r1 r2 mask_b
+Options.process, Options.balance
+ = so_balance ref_meanmax r1 r2 mask_b Options.process,
+Options.process
+ = im_blend (get_image mask_b) (get_image r2) (get_image r1);
+ }
+ }
+ }
+}
+
+Tasks_frame_item = Frame_item;
+
+Tasks_print_item = class
+ Menupullright "_Print" "useful stuff for image output" {
+
+ Rotate_item = Image_transform_item.Rotate_item;
+
+ Flip_item = Image_transform_item.Flip_item;
+
+ Resize_item = Image_transform_item.Resize_item;
+
+ Tone_item = Image_levels_item.Tone_item;
+
+ Sharpen_item = class
+ Menuaction "_Sharpen"
+ "unsharp filter tuned for typical inkjet printers" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ target_dpi = Option "Sharpen for print at" [
+ "400 dpi",
+ "300 dpi",
+ "150 dpi",
+ "75 dpi"
+ ] 1;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = sharpen params?0 params?1
+ params?2 params?3 params?4 params?5
+ (colour_transform_to Image_type.LABQ image)
+ {
+ // sharpen params for various dpi
+ // just change the size of the area we search
+ param_table = [
+ [7, 2.5, 40, 20, 0.5, 1.5],
+ [5, 2.5, 40, 20, 0.5, 1.5],
+ [3, 2.5, 40, 20, 0.5, 1.5],
+ [11, 2.5, 40, 20, 0.5, 1.5]
+ ];
+ params = param_table?target_dpi;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Temp_item = Colour_temperature_item;
+
+ ICC_item = Colour_icc_item;
+}
diff --git a/share/nip2/compat/8.5/Widgets.def b/share/nip2/compat/8.5/Widgets.def
new file mode 100644
index 00000000..c7ade22f
--- /dev/null
+++ b/share/nip2/compat/8.5/Widgets.def
@@ -0,0 +1,46 @@
+Widget_slider_item = class
+ Menuaction "_Scale" "make a new scale widget" {
+ icon = "nip-slider-16.png";
+ action = Scale "untitled scale" 0 255 128;
+}
+
+Widget_toggle_item = class
+ Menuaction "_Toggle" "make a new toggle widget" {
+ action = Toggle "untitled toggle" false;
+}
+
+Widget_option_item = class
+ Menuaction "_Option" "make a new option widget" {
+ action = Option "untitled option" ["option0", "option1"] 0;
+}
+
+Widget_string_item = class
+ Menuaction "St_ring" "make a new string widget" {
+ action = String "Enter a string" "sample text";
+}
+
+Widget_number_item = class
+ Menuaction "_Number" "make a new number widget" {
+ action = Number "Enter a number" 42;
+}
+
+Widget_expression_item = class
+ Menuaction "_Expression" "make a new expression widget" {
+ action = Expression "Enter an expression" 42;
+}
+
+Widget_pathname_item = class
+ Menuaction "_File Chooser" "make a new file chooser widget" {
+ action = Pathname "Pick a file"
+ "$VIPSHOME/share/$PACKAGE/data/print_test_image.v";
+}
+
+Widget_font_item = class
+ Menuaction "F_ont Chooser" "make a new font chooser widget" {
+ action = Fontname "Pick a font" Workspaces.Preferences.PAINTBOX_FONT;
+}
+
+Widget_clock_item = class
+ Menuaction "_Clock" "make a new clock widget" {
+ action = Clock 1 1;
+}
diff --git a/share/nip2/compat/8.5/_Object.def b/share/nip2/compat/8.5/_Object.def
new file mode 100644
index 00000000..9562f9b5
--- /dev/null
+++ b/share/nip2/compat/8.5/_Object.def
@@ -0,0 +1,364 @@
+/* Lots of little arg checks. Global for convenience.
+ */
+
+check_any = [(const true), _ "any"];
+check_bool = [is_bool, _ "boolean"];
+check_real = [is_real, _ "real"];
+check_ureal = [is_ureal, _ "unsigned real"];
+check_preal = [is_preal, _ "positive real"];
+check_list = [is_list, _ "list"];
+check_real_list = [is_real_list, _ "list of real"];
+check_string = [is_string, _ "string"];
+check_string_list = [is_string_list, _ "list of string"];
+check_int = [is_int, _ "integer"];
+check_uint = [is_uint, _ "unsigned integer"];
+check_pint = [is_pint, _ "positive integer"];
+check_matrix = [is_matrix, _ "rectangular array of real"];
+check_matrix_display = [Matrix_display.is_display, _ "0|1|2|3"];
+check_image = [is_image, _ "image"];
+check_xy_list = [is_xy_list, _ "list of form [[1, 2], [3, 4], [5, 6], ...]"];
+check_instance name = [is_instanceof name, name];
+check_Image = check_instance "Image";
+check_Matrix = [is_Matrix, _ "Matrix"];
+check_colour_space = [is_colour_space,
+ join_sep "|" Image_type.colour_spaces.names];
+check_rectangular = [is_rectangular, _ "rectangular [[*]]"];
+check_Guide = [is_Guide, _ "HGuide|VGuide"];
+check_Colour = check_instance (_ "Colour");
+check_Mark = check_instance (_ "Mark");
+
+/* Check a set of args to a class. Two members to look at: _check_args and
+ * _check_all.
+ *
+ * - each line in _check_args is [arg, "arg name", [test_fn, "arg type"]]
+ * same number of lines as there are args
+ *
+ * stuff like "arg 2 must be real"
+ *
+ * - each line in _check_all is [test, "description"]
+ * any number of lines
+ *
+ * stuff like "to must be greater than from"
+ *
+ * generate an error dialog with a helpful message on failure.
+ *
+ * Have as a separate function to try to keep the size of _Object down.
+ */
+check_args x
+ = error message, badargs != [] || badalls != []
+ = x
+{
+ argcheck = x._check_args;
+ allcheck = x._check_all;
+
+ // indent string
+ indent = " ";
+
+ // test for a condition in a check line fails
+ test_fail x = ! x?0;
+
+ // set of failed argcheck indexes
+ badargs = map (extract 1)
+ (filter test_fail (zip2 (map testarg argcheck) [0..]))
+ {
+ testarg x = x?2?0 x?0;
+ }
+
+ // set of failed allcheck indexes
+ badalls = map (extract 1)
+ (filter test_fail (zip2 (map hd allcheck) [0..]));
+
+ // the error message
+ message = _ "bad properties for " ++ "\"" ++ x.name ++ "\"\n" ++
+ argmsg ++ allmsg ++ "\n" ++
+ _ "where" ++ "\n" ++ arg_types ++ extra;
+
+ // make the failed argcheck messages ... eg. ""value" should be
+ // real, you passed " etc.
+ argmsg = concat (map fmt badargs)
+ {
+ fmt n = indent ++ "\"" ++ argcheck?n?1 ++ "\"" ++
+ _ " should be of type " ++ argcheck?n?2?1 ++ ", " ++
+ _ "you passed" ++ ":\n" ++
+ indent ++ indent ++ print argcheck?n?0 ++ "\n";
+ }
+
+ // make the failed allcheck messages ... eg "condition failed:
+ // x < y" ... don't make a message if any typechecks have
+ // failed, as we'll probably error horribly
+ allmsg
+ = [], badargs != []
+ = concat (map fmt badalls) ++
+ _ "you passed" ++ "\n" ++
+ concat (map fmt_arg argcheck)
+ {
+ fmt n = _ "condition failed" ++ ": " ++ allcheck?n?1 ++ "\n";
+ fmt_arg l = indent ++ l?1 ++ " = " ++ print l?0 ++ "\n";
+ }
+
+ // make arg type notes
+ arg_types = join_sep "\n" (map fmt argcheck)
+ {
+ fmt l = indent ++ l?1 ++ " is of type " ++ l?2?1;
+ }
+
+ // extra bit at the bottom, if we have any conditions
+ extra
+ = [], allcheck == []
+ = "\n" ++ _ "and" ++ "\n" ++ all_desc;
+
+ // make a list of all the allcheck descriptions, with a few
+ // spaces in front
+ all_desc_list = map (join indent @ extract 1) allcheck;
+
+ // join em up to make a set of condition notes
+ all_desc = join_sep "\n" all_desc_list;
+}
+
+/* Operator overloading stuff.
+ */
+
+Operator_type = class {
+ ARITHMETIC = 1; // eg. add
+ RELATIONAL = 2; // eg. less
+ COMPOUND = 3; // eg. max/mean/etc.
+ COMPOUND_REWRAP = 4; // eg. transpose
+}
+
+Operator op_name fn type symmetric = class {
+}
+
+/* Form the converse of an Operator.
+ */
+oo_converse op
+ = Operator (converse_name op.op_name)
+ (converse op.fn) op.type op.symmetric
+{
+ converse_name x
+ = init x, last x == last "'"
+ = x ++ "'";
+}
+
+/* Given an operator name, look up the definition.
+ */
+oo_binary_lookup op_name
+ = matches?0, matches != []
+ = error (_ "unknown binary operator" ++ ": " ++ print op_name)
+{
+ operator_table = [
+ Operator "add" add
+ Operator_type.ARITHMETIC true,
+ Operator "subtract" subtract
+ Operator_type.ARITHMETIC false,
+ Operator "remainder" remainder
+ Operator_type.ARITHMETIC false,
+ Operator "power" power
+ Operator_type.ARITHMETIC false,
+ Operator "subscript" subscript
+ Operator_type.ARITHMETIC false,
+ Operator "left_shift" left_shift
+ Operator_type.ARITHMETIC false,
+ Operator "right_shift" right_shift
+ Operator_type.ARITHMETIC false,
+ Operator "divide" divide
+ Operator_type.ARITHMETIC false,
+ Operator "join" join
+ Operator_type.ARITHMETIC false,
+ Operator "multiply" multiply
+ Operator_type.ARITHMETIC true,
+ Operator "logical_and" logical_and
+ Operator_type.ARITHMETIC true,
+ Operator "logical_or" logical_or
+ Operator_type.ARITHMETIC true,
+ Operator "bitwise_and" bitwise_and
+ Operator_type.ARITHMETIC true,
+ Operator "bitwise_or" bitwise_or
+ Operator_type.ARITHMETIC true,
+ Operator "eor" eor
+ Operator_type.ARITHMETIC true,
+ Operator "comma" comma
+ Operator_type.ARITHMETIC false,
+ Operator "if_then_else" if_then_else
+ Operator_type.ARITHMETIC false,
+ Operator "equal" equal
+ Operator_type.RELATIONAL true,
+ Operator "not_equal" not_equal
+ Operator_type.RELATIONAL true,
+ Operator "less" less
+ Operator_type.RELATIONAL false,
+ Operator "less_equal" less_equal
+ Operator_type.RELATIONAL false
+ ];
+
+ matches = filter test_name operator_table;
+ test_name x = x.op_name == op_name;
+}
+
+/* Given an operator name, look up a function that implements that
+ * operator.
+ */
+oo_unary_lookup op_name
+ = matches?0, matches != []
+ = error (_ "unknown unary operator" ++ ": " ++ print op_name)
+{
+ operator_table = [
+ /* Operators.
+ */
+ Operator "cast_signed_char" cast_signed_char
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_char" cast_unsigned_char
+ Operator_type.ARITHMETIC false,
+ Operator "cast_signed_short" cast_signed_short
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_short" cast_unsigned_short
+ Operator_type.ARITHMETIC false,
+ Operator "cast_signed_int" cast_signed_int
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_int" cast_unsigned_int
+ Operator_type.ARITHMETIC false,
+ Operator "cast_float" cast_float
+ Operator_type.ARITHMETIC false,
+ Operator "cast_double" cast_double
+ Operator_type.ARITHMETIC false,
+ Operator "cast_complex" cast_complex
+ Operator_type.ARITHMETIC false,
+ Operator "cast_double_complex" cast_double_complex
+ Operator_type.ARITHMETIC false,
+ Operator "unary_minus" unary_minus
+ Operator_type.ARITHMETIC false,
+ Operator "negate" negate
+ Operator_type.RELATIONAL false,
+ Operator "complement" complement
+ Operator_type.ARITHMETIC false,
+ Operator "unary_plus" unary_plus
+ Operator_type.ARITHMETIC false,
+
+ /* Built in projections.
+ */
+ Operator "re" re Operator_type.ARITHMETIC false,
+ Operator "im" im Operator_type.ARITHMETIC false,
+ Operator "hd" hd Operator_type.ARITHMETIC false,
+ Operator "tl" tl Operator_type.ARITHMETIC false,
+
+ /* Maths builtins.
+ */
+ Operator "sin" sin Operator_type.ARITHMETIC false,
+ Operator "cos" cos Operator_type.ARITHMETIC false,
+ Operator "tan" tan Operator_type.ARITHMETIC false,
+ Operator "asin" asin Operator_type.ARITHMETIC false,
+ Operator "acos" acos Operator_type.ARITHMETIC false,
+ Operator "atan" atan Operator_type.ARITHMETIC false,
+ Operator "log" log Operator_type.ARITHMETIC false,
+ Operator "log10" log10 Operator_type.ARITHMETIC false,
+ Operator "exp" exp Operator_type.ARITHMETIC false,
+ Operator "exp10" exp10 Operator_type.ARITHMETIC false,
+ Operator "ceil" ceil Operator_type.ARITHMETIC false,
+ Operator "floor" floor Operator_type.ARITHMETIC false
+ ];
+
+ matches = filter test_name operator_table;
+ test_name x = x.op_name == op_name;
+}
+
+/* Find the matching methods in a method table.
+ */
+oo_method_lookup table = map (extract 0) (filter (extract 1) table);
+
+/* A binary op: a is a class, b may be a class ... eg. "add" a b
+
+ two obvious ways to find a method:
+
+ - a.oo_binary_search "add" (+) b
+ - b.oo_binary_search "add'" (converse (+)) a, is_class b
+
+ if these fail but op is a symmetric operator (eg. a + b == b + a), we can
+ also try reversing the args
+
+ - a.oo_binary_search "add'" (converse (+)) b
+ - b.oo_binary_search "add" (+) a, is_class b
+
+ if those fail as well, but this is ==, do pointer equals as a fallback
+
+ */
+oo_binary_function op a b
+ = matches1?0,
+ matches1 != []
+ = matches2?0,
+ is_class b && matches2 != []
+ = matches3?0,
+ op.symmetric && matches3 != []
+ = matches4?0,
+ op.symmetric && is_class b && matches4 != []
+ = pointer_equal a b,
+ op.op_name == "equal" || op.op_name == "equal'"
+ = not_pointer_equal a b,
+ op.op_name == "not_equal" || op.op_name == "not_equal'"
+ = error (_ "No method found for binary operator." ++ "\n" ++
+ _ "left" ++ " = " ++ print a ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "right" ++ " = " ++ print b)
+{
+ matches1 = oo_method_lookup (a.oo_binary_table op b);
+ matches2 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
+ matches3 = oo_method_lookup (a.oo_binary_table (oo_converse op) b);
+ matches4 = oo_method_lookup (b.oo_binary_table op a);
+}
+
+/* A binary op: a is not a class, b is a class ... eg. "subtract" a b
+
+ only one way to find a method:
+
+ - b.oo_binary_search "subtract'" (converse (-)) a
+
+ if this fails but op is a symmetric operator (eg. a + b == b + a), we can
+ try reversing the args
+
+ - b.oo_binary_search "add" (+) a, is_class b
+
+ if that fails as well, but this is ==, do pointer equals as a fallback
+
+ */
+oo_binary'_function op a b
+ = matches1?0, matches1 != []
+ = matches2?0, op.symmetric && matches2 != []
+ = pointer_equal a b,
+ op.op_name == "equal" || op.op_name == "equal'"
+ = not_pointer_equal a b,
+ op.op_name == "not_equal" || op.op_name == "not_equal'"
+ = error (_ "No method found for binary operator." ++ "\n" ++
+ _ "left" ++ " = " ++ print a ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "right" ++ " = " ++ print b)
+{
+ matches1 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
+ matches2 = oo_method_lookup (b.oo_binary_table op a);
+}
+
+oo_unary_function op x
+ = matches?0, matches != []
+ = error (_ "No method found for unary operator." ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "argument" ++ " = " ++ print x)
+{
+ matches = oo_method_lookup (x.oo_unary_table op);
+}
+
+/* Base class for nip's built-in classes ... base check function, base
+ * operator overload functions.
+ */
+_Object = class {
+ check = check_args this;
+
+ // these should always be defined
+ _check_args = [];
+ _check_all = [];
+
+ /* Operator overloading stuff.
+ */
+ oo_binary op x = oo_binary_function (oo_binary_lookup op) this x;
+ oo_binary' op x = oo_binary'_function (oo_binary_lookup op) x this;
+ oo_unary op = oo_unary_function (oo_unary_lookup op) this;
+
+ oo_binary_table op x = [];
+ oo_unary_table op = [];
+}
diff --git a/share/nip2/compat/8.5/_convert.def b/share/nip2/compat/8.5/_convert.def
new file mode 100644
index 00000000..c8002860
--- /dev/null
+++ b/share/nip2/compat/8.5/_convert.def
@@ -0,0 +1,685 @@
+
+/* Try to make a Matrix ... works for Vector/Image/Real, plus image/real
+ */
+to_matrix x
+ = to_matrix x.expr, is_Expression x
+ = x, is_Matrix x
+ = oo_unary_function to_matrix_op x, is_class x
+ = tom x
+{
+ to_matrix_op = Operator "to_matrix" tom Operator_type.COMPOUND false;
+
+ tom x
+ = Matrix (itom x), is_image x
+ = Matrix [[x]], is_real x
+ = Matrix [x], is_real_list x
+ = Matrix x, is_matrix x
+ = error (_ "bad arguments to " ++ "to_matrix");
+
+ itom i
+ = (im_vips2mask ((double) i)).value, is_image i
+ = error (_ "not image");
+}
+
+/* Try to make a Vector ... works for Vector/Image/Real, plus image/real
+ */
+to_vector x
+ = to_vector x.expr, is_Expression x
+ = x, is_Vector x
+ = oo_unary_function to_vector_op x, is_class x
+ = tov x
+{
+ to_vector_op = Operator "to_vector" tov Operator_type.COMPOUND false;
+
+ tov x
+ = Vector (itov x), is_image x
+ = Vector [x], is_real x
+ = Vector x, is_real_list x
+ = Vector x?0, is_matrix x && len x == 1
+ = Vector (transpose x)?0, is_matrix x && len x?0 == 1
+ = error (_ "bad arguments to " ++ "to_vector");
+
+ itov i
+ = v, is_image i
+ = error (_ "not image")
+ {
+ m = im_vips2mask ((double) i);
+ v
+ = m.value?0, m.height == 1
+ = (transpose m.value)?0, m.width == 1
+ = error (_ "image is not 1xN or Nx1");
+ }
+}
+
+/* Try to make an Image ... works for Vector/Matrix/Real, plus image/real
+ * Special case for Colour ... pull out the colour_space and set Type in the
+ * image.
+ */
+to_image x
+ = to_image x.expr, is_Expression x
+ = Image x.value, is_Plot x
+ = x, is_Image x
+ = Image (image_set_type
+ (Image_type.colour_spaces.lookup 0 1 x.colour_space)
+ (mtoi [x.value])),
+ is_Colour x
+ = oo_unary_function to_image_op x, is_class x
+ = toi x
+{
+ to_image_op = Operator "to_image" toi Operator_type.COMPOUND false;
+
+ toi x
+ = Image x, is_image x
+ = Image (mtoi [[x]]), is_real x
+ = Image (mtoi [x]), is_real_list x
+ = Image (mtoi x), is_matrix x
+ = error (_ "bad arguments to " ++ "to_image");
+
+ // [[real]] -> image
+ mtoi m
+ = im_mask2vips (Matrix m), width != 3
+ = joinup (im_mask2vips (Matrix m))
+ {
+ width = len m?0;
+ height = len m;
+ joinup i
+ = b1 ++ b2 ++ b3
+ {
+ b1 = extract_area 0 0 1 height i;
+ b2 = extract_area 1 0 1 height i;
+ b3 = extract_area 2 0 1 height i;
+ }
+ }
+}
+
+// like to_image, but we do 1x1 pixel + x, then embed it up
+// always make an unwrapped image for speed ... this gets used by ifthenelse
+// and stuff like that
+// format can be NULL, meaning set format from x
+to_image_size width height bands format x
+ = x, is_image x
+ = x.value, is_Image x
+ = im''
+{
+ // we want x to set the target format if we don't have one, so we
+ // can't use image_new
+ im = im_black 1 1 bands + x;
+ im'
+ = clip2fmt format im, format != NULL
+ = im;
+ im'' = embed 1 0 0 width height im';
+}
+
+/* Try to make a Colour.
+ */
+to_colour x
+ = to_colour x.expr, is_Expression x
+ = x, is_Colour x
+ = to_colour (extract_area x.left x.top 1 1 x.image), is_Mark x
+ = oo_unary_function to_colour_op x, is_class x
+ = toc x
+{
+ to_colour_op = Operator "to_colour" toc Operator_type.COMPOUND false;
+
+ toc x
+ = Colour (colour_space (get_type x))
+ (map mean (bandsplit (get_image x))),
+ has_image x && has_type x
+ = Colour "sRGB" [x, x, x], is_real x // since Colour can't do mono
+ = Colour "sRGB" x, is_real_list x && is_list_len 3 x
+ = map toc x, is_matrix x
+ = error (_ "bad arguments to " ++ "to_colour");
+
+ colour_space type
+ = table.get_name type, table.has_name type
+ = error (_ "unable to make Colour from " ++ table.get_name type ++
+ _ " image")
+ {
+ table = Image_type.colour_spaces;
+ }
+}
+
+/* Try to make a real. (not a Real!)
+ */
+to_real x
+ = to_real x.expr, is_Expression x
+ = oo_unary_function to_real_op x, is_class x
+ = tor x
+{
+ to_real_op = Operator "to_real" tor Operator_type.COMPOUND false;
+
+ tor x
+ = x, is_real x
+ = abs x, is_complex x
+ = 1, is_bool x && x
+ = 0, is_bool x && !x
+ = error (_ "bad arguments to " ++ "to_real");
+}
+
+to_int x = (int) (to_real x);
+
+/* Try to make a list ... ungroup, basically. We remove the innermost layer of
+ * Groups.
+ */
+to_list x
+ = x.value, is_Group x && !contains_Group x.value
+ = Group (map to_list x.value), is_Group x
+ = x;
+
+/* Try to make a group. The outermost list objects become Group()'d.
+ */
+to_group x
+ = Group x, is_list x
+ = Group (map to_group x.value), is_Group x
+ = x;
+
+/* Parse a positive integer.
+ */
+parse_pint l
+ = foldl acc 0 l
+{
+ acc sofar ch = sofar * 10 + parse_c ch;
+
+ /* Turn a char digit to a number.
+ */
+ parse_c ch
+ = error (_ "not a digit"), !is_digit ch
+ = (int) ch - (int) '0';
+}
+
+/* Parse an integer, with an optional sign character.
+ */
+parse_int l
+ = error (_ "badly formed number"), !is_list_len 2 parts
+ = sign * n
+{
+ parts = splitpl [member "+-", is_digit] l;
+
+ n = parse_pint parts?1;
+ sign
+ = 1, parts?0 == [] || parts?0 == "+"
+ = -1;
+}
+
+/* Parse a float.
+ * [+-]?[0-9]*([.][0-9]*)?(e[0-9]+)?
+ */
+parse_float l
+ = err, !is_list_len 4 parts
+ = sign * (abs ipart + fpart) * 10 ** exp
+{
+ err = error (_ "badly formed number");
+
+ parts = splitpl [
+ member "+-0123456789",
+ member ".0123456789",
+ member "eE",
+ member "+-0123456789"
+ ] l;
+
+ ipart = parse_int parts?0;
+ sign
+ = 1, ipart >= 0
+ = -1;
+ fpart
+ = 0, parts?1 == [];
+ = err, parts?1?0 != '.'
+ = parse_pint (tl parts?1) / 10 ** (len parts?1 - 1);
+ exp
+ = 0, parts?2 == [] && parts?3 == []
+ = err, parts?2 == []
+ = parse_int parts?3;
+
+}
+
+/* Parse a time in "hh:mm:ss" into seconds.
+
+We could do this in one line :)
+
+ = (sum @ map2 multiply (iterate (multiply 60) 1) @ reverse @ map
+ parse_pint @ map (subscript (splitpl [is_digit, equal ':', is_digit,
+ equal ':', is_digit] l))) [0,2,4];
+
+but it's totally unreadable.
+
+ */
+parse_time l
+ = error (_ "badly formed time"), !is_list_len 5 parts
+ = s + 60 * m + 60 * 60 * h
+{
+ parts = splitpl [is_digit, equal ':', is_digit, equal ':', is_digit] l;
+ h = parse_int parts?0;
+ m = parse_int parts?2;
+ s = parse_int parts?4;
+}
+
+/* matrix to convert D65 XYZ to D50 XYZ ... direct conversion, found by
+ * measuring a macbeth chart in D50 and D65 and doing a LMS to get a matrix
+ */
+D652D50_direct = Matrix
+ [[ 1.13529, -0.0604663, -0.0606321 ],
+ [ 0.0975399, 0.935024, -0.0256156 ],
+ [ -0.0336428, 0.0414702, 0.994135 ]];
+
+D502D65_direct = D652D50_direct ** -1;
+
+/* Convert normalised XYZ to bradford RGB.
+ */
+XYZ2RGBbrad = Matrix
+ [[0.8951, 0.2664, -0.1614],
+ [-0.7502, 1.7135, 0.0367],
+ [0.0389, -0.0685, 1.0296]];
+
+/* Convert bradford RGB to normalised XYZ.
+ */
+RGBbrad2XYZ = XYZ2RGBbrad ** -1;
+
+D93_whitepoint = Vector [89.7400, 100, 130.7700];
+D75_whitepoint = Vector [94.9682, 100, 122.5710];
+D65_whitepoint = Vector [95.0470, 100, 108.8827];
+D55_whitepoint = Vector [95.6831, 100, 92.0871];
+D50_whitepoint = Vector [96.4250, 100, 82.4680];
+A_whitepoint = Vector [109.8503, 100, 35.5849]; // 2856K
+B_whitepoint = Vector [99.0720, 100, 85.2230]; // 4874K
+C_whitepoint = Vector [98.0700, 100, 118.2300]; // 6774K
+E_whitepoint = Vector [100, 100, 100]; // ill. free
+D3250_whitepoint = Vector [105.6590, 100, 45.8501];
+
+Whitepoints = Enum [
+ $D93 => D93_whitepoint,
+ $D75 => D75_whitepoint,
+ $D65 => D65_whitepoint,
+ $D55 => D55_whitepoint,
+ $D50 => D50_whitepoint,
+ $A => A_whitepoint,
+ $B => B_whitepoint,
+ $C => C_whitepoint,
+ $E => E_whitepoint,
+ $D3250 => D3250_whitepoint
+];
+
+/* Convert D50 XYZ to D65 using the bradford chromatic adaptation approx.
+ */
+im_D502D65 xyz
+ = xyz'''
+{
+ xyz' = xyz / D50_whitepoint;
+
+ rgb = recomb XYZ2RGBbrad xyz';
+
+ // move white in bradford RGB
+ rgb' = rgb / Vector [0.94, 1.02, 1.33];
+
+ xyz'' = recomb RGBbrad2XYZ rgb';
+
+ // back to D65
+ xyz''' = xyz'' * D65_whitepoint;
+}
+
+/* Convert D65 XYZ to D50 using the bradford approx.
+ */
+im_D652D50 xyz
+ = xyz'''
+{
+ xyz' = xyz / D65_whitepoint;
+
+ rgb = recomb XYZ2RGBbrad xyz';
+
+ // move white in bradford RGB
+ rgb' = rgb * Vector [0.94, 1.02, 1.33];
+
+ xyz'' = recomb RGBbrad2XYZ rgb';
+
+ xyz''' = xyz'' * D50_whitepoint;
+}
+
+/* Convert D50 XYZ to Lab.
+ */
+im_D50XYZ2Lab xyz
+ = im_XYZ2Lab_temp xyz
+ D50_whitepoint.value?0
+ D50_whitepoint.value?1
+ D50_whitepoint.value?2;
+im_D50Lab2XYZ lab
+ = im_Lab2XYZ_temp lab
+ D50_whitepoint.value?0
+ D50_whitepoint.value?1
+ D50_whitepoint.value?2;
+
+/* ... and mono conversions
+ */
+im_sRGB2mono in
+ = (image_set_type Image_type.B_W @
+ clip2fmt (get_header "BandFmt" in) @
+ recomb (Matrix [[.3, .6, .1]])) in;
+im_mono2sRGB in
+ = image_set_type Image_type.sRGB (in ++ in ++ in);
+
+im_sRGB2Lab = im_XYZ2Lab @ im_sRGB2XYZ;
+
+im_Lab2sRGB = im_XYZ2sRGB @ im_Lab2XYZ;
+
+// from the 16 bit RGB and GREY formats
+im_1628 x = im_clip (x >> 8);
+im_162f x = x / 256;
+
+im_8216 x = (im_clip2us x) << 8;
+im_f216 x = im_clip2us (x * 256);
+
+im_RGB162GREY16 in
+ = (image_set_type Image_type.GREY16 @
+ clip2fmt (get_header "BandFmt" in) @
+ recomb (Matrix [[.3, .6, .1]])) in;
+im_GREY162RGB16 in
+ = image_set_type Image_type.RGB16 (in ++ in ++ in);
+
+/* apply a func to an image ... make it 1 or 3 bands, and reapply other bands
+ * on the way out. Except if it's LABPACK.
+ */
+colour_apply fn x
+ = fn x, b == 1 || b == 3 || c == Image_coding.LABPACK
+ = x''
+{
+ b = get_bands x;
+ c = get_coding x;
+
+ first
+ = extract_bands 0 3 x, b > 3
+ = extract_bands 0 1 x;
+ tail
+ = extract_bands 3 (b - 3) x, b > 3
+ = extract_bands 1 (b - 1) x;
+ x' = fn first;
+ x'' = x' ++ clip2fmt (get_format x') tail;
+}
+
+/* Any 1-ary colour op, applied to Vector/Image/Matrix or image
+ */
+colour_unary fn x
+ = oo_unary_function colour_op x, is_class x
+ = colour_apply fn x, is_image x
+ = colour_apply fn [x], is_real x
+ = error (_ "bad arguments to " ++ "colour_unary")
+{
+ // COMPOUND_REWRAP ... signal to the colour class to go to image and
+ // back
+ colour_op = Operator "colour_unary"
+ colour_object Operator_type.COMPOUND_REWRAP false;
+
+ colour_object x
+ = colour_real_list x, is_real_list x
+ = map colour_real_list x, is_matrix x
+ = colour_apply fn x, is_image x
+ = error (_ "bad arguments to " ++ "colour_unary");
+
+ colour_real_list l
+ = (to_matrix (fn (float) (to_image (Vector l)).value)).value?0;
+}
+
+/* Any symmetric 2-ary colour op, applied to Vector/Image/Matrix or image ...
+ * name is op name for error messages etc.
+ */
+colour_binary name fn x y
+ = oo_binary_function colour_op x y, is_class x
+ = oo_binary'_function colour_op x y, is_class y
+ = fn x y, is_image x && is_image y
+ = error (_ "bad arguments to " ++ name)
+{
+ colour_op = Operator name
+ colour_object Operator_type.COMPOUND_REWRAP true;
+
+ colour_object x y
+ = fn x y, is_image x && is_image y
+ = colour_real_list fn x y, is_real_list x && is_real_list y
+ = map (colour_real_list fn x) y, is_real_list x && is_matrix y
+ = map (colour_real_list (converse fn) y) x,
+ is_matrix x && is_real_list y
+ = map2 (colour_real_list fn) x y, is_matrix x && is_matrix y
+ = error (_ "bad arguments to " ++ name);
+
+ colour_real_list fn l1 l2
+ = (to_matrix (fn i1 i2)).value?0
+ {
+ i1 = (float) (to_image (Vector l1)).value;
+ i2 = (float) (to_image (Vector l2)).value;
+ }
+}
+
+_colour_conversion_table = [
+ /* Lines are [space-from, space-to, conversion function]. Could do
+ * this as a big array, but table lookup feels safer.
+ */
+ [B_W, B_W, image_set_type B_W],
+ [B_W, XYZ, im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, LAB, im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, sRGB, im_mono2sRGB @ im_clip],
+ [B_W, RGB16, image_set_type RGB16 @ im_8216 @ im_mono2sRGB],
+ [B_W, GREY16, image_set_type GREY16 @ im_8216],
+ [B_W, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab @
+ im_mono2sRGB @ im_clip],
+
+ [XYZ, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_clip2f],
+ [XYZ, XYZ, image_set_type XYZ],
+ [XYZ, YXY, im_XYZ2Yxy @ im_clip2f],
+ [XYZ, LAB, im_XYZ2Lab @ im_clip2f],
+ [XYZ, LCH, im_Lab2LCh @ im_XYZ2Lab],
+ [XYZ, UCS, im_XYZ2UCS @ im_clip2f],
+ [XYZ, RGB, im_XYZ2disp @ im_clip2f],
+ [XYZ, sRGB, im_XYZ2sRGB @ im_clip2f],
+ [XYZ, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
+ [XYZ, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
+
+ [YXY, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, XYZ, im_Yxy2XYZ @ im_clip2f],
+ [YXY, YXY, image_set_type YXY],
+ [YXY, LAB, im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LCH, im_Lab2LCh @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, UCS, im_XYZ2UCS @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, RGB, im_XYZ2disp @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, sRGB, im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @
+ im_clip2f],
+
+ [LAB, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_Lab2XYZ @ im_clip2f],
+ [LAB, XYZ, im_Lab2XYZ @ im_clip2f],
+ [LAB, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_clip2f],
+ [LAB, LAB, image_set_type LAB @ im_clip2f],
+ [LAB, LCH, im_Lab2LCh @ im_clip2f],
+ [LAB, UCS, im_Lab2UCS @ im_clip2f],
+ [LAB, RGB, im_Lab2disp @ im_clip2f],
+ [LAB, sRGB, im_Lab2sRGB @ im_clip2f],
+ [LAB, LABQ, im_Lab2LabQ @ im_clip2f],
+ [LAB, LABS, im_Lab2LabS @ im_clip2f],
+
+ [LCH, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
+ [LCH, XYZ, im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
+ [LCH, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
+ [LCH, LAB, im_LCh2Lab @ im_clip2f],
+ [LCH, LCH, image_set_type LCH],
+ [LCH, UCS, im_LCh2UCS @ im_clip2f],
+ [LCH, RGB, im_Lab2disp @ im_LCh2Lab @ im_clip2f],
+ [LCH, sRGB, im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
+ [LCH, LABQ, im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
+ [LCH, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
+
+ [UCS, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_UCS2XYZ @ im_clip2f],
+ [UCS, XYZ, im_UCS2XYZ @ im_clip2f],
+ [UCS, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_UCS2Lab @ im_clip2f],
+ [UCS, LAB, im_UCS2Lab @ im_clip2f],
+ [UCS, LCH, im_UCS2LCh @ im_clip2f],
+ [UCS, UCS, image_set_type UCS],
+ [UCS, RGB, im_Lab2disp @ im_UCS2Lab @ im_clip2f],
+ [UCS, sRGB, im_Lab2sRGB @ im_UCS2Lab @ im_clip2f],
+ [UCS, LABQ, im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
+ [UCS, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
+
+ [RGB, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
+ [RGB, XYZ, im_disp2XYZ @ im_clip],
+ [RGB, YXY, im_XYZ2Yxy @ im_disp2XYZ @ im_clip],
+ [RGB, LAB, im_disp2Lab @ im_clip],
+ [RGB, LCH, im_Lab2LCh @ im_disp2Lab @ im_clip],
+ [RGB, UCS, im_Lab2UCS @ im_disp2Lab @ im_clip],
+ [RGB, RGB, image_set_type RGB],
+ [RGB, sRGB, im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
+ [RGB, RGB16, image_set_type RGB16 @ im_8216],
+ [RGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
+ [RGB, LABQ, im_Lab2LabQ @ im_disp2Lab @ im_clip],
+ [RGB, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_disp2Lab @ im_clip],
+
+ [sRGB, B_W, im_sRGB2mono],
+ [sRGB, XYZ, im_sRGB2XYZ @ im_clip],
+ [sRGB, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_clip],
+ [sRGB, LAB, im_sRGB2Lab @ im_clip],
+ [sRGB, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_clip],
+ [sRGB, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_clip],
+ [sRGB, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_clip],
+ [sRGB, sRGB, image_set_type sRGB],
+ [sRGB, RGB16, image_set_type RGB16 @ im_8216],
+ [sRGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
+ [sRGB, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_clip],
+ [sRGB, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab @ im_clip],
+
+ [RGB16, B_W, im_1628 @ im_sRGB2mono],
+ [RGB16, RGB, image_set_type RGB @ im_1628],
+ [RGB16, sRGB, image_set_type sRGB @ im_1628],
+ [RGB16, RGB16, image_set_type RGB16],
+ [RGB16, GREY16, im_RGB162GREY16],
+ [RGB16, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab],
+
+ [GREY16, B_W, image_set_type B_W @ im_1628],
+ [GREY16, RGB, im_mono2sRGB @ im_1628],
+ [GREY16, sRGB, im_mono2sRGB @ im_1628],
+ [GREY16, RGB16, im_GREY162RGB16],
+ [GREY16, GREY16, image_set_type GREY16],
+
+ [LABQ, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab],
+ [LABQ, XYZ, im_Lab2XYZ @ im_LabQ2Lab],
+ [LABQ, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_LabQ2Lab],
+ [LABQ, LAB, im_LabQ2Lab],
+ [LABQ, LCH, im_Lab2LCh @ im_LabQ2Lab],
+ [LABQ, UCS, im_Lab2UCS @ im_LabQ2Lab],
+ [LABQ, RGB, im_LabQ2disp],
+ [LABQ, sRGB, im_Lab2sRGB @ im_LabQ2Lab],
+ [LABQ, LABQ, image_set_type LABQ],
+ [LABQ, LABS, im_LabQ2LabS],
+
+ [LABS, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab @
+ im_LabS2LabQ @ im_clip2s],
+ [LABS, XYZ, im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, YXY, im_XYZ2Yxy @
+ im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, LAB, im_LabS2Lab],
+ [LABS, LCH, im_Lab2LCh @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, UCS, im_Lab2UCS @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, RGB, im_LabQ2disp @ im_LabS2LabQ @ im_clip2s],
+ [LABS, sRGB, im_XYZ2sRGB @
+ im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, LABQ, im_LabS2LabQ @ im_clip2s],
+ [LABS, LABS, image_set_type LABS]
+]
+{
+ /* From Image_type ... repeat here for brevity. Use same ordering as
+ * in Colour menu for consistency.
+ */
+ B_W = 1;
+ XYZ = 12;
+ YXY = 23;
+ LAB = 13;
+ LCH = 19;
+ UCS = 18;
+ RGB = 17;
+ sRGB = 22;
+ RGB16 = 25;
+ GREY16 = 26;
+ LABQ = 16;
+ LABS = 21;
+}
+
+/* Transform between two colour spaces.
+ */
+colour_transform from to in
+ = colour_unary _colour_conversion_table?i?2 in, i >= 0
+ = error (_ "unable to convert " ++ Image_type.type_names.get_name from ++
+ _ " to " ++ Image_type.type_names.get_name to)
+{
+ match x = x?0 == from && x?1 == to;
+ i = index match _colour_conversion_table;
+}
+
+/* Transform to a colour space, assuming the type field in the input is
+ * correct
+ */
+colour_transform_to to in = colour_transform (get_type in) to in;
+
+/* String for path separator on this platform.
+ */
+path_separator = expand "$SEP";
+
+/* Form a relative pathname.
+ * path_relative ["home", "john"] == "home/john"
+ * path_relative [] == ""
+ */
+path_relative l = join_sep path_separator l;
+
+/* Form an absolute pathname.
+ * path_absolute ["home", "john"] == "/home/john"
+ * path_absolute [] == "/"
+ * If the first component looks like 'A:', don't add an initial separator.
+ */
+path_absolute l
+ = path_relative l,
+ len l?0 > 1 && is_letter l?0?0 && l?0?1 == ':'
+ = path_separator ++ path_relative l;
+
+/* Parse a pathname.
+ * path_parse "/home/john" == ["home", "john"]
+ * path_parse "home/john" == ["home", "john"]
+ */
+path_parse str
+ = split (equal path_separator?0) str;
+
+/* Return $PATH, reformatted as [["comp1", "comp2"], ["comp1", "comp2"] ..]
+ */
+system_search_path
+ = [vipsbin] ++
+ map path_parse (split (equal path_sep) (expand "$PATH"))
+{
+ /* On some platforms we ship vips with a few extra progs. Search
+ * $VIPSHOME/bin first.
+ */
+ vipsbin = path_parse (expand "$VIPSHOME") ++ ["bin"];
+
+ path_sep
+ = ':', expand "$SEP" == "/"
+ = ';';
+}
+
+/* Search $PATH for the first occurence of name, or "".
+ */
+search_for name
+ = hits?0, hits != []
+ = ""
+{
+ exe_name = name ++ expand "$EXEEXT";
+ form_path p = path_absolute (p ++ [exe_name]);
+ paths = map form_path system_search_path;
+ hits = dropwhile (equal []) (map search paths);
+}
+
+/* Search $PATH for the first occurence of name, error on failure.
+ */
+search_for_error name
+ = path, path != ""
+ = error (exe_name ++ " not found on your search path. " ++
+ "Check you have installed the program and it is on your PATH.")
+{
+ exe_name = name ++ expand "$EXEEXT";
+ path = search_for name;
+}
+
diff --git a/share/nip2/compat/8.5/_generate.def b/share/nip2/compat/8.5/_generate.def
new file mode 100644
index 00000000..1ce3af2a
--- /dev/null
+++ b/share/nip2/compat/8.5/_generate.def
@@ -0,0 +1,155 @@
+
+/* make an image of size x by y whose pixels are their coordinates.
+ */
+make_xy x y = im_make_xy (to_real x) (to_real y);
+
+/* make an image with the specified properties ... pixel is (eg.)
+ * Vector [0, 0, 0], or 12. If coding == labq, we ignore bands, format and
+ * type, generate a 3 band float image, and lab2labq it before handing it
+ * back.
+ */
+image_new w h b fmt coding type pixel xoff yoff
+ = embed 1 0 0 w h im''''
+{
+ b'
+ = 3, coding == Image_coding.LABPACK
+ = b;
+ fmt'
+ = Image_format.FLOAT, coding == Image_coding.LABPACK
+ = fmt;
+ type'
+ = Image_type.LAB, coding == Image_coding.LABPACK
+ = type;
+
+ im = im_black 1 1 (to_real b') + pixel;
+
+ im' = clip2fmt fmt' im;
+
+ im''
+ = im_Lab2LabQ im', coding == Image_coding.LABPACK;
+ = im';
+
+ im''' = image_set_type type' im'';
+ im'''' = image_set_origin xoff yoff im''';
+}
+
+mkim options x y b
+ = Image (image_new x y b
+ (opt $format) (opt $coding) (opt $type)
+ (opt $pixel)
+ (opt $xoffset) (opt $yoffset))
+{
+ opt = get_option options [
+ $format => Image_format.UCHAR,
+ $coding => Image_coding.NOCODING,
+ $type => Image_type.sRGB,
+ $pixel => 0,
+ $xoffset => 0,
+ $yoffset => 0
+ ];
+}
+
+/* generate a slice of LAB space size x size pixels for L* == l
+ */
+lab_slice size l
+ = image_set_type Image_type.LAB im
+{
+ L = image_new size size 1
+ Image_format.FLOAT Image_coding.NOCODING Image_type.B_W l 0 0;
+ A1 = im_fgrey (to_real size) (to_real size);
+
+ /* im_fgrey always makes 0-1, so these ranges can be wired in.
+ */
+ A2 = A1 * 256 - 128;
+
+ A4 = im_rot90 A2;
+ im = image_set_origin (size / 2) (size / 2) (L ++ A2 ++ A4);
+}
+
+/* Look at Image, try to make a Colour (failing that, a Vector) which is white
+ * for that image type.
+ */
+image_white im
+ = colour_transform_to type white_lab,
+ bands == 3 && coding == Image_coding.NOCODING &&
+ colour_spaces.present 1 type
+ = white_lab,
+ coding == Image_coding.LABPACK
+ = Vector (replicate bands (max_value.lookup 1 0 format))
+{
+ bands = im.bands;
+ type = im.type;
+ format = im.format;
+ coding = im.coding;
+ colour_spaces = Image_type.colour_spaces;
+
+ // white as LAB
+ white_lab = Colour "Lab" [100, 0, 0];
+
+ // maximum value for this numeric type
+ max_value = Table [
+ [255, Image_format.DPCOMPLEX],
+ [255, Image_format.DOUBLE],
+ [255, Image_format.COMPLEX],
+ [255, Image_format.FLOAT],
+ [2 ** 31 - 1, Image_format.INT],
+ [2 ** 32 - 1, Image_format.UINT],
+ [2 ** 15 - 1, Image_format.SHORT],
+ [2 ** 16 - 1, Image_format.USHORT],
+ [2 ** 7 - 1, Image_format.CHAR],
+ [2 ** 8 - 1, Image_format.UCHAR]
+ ];
+}
+
+/* Make a seperable gaussian mask.
+ */
+matrix_gaussian_blur radius
+ = im_gauss_imask_sep (radius / 3) 0.2;
+
+/* Make a seperable square mask.
+ */
+matrix_blur radius
+ = Matrix_con (sum mask_sq_line) 0 [mask_sq_line]
+{
+ mask_sq_line = replicate (2 * radius - 1) 1;
+}
+
+/* Make a colour from a temperature.
+ */
+colour_from_temp T
+ = error (_ "T out of range"), T < 1667 || T > 25000
+ = Colour "Yxy" [50, x, y]
+{
+ // Kim et all approximation
+ // see eg. http://en.wikipedia.org/wiki/Planckian_locus#Approximation
+ x
+ = -0.2661239 * 10 ** 9 / T ** 3 - 0.2343580 * 10 ** 6 / T ** 2 +
+ 0.8776956 * 10 ** 3 / T + 0.179910, T < 4000
+ = -3.0258469 * 10 ** 9 / T ** 3 + 2.1070379 * 10 ** 6 / T ** 2 +
+ 0.2226347 * 10 ** 3 / T + 0.240390;
+
+ y
+ = -1.1063814 * x ** 3 - 1.34811020 * x ** 2 +
+ 2.18555832 * x - 0.20219638, T < 2222
+ = -0.9549476 * x ** 3 - 1.37418593 * x ** 2 +
+ 2.09137015 * x - 0.16748867, T < 4000
+ = 3.0817580 * x ** 3 - 5.87338670 * x ** 2 +
+ 3.75112997 * x - 0.37001483;
+}
+
+temp_from_colour z
+ = T
+{
+ c = colour_transform_to Image_type.YXY (to_colour z);
+ x = c.value?1;
+ y = c.value?2;
+
+ // McCamy's approximation, see eg.
+ // http://en.wikipedia.org/wiki/Color_temperature#Approximation
+
+ xe = 0.332;
+ ye = 0.1858;
+ n = (x - xe) / (y - ye);
+ T = -449 * n ** 3 + 3525 * n ** 2 - 6823.3 * n + 5520.33;
+}
+
diff --git a/share/nip2/compat/8.5/_joe_extra.def b/share/nip2/compat/8.5/_joe_extra.def
new file mode 100644
index 00000000..0277d261
--- /dev/null
+++ b/share/nip2/compat/8.5/_joe_extra.def
@@ -0,0 +1,470 @@
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Frame_item = class
+ Menupullright "Picture _Frame" "working with images of frames"
+ {
+ ////////////////////////////////////////////////////////////////////////////////////
+ Build_frame_item = class
+ Menupullright "_Build Frame From" "builds a new frame from image a and places it around image b"
+ {
+ ////////////////////////////////////////////////////////////////////////////////////
+ Frame_corner_item = class
+ Menuaction "_Frame Corner"
+ "copies and extends a frame corner, a, to produce a complete frame to fit round a given image, b"
+ {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 0;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ //Scale frame image if required.
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.mount_colour.value, mount_options.apply
+ = 0;
+
+ //Produce scaled and resized frame.
+ frame = corner_frame _a _im_w _im_h _ov _cs _ms _bf;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Simple_frame_item = class
+ Menuaction "_Simple Frame"
+ "extends or shortens the central sections of a simple frame, a, to fit round a given image, b"
+ {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 0;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ //Scale frame image if required.
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.mount_colour.value, mount_options.apply
+ = 0;
+
+ //Produce scaled and resized frame.
+ frame = simple_frame _a _im_w _im_h _ov _cs _ms _bf variables.option;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Complex_frame_item = class
+ Menuaction "_Complex Frame"
+ "extends or shortens the central sections of a frame a, preserving any central edge details, to fit image b" {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 1;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _es = variables.edge_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.colour.value, mount_options.apply
+ = 0;
+
+
+ //Produce scaled and resized frame.
+ frame = complex_frame _a _im_w _im_h _ov _cs _es _ms _bf variables.option;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+ }
+////////////////////////////////////////////////////////////////////////////////////
+ Straighten_frame_item = class
+ Menuaction "_Straighten Frame" "uses four points to square up distorted images of frames" {
+ action a = Perspective_item.action a;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Select_item = class
+ Menupullright "_Select"
+ "select user defined areas of an image" {
+ prefs = Workspaces.Preferences;
+
+ /* Option toggle used to define whether the user is replacing a
+ * dark or a light area.
+ */
+ _control = Option "Make" [
+ "Selection Brighter",
+ "Selection Darker",
+ "Selection Black",
+ "Selection White",
+ "Background Black",
+ "Background White",
+ "Mask"
+ ] 4;
+
+ control_selection mask im no
+ = [
+ if mask then im * 1.2 else im * 1,
+ if mask then im * 0.8 else im * 1,
+ if mask then 0 else im,
+ if mask then 255 else im,
+ if mask then im else 0,
+ if mask then im else 255,
+ mask
+ ]?no;
+
+ Rectangle = class
+ Menuaction "_Rectangle"
+ "use an Arrow or Region x to define a rectangle"
+ {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ im = x.image;
+ mask = Image m
+ {
+ rx
+ = x.region_rect, is_Region x
+ = x;
+ b = image_new im.width im.height 1 0 0 1 0 0 0;
+ w = image_new rx.nwidth rx.nheight 1 0 0 1 255 0 0;
+ m = insert_noexpand rx.nleft rx.ntop w b;
+ }
+ }
+ }
+ }
+
+ Elipse = class
+ Menuaction "_Ellipse"
+ "use a line/arrow x to define the center point radius and direction of an ellipse"
+ {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+ width = Scale "Width" 0.01 1 0.5;
+
+ _result = control_selection mask im control
+ {
+ mask = select_ellipse x width.value;
+ im = x.image;
+ }
+ }
+ }
+
+ Tetragon = class
+ Menuaction "_Tetragon"
+ "selects the convex area defined by four points"
+ {
+ action a b c d = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ mask = select_tetragon a b c d;
+ im = get_image a;
+ }
+ }
+
+ }
+
+ Polygon = class
+ Menuaction "_Polygon"
+ "selects a polygon from an ordered group of points"
+ {
+ action pt_list = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ mask = select_polygon pt_list;
+ im = get_image ((pt_list.value)?0);
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Threshold_item = class
+ Menuaction "Thres_hold" "simple image threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ t
+ = Scale "Threshold" 0 mx (mx / 2)
+ {
+ mx
+ = Image_format.maxval x.format, is_Image x
+ = 255;
+ }
+
+ _result = map_unary (more t.value) x;
+ }
+ }
+
+ Threshold_percent_item = class
+ Menuaction "Per_cent Threshold" "threshold at a percentage of pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ t = Scale "Percentage of pixels" 0 100 50;
+
+ _result
+ = map_unary (more (hist_thresh (t.value / 100) x)) x;
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Segment_item = class
+ Menuaction "_Segment" "break image into disjoint regions" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ segments
+ = Expression "Number of disjoint regions"
+ (map_unary (get_header "n-segments") _result);
+
+ _result = map_unary segment x;
+ }
+ }
+
+ };
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+Perspective_match_item = class
+ Menuaction "_Perspective Match"
+ "rotate, scale and skew one image to match another" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+ _b = find_image y;
+
+ ap1 = Mark_relative _a 0.1 0.1;
+ ap2 = Mark_relative _a 0.9 0.1;
+ ap3 = Mark_relative _a 0.1 0.9;
+ ap4 = Mark_relative _a 0.9 0.9;
+
+ bp1 = Mark_relative _b 0.1 0.1;
+ bp2 = Mark_relative _b 0.9 0.1;
+ bp3 = Mark_relative _b 0.1 0.9;
+ bp4 = Mark_relative _b 0.9 0.9;
+
+ _result = map_binary process x y
+ {
+ f1 = _a.width / _b.width;
+ f2 = _a.height / _b.height;
+
+ rl = sort_pts_clockwise [ap1, ap2, ap3, ap4];
+ pl = sort_pts_clockwise [bp1, bp2, bp3, bp4];
+
+ to = [
+ rl?0.left, rl?0.top,
+ rl?1.left, rl?1.top,
+ rl?2.left, rl?2.top,
+ rl?3.left, rl?3.top
+ ];
+
+ from = [
+ pl?0.left * f1, pl?0.top * f2,
+ pl?1.left * f1, pl?1.top * f2,
+ pl?2.left * f1, pl?2.top * f2,
+ pl?3.left * f1, pl?3.top * f2
+ ];
+
+ trans = perspective_transform to from;
+
+ process a b = transform 1 0 trans b2
+ {
+ b2 = resize Kernel_linear f1 f2 b, (f1 >= 1 && f2 >= 1) || (f1 >= 1 && f2 >= 1)
+ = resize Kernel_linear f1 1 b1
+ {b1 = resize Kernel_linear 1 f2 b;}
+ }
+ }
+ }
+ };
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Perspective_item = class
+ Menuaction "Pe_rspective Distort"
+ "rotate, scale and skew an image with respect to defined points" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+
+ dir = Option "Select distort direction" [ "Distort to points", "Distort to corners" ] 1;
+ ap1 = Mark_relative _a 0.1 0.1;
+ ap2 = Mark_relative _a 0.9 0.1;
+ ap3 = Mark_relative _a 0.9 0.9;
+ ap4 = Mark_relative _a 0.1 0.9;
+
+ _result = map_unary process x
+ {
+ trans = [perspective_transform to from, perspective_transform from to]?(dir.value)
+ {
+ rl = sort_pts_clockwise [ap1, ap2, ap3, ap4];
+ to = [(rl?0).left, (rl?0).top, (rl?1).left, (rl?1).top,
+ (rl?2).left, (rl?2).top, (rl?3).left, (rl?3).top];
+ from=[0, 0, (_a.width - 1), 0,
+ (_a.width - 1), (_a.height - 1), 0, (_a.height - 1)];
+ }
+
+ process a = transform 1 0 trans a;
+ }
+ }
+ };
+
diff --git a/share/nip2/compat/8.5/_joe_utilities.def b/share/nip2/compat/8.5/_joe_utilities.def
new file mode 100644
index 00000000..be7931df
--- /dev/null
+++ b/share/nip2/compat/8.5/_joe_utilities.def
@@ -0,0 +1,705 @@
+/* ******Functions included in start/_NG_utilities.def:******
+ *
+ * so_balance ref_meanmax im1 im2 mask blur gauss *
+ * nonzero_mean im = no_out *
+ * so_meanmax im = result *
+ * so_calculate ref_meanmax im mask = result *
+ * simple_frame frame im_w im_h ov cs ms bf option = result *
+ * corner_frame frame im_w im_h ov cs ms bf = result *
+ * build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result *
+ * complex_frame frame im_w im_h ov cs es ms bf option= result *
+ * complex_edge ra rb t bl d = rc *
+ * frame_lr_min r_l r_r target bw = result *
+ * frame_tb_min r_t r_b target bw = result *
+ * frame_position_image im ref os colour= result *
+ * merge_array bw arr = result *
+ * merge_to_scale im target blend dir = result *
+ * select_ellipse line width = mask *
+ * select_tetragon p1 p2 p3 p4 = mask *
+ * select_polygon pt_list = mask *
+ * perspective_transform to from = trans'' *
+ * sort_pts_clockwise l = l'' *
+ */
+
+/* Called from:
+* _NG_Extra.def Clone_area_item
+*/
+so_balance ref_meanmax im1 im2 mask gauss
+ = result
+ {
+ //ref_meanmax = so_meanmax im1;
+ so_values = so_calculate ref_meanmax im2 mask;
+ im2_cor_a = clip2fmt im2.format im2'', has_member "format" im2
+ = im2''
+ {im2'' = im2 * (so_values?0) + (so_values?1);}
+ // Option to convert replacement image to scaled gaussian noise
+ im2_cor = im2_cor_a, gauss == false
+ = clip2fmt im2_cor_a.format gauss_im
+ {gauss_im =
+ gaussnoise im2_cor_a.width im2_cor_a.height ref_meanmax?0
+(deviation im2_cor_a);}
+ result = im_blend (get_image mask) (get_image
+im2_cor) (get_image im1);
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the mean of the non zero pixels.
+ *
+ * Called from:
+ * _NG_utilities so_meanmax
+ */
+nonzero_mean im = no_out
+ {
+ zero_im = (im == 0);
+ zero_mean = mean zero_im;
+ no_mean = mean im;
+ no_out = no_mean/(1 - (zero_mean/255));
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the max and nonzero mean of an image
+ *
+ * Called from:
+ * _NG_utilities so_balance
+ * _NG_utilities so_calculate
+ * _NG_Extra.def Clone_area_item
+ * _NG_Extra.def Balance_item.Balance_find_item
+ */
+so_meanmax im = result
+ {
+ mean_of_im = nonzero_mean im;
+ adjusted_im = im - mean_of_im;
+ max_of_im = max adjusted_im;
+
+ result = [mean_of_im, max_of_im];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the scale and offset required to match a reference mean and max
+ *
+ * Called from:
+ * _NG_utilities so_balance
+ * _NG_Extra.def Balance_item.Balance_find_item
+ */
+so_calculate ref_meanmax im mask = result
+ {
+ im' = if mask then im else 0;
+ im_values = so_meanmax im';
+
+ mean_of_ref = ref_meanmax?0;
+ mean_of_im = im_values?0;
+
+ max_of_ref = ref_meanmax?1;
+ max_of_im = im_values?1;
+
+ scale = (max_of_ref)/(max_of_im);
+ offset = mean_of_ref - (mean_of_im * scale);
+ result = [ scale, offset ];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Extends or shortens the central sections of a simple frame to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Simple_frame_item
+ */
+simple_frame frame im_w im_h ov cs ms bf option = result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+ ms'' = (1 - cs);
+
+ //Regions
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl, option == true
+ = Region_relative frame cs' 0 cs cs;
+ r_bl = Region_relative frame 0 cs' cs cs;
+ r_br = fliplr r_bl, option == true
+ = Region_relative frame cs' cs' cs cs;
+
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = Region_relative frame ms' ms'' ms cs;
+ r_ml = Region_relative frame 0 ms' cs ms;
+ r_mr = fliplr r_ml, option == true
+ = Region_relative frame ms'' ms' cs ms;
+
+ result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Copies and extends a simple frame corner to produce a complete frame to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Frame_corner_item
+ */
+corner_frame frame im_w im_h ov cs ms bf = result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+
+ //Regions
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl;
+ r_bl = fliptb r_tl;
+ r_br = fliplr r_bl;
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = fliptb r_mt;
+ r_ml = Region_relative frame 0 ms' cs ms;;
+ r_mr = fliplr r_ml;
+ result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Completes the frame building process for simple_frame and corner_frame.
+ *
+ * _NG_utilities simple_frame
+ * _NG_utilities corner_frame
+ */
+build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result
+ {
+ //Find pixel thickness of frames section
+ s_width = r_ml.width - mean (im_profile (map_unary fliplr (r_ml.value)?0) 1);
+ s_height = r_mt.height - mean (im_profile (map_unary fliptb (r_mt.value)?0) 0);
+
+ w_target = im_w + (2 * (s_width - ov));
+ h_target = im_h + (2 * (s_height - ov));
+
+ blend = bf * r_tl.width;
+
+ cw_target = w_target - (2 * r_tl.width) + (2 * blend), w_target > (2 * r_tl.width)
+ = w_target;
+ ch_target = h_target - (2 * r_tl.height) + (2 * blend), h_target > (2 * r_tl.height)
+ = h_target;
+
+ //Use regions to produce sections
+ top = merge_to_scale r_mt cw_target blend 0;
+ bottom = merge_to_scale r_mb cw_target blend 0;
+ left = merge_to_scale r_ml ch_target blend 1;
+ right = merge_to_scale r_mr ch_target blend 1;
+ middle = Image
+ (image_new cw_target ch_target left.bands left.format left.coding left.type 0 0 0);
+
+ //Build sections into full frame.
+ row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_tl, top, r_tr]];
+ row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[left, middle, right]];
+ row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_bl, bottom, r_br]];
+
+ result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
+ = merge_array blend [[row_1], [row_2], [row_3]];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Extends or shortens the central sections of a frame, preserving any central details on each
+ * edge, to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Complex_frame_item
+ */
+complex_frame frame im_w im_h ov cs es ms bf option= result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+ es' = (0.25 - (es/2));
+
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl, option == true
+ = Region_relative frame cs' 0 cs cs;
+ r_bl = Region_relative frame 0 cs' cs cs;
+ r_br = fliplr r_bl, option == true
+ = Region_relative frame cs' cs' cs cs;
+
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = Region_relative frame ms' cs' ms cs;
+ r_ml = Region_relative frame 0 ms' cs ms;
+ r_mr = fliplr r_ml, option == true
+ = Region_relative frame cs' ms' cs ms;
+
+ r_et = Region_relative frame es' 0 es cs;
+ r_eb = Region_relative frame es' cs' es cs;
+ r_el = Region_relative frame 0 es' cs es;
+ r_er = fliplr r_el, option == true
+ = Region_relative frame cs' es' cs es;
+
+ //Find pixel thickness of frames section
+ s_width = r_el.width - mean (im_profile (map_unary fliplr (r_el.value)?0) 1);
+ s_height = r_et.height - mean (im_profile (map_unary fliptb (r_et.value)?0) 0);
+
+ w_target = im_w + (2 * (s_width - ov));
+ h_target = im_h + (2 * (s_height - ov));
+ min_size = foldr1 min_pair [r_tl.width, r_tl.height,
+ r_mt.width, r_mt.height,
+ r_et.width, r_et.height];
+ blend = bf * min_size;
+
+ cw_target = w_target - (2 * r_tl.width) + (2 * blend);
+ ch_target = h_target - (2 * r_tl.height) + (2 * blend);
+
+ top = complex_edge r_mt r_et cw_target blend 0;
+ bottom = complex_edge r_mb r_eb cw_target blend 0;
+ left = complex_edge r_ml r_el ch_target blend 1;
+ right = complex_edge r_mr r_er ch_target blend 1;
+ middle = Image
+ (image_new top.width left.height left.bands left.format left.coding left.type 0 0 0);
+
+ //Build regions into full frame.
+ row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_tl, top, r_tr]];
+ row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[left, middle, right]];
+ row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_bl, bottom, r_br]];
+ result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
+ = merge_array blend [[row_1], [row_2], [row_3]];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Function called by complex frame, used to produce section
+ *
+ * Called from:
+ * _NG_utilities.def complex_frame
+ */
+complex_edge ra rb t bl d = rc
+ {
+ e1 = ceil (ra.width - t)/2, d == 0
+ = 0;
+ e2 = 0, d == 0
+ = ceil (ra.height - t)/2;
+ e3 = t, d == 0
+ = ra.width;
+ e4 = ra.height, d == 0
+ = t;
+
+ check = ra.width, d == 0;
+ = ra.height;
+
+ rai = get_image ra;
+
+ t2 = (t - ra.width + (2 * bl))/2, d == 0
+ = (t - ra.height + (2 * bl))/2;
+
+ rc = ra , t <= 0
+ = Image (im_extract_area rai e1 e2 e3 e4), t <= check
+ = merge_array bl [[rb',ra,rb']], d == 0
+ = merge_array bl [[rb'],[ra],[rb']]
+ {rb' = merge_to_scale rb t2 bl d;}
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Blends two images left/right to produce an image a specific width.
+ *
+ * _NG_utilities build_frame
+ * _NG_utilities complex_frame
+ */
+frame_lr_min r_l r_r target bw = result
+ {
+ //Calculating the new widh required for each image.
+ no = (target/2 + bw);
+ n_w = no, (r_l.width > no)
+ = r_l.width;
+
+ //Removing excess from what will be the middle of the final image.
+ n_l = im_extract_area r_l.value 0 0 n_w r_l.height;
+ n_r = im_extract_area r_r.value (r_r.width - n_w) 0 n_w r_l.height;
+
+ //Merge the two image together with a bw*2 pixel overlap.
+ result = Image (im_lrmerge n_l n_r ((bw*2) - n_w) 0 bw);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Blends two images top/bottom to produce an image a specific width.
+ *
+ * _NG_utilities build_frame
+ * _NG_utilities complex_frame
+ */
+frame_tb_min r_t r_b target bw = result
+ {
+ //Calculating the new height required for each image.
+ no = (target/2 + bw);
+ n_h = no, (r_t.height > no)
+ = r_t.height;
+
+ //Removing excess from what will be the middle of the final image.
+ n_t = im_extract_area r_t.value 0 0 r_t.width n_h;
+ n_b = im_extract_area r_b.value 0 (r_b.height - n_h) r_b.width n_h;
+
+ //Merge the two image together with a 50 pixel overlap.
+ result = Image (im_tbmerge n_t n_b 0 ((bw*2) -n_h) bw);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Resixe canvas of an image to accomodate a frame and possible mount
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Frame_corner_item
+ * _NG_Extra.def Frame_item.Simple_frame_item
+ * _NG_Extra.def Frame_item.Complex_frame_item
+ */
+frame_position_image im ref os colour= result
+ {
+ background = image_new ref.width ref.height
+ im.bands im.format im.coding im.type colour 0 0;
+
+ result = insert_noexpand xp yp im background
+ {
+ xp = (ref.width - im.width)/2;
+ yp = (ref.height - im.height - os)/2;
+ }
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Merges an array of images together according to blend width bw
+ *
+ * Called from:
+ * _NG_Utilites.def build_frame
+ * _NG_Utilites.def complex_frame
+ * _NG_Utilites.def complex_edge
+ */
+merge_array bw arr = result
+ {
+ merge_lr bw im1 im2 = im3
+ {
+ bw' = get_header "Xsize" (get_image im1);
+ bw'' = -(bw' - bw);
+ im3 = im_lrmerge (get_image im1) (get_image im2) bw'' 0 bw;
+ }
+ merge_tb bw im1 im2 = im3
+ {
+ bw' = get_header "Ysize" (get_image im1);
+ bw'' = -(bw' - bw);
+ im3 = im_tbmerge (get_image im1) (get_image im2) 0 bw'' bw;
+ }
+
+ im_out = (image_set_origin 0 0 @
+ foldl1 (merge_tb bw) @
+ map (foldl1 (merge_lr bw))) arr;
+ result = Image im_out;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Repeatably top/bottom add clones of im, with a defined overlap, until final height > target
+ *
+ * Called from:
+ * _NG_Utilites.def build_frame
+ * _NG_Utilites.def complex_edge
+ */
+merge_to_scale im target blend dir = result
+ {
+ blend' = floor blend;
+
+ //allow fir lr or tb process
+ var_a = im.width, dir == 0
+ = im.height;
+
+ var_w = im.width, dir == 1
+ = target, target > blend'
+ = blend';
+ var_h = im.height, dir == 0
+ = target, target > blend'
+ = blend';
+
+ //total numner of copies of im requires, taking overlap into account.
+ no_loops = ceil ((log ((target - blend')/(var_a - blend')))/(log 2));
+
+ process im no = result
+ {
+ pr_a = get_header "Xsize" (get_image im), dir == 0
+ = get_header "Ysize" (get_image im);
+ pr_b = -(pr_a - blend' + 1);
+
+ im' = im_lrmerge (get_image im) (get_image im) pr_b 0 blend', dir == 0
+ = im_tbmerge (get_image im) (get_image im) 0 pr_b blend';
+ no' = no - 1;
+
+ result = im', no' < 1
+ = process im' no';
+ }
+
+ im_tmp = im.value, var_a > target
+ = process im no_loops;
+
+ result = Image (im_extract_area (get_image im_tmp) 0 0 var_w var_h);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects an elispe based on a line and a width
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Elipse
+ */
+select_ellipse line width = mask
+ {
+ im = Image (get_image line);
+
+ //Make a 2 band image whose value equals its coordinates.
+ im_coor = Image (make_xy im.width im.height);
+
+ //Adjust the values to center tham on (line.left, line.top)
+ im_cent = im_coor - Vector [line.left,line.top];
+
+ w = line.width;
+ h = line.height;
+
+ angle = 270, w == 0 && h < 0
+ = 90, w == 0 && h >= 0
+ = 360 + atan (h/w), w > 0 && h < 0
+ = atan (h/w), w > 0 && h >= 0
+ = 180 + atan (h/w);
+
+ a = ( (h ** 2) + (w ** 2) )**0.5;
+ b = a * width;
+
+ x' = ( (cos angle) * im_cent?0) + ( (sin angle) * im_cent?1);
+ y' = ( (cos angle) * im_cent?1) - ( (sin angle) * im_cent?0);
+
+ mask = ( (b**2) * (x'**2) ) + ( (a**2) * (y'**2) ) <= (a * b)**2;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Tetragon
+ * _NG_Extra.def Perspective_item
+ */
+select_tetragon p1 p2 p3 p4 = mask
+ {
+ //Put points in clockwise order starting at the top left.
+ pt_list = sort_pts_clockwise [p1, p2, p3, p4];
+
+ pair_list = [
+ [ pt_list?0, pt_list?1 ],
+ [ pt_list?1, pt_list?2 ],
+ [ pt_list?2, pt_list?3 ],
+ [ pt_list?3, pt_list?0 ] ];
+
+ //Make xy image the same size as p1.image;
+ im_xy = Image (make_xy p1.image.width p1.image.height);
+ white = Image (image_new p1.image.width p1.image.height 1 0 Image_coding.NOCODING 1 255 0 0);
+
+ mask = foldl process white pair_list;
+
+ /* Treat each pair of point as a vector going from p1 to p2,
+ * then select all to right of line. This is done for each pair,
+ * the results are all combined to select the area defined by
+ * the four points.
+ */
+ process im_in pair = im_out
+ {
+ x = (pair?0).left;
+ y = (pair?0).top;
+ x'= (pair?1).left;
+ y'= (pair?1).top;
+
+ w = x' - x;
+ h = y' - y;
+
+ m = 0, x == x'
+ = (y-y')/(x-x');
+ c = 0, x == x'
+ = ((y*x') - (y'*x))/(x' - x);
+
+ mask= im_xy?1 - (im_xy?0 * m) >= c, w > 0
+ = im_xy?1 - (im_xy?0 * m) <= c, w < 0
+ = im_xy?0 <= x, w == 0 && h > 0
+ = im_xy?0 >= x;
+
+ im_out = im_in & mask;
+ }
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Polygon
+ */
+select_polygon pt_list = mask
+ {
+ group_check = is_Group pt_list;
+ pt_l = pt_list.value, group_check
+ = pt_list;
+
+ im = Image (get_image (pt_l?0));
+ im_xy = Image (make_xy im.width im.height);
+ black = Image (image_new im_xy.width im_xy.height 1 0 Image_coding.NOCODING 1 0 0 0);
+
+ x = im_xy?0;
+ y = im_xy?1;
+
+ pt_l' = grp_trip pt_l;
+
+ mask = foldl process black pt_l';
+
+ /*Takes a group adds the first two the end and then creates a lists of
+ *lists [[a, b, c], [b, c, d] .... [x, a, b]]
+ */
+ grp_trip l = l''
+ {
+ px = take 2 l;
+ l' = join l px;
+ start = [(take 3 l')];
+ rest = drop 3 l';
+
+ process a b = c
+ {
+ x = (last a)?1;
+ x'= (last a)?2;
+ x'' = [[x, x', b]];
+ c = join a x'';
+ }
+
+ l'' = foldl process start rest;
+ };
+
+ process im_in triplet = im_out
+ {
+ p1 = triplet?0;
+ p2 = triplet?1;
+ p3 = triplet?2;
+
+ //check for change in x direction between p1-p2 and p2 -p3
+ dir_1 = sign (p2.left - p1.left);
+ dir_2 = sign (p3.left - p2.left);
+ dir = dir_1 + dir_2;
+
+ //define min x limit.
+ min_x = p1.left, p1.left < p2.left
+ = p2.left + 1, dir != 0
+ = p2.left;
+
+ //define max x limit.
+ max_x = p1.left, p1.left > p2.left
+ = p2.left - 1, dir != 0
+ = p2.left;
+
+ //equation of line defined by p1 and p2
+ m = line_m p1 p2;
+ c = line_c p1 p2;
+
+ //Every thing below the line
+ im_test = ((y >= (m * x) + c) & (x >= min_x) & (x <= max_x));
+
+ im_out = im_in ^ im_test;
+ }
+
+ line_c p1 p2 = c
+ {m = line_m p1 p2;
+ c = p1.top - (m * p1.left);};
+
+ line_m p1 p2 = (p2.top - p1.top)/(p2.left - p1.left), p2.left != p1.left
+ = 0;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Perspective_match_item
+ * _NG_Extra.def Perspective_item
+ */
+perspective_transform to from = trans''
+ {
+ /*
+ * Tramsformation matrix is calculated on the bases of the following functions:
+ * x' = c0x + c1y + c2xy + c3
+ * y' = c4x + c5y + c6xy + c7
+ *
+ * The functions used in vips im_transform works based on the functions:
+ * x = x' + b0 + b2x' + b4y' + b6x'y'
+ * y = y' + b1 + b3x' + b5y' + b7x'y'
+ *
+ * and is applied in the form of the matrix:
+ *
+ * [[b0, b1],
+ * [b2, b3],
+ * [b4, b5],
+ * [b6, b7]]
+ *
+ * Therefore our required calculated matrix will be
+ *
+ * [[ c3 , c7],
+ * [(c0 - 1) , c4],
+ * [ c1 , (c5 - 1)],
+ * [ c2 , c6]]
+ *
+ * to = [x1, y1, x2, y2, x3, y3, x4, y4]
+ * from = [x1', y1', x2', y2', x3', y3', x4', y4']
+ * trans = [[c0], [c1], [c2], [c3], [c4], [c5], [c6], [c7]]
+ *
+ */
+
+ to' = Matrix
+ [[to?0, to?1, ((to?0)*(to?1)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?0, to?1, ((to?0)*(to?1)), 1],
+ [to?2, to?3, ((to?2)*(to?3)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?2, to?3, ((to?2)*(to?3)), 1],
+ [to?4, to?5, ((to?4)*(to?5)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?4, to?5, ((to?4)*(to?5)), 1],
+ [to?6, to?7, ((to?6)*(to?7)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?6, to?7, ((to?6)*(to?7)), 1]];
+
+ from' = Matrix (transpose [from]);
+
+ to'' = to' ** (-1);
+
+ trans = to'' * from';
+ trans' = trans.value;
+ trans''= Matrix [[(trans'?3)?0, (trans'?7)?0 ],
+ [((trans'?0)?0 - 1), (trans'?4)?0 ],
+ [(trans'?1)?0, ((trans'?5)?0 - 1)],
+ [(trans'?2)?0, (trans'?6)?0 ]];
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Sort a list of points into clockwise order.
+ *
+ * Called from:
+ * _NG_utilities.def select_tetragon
+ * _NG_Extra.def Perspective_match_item
+ * _NG_Extra.def Perspective_item
+ */
+sort_pts_clockwise l = l''
+ {
+ // sort functions:
+ f_top a b = a.top < b.top;
+ f_left a b = a.left < b.left;
+ f_right a b = a.left > b.left;
+
+ l' = sortc f_top l;
+ l'_a = take 2 l';
+ l'_b = drop 2 l';
+
+ l''_a = sortc f_left l'_a;
+ l''_b = sortc f_right l'_b;
+ l'' = join l''_a l''_b;
+ };
+
+Mount_options _ctype _ppcm = class
+ {
+ _vislevel = 3;
+ apply = Toggle "Apply mount options" false;
+ ls = Expression "Lower mount section bigger by (cm)" 0;
+ mount_colour = Colour _ctype [0, 0, 0];
+ _los = ls.expr * _ppcm;
+ };
+
+Frame_variables comp = class
+ {
+ _vislevel = 3;
+
+ scale_factor = Expression "scale the size of the frame by" 1;
+
+ /* These sliders define the fraction of the frames width or height is extracted
+ * to produce each of the particular regions.
+ */
+ corner_section = Scale "Corner section" 0.1 1 0.5;
+ edge_section = Scale "Edge section" 0.1 1 0.2, comp > 0
+ = "Only required for complex frames";
+ middle_section = Scale "Middle section" 0.1 1 0.2;
+ blend_fraction = Scale "Blend fraction" 0.1 0.9 0.1;
+ option = Toggle "Use mirror of left-side to make right" true;
+ };
+
diff --git a/share/nip2/compat/8.5/_list.def b/share/nip2/compat/8.5/_list.def
new file mode 100644
index 00000000..d2ef4a1f
--- /dev/null
+++ b/share/nip2/compat/8.5/_list.def
@@ -0,0 +1,482 @@
+/* any l: or all the elements of list l together
+ *
+ * any (map (equal 0) list) == true, if any element of list is zero.
+ * any :: [bool] -> bool
+ */
+any = foldr logical_or false;
+
+/* all l: and all the elements of list l together
+ *
+ * all (map (==0) list) == true, if every element of list is zero.
+ * all :: [bool] -> bool
+ */
+all = foldr logical_and true;
+
+/* concat l: join a list of lists together
+ *
+ * concat ["abc","def"] == "abcdef".
+ * concat :: [[*]] -> [*]
+ */
+concat l = foldr join [] l;
+
+/* delete eq x l: delete the first x from l
+ *
+ * delete equal 'b' "abcdb" == "acdb"
+ * delete :: (* -> bool) -> * -> [*] -> [*]
+ */
+delete eq a l
+ = [], l == []
+ = y, eq a b
+ = b : delete eq a y
+{
+ b:y = l;
+}
+
+/* difference eq a b: delete b from a
+ *
+ * difference equal "asdf" "ad" == "sf"
+ * difference :: (* -> bool) -> [*] -> [*] -> [*]
+ */
+difference = foldl @ converse @ delete;
+
+/* drop n l: drop the first n elements from list l
+ *
+ * drop 3 "abcd" == "d"
+ * drop :: num -> [*] -> [*]
+ */
+drop n l
+ = l, n <= 0 || l == []
+ = drop (n - 1) (tl l);
+
+/* dropwhile fn l: drop while fn is true
+ *
+ * dropwhile is_digit "1234pigs" == "pigs"
+ * dropwhile :: (* -> bool) -> [*] -> [*]
+ */
+dropwhile fn l
+ = [], l == []
+ = dropwhile fn x, fn a
+ = l
+{
+ a:x = l;
+}
+
+/* extract n l: extract element at index n from list l
+ */
+extract = converse subscript;
+
+/* filter fn l: return all elements of l for which predicate fn holds
+ *
+ * filter is_digit "1one2two3three" = "123"
+ * filter :: (* -> bool) -> [*] -> [*]
+ */
+filter fn l
+ = foldr addif [] l
+{
+ addif x l
+ = x : l, fn x;
+ = l;
+}
+
+/* flatten x: flatten a list of lists of things into a simple list
+ *
+ * flatten :: [[*]] -> [*]
+ */
+flatten x
+ = foldr flat [] x, is_list x
+ = x
+{
+ flat x sofar
+ = foldr flat sofar x, is_list x
+ = x : sofar;
+}
+
+/* foldl fn st l: fold list l from the left with function fn and start st
+ *
+ * Start from the left hand end of the list (unlike foldr, see below).
+ * foldl is less useful (and much slower).
+ *
+ * foldl fn start [a,b .. z] = ((((st fn a) fn b) ..) fn z)
+ * foldl :: (* -> ** -> *) -> * -> [**] -> *
+ */
+foldl fn st l
+ = st, l == []
+ = foldl fn (fn st x) xs
+{
+ x:xs = l;
+}
+
+/* foldl1 fn l: like foldl, but use the 1st element as the start value
+ *
+ * foldl1 fn [1,2,3] == ((1 fn 2) fn 3)
+ * foldl1 :: (* -> * -> *) -> [*] -> *
+ */
+foldl1 fn l
+ = [], l == []
+ = foldl fn x xs
+{
+ x:xs = l;
+}
+
+/* foldr fn st l: fold list l from the right with function fn and start st
+ *
+ * foldr fn st [a,b..z] = (a fn (b fn (.. (z fn st))))
+ * foldr :: (* -> ** -> **) -> ** -> [*] -> **
+ */
+foldr fn st l
+ = st, l == []
+ = fn x (foldr fn st xs)
+{
+ x:xs = l;
+}
+
+/* foldr1 fn l: like foldr, but use the last element as the start value
+ *
+ * foldr1 fn [1,2,3,4] == (1 fn (2 fn (3 fn 4)))
+ * foldr1 :: (* -> * -> *) -> [*] -> *
+ */
+foldr1 fn l
+ = [], l == []
+ = x, xs == []
+ = fn x (foldr1 fn xs)
+{
+ x:xs = l;
+}
+
+/* Search a list for an element, returning its index (or -1)
+ *
+ * index (equal 12) [13,12,11] == 1
+ * index :: (* -> bool) -> [*] -> real
+ */
+index fn list
+ = search list 0
+{
+ search l n
+ = -1, l == []
+ = n, fn x
+ = search xs (n + 1)
+ {
+ x:xs = l;
+ }
+}
+
+/* init l: remove last element of list l
+ *
+ * The dual of tl.
+ * init [1,2,3] == [1,2]
+ * init :: [*] -> [*]
+ */
+init l
+ = error "init of []", l == [];
+ = [], tl l == [];
+ = x : init xs
+{
+ x:xs = l;
+}
+
+/* iterate f x: repeatedly apply f to x
+ *
+ * return the infinite list [x, f x, f (f x), ..].
+ * iterate (multiply 2) 1 == [1, 2, 4, 8, 16, 32, 64 ... ]
+ * iterate :: (* -> *) -> * -> [*]
+ */
+iterate f x = x : iterate f (f x);
+
+/* join_sep sep l: join a list with a separator
+ *
+ * join_sep ", " (map print [1 .. 4]) == "1, 2, 3, 4"
+ * join_sep :: [*] -> [[*]] -> [*]
+ */
+join_sep sep l
+ = foldl1 fn l
+{
+ fn a b = a ++ sep ++ b;
+}
+
+/* last l: return the last element of list l
+ *
+ * The dual of hd. last [1,2,3] == 3
+ * last :: [*] -> [*]
+ */
+last l
+ = error "last of []", l == []
+ = x, xs == []
+ = last xs
+{
+ x:xs = l;
+}
+
+/* len l: length of list l
+ * (see also is_list_len and friends in predicate.def)
+ *
+ * len :: [*] -> num
+ */
+len l
+ = 0, l == []
+ = 1 + len (tl l);
+
+/* limit l: return the first element of l which is equal to its predecessor
+ *
+ * useful for checking for convergence
+ * limit :: [*] -> *
+ */
+limit l
+ = error "incorrect use of limit",
+ l == [] || tl l == [] || tl (tl l) == []
+ = a, a == b
+ = limit (b : x)
+{
+ a:b:x = l;
+}
+
+/* Turn a function of n args into a function which takes a single arg of an
+ * n-element list.
+ */
+list_1ary fn x = fn x?0;
+list_2ary fn x = fn x?0 x?1;
+list_3ary fn x = fn x?0 x?1 x?2;
+list_4ary fn x = fn x?0 x?1 x?2 x?3;
+list_5ary fn x = fn x?0 x?1 x?2 x?3 x?4;
+list_6ary fn x = fn x?0 x?1 x?2 x?3 x?4 x?5;
+list_7ary fn x = fn x?0 x?1 x?2 x?3 x?4 x?5 x?6;
+
+/* map fn l: map function fn over list l
+ *
+ * map :: (* -> **) -> [*] -> [**]
+ */
+map f l
+ = [], l == [];
+ = f (hd l) : map f (tl l);
+
+/* map2 fn l1 l2: map two lists together with fn
+ *
+ * map2 :: (* -> ** -> ***) -> [*] -> [**] -> [***]
+ */
+map2 fn l1 l2 = map (list_2ary fn) (zip2 l1 l2);
+
+/* map3 fn l1 l2 l3: map three lists together with fn
+ *
+ * map3 :: (* -> ** -> *** -> ****) -> [*] -> [**] -> [***] -> [****]
+ */
+map3 fn l1 l2 l3 = map (list_3ary fn) (zip3 l1 l2 l3);
+
+/* member l x: true if x is a member of list l
+ *
+ * is_digit == member "0123456789"
+ * member :: [*] -> * -> bool
+ */
+member l x = any (map (equal x) l);
+
+/* merge b l r: merge two lists based on a bool list
+ *
+ * merge :: [bool] -> [*] -> [*] -> [*]
+ */
+merge p l r
+ = [], p == [] || l == [] || r == []
+ = a : merge z x y, c
+ = b : merge z x y
+{
+ a:x = l;
+ b:y = r;
+ c:z = p;
+}
+
+/* mkset eq l: remove duplicates from list l using equality function
+ *
+ * mkset :: (* -> bool) -> [*] -> [*]
+ */
+mkset eq l
+ = [], l == []
+ = a : filter (not @ eq a) (mkset eq x)
+{
+ a:x = l;
+}
+
+/* postfix l r: add r to the end of list l
+ *
+ * The dual of ':'.
+ * postfix :: [*] -> ** -> [*,**]
+ */
+postfix l r = l ++ [r];
+
+/* repeat x: make an infinite list of xes
+ *
+ * repeat :: * -> [*]
+ */
+repeat x = map (const x) [1..];
+
+/* replicate n x: make n copies of x in a list
+ *
+ * replicate :: num -> * -> [*]
+ */
+replicate n x = take n (repeat x);
+
+/* reverse l: reverse list l
+ *
+ * reverse :: [*] -> [*]
+ */
+reverse l = foldl (converse cons) [] l;
+
+/* scanl fn st l: apply (foldl fn r) to every initial segment of a list
+ *
+ * scanl add 0 [1,2,3] == [1,3,6]
+ * scanl :: (* -> ** -> *) -> * -> [**] -> [*]
+ */
+scanl fn st l
+ = st, l == []
+ = st' : scanl fn st' xs
+{
+ x:xs = l;
+ st' = fn st x;
+}
+
+/* sort l: sort list l into ascending order
+ *
+ * sort :: [*] -> [*]
+ */
+sort l = sortc less_equal l;
+
+/* sortc comp l: sort list l into order using a comparision function
+ *
+ * Uses merge sort (n log n behaviour)
+ * sortc :: (* -> * -> bool) -> [*] -> [*]
+ */
+sortc comp l
+ = l, n <= 1
+ = merge (sortc comp (take n2 l)) (sortc comp (drop n2 l))
+{
+ n = len l;
+ n2 = (int) (n / 2);
+
+ /* merge l1 l2: merge sorted lists l1 and l2 to make a single
+ * sorted list
+ */
+ merge l1 l2
+ = l2, l1 == []
+ = l1, l2 == []
+ = a : merge x (b : y), comp a b
+ = b : merge (a : x) y
+ {
+ a:x = l1;
+ b:y = l2;
+ }
+}
+
+/* sortpl pl l: sort by a list of predicates
+ *
+ * sortpl :: (* -> bool) -> [*] -> [*]
+ */
+sortpl pl l
+ = sortc (test pl) l
+{
+ /* Comparision function ... put true before false, if equal move on to
+ * the next predicate.
+ */
+ test pl a b
+ = true, pl == []
+ = ta, ta != tb
+ = test (tl pl) a b
+ {
+ ta = pl?0 a;
+ tb = pl?0 b;
+ }
+}
+
+/* sortr l: sort list l into descending order
+ *
+ * sortr :: [*] -> [*]
+ */
+sortr l = sortc more l;
+
+/* split fn l: break a list into sections separated by many fn
+ *
+ * split is_space " hello world " == ["hello", "world"]
+ * split is_space " " == []
+ * split :: (* -> bool) -> [*] -> [[*]]
+ */
+split fn l
+ = [], l == [] || l' == []
+ = head : split fn tail
+{
+ nfn = not @ fn;
+
+ l' = dropwhile fn l;
+ head = takewhile nfn l';
+ tail = dropwhile nfn l';
+}
+
+/* splits fn l: break a list into sections separated by a single fn
+ *
+ * split (equal ',') ",,1" == ["", "", "1"]
+ * split :: (* -> bool) -> [*] -> [[*]]
+ */
+splits fn l
+ = [], l == []
+ = head : splits fn tail
+{
+ fn' = not @ fn;
+ dropif x
+ = [], x == []
+ = tl x;
+
+ head = takewhile fn' l;
+ tail = dropif (dropwhile fn' l);
+}
+
+/* splitpl fnl l: split a list up with a list of predicates
+ *
+ * splitpl [is_digit, is_letter, is_digit] "123cat" == ["123", "cat", []]
+ * splitpl :: [* -> bool] -> [*] -> [[*]]
+ */
+splitpl fnl l
+ = l, fnl == []
+ = head : splitpl (tl fnl) tail
+{
+ head = takewhile (hd fnl) l;
+ tail = dropwhile (hd fnl) l;
+}
+
+/* split_lines n l: split a list into equal length lines
+ *
+ * split_lines 4 "1234567" == ["1234", "567"]
+ * splitl :: int -> [*] -> [[*]]
+ */
+split_lines n l
+ = [], l == []
+ = take n l : split_lines n (drop n l);
+
+/* take n l: take the first n elements from list l
+ * take :: num -> [*] -> [*]
+ */
+take n l
+ = [], n <= 0
+ = [], l == []
+ = hd l : take (n-1) (tl l);
+
+/* takewhile fn l: take from the front of a list while predicate fn holds
+ *
+ * takewhile is_digit "123onetwothree" == "123"
+ * takewhile :: (* -> bool) -> [*] -> [*]
+ */
+takewhile fn l
+ = [], l == []
+ = hd l : takewhile fn (tl l), fn (hd l)
+ = [];
+
+/* zip2 l1 l2: zip two lists together
+ *
+ * zip2 [1,2] ['a', 'b', 'c'] == [[1,'a'],[2,'b']]
+ * zip2 :: [*] -> [**] -> [[*,**]]
+ */
+zip2 l1 l2
+ = [], l1 == [] || l2 == []
+ = [hd l1, hd l2] : zip2 (tl l1) (tl l2);
+
+/* zip3 l1 l2 l3: zip three lists together
+ *
+ * zip3 [1,2] ['a', 'b', 'c'] [true] == [[1,'a',true]]
+ * zip3 :: [*] -> [**] ->[***] -> [[*,**,***]]
+ */
+zip3 l1 l2 l3
+ = [], l1 == [] || l2 == [] || l3 == []
+ = [hd l1, hd l2, hd l3] : zip3 (tl l1) (tl l2) (tl l3);
diff --git a/share/nip2/compat/8.5/_magick.def b/share/nip2/compat/8.5/_magick.def
new file mode 100644
index 00000000..01c22b18
--- /dev/null
+++ b/share/nip2/compat/8.5/_magick.def
@@ -0,0 +1,1107 @@
+/*
+
+ ImageMagick operations edited by Alan Gibson (aka "snibgo"; snibgo at earthling dot net).
+
+ 1-Apr-2014
+ Minor corrections to Geometry_widget and Alpha.
+ Added loads of widgets and Menuactions.
+ Not fully tested.
+ 5-Apr-2014
+ Many more menu actions.
+ Reorganised Magick menu.
+ 10-Apr-2014
+ Many more menu actions.
+ 11-Apr-2014 jcupitt
+ Split to separate _magick.def
+ Add 0-ary and 2-ary system
+ Put utility funcs into a Magick class
+ 11-Apr-2014 snibgo
+ Added VirtualPixelBack for cases where background is only relevant when VP=Background
+ 17-Apr-2014 snibgo
+ Many small changes.
+ 2-May-2014 jcupitt
+ Added Magick.version
+ 30-June-2014
+ Put single-quotes around command exe to help win
+ 1-July-2014
+ Automatically fall back to gm if we can't find convert
+ 17-July-2014
+ better GM support
+
+
+ Last update: 17-July-2014.
+
+ For details of ImageMagick operations, see http://www.imagemagick.org/script/command-line-options.php etc.
+
+*/
+
+/* Put these in a class to avoid filling the main namespace with IM stuff.
+ */
+
+Magick = class {
+
+ // first gm on path, or ""
+ gm_path = search_for "gm";
+
+ // first convert on $PATH, or ""
+ // we check for the convert we ship first
+ convert_path
+ = vips_convert, vips_convert != ""
+ = search_for "convert"
+ {
+ // the convert we ship with the vips binary on some platforms, or ""
+ vips_convert
+ = search (path_absolute convert)
+ {
+ vipshome = path_parse (expand "$VIPSHOME");
+ convert = vipshome ++ ["bin", "convert" ++ expand "$EXEEXT"];
+ }
+ }
+
+ use_gm_pref = Workspaces.Preferences.USE_GRAPHICSMAGICK;
+
+ // Are we in GM or IM mode?
+ use_gm
+ = true, use_gm_pref && gm_path != ""
+ = false, !use_gm_pref && convert_path != ""
+ = false, convert_path != ""
+ = true, gm_path != ""
+ = error "neither IM nor GM executable found";
+
+ command_path
+ = gm_path, use_gm
+ = convert_path;
+
+ // try to get the version as eg. [6, 7, 7, 10]
+ // GM versions are smaller, typically [1, 3, 18]
+ version
+ = map parse_int (split (member ".-") version_string)
+ {
+ [output] = vips_call "system"
+ ["'" ++ command_path ++ "' -version"] [$log=>true];
+ version_string
+ = (split (equal ' ') output)?1, use_gm
+ = (split (equal ' ') output)?2;
+ }
+
+ // make a command-line ... args is a [str] we join with spaces
+ command args
+ = "'" ++ command_path ++ "' " ++ join_sep " " args'
+ {
+ args'
+ = ["convert"] ++ args, use_gm
+ = args;
+ }
+
+ // capabilities ... different versions support different features, we
+ // turn features on and off based on these
+
+ // would probably be better to test for caps somehow
+ has_intensity
+ = false, use_gm
+ = version?0 > 6 || version?1 > 7;
+ has_channel
+ = false, use_gm
+ = version?0 > 6 || version?1 > 7;
+
+ system0 cmd = system_image0 cmd;
+ system cmd x = map_unary (system_image cmd) x;
+ system2 cmd x y = map_binary (system_image2 cmd) x y;
+ system3 cmd x y z = map_trinary (system_image3 cmd) x y z;
+
+ radius_widget = Scale "Radius" 0 100 10;
+ sigma_widget = Scale "Sigma" 0.1 10 1;
+ angle_widget = Scale "Angle (degrees)" (-360) 360 0;
+ text_widget = String "Text to draw" "AaBbCcDdEe";
+
+ gamma_widget = Scale "Gamma" 0 10 1;
+ colors_widget = Scale "Colors" 1 10 3;
+ resize_widget = Scale "Resize (percent)" 0 500 100;
+ fuzz_widget = Scale "Fuzz (percent)" 0 100 0;
+ blur_rad_widget = Scale "Radius (0=auto)" 0 100 0;
+
+ // a colour with no enclosing quotes ... use this if we know there are
+ // some quotes at an outer level
+ print_colour_nq triple
+ = concat ["#", concat (map fmt triple)]
+ {
+ fmt x = reverse (take 2 (reverse (print_base 16 (x + 256))));
+ }
+
+ // we need the quotes because # is the comment character in *nix
+ print_colour triple = "\"" ++ print_colour_nq triple ++ "\"";
+
+ Foreground triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-fill " ++ print_colour triple;
+
+ Colour_edit space triple = this.Foreground triple;
+ }
+ foreground_widget = Foreground [0, 0, 0];
+
+ GeneralCol triple = class
+ Colour "sRGB" triple {
+
+ _flag = print_colour_nq triple;
+
+ Colour_edit space triple = this.GeneralCol triple;
+ }
+ generalcol_widget = GeneralCol [0, 0, 0];
+
+ Background triple = class
+ Colour "sRGB" triple {
+
+ isNone = Toggle "None (transparent black)" false;
+
+ _flag = "-background " ++ if isNone then "None" else print_colour triple;
+
+ Colour_edit space triple = this.Background triple;
+ }
+ background_widget = Background [255, 255, 255];
+
+ Bordercol triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-bordercolor " ++ print_colour triple;
+
+ Colour_edit space triple = this.Bordercol triple;
+ }
+ bordercol_widget = Bordercol [0, 0, 0];
+
+ Mattecol triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-mattecolor " ++ print_colour triple;
+
+ Colour_edit space triple = this.Mattecol triple;
+ }
+ mattecol_widget = Mattecol [189, 189, 189];
+
+ // FIXME: Undercolour, like many others, can have alpha channel.
+ // How does user input this? With a slider?
+ Undercol triple = class
+ Colour "sRGB" triple {
+
+ isNone = Toggle "None (transparent black)" true;
+
+ _flag = if isNone then "" else ("-undercolor " ++ print_colour triple);
+
+ Colour_edit space triple = this.Undercol triple;
+ }
+ undercol_widget = Undercol [0, 0, 0];
+
+ changeCol_widget = class {
+ _vislevel = 3;
+
+ colour = GeneralCol [0, 0, 0];
+ fuzz = fuzz_widget;
+ nonMatch = Toggle "change non-matching colours" false;
+ }
+
+ Alpha alpha = class
+ Option_string "Alpha" [
+ "On",
+ "Off",
+ "Set",
+ "Opaque",
+ "Transparent",
+ "Extract",
+ "Copy",
+ "Shape",
+ "Remove",
+ "Background"
+ ] alpha {
+
+ _flag = "-alpha " ++ alpha;
+
+ Option_edit caption labels value = this.Alpha labels?value;
+ }
+ alpha_widget = Alpha "On";
+
+ Antialias value = class
+ Toggle "Antialias" value {
+
+ _flag
+ = "-antialias", value
+ = "+antialias";
+
+ Toggle_edit caption value = this.Antialias value;
+ }
+ antialias_widget = Antialias true;
+
+ Builtin builtin = class
+ Option_string "Builtin" [
+ // See http://www.imagemagick.org/script/formats.php
+ "rose:",
+ "logo:",
+ "wizard:",
+ "granite:",
+ "netscape:"
+ ] builtin {
+
+ _flag = builtin;
+
+ Option_edit caption labels value = this.Builtin labels?value;
+ }
+ builtin_widget = Builtin "rose:";
+
+
+ channels_widget = class {
+ // FIXME? Can we grey-out alpha when we have no alpha channel,
+ // show CMY(K) instead of RGB(K) etc?
+ // Yes, perhaps we can create different widgets for RGB, RGBA, CMY, CMYK, CMYA, CMYKA.
+ ChanR valueR = class
+ Toggle "Red" valueR {
+
+ _flag
+ = "R", valueR
+ = "";
+
+ Toggle_edit caption valueR = this.ChanR valueR;
+ }
+ channelR = ChanR true;
+
+ ChanG valueG = class
+ Toggle "Green" valueG {
+
+ _flag
+ = "G", valueG
+ = "";
+
+ Toggle_edit caption valueG = this.ChanG valueG;
+ }
+ channelG = ChanG true;
+
+ ChanB valueB = class
+ Toggle "Blue" valueB {
+
+ _flag
+ = "B", valueB
+ = "";
+
+ Toggle_edit caption valueB = this.ChanB valueB;
+ }
+ channelB = ChanB true;
+
+ ChanK valueK = class
+ Toggle "Black" valueK {
+
+ _flag
+ = "K", valueK
+ = "";
+
+ Toggle_edit caption valueK = this.ChanK valueK;
+ }
+ channelK = ChanK true;
+
+ ChanA valueA = class
+ Toggle "Alpha" valueA {
+
+ _flag
+ = "A", valueA
+ = "";
+
+ Toggle_edit caption valueA = this.ChanA valueA;
+ }
+ channelA = ChanA false;
+
+ ChanSy valueSy = class
+ Toggle "Sync" valueSy {
+
+ _flag
+ = ",sync", valueSy
+ = "";
+
+ Toggle_edit caption valueSy = this.ChanSy valueSy;
+ }
+ channelSy = ChanSy true;
+
+ _rgbka = concat [channelR._flag,
+ channelG._flag,
+ channelB._flag,
+ channelK._flag,
+ channelA._flag
+ ];
+
+ _flag
+ = "", _rgbka == "" || !has_channel
+ = concat [ "-channel ",
+ _rgbka,
+ channelSy._flag
+ ];
+ }
+
+ ch_widget = channels_widget;
+
+ Colorspace colsp = class
+ Option_string "Colorspace" [
+ "CIELab",
+ "CMY",
+ "CMYK",
+ "Gray",
+ "HCL",
+ "HCLp",
+ "HSB",
+ "HSI",
+ "HSL",
+ "HSV",
+ "HWB",
+ "Lab",
+ "LCH",
+ "LCHab",
+ "LCHuv",
+ "LMS",
+ "Log",
+ "Luv",
+ "OHTA",
+ "Rec601Luma",
+ "Rec601YCbCr",
+ "Rec709Luma",
+ "Rec709YCbCr",
+ "RGB",
+ "scRGB",
+ "sRGB",
+ "Transparent",
+ "XYZ",
+ "YCbCr",
+ "YDbDr",
+ "YCC",
+ "YIQ",
+ "YPbPr",
+ "YUV"
+ ] colsp {
+
+ _flag = colsp;
+
+ Option_edit caption labels value = this.Colorspace labels?value;
+ }
+ colorspace_widget = Colorspace "sRGB";
+
+ Compose comp = class
+ Option_string "Compose method" [
+ "Atop",
+ "Blend",
+ "Blur",
+ "Bumpmap",
+ "ChangeMask",
+ "Clear",
+ "ColorBurn",
+ "ColorDodge",
+ "Colorize",
+ "CopyBlack",
+ "CopyBlue",
+ "CopyCyan",
+ "CopyGreen",
+ "Copy",
+ "CopyMagenta",
+ "CopyOpacity",
+ "CopyRed",
+ "CopyYellow",
+ "Darken",
+ "DarkenIntensity",
+ "DivideDst",
+ "DivideSrc",
+ "Dst",
+ "Difference",
+ "Displace",
+ "Dissolve",
+ "Distort",
+ "DstAtop",
+ "DstIn",
+ "DstOut",
+ "DstOver",
+ "Exclusion",
+ "HardLight",
+ "Hue",
+ "In",
+ "Lighten",
+ "LightenIntensity",
+ "LinearBurn",
+ "LinearDodge",
+ "LinearLight",
+ "Luminize",
+ "Mathematics",
+ "MinusDst",
+ "MinusSrc",
+ "Modulate",
+ "ModulusAdd",
+ "ModulusSubtract",
+ "Multiply",
+ "None",
+ "Out",
+ "Overlay",
+ "Over",
+ "PegtopLight",
+ "PinLight",
+ "Plus",
+ "Replace",
+ "Saturate",
+ "Screen",
+ "SoftLight",
+ "Src",
+ "SrcAtop",
+ "SrcIn",
+ "SrcOut",
+ "SrcOver",
+ "VividLight",
+ "Xor"
+ ] comp {
+
+ _flag = "-compose " ++ comp;
+
+ Option_edit caption labels value = this.Compose labels?value;
+ }
+ compose_widget = Compose "Over";
+ // FIXME: Some compose mehods (Displace, Distort, Mathematics) need a string.
+
+ // FIXME: we could use a class that does both -compose and -intensity, for methods LightenIntensity, DarkenIntensity, CopyOpacity, CopyBlack
+
+ coordinate_widget = class {
+ _vislevel = 3;
+
+ x = Expression "X" 0;
+ y = Expression "Y" 0;
+
+ _flag = concat [print x.expr, ",", print y.expr];
+ };
+
+ Distort distort = class
+ Option_string "Distort" [
+ "Affine",
+ "AffineProjection",
+ "ScaleRotateTranslate",
+ "SRT",
+ "Perspective",
+ "PerspectiveProjection",
+ "BilinearForward",
+ "BilinearReverse",
+ "Polynomial",
+ "Arc",
+ "Polar",
+ "DePolar",
+ "Barrel",
+ "BarrelInverse",
+ "Shepards",
+ "Resize"
+ ] distort {
+
+ _flag = distort;
+
+ Option_edit caption labels value = this.Distort labels?value;
+ }
+ distort_widget = Distort "SRT";
+
+ Dither dither = class
+ Option_string "Dither" [
+ "None",
+ "FloydSteinberg",
+ "Riemersma"
+ ] dither {
+
+ _flag = "-dither " ++ dither;
+
+ Option_edit caption labels value = this.Dither labels?value;
+ }
+ dither_widget = Dither "FloydSteinberg";
+
+ Evaluate eval = class
+ Option_string "Evaluate operation" [
+ "Abs",
+ "Add",
+ "AddModulus",
+ "And",
+ "Cos",
+ "Cosine",
+ "Divide",
+ "Exp",
+ "Exponential",
+ "GaussianNoise",
+ "ImpulseNoise",
+ "LaplacianNoise",
+ "LeftShift",
+ "Log",
+ "Max",
+ "Mean",
+ "Median",
+ "Min",
+ "MultiplicativeNoise",
+ "Multiply",
+ "Or",
+ "PoissonNoise",
+ "Pow",
+ "RightShift",
+ "Set",
+ "Sin",
+ "Sine",
+ "Subtract",
+ "Sum",
+ "Threshold",
+ "ThresholdBlack",
+ "ThresholdWhite",
+ "UniformNoise",
+ "Xor"
+ ] eval {
+
+ _flag = "-evaluate " ++ eval;
+
+ Option_edit caption labels value = this.Evaluate labels?value;
+ }
+ evaluate_widget = Evaluate "Add";
+
+ Filter filt = class
+ Option_string "Filter" [
+ "default",
+ "Bartlett",
+ "Blackman",
+ "Bohman",
+ "Box",
+ "Catrom",
+ "Cosine",
+ "Cubic",
+ "Gaussian",
+ "Hamming",
+ "Hann",
+ "Hermite",
+ "Jinc",
+ "Kaiser",
+ "Lagrange",
+ "Lanczos",
+ "Lanczos2",
+ "Lanczos2Sharp",
+ "LanczosRadius",
+ "LanczosSharp",
+ "Mitchell",
+ "Parzen",
+ "Point",
+ "Quadratic",
+ "Robidoux",
+ "RobidouxSharp",
+ "Sinc",
+ "SincFast",
+ "Spline",
+ "Triangle",
+ "Welch"
+ ] filt {
+
+ _flag = if filt == "default" then "" else "-filter " ++ filt;
+
+ Option_edit caption labels value = this.Filter labels?value;
+ }
+ filter_widget = Filter "default";
+
+ Function func = class
+ Option_string "Function" [
+ "Polynomial",
+ "Sinusoid",
+ "Arcsin",
+ "Arctan"
+ ] func {
+
+ _flag = func;
+
+ Option_edit caption labels value = this.Function labels?value;
+ }
+ function_widget = Function "Polynomial";
+
+// "Polynomial (a[n], a[n-1], ... a[1], a[0])",
+// "Sinusoid (freq, phase, amp, bias)",
+// "Arcsin (width, centre, range, bias)",
+// "Arctan (slope, centre, range, bias)"
+
+ Gravity gravity = class
+ Option_string "Gravity" [
+ "None",
+ "Center",
+ "East",
+ "Forget",
+ "NorthEast",
+ "North",
+ "NorthWest",
+ "SouthEast",
+ "South",
+ "SouthWest",
+ "West",
+ "Static"
+ ] gravity {
+
+ _flag = "-gravity " ++ gravity;
+
+ Option_edit caption labels value = this.Gravity labels?value;
+ }
+ gravity_widget = Gravity "Center";
+
+ ImageType imagetype = class
+ Option_string "Image type" [
+ "Bilevel",
+ "ColorSeparation",
+ "ColorSeparationAlpha",
+ "ColorSeparationMatte",
+ "Grayscale",
+ "GrayscaleAlpha",
+ "GrayscaleMatte",
+ "Optimize",
+ "Palette",
+ "PaletteBilevelAlpha",
+ "PaletteBilevelMatte",
+ "PaletteAlpha",
+ "PaletteMatte",
+ "TrueColorAlpha",
+ "TrueColorMatte",
+ "TrueColor"
+ ] imagetype {
+
+ _flag = "-type " ++ imagetype;
+
+ Option_edit caption labels value = this.ImageType labels?value;
+ }
+ imagetype_widget = ImageType "TrueColor";
+
+ Intensity intensity = class
+ Option_string "Intensity (gray conversion)" [
+ "Average",
+ "Brightness",
+ "Lightness",
+ "MS",
+ "Rec601Luma",
+ "Rec601Luminance",
+ "Rec709Luma",
+ "Rec709Luminance",
+ "RMS"
+ ] intensity {
+
+ _flag
+ = "-intensity " ++ intensity, has_intensity
+ = "";
+
+ Option_edit caption labels value = this.Intensity labels?value;
+ }
+ intensity_widget = Intensity "Rec709Luminance";
+
+ Interpolate interp = class
+ Option_string "Interpolate" [
+ "default",
+ "Average",
+ "Average4",
+ "Average9",
+ "Average16",
+ "Background",
+ "Bilinear",
+ "Blend",
+ "Integer",
+ "Mesh",
+ "Nearest",
+ "NearestNeighbor",
+ "Spline"
+ ] interp {
+
+ _flag = if interp == "default" then "" else "-interpolate " ++ interp;
+
+ Option_edit caption labels value = this.Interpolate labels?value;
+ }
+ interpolate_widget = Interpolate "default";
+
+ Kernel kernel = class
+ Option_string "Kernel" [
+ "Unity",
+ "Gaussian",
+ "DoG",
+ "LoG",
+ "Blur",
+ "Comet",
+ "Binomial",
+ "Laplacian",
+ "Sobel",
+ "FreiChen",
+ "Roberts",
+ "Prewitt",
+ "Compass",
+ "Kirsch",
+ "Diamond",
+ "Square",
+ "Rectangle",
+ "Disk",
+ "Octagon",
+ "Plus",
+ "Cross",
+ "Ring",
+ "Peaks",
+ "Edges",
+ "Corners",
+ "Diagonals",
+ "LineEnds",
+ "LineJunctions",
+ "Ridges",
+ "ConvexHull",
+ "ThinSe",
+ "Skeleton",
+ "Chebyshev",
+ "Manhattan",
+ "Octagonal",
+ "Euclidean"
+ // FIXME: custom kernel
+ ] kernel {
+
+ _flag = kernel;
+
+ Option_edit caption labels value = this.Kernel labels?value;
+ }
+ kernel_widget = Kernel "Unity";
+
+ ModColSp msp = class
+ Option_string "modulate colorspace" [
+ "HCL",
+ "HCLp",
+ "HSB",
+ "HSI",
+ "HSL",
+ "HSV",
+ "HWB",
+ "LCH"
+ ] msp {
+
+ _flag = "-set option:modulate:colorspace " ++ msp;
+
+ Option_edit caption labels value = this.ModColSp labels?value;
+ }
+ ModColSp_widget = ModColSp "HSL";
+
+ MorphMeth morph = class
+ Option_string "Method" [
+ "Correlate",
+ "Convolve",
+ "Dilate",
+ "Erode",
+ "Close",
+ "Open",
+ "DilateIntensity",
+ "ErodeIntensity",
+ "CloseIntensity",
+ "OpenIntensity",
+ "Smooth",
+ "EdgeOut",
+ "EdgeIn",
+ "Edge",
+ "TopHat",
+ "BottomHat",
+ "HitAndMiss",
+ "Thinning",
+ "Thicken",
+ "Distance",
+ "IterativeDistance"
+ ] morph {
+
+ _flag = morph;
+
+ Option_edit caption labels value = this.MorphMeth labels?value;
+ }
+ morphmeth_widget = MorphMeth "Dilate";
+
+ Noise noise = class
+ Option_string "Noise" [
+ "Gaussian",
+ "Impulse",
+ "Laplacian",
+ "Multiplicative",
+ "Poisson",
+ "Random",
+ "Uniform"
+ ] noise {
+
+ _flag = "+noise " ++ noise;
+
+ Option_edit caption labels value = this.Noise labels?value;
+ }
+ noise_widget = Noise "Gaussian";
+
+ Pattern pattern = class
+ Option_string "Noise" [
+ // See http://www.imagemagick.org/script/formats.php
+ "bricks",
+ "checkerboard",
+ "circles",
+ "crosshatch",
+ "crosshatch30",
+ "crosshatch45",
+ "gray0",
+ "gray5",
+ "gray10",
+ "gray15",
+ "gray20",
+ "gray25",
+ "gray30",
+ "gray35",
+ "gray40",
+ "gray45",
+ "gray50",
+ "gray55",
+ "gray60",
+ "gray65",
+ "gray70",
+ "gray75",
+ "gray80",
+ "gray85",
+ "gray90",
+ "gray95",
+ "gray100",
+ "hexagons",
+ "horizontal",
+ "horizontal2",
+ "horizontal3",
+ "horizontalsaw",
+ "hs_bdiagonal",
+ "hs_cross",
+ "hs_diagcross",
+ "hs_fdiagonal",
+ "hs_horizontal",
+ "hs_vertical",
+ "left30",
+ "left45",
+ "leftshingle",
+ "octagons",
+ "right30",
+ "right45",
+ "rightshingle",
+ "smallfishscales",
+ "vertical",
+ "vertical2",
+ "vertical3",
+ "verticalbricks",
+ "verticalleftshingle",
+ "verticalrightshingle",
+ "verticalsaw"
+ ] pattern {
+
+ _flag = "pattern:" ++ pattern;
+
+ Option_edit caption labels value = this.Pattern labels?value;
+ }
+ pattern_widget = Pattern "bricks";
+
+ ResizeType resizet = class
+ Option_string "Resize type" [
+ "resize",
+ "scale",
+ "sample",
+ "adaptive-resize"
+ ] resizet {
+
+ _flag = resizet;
+
+ Option_edit caption labels value = this.ResizeType labels?value;
+ }
+ ResizeType_widget = ResizeType "resize";
+
+ Size_widget = class {
+ _vislevel = 3;
+
+ width = Expression "Width (pixels)" 64;
+ height = Expression "Height (pixels)" 64;
+
+ _flag = "-size " ++
+ print width.expr ++ "x" ++ print height.expr;
+
+ };
+
+ StatType statt = class
+ Option_string "Statistic type" [
+ "Gradient",
+ "Maximum",
+ "Mean",
+ "Median",
+ "Minimum",
+ "Mode",
+ "Nonpeak",
+ "StandardDeviation"
+ ] statt {
+
+ _flag = statt;
+
+ Option_edit caption labels value = this.StatType labels?value;
+ }
+ StatType_widget = StatType "Mean";
+
+ VirtualPixel vp = class
+ Option_string "Virtual pixel" [
+ "Background",
+ "Black",
+ "CheckerTile",
+ "Dither",
+ "Edge",
+ "Gray",
+ "HorizontalTile",
+ "HorizontalTileEdge",
+ "Mirror",
+ "None",
+ "Random",
+ "Tile",
+ "Transparent",
+ "VerticalTile",
+ "VerticalTileEdge",
+ "White"
+ ] vp {
+
+ _flag = "-virtual-pixel " ++ vp;
+
+ _isBackground = (vp == "Background");
+
+ Option_edit caption labels value = this.VirtualPixel labels?value;
+ }
+ VirtualPixel_widget = VirtualPixel "Edge";
+
+ VirtualPixelBack_widget = class {
+ virtpix = Magick.VirtualPixel_widget;
+ background = Magick.background_widget;
+ _flag = (if virtpix._isBackground then (background._flag ++ " ") else "")
+ ++ virtpix._flag;
+ }
+
+ Geometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "X" 0;
+ y = Expression "Y" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ AnnotGeometry_widget = class {
+ _vislevel = 3;
+
+ shearX = Expression "shear X (degrees)" 0;
+ shearY = Expression "shear Y (degrees)" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print shearX.expr, "x", print shearY.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ OffsetGeometry_widget = class {
+ _vislevel = 3;
+
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag = concat [format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ WhxyGeometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "Width" 0;
+ y = Expression "Height" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ FrameGeometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "Width" 0;
+ y = Expression "Height" 0;
+ outbev = Expression "Outer bevel thickness" 0;
+ inbev = Expression "Inner bevel thickness" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format outbev, format inbev]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ Font_widget = class {
+ _vislevel = 3;
+
+ family = Option_string "Family" [
+ "Arial",
+ "ArialBlack",
+ "AvantGarde",
+ "BitstreamCharter",
+ "Bookman",
+ "CenturySchoolbook",
+ "ComicSansMS",
+ "Courier",
+ "CourierNew",
+ "DejaVuSans",
+ "DejaVuSansMono",
+ "DejaVuSerif",
+ "Dingbats",
+ "FreeMono",
+ "FreeSans",
+ "FreeSerif",
+ "Garuda",
+ "Georgia",
+ "Helvetica",
+ "HelveticaNarrow",
+ "Impact",
+ "LiberationMono",
+ "LiberationSans",
+ "LiberationSerif",
+ "NewCenturySchlbk",
+ "Palatino",
+ "Purisa",
+ "Symbol",
+ "Times",
+ "TimesNewRoman",
+ "Ubuntu",
+ "Verdana",
+ "Webdings"
+ ] "Arial";
+ style = Option_string "Style" [
+ "Any", "Italic", "Normal", "Oblique"
+ ] "Normal";
+ weight = Scale "Weight" 1 800 400;
+ size = Scale "Point size" 1 100 12;
+ stretch = Option_string "Stretch" [
+ "Any", "Condensed", "Expanded", "ExtraCondensed", "ExtraExpanded",
+ "Normal", "SemiCondensed", "SemiExpanded", "UltraCondensed",
+ "UltraExpanded"
+ ] "Normal";
+
+ _flag = join_sep " " [
+ "-family", family.item,
+ "-weight", print weight.value,
+ "-pointsize", print size.value,
+ "-style", style.item,
+ "-stretch", stretch.item];
+ }
+}
+
diff --git a/share/nip2/compat/8.5/_predicate.def b/share/nip2/compat/8.5/_predicate.def
new file mode 100644
index 00000000..14872802
--- /dev/null
+++ b/share/nip2/compat/8.5/_predicate.def
@@ -0,0 +1,528 @@
+
+/* is_colour_space str: is a string one of nip's colour space names
+ */
+is_colour_space str = Image_type.colour_spaces.present 0 str;
+
+/* is_colour_type n: is a number one of VIPS's colour spaces
+ */
+is_colour_type n = Image_type.colour_spaces.present 1 n;
+
+/* is_number: is a real or a complex number.
+ */
+is_number a = is_real a || is_complex a;
+
+/* is_int: is an integer
+ */
+is_int a = is_real a && a == (int) a;
+
+/* is_uint: is an unsigned integer
+ */
+is_uint a = is_int a && a >= 0;
+
+/* is_pint: is a positive integer
+ */
+is_pint a = is_int a && a > 0;
+
+/* is_preal: is a positive real
+ */
+is_preal a = is_real a && a > 0;
+
+/* is_ureal: is an unsigned real
+ */
+is_ureal a = is_real a && a >= 0;
+
+/* is_letter c: true if character c is an ASCII letter
+ *
+ * is_letter :: char -> bool
+ */
+is_letter c = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
+
+/* is_digit c: true if character c is an ASCII digit
+ *
+ * is_digit :: char->bool
+ */
+is_digit x = '0' <= x && x <= '9';
+
+/* A whitespace character.
+ *
+ * is_space :: char->bool
+ */
+is_space = member " \n\t";
+
+/* List str starts with section prefix.
+ *
+ * is_prefix "hell" "hello world!" == true
+ * is_prefix :: [*] -> [*] -> bool
+ */
+is_prefix prefix str = take (len prefix) str == prefix;
+
+/* List str ends with section suffix.
+ *
+ * is_suffix "ld!" "hello world!" == true
+ * is_suffix :: [*] -> [*] -> bool
+ */
+is_suffix suffix str = take (len suffix) (reverse str) == reverse suffix;
+
+/* List contains seqence.
+ *
+ * is_substr "llo" "hello world!" == true
+ * is_substr :: [*] -> [*] -> bool
+ */
+is_substr seq str = any (map (is_prefix seq) (iterate tl str));
+
+/* is_listof p s: true if finite list with p true for every element.
+ */
+is_listof p l = is_list l && all (map p l);
+
+/* is_string s: true if finite list of char.
+ */
+is_string s = is_listof is_char s;
+
+/* is_real_list l: is l a list of real numbers ... test each element,
+ * so no infinite lists pls.
+ */
+is_real_list l = is_listof is_real l;
+
+/* is_string_list l: is l a finite list of finite strings.
+ */
+is_string_list l = is_listof is_string l;
+
+/* Test list length ... quicker than len x == n for large lists.
+ */
+is_list_len n x
+ = true, x == [] && n == 0
+ = false, x == [] || n == 0
+ = is_list_len (n - 1) (tl x);
+
+is_list_len_more n x
+ = true, x != [] && n == 0
+ = false, x == [] || n == 0
+ = is_list_len_more (n - 1) (tl x);
+
+is_list_len_more_equal n x
+ = true, n == 0
+ = false, x == []
+ = is_list_len_more_equal (n - 1) (tl x);
+
+/* is_rectangular l: is l a rectangular data structure
+ */
+is_rectangular l
+ = true, !is_list l
+ = true, all (map is_obj l)
+ = true, all (map is_list l) &&
+ all (map (not @ is_obj) l) &&
+ all (map is_rectangular l) &&
+ is_list_len_more 0 l &&
+ all (map (is_list_len (len (hd l))) (tl l))
+ = false
+{
+ // treat strings as a base type, not [char]
+ is_obj x = !is_list x || is_string x;
+}
+
+/* is_matrix l: is l a list of lists of real numbers, all the same length
+ *
+ * [[]] is the empty matrix, [] is the empty list ... disallow []
+ */
+is_matrix l = l != [] && is_listof is_real_list l && is_rectangular l;
+
+/* is_square_matrix l: is l a matrix with width == height
+ */
+is_square_matrix l
+ = true, l == [[]]
+ = is_matrix l && is_list_len (len (hd l)) l;
+
+/* is_oddmatrix l: is l a matrix with odd-length sides
+ */
+is_oddmatrix l
+ = true, l == [[]]
+ = is_matrix l && len l % 2 == 1 && len l?0 % 2 == 1;
+
+/* is_odd_square_matrix l: is l a square_matrix with odd-length sides
+ */
+is_odd_square_matrix l = is_square_matrix l && len l % 2 == 1;
+
+/* Is an item in a column of a table?
+ */
+is_incolumn n table x = member (map (extract n) table) x;
+
+/* Is HGuide or VGuide.
+ */
+is_HGuide x = is_instanceof "HGuide" x;
+
+is_VGuide x = is_instanceof "VGuide" x;
+
+is_Guide x = is_HGuide x || is_VGuide x;
+
+is_Mark x = is_instanceof "Mark" x;
+
+is_Group x = is_instanceof "Group" x;
+
+is_NULL x = is_instanceof "NULL" x;
+
+is_List x = is_instanceof "List" x;
+
+is_Image x = is_instanceof "Image" x;
+
+is_Plot x = is_instanceof "Plot" x;
+
+is_Region x = is_instanceof "Region" x;
+
+is_Real x = is_instanceof "Real" x;
+
+is_Matrix x = is_instanceof "Matrix_base" x;
+
+is_Vector x = is_instanceof "Vector" x;
+
+is_Colour x = is_instanceof "Colour" x;
+
+is_Arrow x = is_instanceof "Arrow" x;
+
+is_Bool x = is_instanceof "Bool" x;
+
+is_Scale x = is_instanceof "Scale" x;
+
+is_Rect x = is_instanceof "Rect" x;
+
+is_Number x = is_instanceof "Number" x;
+
+is_Expression x = is_instanceof "Expression" x;
+
+is_String x = is_instanceof "String" x;
+
+/* A list of the form [[1,2],[3,4],[5,6]...]
+ */
+is_xy_list l
+ = is_list l && all (map xy l)
+{
+ xy l = is_real_list l && is_list_len 2 l;
+}
+
+// does a nested list structure contain a Group object?
+contains_Group l
+ = true, is_list l && any (map is_Group l)
+ = any (map contains_Group l), is_list l
+ = false;
+
+/* Does an object have a sensible VIPS type?
+ */
+has_type x = is_image x || is_Image x || is_Arrow x || is_Colour x;
+
+/* Try to get a VIPS image type from an object.
+ */
+get_type x
+ = get_type_im x, is_image x
+ = get_type_im x.value, is_Image x
+ = get_type_im x.image.value, is_Arrow x
+ = Image_type.colour_spaces.lookup 0 1 x.colour_space, is_Colour x
+ // slightly odd ... but our display is always 0-255, so it makes sense for
+ // a plain number to be in the same range
+ = Image_type.sRGB, is_real x
+ = oo_unary_function get_type_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_type")
+{
+ get_type_op = Operator "get_type" get_type
+ Operator_type.COMPOUND false;
+
+ // get the type from a VIPS image ... but only if it makes sense with
+ // the rest of the image
+
+ // we often have Type set wrong, hence the ugly guessing :-(
+ // can have alpha, hence we let bands be one more than you might think
+
+ get_type_im im
+ = Image_type.LABQ, coding == Image_coding.LABPACK
+ = Image_type.GREY16, type == Image_type.GREY16 && is_bands 1
+ = Image_type.HISTOGRAM, type == Image_type.HISTOGRAM &&
+ (width == 1 || height == 1)
+ = Image_type.B_W, is_bands 1
+ = Image_type.CMYK, type == Image_type.CMYK && is_bands 4
+ = type, is_colorimetric && is_bands 3
+ = Image_type.sRGB, !is_colorimetric && is_bands 3
+ = Image_type.MULTIBAND, !is_colorimetric && !is_bands 3
+ = type
+ {
+ type = get_header "Type" im;
+ coding = get_header "Coding" im;
+ bands = get_header "Bands" im;
+ width = get_header "Xsize" im;
+ height = get_header "Ysize" im;
+
+ // 3-band colorimetric types we allow ... the things which the
+ // Colour/Convert To menu can make, excluding mono.
+ ok_types = [
+ Image_type.sRGB,
+ Image_type.RGB16,
+ Image_type.LAB,
+ Image_type.LABQ,
+ Image_type.LABS,
+ Image_type.LCH,
+ Image_type.XYZ,
+ Image_type.YXY,
+ Image_type.UCS
+ ];
+ is_colorimetric = member ok_types type;
+
+ // is bands n, with an optional alpha (ie. can be n + 1 too)
+ is_bands n = bands == n || bands == n + 1;
+ }
+}
+
+has_format x = has_member "format" x || is_Arrow x || is_image x;
+
+get_format x
+ = x.format, has_member "format" x
+ = x.image.format, is_Arrow x
+ = get_header "BandFmt" x, is_image x
+ = oo_unary_function get_format_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_format")
+{
+ get_format_op = Operator "get_format" get_format
+ Operator_type.COMPOUND false;
+}
+
+has_bits x = has_member "bits" x || is_Arrow x || is_image x;
+
+get_bits x
+ = x.bits, has_member "bits" x
+ = x.image.bits, is_Arrow x
+ = get_header "Bbits" x, is_image x
+ = oo_unary_function get_bits_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_bits")
+{
+ get_bits_op = Operator "get_bits" get_format
+ Operator_type.COMPOUND false;
+}
+
+has_bands x = is_image x || has_member "bands" x || is_Arrow x;
+
+get_bands x
+ = x.bands, has_member "bands" x
+ = x.image.bands, is_Arrow x
+ = get_header "Bands" x, is_image x
+ = 1, is_real x
+ = len x, is_real_list x
+ = oo_unary_function get_bands_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_bands")
+{
+ get_bands_op = Operator "get_bands" get_bands
+ Operator_type.COMPOUND false;
+}
+
+has_coding x = has_member "coding" x || is_Arrow x || is_image x;
+
+get_coding x
+ = x.coding, has_member "coding" x
+ = x.image.coding, is_Arrow x
+ = get_header "Coding" x, is_image x
+ = Image_coding.NOCODING, is_real x
+ = oo_unary_function get_coding_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_coding")
+{
+ get_coding_op = Operator "get_coding" get_coding
+ Operator_type.COMPOUND false;
+}
+
+has_xres x = has_member "xres" x || is_Arrow x || is_image x;
+
+get_xres x
+ = x.xres, has_member "xres" x
+ = x.image.xres, is_Arrow x
+ = get_header "Xres" x, is_image x
+ = oo_unary_function get_xres_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_xres")
+{
+ get_xres_op = Operator "get_xres" get_xres
+ Operator_type.COMPOUND false;
+}
+
+has_yres x = has_member "yres" x || is_Arrow x || is_image x;
+
+get_yres x
+ = x.yres, has_member "yres" x
+ = x.image.yres, is_Arrow x
+ = get_header "Yres" x, is_image x
+ = oo_unary_function get_yres_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_yres")
+{
+ get_yres_op = Operator "get_yres" get_yres
+ Operator_type.COMPOUND false;
+}
+
+has_xoffset x = has_member "xoffset" x || is_Arrow x || is_image x;
+
+get_xoffset x
+ = x.xoffset, has_member "xoffset" x
+ = x.image.xoffset, is_Arrow x
+ = get_header "Xoffset" x, is_image x
+ = oo_unary_function get_xoffset_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_xoffset")
+{
+ get_xoffset_op = Operator "get_xoffset" get_xoffset
+ Operator_type.COMPOUND false;
+}
+
+has_yoffset x = has_member "yoffset" x || is_Arrow x || is_image x;
+
+get_yoffset x
+ = x.yoffset, has_member "yoffset" x
+ = x.image.yoffset, is_Arrow x
+ = get_header "Yoffset" x, is_image x
+ = oo_unary_function get_yoffset_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_yoffset")
+{
+ get_yoffset_op = Operator "get_yoffset" get_yoffset
+ Operator_type.COMPOUND false;
+}
+
+has_value = has_member "value";
+
+get_value x = x.value;
+
+has_image x = is_image x || is_Image x || is_Arrow x;
+
+get_image x
+ = x.value, is_Image x
+ = x.image.value, is_Arrow x
+ = x, is_image x
+ = oo_unary_function get_image_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_image")
+{
+ get_image_op = Operator "get_image" get_image
+ Operator_type.COMPOUND false;
+}
+
+has_number x = is_number x || is_Real x;
+
+get_number x
+ = x.value, is_Real x
+ = x, is_number x
+ = oo_unary_function get_number_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_number")
+{
+ get_number_op = Operator "get_number" get_number
+ Operator_type.COMPOUND false;
+}
+
+has_real x = is_real x || is_Real x;
+
+get_real x
+ = x.value, is_Real x
+ = x, is_real x
+ = oo_unary_function get_real_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_real")
+{
+ get_real_op = Operator "get_real" get_real
+ Operator_type.COMPOUND false;
+}
+
+has_width x = has_member "width" x || is_image x;
+
+get_width x
+ = x.width, has_member "width" x
+ = get_header "Xsize" x, is_image x
+ = oo_unary_function get_width_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_width")
+{
+ get_width_op = Operator "get_width" get_width
+ Operator_type.COMPOUND false;
+}
+
+has_height x = has_member "height" x || is_image x;
+
+get_height x
+ = x.height, has_member "height" x
+ = get_header "Ysize" x, is_image x
+ = oo_unary_function get_height_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_height")
+{
+ get_height_op = Operator "get_height" get_height
+ Operator_type.COMPOUND false;
+}
+
+has_left x = has_member "left" x;
+
+get_left x
+ = x.left, has_member "left" x
+ = oo_unary_function get_left_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_left")
+{
+ get_left_op = Operator "get_left" get_left
+ Operator_type.COMPOUND false;
+}
+
+has_top x = has_member "top" x;
+
+get_top x
+ = x.top, has_member "top" x
+ = oo_unary_function get_top_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_top")
+{
+ get_top_op = Operator "get_top" get_top
+ Operator_type.COMPOUND false;
+}
+
+// like has/get member, but first in a lst of objects
+has_member_list has objects
+ = filter has objects != [];
+
+// need one with the args swapped
+get_member = converse dot;
+
+// get a member from the first of a list of objects to have it
+get_member_list has get objects
+ = hd members, members != []
+ = error "unable to get property"
+{
+ members = map get (filter has objects);
+}
+
+is_hist x
+ = has_image x && (h == 1 || w == 1 || t == Image_type.HISTOGRAM)
+{
+ im = get_image x;
+ w = get_width im;
+ h = get_height im;
+ t = get_type im;
+}
+
+get_header field x
+ = oo_unary_function get_header_op x, is_class x
+ = get_header_image x, is_image x
+ = error (_ "bad arguments to " ++ "get_header")
+{
+ get_header_op = Operator "get_header" (get_header field)
+ Operator_type.COMPOUND false;
+ get_header_image im
+ = im_header_int field im, type == itype
+ = im_header_double field im, type == dtype
+ = im_header_string field im, type == stype1 || type == stype2
+ = error (_ "image has no field " ++ field), type == 0
+ = error (_ "unknown type for field " ++ field)
+ {
+ type = im_header_get_typeof field im;
+
+ itype = name2gtype "gint";
+ dtype = name2gtype "gdouble";
+ stype1 = name2gtype "VipsRefString";
+ stype2 = name2gtype "gchararray";
+ }
+}
+
+get_header_type field x
+ = oo_unary_function get_header_type_op x, is_class x
+ = im_header_get_typeof field x, is_image x
+ = error (_ "bad arguments to " ++ "get_header_type")
+{
+ get_header_type_op = Operator "get_header_type" (get_header_type field)
+ Operator_type.COMPOUND false;
+}
+
+set_header field value x
+ = oo_unary_function set_header_op x, is_class x
+ = im_copy_set_meta x field value, is_image x
+ = error (_ "bad arguments to " ++ "set_header")
+{
+ set_header_op = Operator "set_header" (set_header field value)
+ Operator_type.COMPOUND false;
+}
diff --git a/share/nip2/compat/8.5/_stdenv.def b/share/nip2/compat/8.5/_stdenv.def
new file mode 100644
index 00000000..a7cf8f3a
--- /dev/null
+++ b/share/nip2/compat/8.5/_stdenv.def
@@ -0,0 +1,2597 @@
+/* optional args to functions
+ */
+
+get_option options defaults f
+ = error (_ "unknown parameter " ++ f), hits == []
+ = hits?0
+{
+ hits = [v :: [n, v] <- options ++ defaults; n == f];
+}
+
+/* Various operators as functions.
+ */
+
+logical_and a b = a && b;
+logical_or a b = a || b;
+bitwise_and a b = a & b;
+bitwise_or a b = a | b;
+eor a b = a ^ b;
+left_shift a b = a << b;
+right_shift a b = a >> b;
+not a = !a;
+
+less a b = a < b;
+more a b = a > b;
+less_equal a b = a <= b;
+more_equal a b = a >= b;
+equal a b = a == b;
+not_equal a b = a != b;
+pointer_equal a b = a === b;
+not_pointer_equal a b = a !== b;
+
+add a b = a + b;
+subtract a b = a - b;
+multiply a b = a * b;
+divide a b = a / b;
+idivide a b = (int) ((int) a / (int) b);
+power a b = a ** b;
+square x = x * x;
+remainder a b = a % b;
+
+cons a b = a : b;
+dot a b = a . ( b );
+join a b = a ++ b;
+// 'difference' is defined in _list
+subscript a b = a ? b;
+
+generate s n f = [s, n .. f];
+comma r i = (r, i);
+
+compose f g = f @ g;
+
+// our only trinary operator is actually a binary operator
+if_then_else a x = if a then x?0 else x?1;
+
+cast_unsigned_char x = (unsigned char) x;
+cast_signed_char x = (signed char) x;
+cast_unsigned_short x = (unsigned short) x;
+cast_signed_short x = (signed short) x;
+cast_unsigned_int x = (unsigned int) x;
+cast_signed_int x = (signed int) x;
+cast_float x = (float) x;
+cast_double x = (double) x;
+cast_complex x = (complex) x;
+cast_double_complex x = (double complex) x;
+
+unary_minus x = -x;
+negate x = !x;
+complement x = ~x;
+unary_plus x = +x;
+
+// the function we call for "a -> v" expressions
+mksvpair s v
+ = [s, v], is_string s
+ = error "not str on lhs of ->";
+
+// the vector ops ... im is an image, vec is a real_list
+vec op_name im vec
+ = im_lintra_vec ones im vec,
+ op_name == "add" || op_name == "add'"
+ = im_lintra_vec ones (-1 * im) vec,
+ op_name == "subtract'"
+ = im_lintra_vec ones im inv,
+ op_name == "subtract"
+ = im_lintra_vec vec im zeros,
+ op_name == "multiply" || op_name == "multiply'"
+ = im_lintra_vec vec (1 / im) zeros,
+ op_name == "divide'"
+ = im_lintra_vec recip im zeros,
+ op_name == "divide"
+ = im_expntra_vec im vec,
+ op_name == "power'"
+ = im_powtra_vec im vec,
+ op_name == "power"
+ = im_remainderconst_vec im vec,
+ op_name == "remainder"
+ = im_andimage_vec im vec,
+ op_name == "bitwise_and" || op_name == "bitwise_and'"
+ = im_orimage_vec im vec,
+ op_name == "bitwise_or" || op_name == "bitwise_or'"
+ = im_eorimage_vec im vec,
+ op_name == "eor" || op_name == "eor'"
+ = im_equal_vec im vec,
+ op_name == "equal" || op_name == "equal'"
+ = im_notequal_vec im vec,
+ op_name == "not_equal" || op_name == "not_equal'"
+ = im_less_vec im vec,
+ op_name == "less"
+ = im_moreeq_vec im vec,
+ op_name == "less'"
+ = im_lesseq_vec im vec,
+ op_name == "less_equal"
+ = im_more_vec im vec,
+ op_name == "less_equal'"
+ = error ("unimplemented vector operation: " ++ op_name)
+{
+ zeros = replicate (len vec) 0;
+ ones = replicate (len vec) 1;
+ recip = map (divide 1) vec;
+ inv = map (multiply (-1)) vec;
+}
+
+// make a name value pair
+mknvpair n v
+ = [n, v], is_string n
+ = error "not [char] on LHS of =>";
+
+/* Macbeth chart patch names.
+ */
+macbeth_names = [
+ "Dark skin",
+ "Light skin",
+ "Blue sky",
+ "Foliage",
+ "Blue flower",
+ "Bluish green",
+ "Orange",
+ "Purplish blue",
+ "Moderate red",
+ "Purple",
+ "Yellow green",
+ "Orange yellow",
+ "Blue",
+ "Green",
+ "Red",
+ "Yellow",
+ "Magenta",
+ "Cyan",
+ "White (density 0.05)",
+ "Neutral 8 (density 0.23)",
+ "Neutral 6.5 (density 0.44)",
+ "Neutral 5 (density 0.70)",
+ "Neutral 3.5 (density 1.05)",
+ "Black (density 1.50)"
+];
+
+bandsplit x
+ = oo_unary_function bandsplit_op x, is_class x
+ = map (subscript x) [0 .. bands - 1], is_image x
+ = error (_ "bad arguments to " ++ "bandsplit")
+{
+ bands = get_header "Bands" x;
+ bandsplit_op = Operator "bandsplit" (map Image @ bandsplit)
+ Operator_type.COMPOUND false;
+}
+
+bandjoin l
+ = wrapper joined, has_wrapper
+ = joined, is_listof has_image l
+ = error (_ "bad arguments to " ++ "bandjoin")
+{
+ has_wrapper = has_member_list (has_member "Image") l;
+ wrapper = get_member_list (has_member "Image") (get_member "Image") l;
+ joined = im_gbandjoin (map get_image l);
+}
+
+bandand x
+ = oo_unary_function bandand_op x, is_class x
+ = foldr1 bitwise_and (bandsplit x), is_image x
+ = error (_ "bad arguments to " ++ "bandand")
+{
+ bandand_op = Operator "bandand" bandand Operator_type.COMPOUND_REWRAP false;
+}
+
+bandor x
+ = oo_unary_function bandor_op x, is_class x
+ = foldr1 bitwise_or (bandsplit x), is_image x
+ = error (_ "bad arguments to " ++ "bandor")
+{
+ bandor_op = Operator "bandor" bandor Operator_type.COMPOUND_REWRAP false;
+}
+
+sum x
+ = oo_unary_function sum_op x, is_class x
+ = im_avg x * (get_width x) * (get_height x) * (get_bands x), is_image x
+ = sum_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "sum")
+{
+ sum_op = Operator "sum" sum Operator_type.COMPOUND false;
+
+ // add elements in a nested-list thing
+ sum_list l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + sum x, is_list x
+ = total + x;
+ }
+}
+
+product x
+ = oo_unary_function product_op x, is_class x
+ = product_list x, is_list x
+ // (product image) doesn't make much sense :(
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "product")
+{
+ product_op = Operator "product" product Operator_type.COMPOUND false;
+
+ product_list l
+ = foldr prod 1 l
+ {
+ prod x total
+ = total * product x, is_list x
+ = total * x;
+ }
+}
+
+mean x
+ = oo_unary_function mean_op x, is_class x
+ = im_avg x, is_image x
+ = mean_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "mean")
+{
+ mean_op = Operator "mean" mean Operator_type.COMPOUND false;
+
+ mean_list l = sum l / size l;
+
+ // number of elements in some sort of nested-list thing
+ size l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + size x, is_list x
+ = total + 1;
+ }
+}
+
+meang x
+ = (appl (power e) @ mean @ appl log) x
+{
+ appl fn x
+ = map fn x, is_list x
+ = fn x;
+}
+
+skew x
+ = oo_unary_function skew_op x, is_class x
+ = sum ((x - m) ** 3) / ((N - 1) * s ** 3), is_image x
+ = sum ((Group x' - m) ** 3).value / ((N - 1) * s ** 3), is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "skew")
+{
+ skew_op = Operator "skew" skew Operator_type.COMPOUND false;
+
+ // squash any large matrix down to a flat list ... much simpler
+ x'
+ = x, is_image x;
+ = flatten x;
+
+ m = mean x';
+ s = deviation x';
+ w = get_width x';
+ h = get_height x';
+ b = get_bands x';
+
+ N
+ = w * h * b, is_image x'
+ = len x';
+}
+
+kurtosis x
+ = oo_unary_function kurtosis_op x, is_class x
+ = sum ((x - m) ** 4) / ((N - 1) * s ** 4), is_image x
+ = sum ((Group x' - m) ** 4).value / ((N - 1) * s ** 4), is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "kurtosis")
+{
+ kurtosis_op = Operator "kurtosis" kurtosis Operator_type.COMPOUND false;
+
+ // squash any large matrix down to a flat list ... much simpler
+ x'
+ = x, is_image x;
+ = flatten x;
+
+ m = mean x';
+ s = deviation x';
+ w = get_width x';
+ h = get_height x';
+ b = get_bands x';
+
+ N
+ = len x', is_list x';
+ = w * h * b;
+}
+
+// zero-excluding mean
+meanze x
+ = oo_unary_function meanze_op x, is_class x
+ = meanze_image_hist x, is_image x &&
+ (fmt == Image_format.UCHAR || fmt == Image_format.USHORT)
+ = meanze_image x, is_image x
+ = meanze_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "meanze")
+{
+ fmt = get_format x;
+
+ meanze_op = Operator "meanze" meanze Operator_type.COMPOUND false;
+
+ meanze_list l = sum l / size l;
+
+ // number of non-zero elements in some sort of nested-list thing
+ size l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + size x, is_list x
+ = total + 1, x != 0;
+ = total;
+ }
+
+ // add elements in a nested-list thing
+ sum l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + sum x, is_list x
+ = total + x;
+ }
+
+ // image mean, for any image type
+ meanze_image i
+ = sum / N
+ {
+ w = get_width i;
+ h = get_height i;
+ b = get_bands i;
+
+ st = stats i;
+ sum = st.value?0?2;
+
+ // find non-zero pixels (not zero in all bands)
+ zp = im_notequal_vec i (replicate b 0);
+
+ // number of non-zero pixels
+ N = b * (mean zp * w * h) / 255;
+ }
+
+ // image mean for 8 and 16-bit unsigned images
+ // we can use a histogram, yay, and save a pass through the image
+ meanze_image_hist i
+ = sum / N
+ {
+ // histogram, knock out zeros
+ hist = hist_find i;
+ black = image_new 1 1 (get_bands hist)
+ (get_format hist) (get_coding hist) (get_type hist) 0 0 0;
+ histze = insert 0 0 black hist;
+
+ // matching identity
+ iden
+ = im_identity_ushort (get_bands hist) (get_width hist),
+ (get_width hist) > 256
+ = im_identity (get_bands hist);
+
+ // number of non-zero pixels
+ N = mean histze * 256;
+
+ // sum of pixels
+ sum = mean (hist * iden) * 256;
+ }
+}
+
+deviation x
+ = oo_unary_function deviation_op x, is_class x
+ = im_deviate x, is_image x
+ = deviation_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "deviation")
+{
+ deviation_op = Operator "deviation"
+ deviation Operator_type.COMPOUND false;
+
+ deviation_list l
+ = (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
+ {
+ [n, s, s2] = sum_sum2_list l;
+ }
+
+ // return n, sum, sum of squares for a list of reals
+ sum_sum2_list x
+ = foldr accumulate [0, 0, 0] x
+ {
+ accumulate x sofar
+ = [n + 1, x + s, x * x + s2], is_real x
+ = [n + n', s + s', s2 + s2'], is_list x
+ = error "sum_sum2_list: not real or [real]"
+ {
+ [n, s, s2] = sofar;
+ [n', s', s2'] = sum_sum2_list x;
+ }
+ }
+}
+
+deviationze x
+ = oo_unary_function deviationze_op x, is_class x
+ = deviationze_image x, is_image x
+ = deviationze_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "deviationze")
+{
+ deviationze_op = Operator "deviationze"
+ deviationze Operator_type.COMPOUND false;
+
+ deviationze_list l
+ = (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
+ {
+ [n, s, s2] = sum_sum2_list l;
+ }
+
+ // return number of non-zero elements, sum, sum of squares for a list of
+ // reals
+ sum_sum2_list x
+ = foldr accumulate [0, 0, 0] x
+ {
+ accumulate x sofar
+ = sofar, is_real x && x == 0
+ = [n + 1, x + s, x * x + s2], is_real x
+ = [n + n', s + s', s2 + s2'], is_list x
+ = error "sum_sum2_list: not real or [real]"
+ {
+ [n, s, s2] = sofar;
+ [n', s', s2'] = sum_sum2_list x;
+ }
+ }
+
+ deviationze_image i
+ = ((sum2 - sum * sum / N) / (N - 1)) ** 0.5
+ {
+ w = get_width i;
+ h = get_height i;
+ b = get_bands i;
+
+ st = stats i;
+ sum = st.value?0?2;
+ sum2 = st.value?0?3;
+
+ // find non-zero pixels (not zero in all bands)
+ zp = im_notequal_vec i (replicate b 0);
+
+ // number of non-zero pixels
+ N = b * (mean zp * w * h) / 255;
+ }
+}
+
+// find the centre of gravity of a histogram
+gravity x
+ = oo_unary_function gravity_op x, is_class x
+ = im_hist_gravity x, is_hist x
+ = gravity_list x, is_list x
+ = error (_ "bad arguments to " ++ "gravity")
+{
+ gravity_op = Operator "gravity" gravity Operator_type.COMPOUND false;
+
+ // centre of gravity of a histogram... use the histogram to weight an
+ // identity, then sum, then find the mean element
+ im_hist_gravity h
+ = m
+ {
+ // make horizontal
+ h'
+ = rot270 h, get_width h == 1
+ = h, get_height h == 1
+ = error "width or height not 1";
+
+ // number of elements
+ w = get_width h';
+
+ // matching identity
+ i
+ = im_identity_ushort 1 w, w <= 2 ** 16 - 1
+ = make_xy w 1 ? 0;
+
+ // weight identity and sum
+ s = mean (i * h') * w;
+
+ // sum of original histogram
+ s' = mean h * w;
+
+ // weighted mean
+ m = s / s';
+ }
+
+ gravity_list l
+ = m
+ {
+ w = len l;
+
+ // matching identity
+ i = [0, 1 .. w - 1];
+
+ // weight identity and sum
+ s = sum (map2 multiply i l);
+
+ // sum of original histogram
+ s' = sum l;
+
+ // weighted mean
+ m = s / s';
+ }
+}
+
+project x
+ = oo_unary_function project_op x, is_class x
+ = im_project x, is_image x
+ = error (_ "bad arguments to " ++ "project")
+{
+ project_op = Operator "project" project Operator_type.COMPOUND false;
+}
+
+abs x
+ = oo_unary_function abs_op x, is_class x
+ = im_abs x, is_image x
+ = abs_cmplx x, is_complex x
+ = abs_num x, is_real x
+ = abs_list x, is_real_list x
+ = abs_list (map abs_list x), is_matrix x
+ = error (_ "bad arguments to " ++ "abs")
+{
+ abs_op = Operator "abs" abs Operator_type.COMPOUND false;
+
+ abs_list l = (sum (map square l)) ** 0.5;
+
+ abs_num n
+ = n, n >= 0
+ = -n;
+
+ abs_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
+}
+
+copy x
+ = oo_unary_function copy_op x, is_class x
+ = im_copy x, is_image x
+ = x
+{
+ copy_op = Operator "copy" copy Operator_type.COMPOUND_REWRAP false;
+}
+
+// like abs, but treat pixels as vectors ... ie. always get a 1-band image
+// back ... also treat matricies as lists of vectors
+// handy for dE from object difference
+abs_vec x
+ = oo_unary_function abs_vec_op x, is_class x
+ = abs_vec_image x, is_image x
+ = abs_vec_cmplx x, is_complex x
+ = abs_vec_num x, is_real x
+ = abs_vec_list x, is_real_list x
+ = mean (map abs_vec_list x), is_matrix x
+ = error (_ "bad arguments to " ++ "abs_vec")
+{
+ abs_vec_op = Operator "abs_vec"
+ abs_vec Operator_type.COMPOUND false;
+
+ abs_vec_list l = (sum (map square l)) ** 0.5;
+
+ abs_vec_num n
+ = n, n >= 0
+ = -n;
+
+ abs_vec_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
+
+ abs_vec_image im
+ = (sum (map square (bandsplit im))) ** 0.5;
+}
+
+transpose x
+ = oo_unary_function transpose_op x, is_class x
+ = transpose_image x, is_image x
+ = transpose_list x, is_listof is_list x
+ = error (_ "bad arguments to " ++ "transpose")
+{
+ transpose_op = Operator "transpose"
+ transpose Operator_type.COMPOUND_REWRAP false;
+
+ transpose_list l
+ = [], l' == []
+ = (map hd l') : (transpose_list (map tl l'))
+ {
+ l' = takewhile (not_equal []) l;
+ }
+
+ transpose_image = im_flipver @ im_rot270;
+}
+
+rot45 x
+ = oo_unary_function rot45_op x, is_class x
+ = error "rot45 image: not implemented", is_image x
+ = error (_ "bad arguments to " ++ "rot45")
+{
+ rot45_op = Operator "rot45"
+ rot45_object Operator_type.COMPOUND_REWRAP false;
+
+ rot45_object x
+ = rot45_matrix x, is_odd_square_matrix x
+ = error "rot45 image: not implemented", is_image x
+ = error (_ "bad arguments to " ++ "rot45");
+
+ // slow, but what the heck
+ rot45_matrix l = (im_rotate_dmask45 (Matrix l)).value;
+}
+
+// apply an image function to a [[real]] ... matrix is converted to a 1 band
+// image for processing
+apply_matrix_as_image fn m
+ = (get_value @ im_vips2mask @ fn @ im_mask2vips @ Matrix) m;
+
+// a general image/matrix operation where the mat version is most easily done
+// by converting mat->image->mat
+apply_matim_operation name fn x
+ = oo_unary_function class_op x, is_class x
+ = fn x, is_image x
+ = apply_matrix_as_image fn x, is_matrix x
+ = error (_ "bad arguments to " ++ name)
+{
+ class_op = Operator name
+ (apply_matim_operation name fn) Operator_type.COMPOUND_REWRAP false;
+}
+
+rot90 = apply_matim_operation "rot90" im_rot90;
+rot180 = apply_matim_operation "rot180" im_rot180;
+rot270 = apply_matim_operation "rot270" im_rot270;
+rotquad = apply_matim_operation "rotquad" im_rotquad;
+fliplr = apply_matim_operation "fliplr" im_fliphor;
+fliptb = apply_matim_operation "flipud" im_flipver;
+
+image_set_type type x
+ = oo_unary_function image_set_type_op x, is_class x
+ = im_copy_set x (to_real type)
+ (get_header "Xres" x) (get_header "Yres" x)
+ (get_header "Xoffset" x) (get_header "Yoffset" x),
+ is_image x
+ = error (_ "bad arguments to " ++ "image_set_type:" ++
+ print type ++ " " ++ print x)
+{
+ image_set_type_op = Operator "image_set_type"
+ (image_set_type type) Operator_type.COMPOUND_REWRAP false;
+}
+
+image_set_origin xoff yoff x
+ = oo_unary_function image_set_origin_op x, is_class x
+ = im_copy_set x
+ (get_header "Type" x)
+ (get_header "Xres" x) (get_header "Yres" x)
+ (to_real xoff) (to_real yoff),
+ is_image x
+ = error (_ "bad arguments to " ++ "image_set_origin")
+{
+ image_set_origin_op = Operator "image_set_origin"
+ (image_set_origin xoff yoff)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+cache tile_width tile_height max_tiles x
+ = oo_unary_function cache_op x, is_class x
+ = im_tile_cache_random x (to_real tile_width) (to_real tile_height)
+ (to_real max_tiles), is_image x
+ = error (_ "bad arguments to " ++ "cache")
+{
+ cache_op = Operator "cache"
+ (cache tile_width tile_height max_tiles)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+tile across down x
+ = oo_unary_function tile_op x, is_class x
+ = im_replicate x (to_real across) (to_real down), is_image x
+ = error (_ "bad arguments to " ++ "tile")
+{
+ tile_op = Operator "tile"
+ (tile across down) Operator_type.COMPOUND_REWRAP false;
+}
+
+grid tile_height across down x
+ = oo_unary_function grid_op x, is_class x
+ = im_grid x (to_real tile_height) (to_real across) (to_real down),
+ is_image x
+ = error (_ "bad arguments to " ++ "grid")
+{
+ grid_op = Operator "grid"
+ (grid tile_height across down) Operator_type.COMPOUND_REWRAP false;
+}
+
+max_pair a b
+ = a, a > b
+ = b;
+
+min_pair a b
+ = a, a < b
+ = b;
+
+range min value max = min_pair max (max_pair min value);
+
+max x
+ = oo_unary_function max_op x, is_class x
+ = im_max x, is_image x
+ = max_list x, is_list x
+ = x, is_number x
+ = error (_ "bad arguments to " ++ "max")
+{
+ max_op = Operator "max" max Operator_type.COMPOUND false;
+
+ max_list x
+ = error "max []", x == []
+ = foldr1 max_pair x, is_real_list x
+ = foldr1 max_pair (map max_list x), is_list x
+ = max x;
+}
+
+min x
+ = oo_unary_function min_op x, is_class x
+ = im_min x, is_image x
+ = min_list x, is_list x
+ = x, is_number x
+ = error (_ "bad arguments to " ++ "min")
+{
+ min_op = Operator "min" min Operator_type.COMPOUND false;
+
+ min_list x
+ = error "min []", x == []
+ = foldr1 min_pair x, is_real_list x
+ = foldr1 min_pair (map min_list x), is_list x
+ = min x;
+}
+
+maxpos x
+ = oo_unary_function maxpos_op x, is_class x
+ = im_maxpos x, is_image x
+ = maxpos_matrix x, is_matrix x
+ = maxpos_list x, is_list x
+ = error (_ "bad arguments to " ++ "maxpos")
+{
+ maxpos_op = Operator "maxpos" maxpos Operator_type.COMPOUND false;
+
+ maxpos_matrix m
+ = (-1, -1), m == [[]]
+ = (indexes?row, row)
+ {
+ max_value = max (Matrix m);
+ indexes = map (index (equal max_value)) m;
+ row = index (not_equal (-1)) indexes;
+ }
+
+ maxpos_list l
+ = -1, l == []
+ = index (equal (max l)) l;
+}
+
+minpos x
+ = oo_unary_function minpos_op x, is_class x
+ = im_minpos x, is_image x
+ = minpos_matrix x, is_matrix x
+ = minpos_list x, is_list x
+ = error (_ "bad arguments to " ++ "minpos")
+{
+ minpos_op = Operator "minpos" minpos Operator_type.COMPOUND false;
+
+ minpos_matrix m
+ = (-1, -1), m == [[]]
+ = (indexes?row, row)
+ {
+ min_value = min (Matrix m);
+ indexes = map (index (equal min_value)) m;
+ row = index (not_equal (-1)) indexes;
+ }
+
+ minpos_list l
+ = -1, l == []
+ = index (equal (min l)) l;
+}
+
+stats x
+ = oo_unary_function stats_op x, is_class x
+ = im_stats x, is_image x
+ = im_stats (to_image x).value, is_matrix x
+ = error (_ "bad arguments to " ++ "stats")
+{
+ stats_op = Operator "stats"
+ stats Operator_type.COMPOUND false;
+}
+
+e = 2.7182818284590452354;
+
+pi = 3.14159265358979323846;
+
+rad d = 2 * pi * (d / 360);
+
+deg r = 360 * r / (2 * pi);
+
+sign x
+ = oo_unary_function sign_op x, is_class x
+ = im_sign x, is_image x
+ = sign_cmplx x, is_complex x
+ = sign_num x, is_real x
+ = error (_ "bad arguments to " ++ "sign")
+{
+ sign_op = Operator "sign" sign Operator_type.COMPOUND_REWRAP false;
+
+ sign_num n
+ = 0, n == 0
+ = 1, n > 0
+ = -1;
+
+ sign_cmplx c
+ = (0, 0), mod == 0
+ = (re c / mod, im c / mod)
+ {
+ mod = abs c;
+ }
+}
+
+rint x
+ = oo_unary_function rint_op x, is_class x
+ = im_rint x, is_image x
+ = rint_value x, is_number x
+ = error (_ "bad arguments to " ++ "rint")
+{
+ rint_op = Operator "rint" rint Operator_type.ARITHMETIC false;
+
+ rint_value x
+ = (int) (x + 0.5), x > 0
+ = (int) (x - 0.5);
+}
+
+scale x
+ = oo_unary_function scale_op x, is_class x
+ = (unsigned char) x, is_number x
+ = im_scale x, is_image x
+ = scale_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "scale")
+{
+ scale_op = Operator "scale" scale Operator_type.COMPOUND_REWRAP false;
+
+ scale_list l
+ = apply_scale s o l
+ {
+ mn = find_limit min_pair l;
+ mx = find_limit max_pair l;
+ s = 255.0 / (mx - mn);
+ o = -(mn * s);
+ }
+
+ find_limit fn l
+ = find_limit fn (map (find_limit fn) l), is_listof is_list l
+ = foldr1 fn l;
+
+ apply_scale s o x
+ = x * s + o, is_number x
+ = map (apply_scale s o) x;
+}
+
+scaleps x
+ = oo_unary_function scale_op x, is_class x
+ = im_scaleps x, is_image x
+ = error (_ "bad arguments to " ++ "scale")
+{
+ scale_op = Operator "scaleps"
+ scaleps Operator_type.COMPOUND_REWRAP false;
+}
+
+fwfft x
+ = oo_unary_function fwfft_op x, is_class x
+ = im_fwfft x, is_image x
+ = error (_ "bad arguments to " ++ "fwfft")
+{
+ fwfft_op = Operator "fwfft"
+ fwfft Operator_type.COMPOUND_REWRAP false;
+}
+
+invfft x
+ = oo_unary_function invfft_op x, is_class x
+ = im_invfftr x, is_image x
+ = error (_ "bad arguments to " ++ "invfft")
+{
+ invfft_op = Operator "invfft"
+ invfft Operator_type.COMPOUND_REWRAP false;
+}
+
+falsecolour x
+ = oo_unary_function falsecolour_op x, is_class x
+ = image_set_type Image_type.sRGB (im_falsecolour x), is_image x
+ = error (_ "bad arguments to " ++ "falsecolour")
+{
+ falsecolour_op = Operator "falsecolour"
+ falsecolour Operator_type.COMPOUND_REWRAP false;
+}
+
+polar x
+ = oo_unary_function polar_op x, is_class x
+ = im_c2amph x, is_image x
+ = polar_cmplx x, is_complex x
+ = error (_ "bad arguments to " ++ "polar")
+{
+ polar_op = Operator "polar" polar Operator_type.COMPOUND false;
+
+ polar_cmplx r
+ = (l, a)
+ {
+ a
+ = 270, x == 0 && y < 0
+ = 90, x == 0 && y >= 0
+ = 360 + atan (y / x), x > 0 && y < 0
+ = atan (y / x), x > 0 && y >= 0
+ = 180 + atan (y / x);
+
+ l = (x ** 2 + y ** 2) ** 0.5;
+
+ x = re r;
+ y = im r;
+ }
+}
+
+rectangular x
+ = oo_unary_function rectangular_op x, is_class x
+ = im_c2rect x, is_image x
+ = rectangular_cmplx x, is_complex x
+ = error (_ "bad arguments to " ++ "rectangular")
+{
+ rectangular_op = Operator "rectangular"
+ rectangular Operator_type.COMPOUND false;
+
+ rectangular_cmplx p
+ = (x, y)
+ {
+ l = re p;
+ a = im p;
+
+ x = l * cos a;
+ y = l * sin a;
+ }
+}
+
+// we can't use colour_unary: that likes 3 band only
+recomb matrix x
+ = oo_unary_function recomb_op x, is_class x
+ = im_recomb x matrix, is_image x
+ = recomb_real_list x, is_real_list x
+ = map recomb_real_list x, is_matrix x
+ = error (_ "bad arguments to " ++ "recomb")
+{
+ // COMPOUND_REWRAP ... signal to the colour class to go to image and
+ // back
+ recomb_op = Operator "recomb"
+ (recomb matrix) Operator_type.COMPOUND_REWRAP false;
+
+ // process [1,2,3 ..] as an image
+ recomb_real_list l
+ = (to_matrix im').value?0
+ {
+ im = (float) (to_image (Vector l)).value;
+ im' = recomb matrix im;
+ }
+}
+
+extract_area x y w h obj
+ = oo_unary_function extract_area_op obj, is_class obj
+ = im_extract_area obj x' y' w' h', is_image obj
+ = map (extract_range x' w') (extract_range y' h' obj), is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_area")
+{
+ x' = to_real x;
+ y' = to_real y;
+ w' = to_real w;
+ h' = to_real h;
+
+ extract_area_op = Operator "extract_area" (extract_area x y w h)
+ Operator_type.COMPOUND_REWRAP false;
+
+ extract_range from length list
+ = (take length @ drop from) list;
+}
+
+extract_band b obj = subscript obj b;
+
+extract_row y obj
+ = oo_unary_function extract_row_op obj, is_class obj
+ = extract_area 0 y' (get_width obj) 1 obj, is_image obj
+ = [obj?y'], is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_row")
+{
+ y' = to_real y;
+
+ extract_row_op = Operator "extract_row" (extract_row y)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+extract_column x obj
+ = oo_unary_function extract_column_op obj, is_class obj
+ = extract_area x' 0 1 height obj, is_image obj
+ = map (\row [row?x']) obj, is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_column")
+{
+ x' = to_real x;
+ height = get_header "Ysize" obj;
+
+ extract_column_op = Operator "extract_column" (extract_column x)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+blend cond in1 in2
+ = oo_binary_function blend_op cond [in1,in2], is_class cond
+ = im_blend (get_image cond) (get_image in1) (get_image in2),
+ has_image cond && has_image in1 && has_image in2
+ = error (_ "bad arguments to " ++ "blend" ++ ": " ++
+ join_sep ", " (map print [cond, in1, in2]))
+{
+ blend_op = Operator "blend"
+ blend_obj Operator_type.COMPOUND_REWRAP false;
+
+ blend_obj cond x
+ = blend_result_image
+ {
+ [then_part, else_part] = x;
+
+ // get things about our output from inputs in this order
+ objects = [then_part, else_part, cond];
+
+ // properties of our output image
+ target_width = get_member_list has_width get_width objects;
+ target_height = get_member_list has_height get_height objects;
+ target_bands = get_member_list has_bands get_bands objects;
+ target_format = get_member_list has_format get_format objects;
+ target_type = get_member_list has_type get_type objects;
+
+ to_image x
+ = to_image_size target_width target_height
+ target_bands target_format x;
+
+ [then_image, else_image] = map to_image [then_part, else_part];
+
+ blend_result_image = image_set_type target_type
+ (im_blend cond then_image else_image);
+ }
+}
+
+// do big first: we want to keep big's class, if possible
+// eg. big is a Plot, small is a 1x1 Image
+insert x y small big
+ = oo_binary'_function insert_op small big, is_class big
+ = oo_binary_function insert_op small big, is_class small
+ = im_insert big small (to_real x) (to_real y),
+ is_image small && is_image big
+ = error (_ "bad arguments to " ++ "insert")
+{
+ insert_op = Operator "insert"
+ (insert x y) Operator_type.COMPOUND_REWRAP false;
+}
+
+insert_noexpand x y small big
+ = oo_binary_function insert_noexpand_op small big, is_class small
+ = oo_binary'_function insert_noexpand_op small big, is_class big
+ = im_insert_noexpand big small (to_real x) (to_real y),
+ is_image small && is_image big
+ = error (_ "bad arguments to " ++ "insert_noexpand")
+{
+ insert_noexpand_op = Operator "insert_noexpand"
+ (insert_noexpand x y) Operator_type.COMPOUND_REWRAP false;
+}
+
+decode im
+ = oo_unary_function decode_op im, is_class im
+ = decode_im im, is_image im
+ = error (_ "bad arguments to " ++ "decode")
+{
+ decode_op = Operator "decode"
+ decode Operator_type.COMPOUND_REWRAP false;
+
+ decode_im im
+ = im_LabQ2Lab im, get_coding im == Image_coding.LABPACK
+ = im_rad2float im, get_coding im == Image_coding.RAD
+ = im;
+}
+
+measure_draw across down measure image
+ = mark
+{
+ patch_width = image.width / across;
+ sample_width = patch_width * (measure / 100);
+ left_margin = (patch_width - sample_width) / 2;
+ patch_height = image.height / down;
+ sample_height = patch_height * (measure / 100);
+ top_margin = (patch_height - sample_height) / 2;
+
+ cods = [[x * patch_width + left_margin, y * patch_height + top_margin] ::
+ y <- [0 .. down - 1]; x <- [0 .. across - 1]];
+ x = map (extract 0) cods;
+ y = map (extract 1) cods;
+
+ outer = mkim [$pixel => 255] sample_width sample_height 1;
+ inner = mkim [] (sample_width - 4) (sample_height - 4) 1;
+ patch = insert 2 2 inner outer;
+
+ bg = mkim [] image.width image.height 1;
+
+ mask = Image (im_insertset bg.value patch.value x y);
+
+ image' = colour_transform_to Image_type.sRGB image;
+
+ mark = if mask then Vector [0, 255, 0] else image';
+}
+
+measure_sample across down measure image
+ = measures
+{
+ patch_width = image.width / across;
+ sample_width = patch_width * (measure / 100);
+ left_margin = (patch_width - sample_width) / 2;
+ patch_height = image.height / down;
+ sample_height = patch_height * (measure / 100);
+ top_margin = (patch_height - sample_height) / 2;
+
+ cods = [[x * patch_width + left_margin, y * patch_height + top_margin] ::
+ y <- [0 .. down - 1]; x <- [0 .. across - 1]];
+
+ image' = decode image;
+ patches = map (\p extract_area p?0 p?1 sample_width sample_height image')
+ cods;
+ measures = Matrix (map (map mean) (map bandsplit patches));
+}
+
+extract_bands b n obj
+ = oo_unary_function extract_bands_op obj, is_class obj
+ = im_extract_bands obj (to_real b) (to_real n), is_image obj
+ = error (_ "bad arguments to " ++ "extract_bands")
+{
+ extract_bands_op = Operator "extract_bands"
+ (extract_bands b n) Operator_type.COMPOUND_REWRAP false;
+}
+
+invert x
+ = oo_unary_function invert_op x, is_class x
+ = im_invert x, is_image x
+ = 255 - x, is_real x
+ = error (_ "bad arguments to " ++ "invert")
+{
+ invert_op = Operator "invert" invert Operator_type.COMPOUND false;
+}
+
+transform ipol wrap params image
+ = oo_unary_function transform_op image, is_class image
+ = im_transform image
+ (to_matrix params) (to_real ipol) (to_real wrap), is_image image
+ = error (_ "bad arguments to " ++ "transform")
+{
+ transform_op = Operator "transform"
+ (transform ipol wrap params)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+transform_search max_error max_iterations order ipol wrap sample reference
+ = oo_binary_function transform_search_op sample reference, is_class sample
+ = oo_binary'_function transform_search_op sample reference,
+ is_class reference
+ = im_transform_search sample reference
+ (to_real max_error) (to_real max_iterations) (to_real order)
+ (to_real ipol) (to_real wrap),
+ is_image sample && is_image reference
+ = error (_ "bad arguments to " ++ "transform_search")
+{
+ transform_search_op = Operator "transform_search"
+ (transform_search max_error max_iterations order ipol wrap)
+ Operator_type.COMPOUND false;
+}
+
+rotate interp angle image
+ = oo_binary_function rotate_op angle image, is_class angle
+ = oo_binary'_function rotate_op angle image, is_class image
+ = im_affinei_all image interp.value a (-b) b a 0 0,
+ is_real angle && is_image image
+ = error (_ "bad arguments to " ++ "rotate")
+{
+ rotate_op = Operator "rotate"
+ (rotate interp) Operator_type.COMPOUND_REWRAP false;
+ a = cos angle;
+ b = sin angle;
+}
+
+matrix_binary fn a b
+ = itom (fn (mtoi a) (mtoi b))
+{
+ mtoi x = im_mask2vips (Matrix x);
+ itom x = (im_vips2mask x).value;
+}
+
+join_lr a b
+ = oo_binary_function join_lr_op a b, is_class a
+ = oo_binary'_function join_lr_op a b, is_class b
+ = join_im a b, is_image a && is_image b
+ = matrix_binary join_im a b, is_matrix a && is_matrix b
+ = error (_ "bad arguments to " ++ "join_lr")
+{
+ join_lr_op = Operator "join_lr"
+ join_lr Operator_type.COMPOUND_REWRAP false;
+
+ join_im a b = insert (get_width a) 0 b a;
+}
+
+join_tb a b
+ = oo_binary_function join_tb_op a b, is_class a
+ = oo_binary'_function join_tb_op a b, is_class b
+ = join_im a b, is_image a && is_image b
+ = matrix_binary join_im a b, is_matrix a && is_matrix b
+ = error (_ "bad arguments to " ++ "join_tb")
+{
+ join_tb_op = Operator "join_tb"
+ join_tb Operator_type.COMPOUND_REWRAP false;
+
+ join_im a b = insert 0 (get_height a) b a;
+}
+
+conj x
+ = oo_unary_function conj_op x, is_class x
+ = (re x, -im x),
+ is_complex x ||
+ (is_image x && format == Image_format.COMPLEX) ||
+ (is_image x && format == Image_format.DPCOMPLEX)
+ // assume it's some sort of real
+ = x
+{
+ format = get_header "BandFmt" x;
+ conj_op = Operator "conj" conj Operator_type.COMPOUND false;
+}
+
+clip2fmt format image
+ = oo_unary_function clip2fmt_op image, is_class image
+ = im_clip2fmt image (to_real format), is_image image
+ = error (_ "bad arguments to " ++ "clip2fmt")
+{
+ clip2fmt_op = Operator "clip2fmt"
+ (clip2fmt format) Operator_type.COMPOUND_REWRAP false;
+}
+
+embed type x y w h im
+ = oo_unary_function embed_op im, is_class im
+ = im_embed im (to_real type)
+ (to_real x) (to_real y) (to_real w) (to_real h), is_image im
+ = error (_ "bad arguments to " ++ "embed")
+{
+ embed_op = Operator "embed"
+ (embed type x y w h) Operator_type.COMPOUND_REWRAP false;
+}
+
+/* Morph a mask with a [[real]] matrix ... turn m2 into an image, morph it
+ * with m1, turn it back to a matrix again.
+ */
+_morph_2_masks fn m1 m2
+ = m''
+{
+ // The [[real]] can contain 128 values ... squeeze them out!
+ image = im_mask2vips (Matrix m2) == 255;
+ m2_width = get_width image;
+ m2_height = get_height image;
+
+ // need to embed m2 in an image large enough for us to be able to
+ // position m1 all around the edges, with a 1 pixel overlap
+ image' = embed 0
+ (m1.width / 2) (m1.height / 2)
+ (m2_width + (m1.width - 1)) (m2_height + (m1.height - 1))
+ image;
+
+ // morph!
+ image'' = fn m1 image';
+
+ // back to mask
+ m' = im_vips2mask ((double) image'');
+
+ // Turn 0 in output to 128 (don't care).
+ m''
+ = map (map fn) m'.value
+ {
+ fn a
+ = 128, a == 0;
+ = a;
+ }
+}
+
+dilate mask image
+ = oo_unary_function dilate_op image, is_class image
+ = im_dilate image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "dilate")
+{
+ dilate_op = Operator "dilate"
+ dilate_object Operator_type.COMPOUND_REWRAP false;
+
+ dilate_object x
+ = _morph_2_masks dilate mask x, is_matrix x
+ = dilate mask x;
+}
+
+erode mask image
+ = oo_unary_function erode_op image, is_class image
+ = im_erode image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "erode")
+{
+ erode_op = Operator "erode"
+ erode_object Operator_type.COMPOUND_REWRAP false;
+
+ erode_object x
+ = _morph_2_masks erode mask x, is_matrix x
+ = erode mask x;
+}
+
+conv mask image
+ = oo_unary_function conv_op image, is_class image
+ = im_conv image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "conv" ++ ": " ++
+ "conv (" ++ print mask ++ ") (" ++ print image ++ ")")
+{
+ conv_op = Operator "conv"
+ (conv mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convf mask image
+ = oo_unary_function convf_op image, is_class image
+ = im_conv_f image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convf" ++ ": " ++
+ "convf (" ++ print mask ++ ") (" ++ print image ++ ")")
+{
+ convf_op = Operator "convf"
+ (convf mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convsep mask image
+ = oo_unary_function convsep_op image, is_class image
+ = im_convsep image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convsep")
+{
+ convsep_op = Operator "convsep"
+ (convsep mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+aconvsep layers mask image
+ = oo_unary_function aconvsep_op image, is_class image
+ = im_aconvsep image (to_matrix mask) (to_real layers), is_image image
+ = error (_ "bad arguments to " ++ "aconvsep")
+{
+ aconvsep_op = Operator "aconvsep"
+ (aconvsep layers mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convsepf mask image
+ = oo_unary_function convsepf_op image, is_class image
+ = im_convsep_f image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convsepf")
+{
+ convsepf_op = Operator "convsepf"
+ (convsepf mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+rank w h n image
+ = oo_unary_function rank_op image, is_class image
+ = im_rank image (to_real w) (to_real h) (to_real n), is_image image
+ = error (_ "bad arguments to " ++ "rank")
+{
+ rank_op = Operator "rank"
+ (rank w h n) Operator_type.COMPOUND_REWRAP false;
+}
+
+rank_image n x
+ = rlist x.value, is_Group x
+ = rlist x, is_list x
+ = error (_ "bad arguments to " ++ "rank_image")
+{
+ rlist l
+ = wrapper ranked, has_wrapper
+ = ranked
+ {
+ has_wrapper = has_member_list (has_member "Image") l;
+ wrapper = get_member_list (has_member "Image") (get_member "Image") l;
+ ranked = im_rank_image (map get_image l) (to_real n);
+ }
+}
+
+// find the correlation surface for a small image within a big one
+correlate small big
+ = oo_binary_function correlate_op small big, is_class small
+ = oo_binary'_function correlate_op small big, is_class big
+ = im_spcor big small, is_image small && is_image big
+ = error (_ "bad arguments to " ++ "correlate")
+{
+ correlate_op = Operator "correlate"
+ correlate Operator_type.COMPOUND_REWRAP false;
+}
+
+// just sum-of-squares-of-differences
+correlate_fast small big
+ = oo_binary_function correlate_fast_op small big, is_class small
+ = oo_binary'_function correlate_fast_op small big, is_class big
+ = im_fastcor big small, is_image small && is_image big
+ = error (_ "bad arguments to " ++ "correlate_fast")
+{
+ correlate_fast_op = Operator "correlate_fast"
+ correlate_fast Operator_type.COMPOUND_REWRAP false;
+}
+
+// set Type, wrap as Plot_hist if it's a class
+hist_tag x
+ = oo_unary_function hist_tag_op x, is_class x
+ = image_set_type Image_type.HISTOGRAM x, is_image x
+ = error (_ "bad arguments to " ++ "hist_tag")
+{
+ hist_tag_op = Operator "hist_tag"
+ (Plot_histogram @ hist_tag) Operator_type.COMPOUND false;
+}
+
+hist_find x
+ = oo_unary_function hist_find_op x, is_class x
+ = im_histgr x (-1), is_image x
+ = error (_ "bad arguments to " ++ "hist_find")
+{
+ hist_find_op = Operator "hist_find"
+ (Plot_histogram @ hist_find) Operator_type.COMPOUND false;
+}
+
+hist_find_nD bins image
+ = oo_unary_function hist_find_nD_op image, is_class image
+ = im_histnD image (to_real bins), is_image image
+ = error (_ "bad arguments to " ++ "hist_find_nD")
+{
+ hist_find_nD_op = Operator "hist_find_nD"
+ (hist_find_nD bins) Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_find_indexed index value
+ = oo_binary_function hist_find_indexed_op index value, is_class index
+ = oo_binary'_function hist_find_indexed_op index value, is_class value
+ = im_hist_indexed index value, is_image index && is_image value
+ = error (_ "bad arguments to " ++ "hist_find_indexed")
+{
+ hist_find_indexed_op = Operator "hist_find_indexed"
+ (compose (compose Plot_histogram) hist_find_indexed)
+ Operator_type.COMPOUND false;
+}
+
+hist_map hist image
+ = oo_binary_function hist_map_op hist image, is_class hist
+ = oo_binary'_function hist_map_op hist image, is_class image
+ = im_maplut image hist, is_image hist && is_image image
+ = error (_ "bad arguments to " ++ "hist_map")
+{
+ // can't use rewrap, as we want to always wrap as image
+ hist_map_op = Operator "hist_map"
+ (compose (compose Image) hist_map) Operator_type.COMPOUND false;
+}
+
+hist_cum hist
+ = oo_unary_function hist_cum_op hist, is_class hist
+ = im_histcum hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_cum")
+{
+ hist_cum_op = Operator "hist_cum"
+ hist_cum Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_diff hist
+ = oo_unary_function hist_diff_op hist, is_class hist
+ = im_histdiff hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_diff")
+{
+ hist_diff_op = Operator "hist_diff"
+ hist_diff Operator_type.COMPOUND_REWRAP false;
+
+ im_histdiff h
+ = (conv (Matrix [[-1, 1]]) @ clip2fmt (fmt (get_format h))) h
+ {
+ // up the format so it can represent the whole range of
+ // possible values from this mask
+ fmt x
+ = Image_format.SHORT,
+ x == Image_format.UCHAR || x == Image_format.CHAR
+ = Image_format.INT,
+ x == Image_format.USHORT || x == Image_format.SHORT ||
+ x == Image_format.UINT
+ = x;
+ }
+}
+
+hist_norm hist
+ = oo_unary_function hist_norm_op hist, is_class hist
+ = im_histnorm hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_norm")
+{
+ hist_norm_op = Operator "hist_norm"
+ hist_norm Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_inv hist
+ = oo_unary_function hist_inv_op hist, is_class hist
+ = inv hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_inv")
+{
+ hist_inv_op = Operator "hist_inv"
+ hist_inv Operator_type.COMPOUND_REWRAP false;
+
+ inv im
+ = im_invertlut (to_matrix im''') len
+ {
+ // need a vertical doublemask
+ im'
+ = rot90 im, get_width im > 1 && get_height im == 1
+ = im, get_width im == 1 && get_height im > 1
+ = error (_ "not a hist");
+ len = get_height im';
+
+ // values must be scaled to 0 - 1
+ im'' = im' / (max im');
+
+ // add an index column on the left
+ // again, must be in 0-1
+ y = ((make_xy 1 len)?1) / len;
+ im''' = y ++ im'';
+ }
+}
+
+hist_match in ref
+ = oo_binary_function hist_match_op in ref, is_class in
+ = oo_binary'_function hist_match_op in ref, is_class ref
+ = im_histspec in ref, is_image in && is_image ref
+ = error (_ "bad arguments to " ++ "hist_match")
+{
+ hist_match_op = Operator "hist_match"
+ hist_match Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_equalize x = hist_map ((hist_norm @ hist_cum @ hist_find) x) x;
+
+hist_equalize_local w h l image
+ = oo_unary_function hist_equalize_local_op image, is_class image
+ = out, is_image image
+ = error (_ "bad arguments to " ++ "hist_equalize_local")
+{
+ hist_equalize_local_op = Operator "hist_equalize_local"
+ (hist_equalize_local w h l) Operator_type.COMPOUND_REWRAP false;
+
+ [out] = vips_call "hist_local"
+ [image, to_real w, to_real h] [$max_slope => to_real l];
+}
+
+// find the threshold below which are percent of the image (percent in [0,1])
+// eg. hist_thresh 0.1 x == 12, then x < 12 will light up 10% of the pixels
+hist_thresh percent image
+ = x
+{
+ // our own normaliser ... we don't want to norm channels separately
+ // norm to [0,1]
+ my_hist_norm h = h / max h;
+
+ // normalised cumulative hist
+ // we sum the channels before we normalise, because we want to treat them
+ // all the same
+ h = (my_hist_norm @ sum @ bandsplit @ hist_cum @ hist_find)
+ image.value;
+
+ // threshold that, then use im_profile to search for the x position in the
+ // histogram
+ x = mean (im_profile (h > percent) 1);
+}
+
+/* Sometimes useful, despite plotting now being built in, for making
+ * diagnostic images.
+ */
+hist_plot hist
+ = oo_unary_function hist_plot_op hist, is_class hist
+ = im_histplot hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_plot")
+{
+ hist_plot_op = Operator "hist_plot"
+ (Image @ hist_plot) Operator_type.COMPOUND false;
+}
+
+zerox d x
+ = oo_unary_function zerox_op x, is_class x
+ = im_zerox x (to_real d), is_image x
+ = error (_ "bad arguments to " ++ "zerox")
+{
+ zerox_op = Operator "zerox"
+ (zerox d) Operator_type.COMPOUND_REWRAP false;
+}
+
+reduce kernel xshr yshr image
+ = oo_unary_function reduce_op image, is_class image
+ = reduce_im image, is_image image
+ = error (_ "bad arguments to " ++ "reduce")
+{
+ reduce_op = Operator "reduce"
+ reduce_im Operator_type.COMPOUND_REWRAP false;
+
+ reduce_im im
+ = out
+ {
+ [out] = vips_call "reduce" [im, xshr, yshr] [$kernel => kernel.value];
+ }
+}
+
+similarity interpolate scale angle image
+ = oo_unary_function similarity_op image, is_class image
+ = similarity_im image, is_image image
+ = error (_ "bad arguments to " ++ "similarity")
+{
+ similarity_op = Operator "similarity"
+ similarity_im Operator_type.COMPOUND_REWRAP false;
+
+ similarity_im im
+ = out
+ {
+ [out] = vips_call "similarity" [im] [
+ $interpolate => interpolate.value,
+ $scale => scale,
+ $angle => angle
+ ];
+ }
+}
+
+resize kernel xfac yfac image
+ = oo_unary_function resize_op image, is_class image
+ = resize_im image, is_image image
+ = error (_ "bad arguments to " ++ "resize")
+{
+ resize_op = Operator "resize"
+ resize_im Operator_type.COMPOUND_REWRAP false;
+
+ xfac' = to_real xfac;
+ yfac' = to_real yfac;
+
+ rxfac' = 1 / xfac';
+ ryfac' = 1 / yfac';
+
+ is_nn = kernel.type == Kernel_type.NEAREST_NEIGHBOUR;
+
+ resize_im im
+ // upscale by integer factor, nearest neighbour
+ = im_zoom im xfac' yfac',
+ is_int xfac' && is_int yfac' &&
+ xfac' >= 1 && yfac' >= 1 &&
+ is_nn
+
+ // downscale by integer factor, nearest neighbour
+ = im_subsample im rxfac' ryfac',
+ is_int rxfac' && is_int ryfac' &&
+ rxfac' >= 1 && ryfac' >= 1 &&
+ is_nn
+
+ // everything else ... we just pass on to vips_resize()
+ = vips_resize kernel xfac' yfac' im
+ {
+ vips_resize kernel hscale vscale im
+ = out
+ {
+ [out] = vips_call "resize" [im, hscale]
+ [$vscale => vscale, $kernel => kernel.value];
+ }
+ }
+}
+
+sharpen radius x1 y2 y3 m1 m2 in
+ = oo_unary_function sharpen_op in, is_class in
+ = im_sharpen in (to_real radius)
+ (to_real x1) (to_real y2) (to_real y3)
+ (to_real m1) (to_real m2), is_image in
+ = error (_ "bad arguments to " ++ "sharpen")
+{
+ sharpen_op = Operator "sharpen"
+ (sharpen radius x1 y2 y3 m1 m2)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+tone_analyse s m h sa ma ha in
+ = oo_unary_function tone_analyse_op in, is_class in
+ = im_tone_analyse in
+ (to_real s) (to_real m) (to_real h)
+ (to_real sa) (to_real ma) (to_real ha), is_image in
+ = error (_ "bad arguments to " ++ "tone_analyse")
+{
+ tone_analyse_op = Operator "tone_analyse"
+ (Plot_histogram @ tone_analyse s m h sa ma ha)
+ Operator_type.COMPOUND false;
+}
+
+tone_map hist image
+ = oo_binary_function tone_map_op hist image, is_class hist
+ = oo_binary'_function tone_map_op hist image, is_class image
+ = im_tone_map image hist, is_image hist && is_image image
+ = error (_ "bad arguments to " ++ "tone_map")
+{
+ tone_map_op = Operator "tone_map"
+ tone_map Operator_type.COMPOUND_REWRAP false;
+}
+
+tone_build fmt b w s m h sa ma ha
+ = (Plot_histogram @ clip2fmt fmt)
+ (im_tone_build_range mx mx
+ (to_real b) (to_real w)
+ (to_real s) (to_real m) (to_real h)
+ (to_real sa) (to_real ma) (to_real ha))
+{
+ mx = Image_format.maxval fmt;
+}
+
+icc_export depth profile intent in
+ = oo_unary_function icc_export_op in, is_class in
+ = im_icc_export_depth in
+ (to_real depth) (expand profile) (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_export")
+{
+ icc_export_op = Operator "icc_export"
+ (icc_export depth profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_import profile intent in
+ = oo_unary_function icc_import_op in, is_class in
+ = im_icc_import in
+ (expand profile) (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_import")
+{
+ icc_import_op = Operator "icc_import"
+ (icc_import profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_import_embedded intent in
+ = oo_unary_function icc_import_embedded_op in, is_class in
+ = im_icc_import_embedded in (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_import_embedded")
+{
+ icc_import_embedded_op = Operator "icc_import_embedded"
+ (icc_import_embedded intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_transform in_profile out_profile intent in
+ = oo_unary_function icc_transform_op in, is_class in
+ = im_icc_transform in
+ (expand in_profile) (expand out_profile)
+ (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_transform")
+{
+ icc_transform_op = Operator "icc_transform"
+ (icc_transform in_profile out_profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_ac2rc profile in
+ = oo_unary_function icc_ac2rc_op in, is_class in
+ = im_icc_ac2rc in (expand profile), is_image in
+ = error (_ "bad arguments to " ++ "icc_ac2rc")
+{
+ icc_ac2rc_op = Operator "icc_ac2rc"
+ (icc_ac2rc profile)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+
+// Given a xywh rect, flip it around so wh are always positive
+rect_normalise x y w h
+ = [x', y', w', h']
+{
+ x'
+ = x + w, w < 0
+ = x;
+ y'
+ = y + h, h < 0
+ = y;
+ w' = abs w;
+ h' = abs h;
+}
+
+draw_flood_blob x y ink image
+ = oo_unary_function draw_flood_blob_op image, is_class image
+ = im_draw_flood_blob image (to_real x) (to_real y) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_flood_blob")
+{
+ draw_flood_blob_op = Operator "draw_flood_blob"
+ (draw_flood_blob x y ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_flood x y ink image
+ = oo_unary_function draw_flood_op image, is_class image
+ = im_draw_flood image (to_real x) (to_real y) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_flood")
+{
+ draw_flood_op = Operator "draw_flood"
+ (draw_flood x y ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+/* This version of draw_rect uses insert_noexpand and will be fast, even for
+ * huge images.
+ */
+draw_rect_width x y w h f t ink image
+ = oo_unary_function draw_rect_width_op image, is_class image
+ = my_draw_rect_width image (to_int x) (to_int y)
+ (to_int w) (to_int h) (to_int f) (to_int t) ink,
+ is_image image
+ = error (_ "bad arguments to " ++ "draw_rect_width")
+{
+ draw_rect_width_op = Operator "draw_rect_width"
+ (draw_rect_width x y w h f t ink)
+ Operator_type.COMPOUND_REWRAP false;
+
+ my_draw_rect_width image x y w h f t ink
+ = insert x' y' (block w' h') image, f == 1
+ = (insert x' y' (block w' t) @
+ insert (x' + w' - t) y' (block t h') @
+ insert x' (y' + h' - t) (block w' t) @
+ insert x' y' (block t h')) image
+ {
+ insert = insert_noexpand;
+ block w h = image_new w h (get_bands image) (get_format image)
+ (get_coding image) (get_type image) ink' 0 0;
+ ink'
+ = Vector ink, is_list ink
+ = ink;
+ [x', y', w', h'] = rect_normalise x y w h;
+ }
+}
+
+/* Default to 1 pixel wide edges.
+ */
+draw_rect x y w h f ink image
+ = draw_rect_width x y w h f 1 ink image;
+
+/* This version of draw_rect uses the paintbox rect draw operation. It is an
+ * inplace operation and will use bucketloads of memory.
+ */
+draw_rect_paintbox x y w h f ink image
+ = oo_unary_function draw_rect_op image, is_class image
+ = im_draw_rect image (to_real x) (to_real y)
+ (to_real w) (to_real h) (to_real f) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_rect_paintbox")
+{
+ draw_rect_op = Operator "draw_rect"
+ (draw_rect x y w h f ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_circle x y r f ink image
+ = oo_unary_function draw_circle_op image, is_class image
+ = im_draw_circle image (to_real x) (to_real y)
+ (to_real r) (to_real f) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_circle")
+{
+ draw_circle_op = Operator "draw_circle"
+ (draw_circle x y r f ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_line x1 y1 x2 y2 ink image
+ = oo_unary_function draw_line_op image, is_class image
+ = im_draw_line image (to_real x1) (to_real y1)
+ (to_real x2) (to_real y2) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_line")
+{
+ draw_line_op = Operator "draw_line"
+ (draw_line x1 y1 x2 y2 ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+print_base base in
+ = oo_unary_function print_base_op in, is_class in
+ = map (print_base base) in, is_list in
+ = print_base_real, is_real in
+ = error (_ "bad arguments to " ++ "print_base")
+{
+ print_base_op
+ = Operator "print_base" (print_base base) Operator_type.COMPOUND false;
+
+ print_base_real
+ = error "print_base: bad base", base < 2 || base > 16
+ = "0", in < 0 || chars == []
+ = reverse chars
+ {
+ digits = map (\x x % base)
+ (takewhile (not_equal 0)
+ (iterate (\x idivide x base) in));
+ chars = map tohd digits;
+
+ tohd x
+ = (char) ((int) '0' + x), x < 10
+ = (char) ((int) 'A' + (x - 10));
+ }
+}
+
+/* id x: the identity function
+ *
+ * id :: * -> *
+ */
+id x = x;
+
+/* const x y: junk y, return x
+ *
+ * (const 3) is the function that always returns 3.
+ * const :: * -> ** -> *
+ */
+const x y = x;
+
+/* converse fn a b: swap order of args to fn
+ *
+ * converse fn a b == fn b a
+ * converse :: (* -> ** -> ***) -> ** -> * -> ***
+ */
+converse fn a b = fn b a;
+
+/* fix fn x: find the fixed point of a function
+ */
+fix fn x = limit (iterate fn x);
+
+/* until pred fn n: apply fn to n until pred succeeds; return that value
+ *
+ * until (more 1000) (multiply 2) 1 = 1024
+ * until :: (* -> bool) -> (* -> *) -> * -> *
+ */
+until pred fn n
+ = n, pred n
+ = until pred fn (fn n);
+
+/* Infinite list of primes.
+ */
+primes
+ = 1 : (sieve [2 ..])
+{
+ sieve l = hd l : sieve (filter (nmultiple (hd l)) (tl l));
+ nmultiple n x = x / n != (int) (x / n);
+}
+
+/* Map an n-ary function (pass the args as a list) over groups of objects.
+ * The objects can be single objects or groups. If more than one
+ * object is a group, we iterate for the length of the smallest group.
+ * Don't loop over plain lists, since we want (eg.) "mean [1, 2, 3]" to work.
+ * Treat [] as no-value --- ie. if any of the inputs are [] we put [] into the
+ * output and don't apply the function.
+
+ copy-pasted into _types, keep in sync
+
+ */
+map_nary fn args
+ = fn args, groups == []
+ = Group (map process [0, 1 .. shortest - 1])
+{
+ // find all the group arguments
+ groups = filter is_Group args;
+
+ // what's the length of the shortest group arg?
+ shortest = foldr1 min_pair (map (len @ get_value) groups);
+
+ // process index n ... pull that member from each argument
+ // recurse to handle application, so we work for nested groups too
+ process n
+ = NULL, any (map (is_noval n) args)
+ = map_nary fn (map (extract n) args)
+ {
+ extract n arg
+ = arg.value?n, is_Group arg
+ = arg;
+
+ is_noval n arg = is_Group arg && arg.value?n == NULL;
+ }
+}
+
+/* Map a 1-ary function over an object.
+ */
+map_unary fn a = map_nary (list_1ary fn) [a];
+
+/* Map a 2-ary function over a pair of objects.
+ */
+map_binary fn a b = map_nary (list_2ary fn) [a, b];
+
+/* Map a 3-ary function over three objects.
+ */
+map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
+
+/* Map a 4-ary function over three objects.
+ */
+map_quaternary fn a b c d = map_nary (list_4ary fn) [a, b, c, d];
+
+/* Same as map_nary, but for lists. Handy for (eg.) implementing arith ops on
+ * vectors and matricies.
+ */
+map_nary_list fn args
+ = fn args, lists == []
+ = map process [0, 1 .. shortest - 1]
+{
+ // find all the list arguments
+ lists = filter is_list args;
+
+ // what's the length of the shortest list arg?
+ shortest = foldr1 min_pair (map len lists);
+
+ // process index n ... pull that member from each argument
+ // recurse to handle application, so we work for nested lists too
+ process n
+ = map_nary_list fn (map (extract n) args)
+ {
+ extract n arg
+ = arg?n, is_list arg
+ = arg;
+ }
+}
+
+map_unaryl fn a = map_nary_list (list_1ary fn) [a];
+
+map_binaryl fn a b = map_nary_list (list_2ary fn) [a, b];
+
+/* Remove features smaller than x pixels across from an image. This used to be
+ * rather complex ... convsep is now good enough to use.
+ */
+smooth x image = convsep (matrix_gaussian_blur (to_real x * 2)) image;
+
+/* Chop up an image into a list of lists of smaller images. Pad edges with
+ * black.
+ */
+imagearray_chop tile_width tile_height hoverlap voverlap i
+ = map chop' [0, vstep .. last_y]
+{
+ width = get_width i;
+ height = get_height i;
+ bands = get_bands i;
+ format = get_format i;
+ type = get_type i;
+
+ tile_width' = to_real tile_width;
+ tile_height' = to_real tile_height;
+ hoverlap' = to_real hoverlap;
+ voverlap' = to_real voverlap;
+
+ /* Unique pixels per tile.
+ */
+ hstep = tile_width' - hoverlap';
+ vstep = tile_height' - voverlap';
+
+ /* Number of tiles across and down. Remember the case where width ==
+ * hstep.
+ */
+ across = (int) ((width - 1) / hstep) + 1;
+ down = (int) ((height - 1) / vstep) + 1;
+
+ /* top/left of final tile.
+ */
+ last_x = hstep * (across - 1);
+ last_y = vstep * (down - 1);
+
+ /* How much do we need to pad by?
+ */
+ sx = last_x + tile_width';
+ sy = last_y + tile_height';
+
+ /* Expand image with black to pad size.
+ */
+ pad = embed 0 0 0 sx sy i;
+
+ /* Chop up a row.
+ */
+ chop' y
+ = map chop'' [0, hstep .. last_x]
+ {
+ chop'' x = extract_area x y tile_width' tile_height' pad;
+ }
+}
+
+/* Reassemble image.
+ */
+imagearray_assemble hoverlap voverlap il
+ = (image_set_origin 0 0 @ foldl1 tbj @ map (foldl1 lrj)) il
+{
+ lrj l r = insert (get_width l + hoverlap) 0 r l;
+ tbj t b = insert 0 (get_height t + voverlap) b t;
+}
+
+/* Generate an nxn identity matrix.
+ */
+identity_matrix n
+ = error "identity_matrix: n > 0", n < 1
+ = map line [0 .. n - 1]
+{
+ line p = take p [0, 0 ..] ++ [1] ++ take (n - p - 1) [0, 0 ..];
+}
+
+/* Incomplete gamma function Q(a, x) == 1 - P(a, x)
+
+ FIXME ... this is now a builtin, until we can get a nice List class
+ (requires overloadable ':')
+
+gammq a x
+ = error "bad args", x < 0 || a <= 0
+ = 1 - gamser, x < a + 1
+ = gammcf
+{
+ gamser = (gser a x)?0;
+ gammcf = (gcf a x)?0;
+}
+ */
+
+/* Incomplete gamma function P(a, x) evaluated as series representation. Also
+ * return ln(gamma(a)) ... nr in c, pp 218
+ */
+gser a x
+ = [gamser, gln]
+{
+ gln = gammln a;
+ gamser
+ = error "bad args", x < 0
+ = 0, x == 0
+ = 1 // fix this
+ {
+ // maximum iterations
+ maxit = 100;
+
+ ap = List [a + 1, a + 2 ...];
+ xoap = x / ap;
+
+ del = map product (prefixes xoap.value);
+
+
+
+
+
+
+
+/*
+ del = map (multiply (1 / a)) (map product (prefixes xoap))
+
+ del =
+
+ xap = iterate (multiply
+ */
+
+ /* Generate all prefixes of a list ... [1,2,3] -> [[1], [1, 2], [1, 2,
+ * 3], [1, 2, 3, 4] ...]
+ */
+ prefixes l = map (converse take l) [1..];
+
+ }
+}
+
+/* ln(gamma(xx)) ... nr in c, pp 214
+ */
+gammln xx
+ = gln
+{
+ cof = [76.18009172947146, -86.50532032941677, 24.01409824083091,
+ -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
+ y = take 6 (iterate (add 1) (xx + 1));
+ ser = 1.000000000190015 + sum (map2 divide cof y);
+ tmp = xx + 0.5;
+ tmp' = tmp - ((xx + 0.5) * log tmp);
+ gln = -tmp + log (2.5066282746310005 * ser / xx);
+}
+
+/* make a LUT from a scatter
+ */
+buildlut x
+ = Plot_histogram (im_buildlut x), is_Matrix x && x.width > 1
+ = im_buildlut (Matrix x), is_matrix x && is_list_len_more 1 x?0
+ = error (_ "bad arguments to " ++ "buildlut");
+
+/* Linear regression. Return a class with the stuff we need in.
+ * from s15.2, p 665 NR in C
+ *
+ * Also calculate R2, see eg.:
+ * https://en.wikipedia.org/wiki/Coefficient_of_determination
+ */
+linreg xes yes
+ = obj
+{
+ obj = class {
+ // in case we ever get shown in the workspace
+ _vislevel = 2;
+
+ slope = sum [t * y :: [t, y] <- zip2 tes yes] / st2;
+ intercept = (sy - sx * slope) / ss;
+
+ chi2 = sum [(y - intercept - slope * x) ** 2 :: [x, y] <- zip2 xes yes];
+
+ siga = (chi2 / (ss - 2)) ** 0.5 *
+ ((1 + sx ** 2 / (ss * st2)) / ss) ** 0.5;
+ sigb = (chi2 / (ss - 2)) ** 0.5 * (1 / st2) ** 0.5;
+
+ // for compat with linregw, see below
+ q = 1.0;
+
+ R2 = 1 - chi2 / sum [(y - my) ** 2 :: y <- yes];
+ }
+
+ ss = len xes;
+ sx = sum xes;
+ sy = sum yes;
+ my = sy / ss;
+ sxoss = sx / ss;
+
+ tes = [x - sxoss :: x <- xes];
+ st2 = sum [t ** 2 :: t <- tes];
+}
+
+/* Weighted linear regression. Xes, yes and a list of deviations.
+ */
+linregw xes yes devs
+ = obj
+{
+ obj = class {
+ // in case we ever get shown in the workspace
+ _vislevel = 2;
+
+ slope = sum [(t * y) / sd :: [t, y, sd] <- zip3 tes yes devs] / st2;
+ intercept = (sy - sx * slope) / ss;
+
+ chi2 = sum [((y - intercept - slope * x) / sd) ** 2 ::
+ [x, y, sd] <- zip3 xes yes devs];
+
+ siga = ((1 + sx * sx / (ss * st2)) / ss) ** 0.5;
+ sigb = (1 / st2) ** 0.5;
+
+ q = gammq (0.5 * (len xes - 2)) (0.5 * chi2);
+
+ R2 = 1 - chi2 / sum [(y - my) ** 2 :: y <- yes];
+ }
+
+ wt = [sd ** -0.5 :: sd <- devs];
+
+ ss = sum wt;
+ sx = sum [x * w :: [x, w] <- zip2 xes wt];
+ sy = sum [y * w :: [y, w] <- zip2 yes wt];
+ my = sy / len xes;
+ sxoss = sx / ss;
+
+ tes = [(x - sxoss) / sd :: [x, sd] <- zip2 xes devs];
+ st2 = sum [t ** 2 :: t <- tes];
+}
+
+/* Clustering: pass in a list of points, repeatedly merge the
+ * closest two points until no two points are closer than the threshold.
+ * Return [merged-points, corresponding-weights]. A weight is a list of the
+ * indexes we merged to make that point, ie. len weight == how significant
+ * this point is.
+ *
+ * eg.
+ * cluster 12 [152,154,155,42,159] ==
+ * [[155,42],[[1,2,0,4],[3]]]
+ */
+cluster thresh points
+ = oo_unary_function cluster_op points, is_class points
+ // can't use [0..len points - 1], in case len points == 0
+ = merge [points, map (converse cons []) (take (len points) [0 ..])],
+ is_list points
+ = error (_ "bad arguments to " ++ "cluster")
+{
+ cluster_op = Operator "cluster"
+ (cluster thresh) Operator_type.COMPOUND false;
+
+ merge x
+ = x, m < 2 || d > thresh
+ = merge [points', weights']
+ {
+ [points, weights] = x;
+ m = len points;
+
+ // generate indexes of all possible pairs, avoiding comparing a thing
+ // to itself, and assuming that dist is reflexive
+ // first index is always less than 2nd index
+ // the +1,+2 makes sure we have an increasing generator, otherwise we
+ // can get [3 .. 4] (for example), which will make a decreasing
+ // sequence
+ pairs = [[x, y] :: x <- [0 .. m - 1]; y <- [x + 1, x + 2 .. m - 1]];
+
+ // distance function
+ // arg is eg. [3,1], meaning get distance from point 3 to point 1
+ dist x
+ = abs (points?i - points?j)
+ {
+ [i, j] = x;
+ }
+
+ // smallest distance, then the two points we merge
+ p = minpos (map dist pairs);
+ d = dist pairs?p;
+ [i, j] = pairs?p;
+
+ // new point and new weight
+ nw = weights?i ++ weights?j;
+ np = (points?i * len weights?i + points?j * len weights?j) / len nw;
+
+ // remove element i from a list
+ remove i l = take i l ++ drop (i + 1) l;
+
+ // remove two old points, add the new merged one
+ // i < j (see "pairs", above)
+ points' = np : remove i (remove j points);
+ weights' = nw : remove i (remove j weights);
+ }
+}
+
+/* Extract the area of an image around an arrow.
+ * Transform the image to make the arrow horizontal, then displace by hd and
+ * vd pxels, then cut out a bit h pixels high centered on the arrow.
+ */
+extract_arrow hd vd h arrow
+ = extract_area (re p' + hd) (im p' - h / 2 + vd) (re pv) h im'
+{
+ // the line as a polar vector
+ pv = polar (arrow.width, arrow.height);
+ a = im pv;
+
+ // smallest rotation that will make the line horizontal
+ a'
+ = 360 - a, a > 270
+ = 180 - a, a > 90
+ = -a;
+
+ im' = rotate Interpolate_bilinear a' arrow.image;
+
+ // look at the start and end of the arrow, pick the leftmost
+ p
+ = (arrow.left, arrow.top), arrow.left <= arrow.right
+ = (arrow.right, arrow.bottom);
+
+ // transform that point to im' space
+ p' = rectangular (polar p + (0, a')) + (im'.xoffset, im'.yoffset);
+}
+
+/* You'd think these would go in _convert, but they are not really colour ops,
+ * so put them here.
+ */
+rad2float image
+ = oo_unary_function rad2float_op image, is_class image
+ = im_rad2float image, is_image image
+ = error (_ "bad arguments to " ++ "rad2float")
+{
+ rad2float_op = Operator "rad2float"
+ rad2float Operator_type.COMPOUND_REWRAP false;
+}
+
+float2rad image
+ = oo_unary_function float2rad_op image, is_class image
+ = im_float2rad image, is_image image
+ = error (_ "bad arguments to " ++ "float2rad")
+{
+ float2rad_op = Operator "float2rad"
+ float2rad Operator_type.COMPOUND_REWRAP false;
+}
+
+segment x
+ = oo_unary_function segment_op x, is_class x
+ = image', is_image x
+ = error (_ "bad arguments to " ++ "segment")
+{
+ segment_op = Operator "segment"
+ segment Operator_type.COMPOUND_REWRAP false;
+
+ [image, nsegs] = im_segment x;
+ image' = im_copy_set_meta image "n-segments" nsegs;
+}
+
+point a b
+ = oo_binary_function point_op a b, is_class a
+ = oo_binary'_function point_op a b, is_class b
+ = im_read_point b x y, is_image b
+ = [b?x?y], is_matrix b
+ = [b?x], is_real_list b && y == 0
+ = [b?y], is_real_list b && x == 0
+ = error (_ "bad arguments to " ++ "point")
+{
+ point_op = Operator "point"
+ (\a\b Vector (point a b)) Operator_type.COMPOUND false;
+
+ (x, y)
+ = a, is_complex a;
+ = (a?0, a?1), is_real_list a && is_list_len 2 a
+ = error "bad position format";
+}
+
+/* One image in, one out.
+ */
+system_image command x
+ = oo_unary_function system_image_op x, is_class x
+ = system x, is_image x
+ = error (_ "bad arguments to " ++ "system_image")
+{
+ system_image_op = Operator "system_image"
+ (system_image command) Operator_type.COMPOUND_REWRAP false;
+
+ system im
+ = image_out
+ {
+ [image_out, log] =
+ im_system_image (get_image im) "%s.tif" "%s.tif" command;
+ }
+}
+
+/* Two images in, one out.
+ */
+system_image2 command x1 x2
+ = oo_binary_function system_image2_op x1 x2, is_class x1
+ = oo_binary'_function system_image2_op x1 x2, is_class x2
+ = system x1 x2, is_image x1 && is_image x2
+ = error (_ "bad arguments to " ++ "system_image2")
+{
+ system_image2_op = Operator "system_image2"
+ (system_image2 command) Operator_type.COMPOUND_REWRAP false;
+
+ system x1 x2
+ = image_out
+ {
+ [image_out] = vips_call "system" [command] [
+ $in => [x1, x2],
+ $out => true,
+ $out_format => "%s.tif",
+ $in_format => "%s.tif"
+ ];
+ }
+}
+
+/* Three images in, one out.
+ */
+system_image3 command x1 x2 x3
+ = oo_binary_function system_image2_op x2 x3, is_class x2
+ = oo_binary'_function system_image2_op x2 x3, is_class x3
+ = system x1 x2 x3, is_image x1 && is_image x2 && is_image x3
+ = error (_ "bad arguments to " ++ "system_image3")
+{
+ system_image2_op = Operator "system_image2"
+ (system_image3 command x1) Operator_type.COMPOUND_REWRAP false;
+
+ system x1 x2 x3
+ = image_out
+ {
+ [image_out] = vips_call "system" [command] [
+ $in => [x1, x2, x3],
+ $out => true,
+ $out_format => "%s.tif",
+ $in_format => "%s.tif"
+ ];
+ }
+}
+
+/* Zero images in, one out.
+ */
+system_image0 command
+ = Image image_out
+{
+ [image_out] = vips_call "system" [command] [
+ $out => true,
+ $out_format => "%s.tif"
+ ];
+}
+
+hough_line w h x
+ = oo_unary_function hough_line_op x, is_class x
+ = hline (to_real w) (to_real h) x, is_image x
+ = error (_ "bad arguments to " ++ "hough_line")
+{
+ hough_line_op = Operator "hough_line"
+ (hough_line w h) Operator_type.COMPOUND_REWRAP false;
+
+ hline w h x
+ = pspace
+ {
+ [pspace] = vips_call "hough_line" [x] [
+ $width => w,
+ $height => h
+ ];
+ }
+}
+
+hough_circle s mn mx x
+ = oo_unary_function hough_circle_op x, is_class x
+ = hcircle (to_real s) (to_real mn) (to_real mx) x, is_image x
+ = error (_ "bad arguments to " ++ "hough_circle")
+{
+ hough_circle_op = Operator "hough_circle"
+ (hough_circle s mn mx) Operator_type.COMPOUND_REWRAP false;
+
+ hcircle s mn mx x
+ = pspace
+ {
+ [pspace] = vips_call "hough_circle" [x] [
+ $scale => s,
+ $min_radius => mn,
+ $max_radius => mx
+ ];
+ }
+}
+
+mapim interp ind in
+ = oo_binary_function mapim_op ind in, is_class ind
+ = oo_binary'_function mapim_op ind in, is_class in
+ = mapim_fn ind in, is_image ind && is_image in
+ = error (_ "bad arguments to " ++ "mapim")
+{
+ mapim_op = Operator "mapim"
+ (mapim interp) Operator_type.COMPOUND_REWRAP false;
+
+ mapim_fn ind im
+ = out
+ {
+ [out] = vips_call "mapim" [im, ind] [$interpolate => interp];
+ }
+}
+
+perlin cell width height
+ = Image im
+{
+ [im] = vips_call "perlin" [to_real width, to_real height] [
+ $cell_size => to_real cell
+ ];
+}
+
+worley cell width height
+ = Image im
+{
+ [im] = vips_call "worley" [to_real width, to_real height] [
+ $cell_size => to_real cell
+ ];
+}
+
+gaussnoise width height mean sigma
+ = im
+{
+ [im] = vips_call "gaussnoise" [to_real width, to_real height] [
+ $mean => to_real mean,
+ $sigma => to_real sigma
+ ];
+}
+
+flattenimage bg x
+ = oo_unary_function flatten_op x, is_class x
+ = flt (to_vector bg) x, is_image x
+ = error (_ "bad arguments to " ++ "flattenimage")
+{
+ flatten_op = Operator "flatten"
+ (flattenimage bg) Operator_type.COMPOUND_REWRAP false;
+
+ flt bg x
+ = out
+ {
+ [out] = vips_call "flatten" [x] [
+ $background => bg.value
+ ];
+ }
+}
+
+premultiply x
+ = oo_unary_function premultiply_op x, is_class x
+ = prem x, is_image x
+ = error (_ "bad arguments to " ++ "premultiply")
+{
+ premultiply_op = Operator "premultiply"
+ premultiply Operator_type.COMPOUND_REWRAP false;
+
+ prem x
+ = out
+ {
+ [out] = vips_call "premultiply" [x] [
+ ];
+ }
+}
+
+unpremultiply x
+ = oo_unary_function unpremultiply_op x, is_class x
+ = unprem x, is_image x
+ = error (_ "bad arguments to " ++ "unpremultiply")
+{
+ unpremultiply_op = Operator "unpremultiply"
+ unpremultiply Operator_type.COMPOUND_REWRAP false;
+
+ unprem x
+ = out
+ {
+ [out] = vips_call "unpremultiply" [x] [
+ ];
+ }
+}
+
+hist_entropy x
+ = oo_unary_function hist_entropy_op x, is_class x
+ = entropy x, is_image x
+ = error (_ "bad arguments to " ++ "hist_entropy")
+{
+ hist_entropy_op = Operator "hist_entropy"
+ hist_entropy Operator_type.COMPOUND_REWRAP false;
+
+ entropy x
+ = out
+ {
+ [out] = vips_call "hist_entropy" [x] [
+ ];
+ }
+}
+
+
diff --git a/share/nip2/compat/8.5/_types.def b/share/nip2/compat/8.5/_types.def
new file mode 100644
index 00000000..7516659b
--- /dev/null
+++ b/share/nip2/compat/8.5/_types.def
@@ -0,0 +1,1285 @@
+/* A list of things. Do automatic iteration of unary and binary operators on
+ * us.
+ * List [1, 2] + [2, 3] -> List [3, 5]
+ * hd (List [2, 3]) -> 2
+ * List [] == [] -> true
+ */
+List value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_list]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [apply2 op value x',
+ op.op_name == "subscript" || op.op_name == "subscript'" ||
+ op.op_name == "equal" || op.op_name == "equal'"],
+ [this.List (apply2 op value x'),
+ op.op_name == "join" || op.op_name == "join'"],
+ [this.List (map2 (apply2 op) value x'),
+ is_list x'],
+ [this.List (map (apply2 op' x) value),
+ true]
+ ] ++ super.oo_binary_table op x
+ {
+ op' = oo_converse op;
+
+ // strip the List wrapper, if any
+ x'
+ = x.value, is_List x
+ = x;
+
+ apply2 op x1 x2
+ = oo_binary_function op x1 x2, is_class x1
+ = oo_binary'_function op x1 x2, is_class x2
+ = op.fn x1 x2;
+ };
+
+ oo_unary_table op = [
+ [apply value,
+ op.op_name == "hd" || op.op_name == "tl"],
+ [this.List (map apply value),
+ true]
+ ] ++ super.oo_unary_table op
+ {
+ apply x
+ = oo_unary_function op x, is_class x
+ = op.fn x;
+ }
+}
+
+/* A group of things. Loop the operation over the group.
+ */
+Group value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_list]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ // if_then_else is really a trinary operator
+ [map_trinary ite this x?0 x?1,
+ op.op_name == "if_then_else"],
+ [map_binary op.fn this x,
+ is_Group x],
+ [map_unary (\a op.fn a x) this,
+ true]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [map_unary op.fn this,
+ true]
+ ] ++ super.oo_unary_table op;
+
+ // we can't call map_trinary directly, since it uses Group and we
+ // don't support mutually recursive top-level functions :-(
+ // copy-paste it here, keep in sync with the version in _stdenv
+ map_nary fn args
+ = fn args, groups == []
+ = Group (map process [0, 1 .. shortest - 1])
+ {
+ groups = filter is_Group args;
+
+ shortest = foldr1 min_pair (map (len @ get_value) groups);
+
+ process n
+ = NULL, any (map (is_noval n) args)
+ = map_nary fn (map (extract n) args)
+ {
+ extract n arg
+ = arg.value?n, is_Group arg
+ = arg;
+
+ is_noval n arg = is_Group arg && arg.value?n == NULL;
+ }
+ }
+
+ // need ite as a true trinary
+ ite a b c = if a then b else c;
+
+ map_unary fn a = map_nary (list_1ary fn) [a];
+ map_binary fn a b = map_nary (list_2ary fn) [a, b];
+ map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
+}
+
+/* Single real number ... eg slider.
+ */
+Real value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_real]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [this.Real (op.fn this.value x.value),
+ is_Real x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Real (op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC],
+ [op.fn this.value x.value,
+ is_Real x &&
+ op.type == Operator_type.RELATIONAL],
+ [op.fn this.value x,
+ !is_class x]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [this.Real (op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* Single bool ... eg Toggle.
+ */
+Bool value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_bool]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [op.fn this.value x,
+ op.op_name == "if_then_else"],
+ [this.Bool (op.fn this.value x.value),
+ is_Bool x],
+ [this.Bool (op.fn this.value x),
+ is_bool x]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [this.Bool (op.fn this.value),
+ op.type == Operator_type.ARITHMETIC ||
+ op.type == Operator_type.RELATIONAL],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* An editable string.
+ */
+String caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* An editable real number.
+ */
+Number caption value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string]
+ ];
+
+ Real x = this.Number caption x;
+}
+
+/* An editable expression.
+ */
+Expression caption expr = class
+ (if is_class expr then expr else _Object) {
+ _check_args = [
+ [caption, "caption", check_string],
+ [expr, "expr", check_any]
+ ];
+}
+
+/* A ticking clock.
+ */
+Clock interval value = class
+ scope.Real value {
+ _check_args = [
+ [interval, "interval", check_real]
+ ];
+
+ Real x = this.Clock interval x;
+}
+
+/* An editable filename.
+ */
+Pathname caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* An editable fontname.
+ */
+Fontname caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* Vector type ... just a finite list of real. Handy for wrapping an
+ * argument to eg. im_lintra_vec. Make it behave like a single pixel image.
+ */
+Vector value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_real_list]
+ ];
+
+ bands = len value;
+
+ // methods
+ oo_binary_table op x = [
+ // Vector ++ Vector means bandwise join
+ [this.Vector (op.fn this.value x.value),
+ is_Vector x &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ [this.Vector (op.fn this.value [get_number x]),
+ has_number x &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ // Vector ? number means extract element
+ [op.fn this.value (get_real x),
+ has_real x &&
+ (op.op_name == "subscript" ||
+ op.op_name == "subscript'")],
+ // extra check for lengths equal
+ [this.Vector (map_binaryl op.fn this.value x.value),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Vector (map_binaryl op.fn this.value (get_real x)),
+ has_real x &&
+ op.type == Operator_type.ARITHMETIC],
+
+ // need extra length check
+ [this.Vector (map bool_to_real
+ (map_binaryl op.fn this.value x.value)),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (map bool_to_real
+ (map_binaryl op.fn this.value (get_real x))),
+ has_real x &&
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (op.fn this.value x.value),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [x.Image (vec op'.op_name x.value value),
+ is_Image x],
+ [vec op'.op_name x value,
+ is_image x],
+ [op.fn this.value x,
+ is_real x]
+ ] ++ super.oo_binary_table op x
+ {
+ op' = oo_converse op;
+ };
+
+ oo_unary_table op = [
+ [this.Vector (map_unaryl op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [this.Vector (map bool_to_real
+ (map_unaryl op.fn this.value)),
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (op.fn this.value),
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+
+ // turn an ip bool (or a number, for Vector) into VIPSs 255/0
+ bool_to_real x
+ = 255, is_bool x && x
+ = 255, is_number x && x != 0
+ = 0;
+}
+
+/* A rectangular array of real.
+ */
+Matrix_base value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_matrix]
+ ];
+
+ // calculate these from value
+ width = len value?0;
+ height = len value;
+
+ // extract a rectanguar area
+ extract left top width height
+ = this.Matrix_base
+ ((map (take width) @ map (drop left) @
+ take height @ drop top) value);
+
+ // methods
+ oo_binary_table op x = [
+ // mat multiply is special
+ [this.Matrix_base mul.value,
+ is_Matrix x &&
+ op.op_name == "multiply"],
+ [this.Matrix_base mul'.value,
+ is_Matrix x &&
+ op.op_name == "multiply'"],
+
+ // mat divide is also special
+ [this.Matrix_base div.value,
+ is_Matrix x &&
+ op.op_name == "divide"],
+ [this.Matrix_base div'.value,
+ is_Matrix x &&
+ op.op_name == "divide'"],
+
+ // power -1 means invert
+ [this.Matrix_base inv.value,
+ is_real x && x == -1 &&
+ op.op_name == "power"],
+ [this.Matrix_base sq.value,
+ is_real x && x == 2 &&
+ op.op_name == "power"],
+ [error "matrix **-1 and **2 only",
+ op.op_name == "power" ||
+ op.op_name == "power'"],
+
+ // matrix op vector ... treat a vector as a 1 row matrix
+ [this.Matrix_base (map (map_binaryl op'.fn x.value) this.value),
+ is_Vector x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Matrix_base (map_binaryl op.fn this.value x.value),
+ (is_Matrix x || is_Real x) &&
+ op.type == Operator_type.ARITHMETIC],
+
+ [this.Matrix_base (map_binaryl op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC],
+
+ // compound ... don't do iteration
+ [this.Matrix_base (op.fn this.value x.value),
+ (is_Matrix x || is_Real x || is_Vector x) &&
+ op.type == Operator_type.COMPOUND_REWRAP],
+
+ [op.fn this.value x,
+ op.type == Operator_type.COMPOUND]
+
+ ] ++ super.oo_binary_table op x
+ {
+ mul = im_matmul this x;
+ mul' = im_matmul x this;
+ div = im_matmul this (im_matinv x);
+ div' = im_matmul x (im_matinv this);
+ inv = im_matinv this;
+ sq = im_matmul this this;
+ op' = oo_converse op;
+ }
+
+ oo_unary_table op = [
+ [this.Matrix_base (map_unaryl op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [this.Matrix_base (op.fn this.value),
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* How to display a matrix: text, sliders, toggles, or text plus scale/offset.
+ */
+Matrix_display = class {
+ text = 0;
+ slider = 1;
+ toggle = 2;
+ text_scale_offset = 3;
+
+ is_display = member [text, slider, toggle, text_scale_offset];
+}
+
+/* A matrix as VIPS sees them ... add scale, offset and filename. For nip, add
+ * a display type as well to control how the widget renders.
+ */
+Matrix_vips value scale offset filename display = class
+ scope.Matrix_base value {
+ _check_args = [
+ [scale, "scale", check_real],
+ [offset, "offset", check_real],
+ [filename, "filename", check_string],
+ [display, "display", check_matrix_display]
+ ];
+
+ Matrix_base x = this.Matrix_vips x scale offset filename display;
+}
+
+/* A plain 'ol matrix which can be passed to VIPS.
+ */
+Matrix value = class
+ Matrix_vips value 1 0 "" Matrix_display.text {}
+
+/* Specialised constructors ... for convolutions, recombinations and
+ * morphologies.
+ */
+Matrix_con scale offset value = class
+ Matrix_vips value scale offset "" Matrix_display.text_scale_offset {};
+
+Matrix_rec value = class
+ Matrix_vips value 1 0 "" Matrix_display.slider {};
+
+Matrix_mor value = class
+ Matrix_vips value 1 0 "" Matrix_display.toggle {};
+
+Matrix_file filename = (im_read_dmask @ expand @ search) filename;
+
+/* A CIE colour ... a triple, plus a format (eg XYZ, Lab etc)
+ */
+Colour colour_space value = class
+ scope.Vector value {
+ _check_args = [
+ [colour_space, "colour_space", check_colour_space]
+ ];
+ _check_all = [
+ [is_list_len 3 value, "len value == 3"]
+ ];
+
+ Vector x = this.Colour colour_space x;
+
+ // make a colour-ish thing from an image
+ // back to Colour if we have another 3 band image
+ // to a vector if bands > 1
+ // to a number otherwise
+ itoc im
+ = this.Colour nip_type (to_matrix im).value?0,
+ bands == 3
+ = scope.Vector (map mean (bandsplit im)),
+ bands > 1
+ = mean im
+ {
+ type = get_header "Type" im;
+ bands = get_header "Bands" im;
+ nip_type = Image_type.colour_spaces.lookup 1 0 type;
+ }
+
+ // methods
+ oo_binary_table op x = [
+ [itoc (op.fn
+ ((float) (to_image this).value)
+ ((float) (to_image x).value)),
+ // here REWRAP means go via image
+ op.type == Operator_type.COMPOUND_REWRAP]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [itoc (op.fn ((float) (to_image this).value)),
+ op.type == Operator_type.COMPOUND_REWRAP]
+ ] ++ super.oo_unary_table op;
+}
+
+// a subclass with widgets for picking a space and value
+Colour_picker default_colour default_value = class
+ Colour space.item colour.expr {
+ _vislevel = 3;
+
+ space = Option_enum "Colour space" Image_type.colour_spaces default_colour;
+ colour = Expression "Colour value" default_value;
+
+ Colour_edit colour_space value =
+ Colour_picker colour_space value;
+}
+
+/* Base scale type.
+ */
+Scale caption from to value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [from, "from", check_real],
+ [to, "to", check_real]
+ ];
+ _check_all = [
+ [from < to, "from < to"]
+ ];
+
+ Real x = this.Scale caption from to x;
+
+ // methods
+ oo_binary_table op x = [
+ [this.Scale caption (op.fn this.from x.from) (op.fn this.to x.to)
+ (op.fn this.value x.value),
+ is_Scale x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Scale caption (op.fn this.from x) (op.fn this.to x)
+ (op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC]
+ ] ++ super.oo_binary_table op x;
+}
+
+/* Base toggle type.
+ */
+Toggle caption value = class
+ scope.Bool value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_bool]
+ ];
+
+ Bool x = this.Toggle caption x;
+}
+
+/* Base option type.
+ */
+Option caption labels value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [labels, "labels", check_string_list],
+ [value, "value", check_uint]
+ ];
+}
+
+/* An option whose value is a string rather than a number.
+ */
+Option_string caption labels item = class
+ Option caption labels (index (equal item) labels) {
+ Option_edit caption labels value
+ = this.Option_string caption labels (labels?value);
+}
+
+/* Make an option from an enum.
+ */
+Option_enum caption enum item = class
+ Option_string caption enum.names item {
+ // corresponding thing
+ value_thing = enum.get_thing item;
+
+ Option_edit caption labels value
+ = this.Option_enum caption enum (enum.names?value);
+}
+
+/* A rectangle. width and height can be -ve.
+ */
+Rect left top width height = class
+ _Object {
+ _check_args = [
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_real],
+ [height, "height", check_real]
+ ];
+
+ // derived
+ right = left + width;
+ bottom = top + height;
+
+ oo_binary_table op x = [
+ [equal x,
+ is_Rect x &&
+ (op.op_name == "equal" || op.op_name == "equal'")],
+ [!equal x,
+ is_Rect x &&
+ (op.op_name == "not_equal" ||
+ op.op_name == "not_equal'")],
+
+ // binops with a complex are the same as (comp op comp)
+ [oo_binary_function op this (Rect (re x) (im x) 0 0),
+ is_complex x],
+
+ // all others are just pairwise
+ [this.Rect left' top' width' height',
+ is_Rect x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Rect left'' top'' width'' height'',
+ has_number x &&
+ op.type == Operator_type.ARITHMETIC]
+ ] ++ super.oo_binary_table op x
+ {
+ left' = op.fn left x.left;
+ top' = op.fn top x.top;
+ width' = op.fn width x.width;
+ height' = op.fn height x.height;
+
+ left'' = op.fn left x';
+ top'' = op.fn top x';
+ width'' = op.fn width x';
+ height'' = op.fn height x';
+ x' = get_number x;
+ }
+
+ oo_unary_table op = [
+ // arithmetic uops just map
+ [this.Rect left' top' width' height',
+ op.type == Operator_type.ARITHMETIC],
+
+ // compound uops are just like ops on complex
+ // do (width, height) so thing like abs(Arrow) work as you'd expect
+ [op.fn (width, height),
+ op.type == Operator_type.COMPOUND]
+ ] ++ super.oo_unary_table op
+ {
+ left' = op.fn left;
+ top' = op.fn top;
+ width' = op.fn width;
+ height' = op.fn height;
+ }
+
+ // empty? ie. contains no pixels
+ is_empty = width == 0 || height == 0;
+
+ // normalised version, ie. make width/height +ve and flip the origin
+ nleft
+ = left + width, width < 0
+ = left;
+ ntop
+ = top + height, height < 0
+ = top;
+ nwidth = abs width;
+ nheight = abs height;
+ nright = nleft + nwidth;
+ nbottom = ntop + nheight;
+
+ equal x = left == x.left && top == x.top &&
+ width == x.width && height == x.height;
+
+ // contains a point?
+ includes_point x y
+ = nleft <= x && x <= nright && ntop <= y && y <= nbottom;
+
+ // contains a rect? just test top left and bottom right points
+ includes_rect r
+ = includes_point r.nleft r.ntop &&
+ includes_point r.nright r.nbottom;
+
+ // bounding box of two rects
+ // if either is empty, can just return the other
+ union r
+ = r, is_empty
+ = this, r.is_empty
+ = Rect left' top' width' height'
+ {
+ left' = min_pair nleft r.nleft;
+ top' = min_pair ntop r.ntop;
+ width' = max_pair nright r.nright - left';
+ height' = max_pair nbottom r.nbottom - top';
+ }
+
+ // intersection of two rects ... empty rect if no intersection
+ intersect r
+ = Rect left' top' width'' height''
+ {
+ left' = max_pair nleft r.nleft;
+ top' = max_pair ntop r.ntop;
+ width' = min_pair nright r.nright - left';
+ height' = min_pair nbottom r.nbottom - top';
+ width''
+ = width', width > 0
+ = 0;
+ height''
+ = height', height > 0
+ = 0;
+ }
+
+ // expand/collapse by n pixels
+ margin_adjust n
+ = Rect (left - n) (top - n) (width + 2 * n) (height + 2 * n);
+}
+
+/* Values for Compression field in image.
+ */
+Image_compression = class {
+ NONE = 0;
+ NO_COMPRESSION = 0;
+ TCSF_COMPRESSION = 1;
+ JPEG_COMPRESSION = 2;
+ LABPACK_COMPRESSED = 3;
+ RGB_COMPRESSED = 4;
+ LUM_COMPRESSED = 5;
+}
+
+/* Values for Coding field in image.
+ */
+Image_coding = class {
+ NONE = 0;
+ NOCODING = 0;
+ COLQUANT = 1;
+ LABPACK = 2;
+ RAD = 6;
+}
+
+/* Values for BandFmt field in image.
+ */
+Image_format = class {
+ DPCOMPLEX = 9;
+ DOUBLE = 8;
+ COMPLEX = 7;
+ FLOAT = 6;
+ INT = 5;
+ UINT = 4;
+ SHORT = 3;
+ USHORT = 2;
+ CHAR = 1;
+ UCHAR = 0;
+ NOTSET = -1;
+
+ maxval fmt
+ = [
+ 255, // UCHAR
+ 127, // CHAR
+ 65535, // USHORT
+ 32767, // SHORT
+ 4294967295, // UINT
+ 2147483647, // INT
+ 255, // FLOAT
+ 255, // COMPLEX
+ 255, // DOUBLE
+ 255 // DPCOMPLEX
+ ] ? fmt, fmt >= 0 && fmt <= DPCOMPLEX
+ = error (_ "bad value for BandFmt");
+}
+
+/* A lookup table.
+ */
+Table value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_rectangular]
+ ];
+
+ /* Extract a column.
+ */
+ column n = map (extract n) value;
+
+ /* present col x: is there an x in column col
+ */
+ present col x = member (column col) x;
+
+ /* Look on column from, return matching item in column to.
+ */
+ lookup from to x
+ = value?n?to, n >= 0
+ = error (_ "item" ++ " " ++ print x ++ " " ++ _ "not in table")
+ {
+ n = index (equal x) (column from);
+ }
+}
+
+/* A two column lookup table with the first column a string and the second a
+ * thing. Used for representing various enums. Option_enum makes a selector
+ * from one of these.
+ */
+Enum value = class
+ Table value {
+ _check_args = [
+ [value, "value", check_enum]
+ ]
+ {
+ check_enum = [is_enum, _ "is [[char, *]]"];
+ is_enum x =
+ is_rectangular x &&
+ is_listof is_string (map (extract 0) x);
+ }
+
+ // handy ... all the names and things as lists
+ names = this.column 0;
+ things = this.column 1;
+
+ // is a legal name or thing
+ has_name x = this.present 1 x;
+ has_thing x = this.present 0 x;
+
+ // map things to strings and back
+ get_name x = this.lookup 1 0 x;
+ get_thing x = this.lookup 0 1 x;
+}
+
+/* Type field.
+ */
+Image_type = class {
+ MULTIBAND = 0;
+ B_W = 1;
+ HISTOGRAM = 10;
+ XYZ = 12;
+ LAB = 13;
+ CMYK = 15;
+ LABQ = 16;
+ RGB = 17;
+ UCS = 18;
+ LCH = 19;
+ LABS = 21;
+ sRGB = 22;
+ YXY = 23;
+ FOURIER = 24;
+ RGB16 = 25;
+ GREY16 = 26;
+ ARRAY = 27;
+
+ /* Table to get names <-> numbers.
+ */
+ type_names = Enum [
+ $MULTIBAND => MULTIBAND,
+ $B_W => B_W,
+ $HISTOGRAM => HISTOGRAM,
+ $XYZ => XYZ,
+ $LAB => LAB,
+ $CMYK => CMYK,
+ $LABQ => LABQ,
+ $RGB => RGB,
+ $UCS => UCS,
+ $LCH => LCH,
+ $LABS => LABS,
+ $sRGB => sRGB,
+ $YXY => YXY,
+ $FOURIER => FOURIER,
+ $RGB16 => RGB16,
+ $GREY16 => GREY16,
+ $ARRAY => ARRAY
+ ];
+
+ /* Table relating nip's colour space names and VIPS's Type numbers.
+ * Options generated from this, so match the order to the order in the
+ * Colour menu.
+ */
+ colour_spaces = Enum [
+ $sRGB => sRGB,
+ $Lab => LAB,
+ $LCh => LCH,
+ $XYZ => XYZ,
+ $Yxy => YXY,
+ $UCS => UCS
+ ];
+
+ /* A slightly larger table ... the types of colorimetric image we can
+ * have. Add mono, and the S and Q forms of LAB.
+ */
+ image_colour_spaces = Enum [
+ $Mono => B_W,
+ $sRGB => sRGB,
+ $RGB16 => RGB16,
+ $GREY16 => GREY16,
+ $Lab => LAB,
+ $LabQ => LABQ,
+ $LabS => LABS,
+ $LCh => LCH,
+ $XYZ => XYZ,
+ $Yxy => YXY,
+ $UCS => UCS
+ ];
+}
+
+/* Base image type. Simple layer over vips_image.
+ */
+Image value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_image]
+ ];
+
+ // fields from VIPS header
+ width = get_width value;
+ height = get_height value;
+ bands = get_bands value;
+ format = get_format value;
+ bits = get_bits value;
+ coding = get_coding value;
+ type = get_type value;
+ xres = get_header "Xres" value;
+ yres = get_header "Yres" value;
+ xoffset = get_header "Xoffset" value;
+ yoffset = get_header "Yoffset" value;
+ filename = get_header "filename" value;
+
+ // convenience ... the area our pixels occupy, as a rect
+ rect = Rect 0 0 width height;
+
+ // operator overloading
+ // (op Image Vector) done in Vector class
+ oo_binary_table op x = [
+ // handle image ++ constant here
+ [wrap join_result_image,
+ (has_real x || is_Vector x) &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ [wrap ite_result_image,
+ op.op_name == "if_then_else"],
+ [wrap (op.fn this.value (get_image x)),
+ has_image x],
+ [wrap (op.fn this.value (get_number x)),
+ has_number x],
+ // if it's not a class on the RHS, handle here ... just apply and
+ // rewrap
+ [wrap (op.fn this.value x),
+ !is_class x]
+ // all other cases handled by other classes
+ ] ++ super.oo_binary_table op x
+ {
+ // wrap the result with this
+ // x can be a non-image, eg. compare "Image v == []" vs.
+ // "Image v == 12"
+ wrap x
+ = x, op.type == Operator_type.COMPOUND ||
+ !is_image x
+ = this.Image x;
+
+ join_result_image
+ = value ++ new_stuff, op.op_name == "join"
+ = new_stuff ++ value
+ {
+ new_stuff = image_new width height new_bands
+ format
+ coding
+ Image_type.B_W x xoffset yoffset;
+ new_bands
+ = get_bands x, has_bands x
+ = 1;
+ }
+
+ [then_part, else_part] = x;
+
+ // get things about our output from inputs in this order
+ objects = [then_part, else_part, this];
+
+ // properties of our output image
+ target_bands = get_member_list has_bands get_bands objects;
+ target_type = get_member_list has_type get_type objects;
+
+ // if one of then/else is an image, get the target format from that
+ // otherwise, let the non-image objects set the target
+ target_format
+ = get_member_list has_format get_format x,
+ has_member_list has_format x
+ = NULL;
+
+ to_image x = to_image_size width height target_bands target_format x;
+
+ [then', else'] = map to_image x;
+
+ ite_result_image = image_set_type target_type
+ (if value then then' else else');
+ }
+
+ // FIXME ... yuk ... don't use operator hints, just always rewrap if
+ // we have an image result
+ // forced on us by things like abs:
+ // abs Vector -> real
+ // abs Image -> Image
+ // does not fit well with COMPOUND/whatever scheme
+ oo_unary_table op = [
+ [this.Image result,
+ is_image result],
+ [result,
+ true]
+ ] ++ super.oo_unary_table op
+ {
+ result = op.fn this.value;
+ }
+}
+
+/* Construct an image from a file.
+ */
+Image_file filename = class
+ Image value {
+ _check_args = [
+ [filename, "filename", check_string]
+ ];
+
+ value = vips_image filename;
+}
+
+Region image left top width height = class
+ Image value {
+ _check_args = [
+ [image, "Image", check_Image],
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_preal],
+ [height, "height", check_preal]
+ ];
+
+ // a rect for our coordinates
+ // region.rect gets the rect for the extracted image
+ region_rect = Rect left top width height;
+
+ // we need to always succeed ... value is our enclosing image if we're
+ // out of bounds
+ value
+ = extract_area left top width height image.value,
+ image.rect.includes_rect region_rect
+ = image.value;
+}
+
+Area image left top width height = class
+ scope.Region image left top width height {
+ Region image left top width height
+ = this.Area image left top width height;
+}
+
+Arrow image left top width height = class
+ scope.Rect left top width height {
+ _check_args = [
+ [image, "Image", check_Image],
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_real],
+ [height, "height", check_real]
+ ];
+
+ Rect l t w h = this.Arrow image l t w h;
+}
+
+HGuide image top = class
+ scope.Arrow image image.rect.left top image.width 0 {
+ Arrow image left top width height = this.HGuide image top;
+}
+
+VGuide image left = class
+ scope.Arrow image left image.rect.top 0 image.height {
+ Arrow image left top width height = this.VGuide image left;
+}
+
+Mark image left top = class
+ scope.Arrow image left top 0 0 {
+ Arrow image left top width height = this.Mark image left top;
+}
+
+// convenience functions: ... specify position as [0 .. 1)
+
+Region_relative image u v w h
+ = Region image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+Area_relative image u v w h
+ = Area image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+Arrow_relative image u v w h
+ = Arrow image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+VGuide_relative image v
+ = VGuide image (image.height * v);
+
+HGuide_relative image u
+ = HGuide image (image.width * u);
+
+Mark_relative image u v
+ = Mark image
+ (image.width * u)
+ (image.height * v);
+
+Kernel_type = class {
+ NEAREST_NEIGHBOUR = 0;
+ LINEAR = 1;
+ CUBIC = 2;
+ LANCZOS2 = 3;
+ LANCZOS3 = 4;
+
+ // Should introspect to get the list of interpolators :-(
+ // We can "dir" on VipsInterpolate to get a list of them, but we
+ // can't get i18n'd descriptions until we have more
+ // introspection stuff in nip2.
+
+ /* Table to map kernel numbers to descriptive strings
+ */
+ descriptions = [
+ _ "Nearest neighbour",
+ _ "Linear",
+ _ "Cubic",
+ _ "Lanczos, two lobes",
+ _ "Lanczos, three lobes"
+ ];
+
+ /* And to vips enum nicknames.
+ */
+ types = [
+ "nearest",
+ "linear",
+ "cubic",
+ "lanczos2",
+ "lanczos3"
+ ];
+}
+
+Kernel type = class {
+ value = Kernel_type.types?type;
+}
+
+Kernel_linear = Kernel Kernel_type.LINEAR;
+
+Kernel_picker default = class
+ Kernel kernel.value {
+ _vislevel = 2;
+
+ kernel = Option "Kernel" Kernel_type.descriptions default;
+}
+
+Interpolate_type = class {
+ NEAREST_NEIGHBOUR = 0;
+ BILINEAR = 1;
+ BICUBIC = 2;
+ LBB = 3;
+ NOHALO = 4;
+ VSQBS = 5;
+
+ // Should introspect to get the list of interpolators :-(
+ // We can "dir" on VipsInterpolate to get a list of them, but we
+ // can't get i18n'd descriptions until we have more
+ // introspection stuff in nip2.
+
+ /* Table to map interpol numbers to descriptive strings
+ */
+ descriptions = [
+ _ "Nearest neighbour",
+ _ "Bilinear",
+ _ "Bicubic",
+ _ "Upsize: reduced halo bicubic (LBB)",
+ _ "Upsharp: reduced halo bicubic with edge sharpening (Nohalo)",
+ _ "Upsmooth: quadratic B-splines with jaggy reduction (VSQBS)"
+ ];
+
+ /* And to vips type names.
+ */
+ types = [
+ "VipsInterpolateNearest",
+ "VipsInterpolateBilinear",
+ "VipsInterpolateBicubic",
+ "VipsInterpolateLbb",
+ "VipsInterpolateNohalo",
+ "VipsInterpolateVsqbs"
+ ];
+}
+
+Interpolate type options = class {
+ value = vips_object_new Interpolate_type.types?type [] options;
+}
+
+Interpolate_bilinear = Interpolate Interpolate_type.BILINEAR [];
+
+Interpolate_picker default = class
+ Interpolate interp.value [] {
+ _vislevel = 2;
+
+ interp = Option "Interpolation" Interpolate_type.descriptions default;
+}
+
+Render_intent = class {
+ PERCEPTUAL = 0;
+ RELATIVE = 1;
+ SATURATION = 2;
+ ABSOLUTE = 3;
+
+ /* Table to get names <-> numbers.
+ */
+ names = Enum [
+ _ "Perceptual" => PERCEPTUAL,
+ _ "Relative" => RELATIVE,
+ _ "Saturation" => SATURATION,
+ _ "Absolute" => ABSOLUTE
+ ];
+}
+
+// abstract base class for toolkit menus
+Menu = class {}
+
+// a "----" line in a menu
+Menuseparator = class Menu {}
+
+// abstract base class for items in menus
+Menuitem label tooltip = class Menu {}
+
+Menupullright label tooltip = class Menuitem label tooltip {}
+
+Menuaction label tooltip = class Menuitem label tooltip {}
+
+/* Plots.
+ */
+
+Plot_style = class {
+ POINT = 0;
+ LINE = 1;
+ SPLINE = 2;
+ BAR = 3;
+
+ names = Enum [
+ _ "Point" => POINT,
+ _ "Line" => LINE,
+ _ "Spline" => SPLINE,
+ _ "Bar" => BAR
+ ];
+}
+
+Plot_format = class {
+ YYYY = 0;
+ XYYY = 1;
+ XYXY = 2;
+
+ names = Enum [
+ _ "YYYY" => YYYY,
+ _ "XYYY" => XYXY,
+ _ "XYXY" => XYXY
+ ];
+}
+
+Plot_type = class {
+ /* Lots of Ys (ie. multiple line plots).
+ */
+ YYYY = 0;
+
+ /* First column of matrix is X position, others are Ys (ie. multiple XY
+ * line plots, all with the same Xes).
+ */
+ XYYY = 1;
+
+ /* Many independent XY plots.
+ */
+ XYXY = 2;
+}
+
+/* "options" is a list of ["key", value] pairs.
+ */
+Plot options value = class
+ scope.Image value {
+ Image value = this.Plot options value;
+ to_image dpi = extract_bands 0 3
+ (graph_export_image (to_real dpi) this);
+}
+
+Plot_matrix options value = class
+ Plot options (to_image value).value {
+}
+
+Plot_histogram value = class
+ scope.Plot [] value {
+}
+
+Plot_xy value = class
+ scope.Plot [$format => Plot_format.XYYY] value {
+}
+
+/* A no-value type. Call it NULL for C-alike fun. Used by Group to indicate
+ * empty slots, for example.
+ */
+NULL = class
+ _Object {
+ oo_binary_table op x = [
+ // the only operation we allow is equality .. use pointer equality,
+ // this lets us test a == NULL and a != NULL
+ [this === x,
+ op.type == Operator_type.RELATIONAL &&
+ op.op_name == "equal"],
+ [this !== x,
+ op.type == Operator_type.RELATIONAL &&
+ op.op_name == "not_equal"]
+ ] ++ super.oo_binary_table op x;
+}
diff --git a/share/nip2/compat/8.6/Colour.def b/share/nip2/compat/8.6/Colour.def
new file mode 100644
index 00000000..6d042103
--- /dev/null
+++ b/share/nip2/compat/8.6/Colour.def
@@ -0,0 +1,690 @@
+
+Colour_new_item = class
+ Menupullright (_ "_New") (_ "make a patch of colour") {
+ Widget_colour_item = class
+ Menuaction (_ "_Colour") (_ "make a patch of colour") {
+ action = Colour_picker "Lab" [50,0,0];
+ }
+
+ LAB_colour = class
+ Menuaction (_ "CIE Lab _Picker") (_ "pick colour in CIE Lab space") {
+ action = widget "Lab" [50, 0, 0];
+
+ // ab_slice size
+ size = 512;
+
+ // range of values ... +/- 128 for ab
+ range = 256;
+
+ // map xy in slice image to ab and back
+ xy2ab x = x / (size / range) - 128;
+ ab2xy a = (a + 128) * (size / range);
+
+ widget space default_value = class
+ Colour space _result {
+ _vislevel = 3;
+
+ [_L, _a, _b] = default_value;
+ L = Scale "Lightness" 0 100 _L;
+ ab_slice = Image (lab_slice size L.value);
+ point = Mark ab_slice (ab2xy _a) (ab2xy _b);
+
+ _result = [L.value, xy2ab point.left, xy2ab point.top];
+
+ Colour_edit colour_space value = widget colour_space value;
+ }
+ }
+
+ CCT_colour = class
+ Menuaction (_ "Colour from CCT") (_ "pick colour by CCT") {
+ action = widget 6500;
+
+ widget x = class
+ _result {
+ _vislevel = 3;
+
+ T = Scale "CCT" 1800 25000 x;
+
+ _result = colour_from_temp (to_real T);
+
+ Colour_edit space value
+ = widget (temp_from_colour (Colour space value));
+ }
+ }
+}
+
+Colour_to_colour_item = class
+ Menuaction (_ "Con_vert to Colour") (_ "convert anything to a colour") {
+ action x = to_colour x;
+}
+
+#separator
+
+Colour_convert_item = class
+ Menupullright (_ "_Colourspace") (_ "convert to various colour spaces") {
+ spaces = Image_type.image_colour_spaces;
+
+ conv dest x = class
+ _result {
+ _vislevel = 3;
+
+ to = Option_enum (_ "Convert to") spaces (spaces.get_name dest);
+
+ _result = map_unary (colour_transform_to to.value_thing) x;
+ }
+
+ Mono_item = class
+ Menuaction (_ "_Monochrome") (_ "convert to mono colourspace") {
+ action x = conv Image_type.B_W x;
+ }
+
+ sRGB_item = class
+ Menuaction (_ "_sRGB") (_ "convert to sRGB colourspace") {
+ action x = conv Image_type.sRGB x;
+ }
+
+ scRGB_item = class
+ Menuaction (_ "_scRGB") (_ "convert to scRGB colourspace") {
+ action x = conv Image_type.scRGB x;
+ }
+
+ GREY16_item = class
+ Menuaction (_ "_GREY16") (_ "convert to GREY16 colourspace") {
+ action x = conv Image_type.GREY16 x;
+ }
+
+ RGB16_item = class
+ Menuaction (_ "_RGB16") (_ "convert to RGB16 colourspace") {
+ action x = conv Image_type.RGB16 x;
+ }
+
+ Lab_item = class
+ Menuaction (_ "_Lab") (_ "convert to Lab colourspace (float Lab)") {
+ action x = conv Image_type.LAB x;
+ }
+
+ LabQ_item = class
+ Menuaction (_ "Lab_Q") (_ "convert to LabQ colourspace (32-bit Lab)") {
+ action x = conv Image_type.LABQ x;
+ }
+
+ LabS_item = class
+ Menuaction (_ "Lab_S") (_ "convert to LabS colourspace (48-bit Lab)") {
+ action x = conv Image_type.LABS x;
+ }
+
+ LCh_item = class
+ Menuaction (_ "L_Ch") (_ "convert to LCh colourspace") {
+ action x = conv Image_type.LCH x;
+ }
+
+ XYZ_item = class
+ Menuaction (_ "_XYZ") (_ "convert to XYZ colourspace") {
+ action x = conv Image_type.XYZ x;
+ }
+
+ Yxy_item = class
+ Menuaction (_ "_Yxy") (_ "convert to Yxy colourspace") {
+ action x = conv Image_type.YXY x;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_UCS") (_ "convert to UCS colourspace") {
+ action x = conv Image_type.UCS x;
+ }
+}
+
+/* mark objects as being in various colourspaces
+ */
+Colour_tag_item = class
+ Menupullright (_ "_Tag As")
+ (_ "tag object as being in various colour spaces") {
+ spaces = Image_type.image_colour_spaces;
+
+ tag dest x = class
+ _result {
+ _vislevel = 3;
+
+ to = Option_enum (_ "Tag as") spaces (spaces.get_name dest);
+
+ _result = map_unary (image_set_type to.value_thing) x;
+ }
+
+ Mono_item = class
+ Menuaction (_ "_Monochrome") (_ "tag as being in mono colourspace") {
+ action x = tag Image_type.B_W x;
+ }
+
+ sRGB_item = class
+ Menuaction (_ "_sRGB") (_ "tag as being in sRGB colourspace") {
+ action x = tag Image_type.sRGB x;
+ }
+
+ scRGB_item = class
+ Menuaction (_ "_scRGB") (_ "tag as being in scRGB colourspace") {
+ action x = tag Image_type.scRGB x;
+ }
+
+ RGB16_item = class
+ Menuaction (_ "_RGB16") (_ "tag as being in RGB16 colourspace") {
+ action x = tag Image_type.RGB16 x;
+ }
+
+ GREY16_item = class
+ Menuaction (_ "_GREY16") (_ "tag as being in GREY16 colourspace") {
+ action x = tag Image_type.GREY16 x;
+ }
+
+ Lab_item = class
+ Menuaction (_ "_Lab")
+ (_ "tag as being in Lab colourspace (float Lab)") {
+ action x = tag Image_type.LAB x;
+ }
+
+ LabQ_item = class
+ Menuaction (_ "Lab_Q")
+ (_ "tag as being in LabQ colourspace (32-bit Lab)") {
+ action x = tag Image_type.LABQ x;
+ }
+
+ LabS_item = class
+ Menuaction (_ "Lab_S")
+ (_ "tag as being in LabS colourspace (48-bit Lab)") {
+ action x = tag Image_type.LABS x;
+ }
+
+ LCh_item = class
+ Menuaction (_ "L_Ch") (_ "tag as being in LCh colourspace") {
+ action x = tag Image_type.LCH x;
+ }
+
+ XYZ_item = class
+ Menuaction (_ "_XYZ") (_ "tag as being in XYZ colourspace") {
+ action x = tag Image_type.XYZ x;
+ }
+
+ Yxy_item = class
+ Menuaction (_ "_Yxy") (_ "tag as being in Yxy colourspace") {
+ action x = tag Image_type.YXY x;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_UCS") (_ "tag as being in UCS colourspace") {
+ action x = tag Image_type.UCS x;
+ }
+}
+
+Colour_temperature_item = class
+ Menupullright (_ "Te_mperature")
+ (_ "colour temperature conversions") {
+ Whitepoint_item = class
+ Menuaction (_ "_Move Whitepoint") (_ "change whitepoint") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ old_white = Option_enum (_ "Old whitepoint") Whitepoints "D65";
+ new_white = Option_enum (_ "New whitepoint") Whitepoints "D50";
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im' *
+ (new_white.value_thing / old_white.value_thing);
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ D65_to_D50_item = class
+ Menupullright (_ "D_65 to D50") (_ "complex conversion") {
+ XYZ_minimal_item = class
+ Menuaction (_ "_Minimal")
+ (_ "D65 to D50 using the minimal 3x3 matrix in XYZ") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = recomb D652D50_direct im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+
+ Bradford_item = class
+ Menuaction (_ "_Bradford") (_ "D65 to D50 in Bradford cone space") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im_D652D50 im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ D50_to_D65_item = class
+ Menupullright (_ "D_50 to D65") (_ "complex conversion") {
+ XYZ_minimal_item = class
+ Menuaction (_ "_Minimal")
+ (_ "D50 to D65 using the minimal 3x3 matrix in XYZ") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = recomb D502D65_direct im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+
+ Bradford_item = class
+ Menuaction (_ "_Bradford") (_ "D60 to D65 in Bradford cone space") {
+ action x
+ = map_unary process x
+ {
+ process im
+ = im'''
+ {
+ im' = colour_transform_to Image_type.XYZ im;
+ im'' = im_D502D65 im';
+ im''' = colour_transform_to (get_type im) im'';
+ }
+ }
+ }
+ }
+
+ Lab_to_D50XYZ_item = class
+ Menuaction (_ "_Lab to D50 XYZ")
+ (_ "Lab to XYZ with a D50 whitepoint") {
+ action x = map_unary (colour_unary im_D50Lab2XYZ) x;
+ }
+
+ D50XYZ_to_Lab_item = class
+ Menuaction (_ "D50 _XYZ to Lab")
+ (_ "XYZ to Lab with a D50 whitepoint") {
+ action x = map_unary (colour_unary im_D50XYZ2Lab) x;
+ }
+
+ sep1 = Menuseparator;
+
+ CCT_item = class
+ Menuaction (_ "Calculate temperature")
+ (_ "estimate CCT using the McCamy approximation") {
+ action z = map_unary temp_from_colour z;
+ }
+
+ Colour_item = Colour_new_item.CCT_colour;
+}
+
+Colour_icc_item = class
+ Menupullright (_ "_ICC") (_ "transform with ICC profiles") {
+ print_profile =
+ "$VIPSHOME/share/$PACKAGE/data/cmyk.icm";
+ monitor_profile =
+ "$VIPSHOME/share/$PACKAGE/data/sRGB.icm";
+ guess_profile image
+ = print_profile,
+ has_type image &&
+ get_type image == Image_type.CMYK &&
+ has_bands image &&
+ get_bands image >= 4
+ = monitor_profile;
+ render_intents = Option_enum (_ "Render intent") Render_intent.names
+ (_ "Absolute");
+
+ Export_item = class
+ Menuaction (_ "_Export") (_ "export from PCS to device space") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ profile = Pathname (_ "Output profile") print_profile;
+ intent = render_intents;
+ depth = Option (_ "Output depth") [_ "8 bit", _ "16 bit"] 0;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_export [8, 16]?depth profile.value
+ intent.value_thing lab
+ {
+ lab = colour_transform_to Image_type.LABQ image;
+ }
+ }
+ }
+ }
+
+ Import_item = class
+ Menuaction (_ "_Import") (_ "import from device space to PCS") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ embedded = Toggle (_ "Use embedded profile if possible") false;
+ profile = Pathname (_ "Default input profile") (guess_profile x);
+ intent = render_intents;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_import_embedded intent.value_thing image,
+ get_header_type "icc-profile-data" image != 0 &&
+ embedded
+ = icc_import profile.value intent.value_thing image;
+ }
+ }
+ }
+
+ Transform_item = class
+ Menuaction (_ "_Transform") (_ "transform between two device spaces") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ in_profile = Pathname (_ "Input profile") (guess_profile x);
+ out_profile = Pathname (_ "Output profile") print_profile;
+ intent = render_intents;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_transform in_profile.value out_profile.value
+ intent.value_thing image;
+ }
+ }
+ }
+
+ AC2RC_item = class
+ Menuaction (_ "_Absolute to Relative")
+ (_ "absolute to relative colorimetry using device profile") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ profile = Pathname (_ "Pick a profile") (guess_profile x);
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = icc_ac2rc profile.value lab
+ {
+ lab = colour_transform_to Image_type.LAB image;
+ }
+ }
+ }
+ }
+}
+
+Colour_rad_item = class
+ Menupullright (_ "_Radiance") (_ "convert to and from Radiance packed format") {
+ Unpack_item = class
+ Menuaction (_ "Unpack")
+ (_ "unpack Radiance format to float") {
+ action x = map_unary rad2float x;
+ }
+
+ Pack_item = class
+ Menuaction (_ "Pack")
+ (_ "pack 3-band float to Radiance format") {
+ action x = map_unary float2rad x;
+ }
+}
+
+#separator
+
+Colour_dE_item = class
+ Menupullright (_ "_Difference") (_ "calculate colour difference") {
+ /* Apply a converter to an object ... convert image or colour (since
+ * we can guess the colour space we're converting from), don't convert
+ * matrix or vector (since we can't tell ... assume it's in the right
+ * space already).
+ */
+ apply_cvt cvt x
+ = cvt x,
+ is_Image x || is_Colour x || is_image x
+ = x;
+
+ diff cvt in1 in2 = abs_vec (apply_cvt cvt in1 - apply_cvt cvt in2);
+
+ /* Converter to LAB.
+ */
+ lab_cvt = colour_transform_to Image_type.LAB;
+
+ /* Converter to UCS ... plain UCS is Ch form, so we go LAB again after
+ * to make sure we get a rectangular coord system.
+ */
+ ucs_cvt = colour_transform Image_type.LCH Image_type.LAB @
+ colour_transform_to Image_type.UCS;
+
+ CIEdE76_item = class
+ Menuaction (_ "CIE dE _76")
+ (_ "calculate CIE dE 1976 for two objects") {
+ action a b = map_binary (diff lab_cvt) a b;
+ }
+
+ CIEdE00_item = class
+ Menuaction (_ "CIE dE _00")
+ (_ "calculate CIE dE 2000 for two objects") {
+ action a b = map_binary
+ (colour_binary (_ "im_dE00_fromLab") im_dE00_fromLab) a b;
+ }
+
+ UCS_item = class
+ Menuaction (_ "_CMC(l:l)") (_ "calculate CMC(l:l) for two objects") {
+ action a b = map_binary (diff ucs_cvt) a b;
+ }
+}
+
+Colour_adjust_item = class
+ Menupullright (_ "_Adjust") (_ "alter colours in various ways") {
+ Recombination_item = class
+ Menuaction (_ "_Recombination")
+ (_ "recombine colour with an editable matrix") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ matrix
+ = Matrix_rec (identity_matrix (bands x))
+ {
+ // try to guess a sensible value for the size of the
+ // matrix
+ bands x
+ = x.bands, is_Image x || is_Colour x
+ = x.width, is_Matrix x
+ = bands x.value?0, is_Group x
+ = x.bands, has_member "bands" x
+ = 3;
+ }
+
+ _result = map_unary (recomb matrix) x;
+ }
+ }
+
+ Cast_item = class
+ Menuaction (_ "_Cast") (_ "displace neutral axis in CIE Lab") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gr = Scale "Green-red" (-20) 20 0;
+ by = Scale "Blue-yellow" (-20) 20 0;
+
+ _result
+ = map_unary adjust_cast x
+ {
+ adjust_cast in
+ = colour_transform_to (get_type in) in''
+ {
+ in' = colour_transform_to Image_type.LAB in;
+ in'' = in' +
+ Vector [0, gr.value, by.value];
+ }
+ }
+ }
+ }
+
+ HSB_item = class
+ Menuaction (_ "_HSB") (_ "adjust hue-saturation-brightness in LCh") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ h = Scale "Hue" 0 360 0;
+ s = Scale "Saturation" 0.01 5 1;
+ b = Scale "Brightness" 0.01 5 1;
+
+ _result
+ = map_unary adjust_hsb x
+ {
+ adjust_hsb in
+ = colour_transform_to (get_type in) in''
+ {
+ in' = colour_transform_to Image_type.LCH in;
+ in'' = in' * Vector [b.value, s.value, 1] +
+ Vector [0, 0, h.value];
+ }
+ }
+ }
+ }
+}
+
+Colour_similar_item = class
+ Menuaction (_ "_Similar Colour") (_ "find pixels with a similar colour") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ target_colour = Colour_picker "Lab" [50, 0, 0];
+ t = Scale "dE threshold" 0 100 10;
+
+ _result
+ = map_unary match x
+ {
+ match in
+ = abs_vec (in' - target) < t
+ {
+ target = colour_transform_to Image_type.LAB target_colour;
+ in' = colour_transform_to Image_type.LAB in;
+ }
+ }
+ }
+}
+
+#separator
+
+Colour_chart_to_matrix_item = class
+ Menuaction (_ "_Measure Colour Chart")
+ (_ "measure average pixel values for a colour chart image") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pacross = Expression (_ "Patches across chart") 6;
+ pdown = Expression (_ "Patches down chart") 4;
+ measure = Scale (_ "Measure area (%)") 1 100 50;
+
+ // get a representative image from an arg
+ get_image x
+ = get_image x.value?0, is_Group x
+ = x;
+
+ _im = get_image x;
+ sample = measure_draw (to_real pacross) (to_real pdown)
+ (to_real measure) _im;
+
+ _result
+ = map_unary chart x
+ {
+ chart in
+ = measure_sample (to_real pacross) (to_real pdown)
+ (to_real measure) in;
+ }
+ }
+}
+
+Colour_matrix_to_chart_item = class
+ Menuaction (_ "Make Synth_etic Colour Chart")
+ (_ "make a colour chart image from a matrix of measurements") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pacross = Expression (_ "Patches across chart") 6;
+ pdown = Expression (_ "Patches down chart") 4;
+ pwidth = Expression (_ "Patch width in pixels") 50;
+ pheight = Expression (_ "Patch height in pixels") 50;
+ bwidth = Expression (_ "Border between patches") 0;
+
+ _result
+ = map_unary build_chart x
+ {
+ build_chart in
+ = Image (imagearray_assemble
+ (to_real bwidth) (to_real bwidth) patch_table)
+ {
+ // patch numbers for row starts
+ rowstart = map (multiply (to_real pacross))
+ [0 .. to_real pdown - 1];
+
+ // assemble patches ... each one a pixel value
+ patches = map (take (to_real pacross))
+ (map (converse drop in.value) rowstart);
+
+ // make an n-band constant image from eg. [1,2,3]
+ // we don't know the format .. use sRGB (well, why not?)
+ patch v = image_new (to_real pwidth) (to_real pheight) (len v)
+ Image_format.FLOAT Image_coding.NOCODING
+ Image_type.sRGB (Vector v) 0 0;
+
+ // make an image for each patch
+ patch_table = map (map patch) patches;
+ }
+ }
+ }
+}
+
+Colour_plot_ab_scatter_item = class
+ Menuaction (_ "_Plot ab Scatter") (_ "plot an ab scatter histogram") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bins = Expression (_ "Number of bins on each axis") 8;
+
+ _result
+ = map_unary plot_scatter x
+ {
+ plot_scatter in
+ = Image (bg * (((90 / mx) * hist) ++ blk))
+ {
+ lab = colour_transform_to Image_type.LAB in.value;
+ ab = (unsigned char) ((lab?1 ++ lab?2) + 128);
+ hist = hist_find_nD bins.expr ab;
+ mx = max hist;
+ bg = lab_slice bins.expr 1;
+ blk = 1 + im_black (to_real bins) (to_real bins) 2;
+ }
+ }
+ }
+}
diff --git a/share/nip2/compat/8.6/Filter.def b/share/nip2/compat/8.6/Filter.def
new file mode 100644
index 00000000..a8ac20c0
--- /dev/null
+++ b/share/nip2/compat/8.6/Filter.def
@@ -0,0 +1,1694 @@
+Filter_conv_item = class
+ Menupullright "_Convolution" "various spatial convolution filters" {
+ /* Some useful masks.
+ */
+ filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
+ filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]];
+ filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]];
+ filter_laplacian = Matrix_con 1 128
+ [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]];
+ filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]];
+ filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]];
+
+ Blur_item = class
+ Menuaction "_Blur" "3x3 blur of image" {
+ action x = map_unary (conv filter_blur) x;
+ }
+
+ Sharpen_item = class
+ Menuaction "_Sharpen" "3x3 sharpen of image" {
+ action x = map_unary (conv filter_sharp) x;
+ }
+
+ Emboss_item = class
+ Menuaction "_Emboss" "1 pixel displace emboss" {
+ action x = map_unary (conv filter_emboss) x;
+ }
+
+ Laplacian_item = class
+ Menuaction "_Laplacian" "3x3 laplacian edge detect" {
+ action x = map_unary (conv filter_laplacian) x;
+ }
+
+ Sobel_item = class
+ Menuaction "So_bel" "3x3 Sobel edge detect" {
+ action x
+ = map_unary sobel x
+ {
+ sobel im
+ = abs (a - 128) + abs (b - 128)
+ {
+ a = conv filter_sobel im;
+ b = conv (rot270 filter_sobel) im;
+ }
+ }
+ }
+
+/* 3x3 line detect of image
+diagonals should be scaled down by root(2) I guess
+Kirk
+*/
+ Linedet_item = class
+ Menuaction "Li_ne Detect" "3x3 line detect" {
+ action x
+ = map_unary lindet x
+ {
+ lindet im
+ = foldr1 max_pair images
+ {
+ masks = take 4 (iterate rot45 filter_lindet);
+ images = map (converse conv im) masks;
+ }
+ }
+ }
+
+ Usharp_item = class
+ Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ size = Option "Radius" [
+ "3 pixels",
+ "5 pixels",
+ "7 pixels",
+ "9 pixels",
+ "11 pixels",
+ "51 pixels"
+ ] 0;
+
+ st = Scale "Smoothness threshold" 0 5 2;
+ bm = Scale "Brighten by at most" 1 50 10;
+ dm = Scale "Darken by at most" 1 50 20;
+ fs = Scale "Sharpen flat areas by" 0 5 0.5;
+ js = Scale "Sharpen jaggy areas by" 0 5 1;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = Image in'''
+ {
+ in' = colour_transform_to Image_type.LABS in.value;
+ in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
+ in''' = colour_transform_to (get_type in) in'';
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Custom_blur_item = class
+ Menuaction "Custom B_lur / Sharpen"
+ "blur or sharpen with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ type = Option "Type" ["Blur", "Sharpen"] 0;
+ r = Scale "Radius" 1 100 1;
+ fac = Scale "Amount" 0 1 1;
+ layers = Scale "Layers" 1 100 10;
+ shape = Option "Mask shape" [
+ "Square",
+ "Gaussian"
+ ] 0;
+ prec = Option "Precision" ["Int", "Float", "Approximate"] 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = clip2fmt blur.format proc
+ {
+ mask
+ = matrix_blur r.value, shape.value == 0
+ = matrix_gaussian_blur r.value;
+ blur = [convsep, convsepf, aconvsep layers]?prec mask in;
+ proc
+ = in + fac * (in - blur), type == 1
+ = blur * fac + in * (1 - fac);
+ }
+ }
+ }
+ }
+
+ Custom_conv_item = class
+ Menuaction "Custom C_onvolution"
+ "convolution filter with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
+ separable
+ = Toggle "Seperable convolution" false,
+ matrix.width == 1 || matrix.height == 1
+ = false;
+ type = Option "Convolution type" ["Int", "Float"] 0;
+ rotate = Option "Rotate" [
+ "Don't rotate",
+ "4 x 45 degrees",
+ "8 x 45 degrees",
+ "2 x 90 degrees"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = in.Image in'
+ {
+ conv_fn
+ = im_lindetect, !separable && type == 0 && rotate == 1
+ = im_compass, !separable && type == 0 && rotate == 2
+ = im_gradient, !separable && type == 0 && rotate == 3
+ = im_conv, !separable && type == 0
+ = im_convsep, separable && type == 0
+ = im_conv_f, !separable && type == 1
+ = im_convsep_f, separable && type == 1
+ = error "boink!";
+ in' = conv_fn in.value matrix;
+ }
+ }
+ }
+ }
+}
+
+Filter_rank_item = class
+ Menupullright "_Rank" "various rank filters" {
+ Median_item = class
+ Menuaction "_Median" "3x3 median rank filter" {
+ action x = map_unary (rank 3 3 4) x;
+ }
+
+ Image_rank_item = class
+ Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ select
+ = Expression "Rank" ((int) (guess_size / 2))
+ {
+ guess_size
+ = len x, is_list x
+ = len x.value, is_Group x
+ = 0;
+ }
+
+ // can't really iterate over groups ... since we allow a group
+ // argument
+ _result = rank_image select x;
+ }
+ }
+
+ Custom_rank_item = class
+ Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ window_width = Expression "Window width" 3;
+ window_height = Expression "Window height" 3;
+ select = Expression "Rank"
+ ((int) ((to_real window_width * to_real window_height) / 2));
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = rank window_width window_height select in;
+ }
+ }
+ }
+}
+
+Filter_morphology_item = class
+ Menupullright "_Morphology" "various morphological filters" {
+ /* Some useful masks.
+ */
+ mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
+ mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
+ mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
+ thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];
+
+ Threshold_item = Select_item.Threshold_item;
+
+ sep1 = Menuseparator;
+
+ Dilate_item = class
+ Menupullright "_Dilate" "morphological dilate" {
+ Dilate8_item = class
+ Menuaction "_8-connected" "dilate with an 8-connected mask" {
+ action x = map_unary (dilate mask8) x;
+ }
+
+ Dilate4_item = class
+ Menuaction "_4-connected" "dilate with a 4-connected mask" {
+ action x = map_unary (dilate mask4) x;
+ }
+ }
+
+ Erode_item = class
+ Menupullright "_Erode" "morphological erode" {
+ Erode8_item = class
+ Menuaction "_8-connected" "erode with an 8-connected mask" {
+ action x = map_unary (erode mask8) x;
+ }
+
+ Erode4_item = class
+ Menuaction "_4-connected" "erode with a 4-connected mask" {
+ action x = map_unary (erode mask4) x;
+ }
+ }
+
+ Custom_morph_item = class
+ Menuaction "Custom _Morphology"
+ "convolution morphological operator" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ mask = mask4;
+ type = Option "Operation" ["Erode", "Dilate"] 1;
+ apply = Expression "Number of times to apply mask" 1;
+
+ _result
+ = map_unary morph x
+ {
+ morph image
+ = Image value'
+ {
+ fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);
+
+ value'
+ = im_erode image.value fatmask, type.value == 0
+ = im_dilate image.value fatmask;
+ }
+ }
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Open_item = class
+ Menuaction "_Open" "open with an 8-connected mask" {
+ action x = map_unary (dilate mask8 @ erode mask8) x;
+ }
+
+ Close_item = class
+ Menuaction "_Close" "close with an 8-connected mask" {
+ action x = map_unary (erode mask8 @ dilate mask8) x;
+ }
+
+ Clean_item = class
+ Menuaction "C_lean" "remove 8-connected isolated points" {
+ action x
+ = map_unary clean x
+ {
+ clean x = x ^ erode mask1 x;
+ }
+ }
+
+ Thin_item = class
+ Menuaction "_Thin" "thin once" {
+ action x
+ = map_unary thinall x
+ {
+ masks = take 8 (iterate rot45 thin);
+ thin1 m x = x ^ erode m x;
+ thinall x = foldr thin1 x masks;
+ }
+ }
+
+}
+
+Filter_fourier_item = class
+ Menupullright "_Fourier" "various Fourier filters" {
+ preview_size = 64;
+
+ sense_option = Option "Sense" [
+ "Pass",
+ "Reject"
+ ] 0;
+
+ // make a visualisation image
+ make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
+ (im_create_fmask preview_size preview_size);
+
+ // make the process function
+ process fn in
+ = (Image @ fn) (im_flt_image_freq in.value);
+
+ New_ideal_item = class
+ Menupullright "_Ideal" "various ideal Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f sense.value fc.value 0 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 6) fc.value
+ rw.value 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject ideal Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 12) fcx.value fcy.value
+ r.value 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+
+ New_gaussian_item = class
+ Menupullright "_Gaussian" "various Gaussian Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 4) fc.value
+ ac.value 0 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 10) fc.value
+ rw.value ac.value 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject Gaussian Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 16) fcx.value fcy.value
+ r.value ac.value 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+
+ New_butterworth_item = class
+ Menupullright "_Butterworth"
+ "various Butterworth Fourier filters" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ "highpass/lowpass Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 2) o.value fc.value ac.value
+ 0 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ "ring pass/reject Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 8) o.value fc.value rw.value
+ ac.value 0;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ "band pass/reject Butterworth Fourier filter" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ o = Scale "Order" 1 10 2;
+
+ // call a freq func with our parameters
+ _params f = f (sense.value + 14) o.value fcx.value fcy.value
+ r.value ac.value;
+
+ visualize_mask = make_vis _params;
+
+ _result = map_unary (process _params) x;
+ }
+ }
+ }
+}
+
+Filter_enhance_item = class
+ Menupullright "_Enhance" "various enhancement filters" {
+ Falsecolour_item = class
+ Menuaction "_False Colour" "false colour a mono image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ o = Scale "Offset" (-255) 255 0;
+ clip = Toggle "Clip colour range" false;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = falsecolour mono''
+ {
+ mono = colour_transform_to Image_type.B_W im;
+ mono' = mono + o;
+ mono''
+ = (unsigned char) mono', clip
+ = (unsigned char) (mono' & 0xff);
+ }
+ }
+ }
+ }
+
+ Statistical_diff_item = class
+ Menuaction "_Statistical Difference"
+ "statistical difference of an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ wsize = Expression "Window size" 11;
+ tmean = Expression "Target mean" 128;
+ mean_weight = Scale "Mean weight" 0 1 0.8;
+ tdev = Expression "Target deviation" 50;
+ dev_weight = Scale "Deviation weight" 0 1 0.8;
+ border = Toggle "Output image matches input image in size" true;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = Image in''
+ {
+ in' = colour_transform_to Image_type.B_W in.value;
+ fn
+ = im_stdif, border
+ = im_stdif_raw;
+ in'' = fn in'
+ mean_weight.value tmean.expr
+ dev_weight.value tdev.expr
+ wsize.expr wsize.expr;
+ }
+ }
+ }
+ }
+
+ Hist_equal_item = class
+ Menupullright "_Equalise Histogram" "equalise contrast" {
+ Global_item = class
+ Menuaction "_Global" "equalise contrast globally" {
+ action x = map_unary hist_equalize x;
+ }
+
+ Local_item = class
+ Menuaction "_Local" "equalise contrast within a roving window" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ window_width = Expression "Window width" 20;
+ window_height = Expression "Window height" 20;
+ max_slope = Scale "Maxium slope" 0 10 0;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = hist_equalize_local
+ window_width
+ window_height
+ max_slope in;
+ }
+ }
+ }
+ }
+}
+
+Filter_correlate_item = class
+ Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
+ Correlate_item = class
+ Menuaction "_Correlate" "calculate correlation coefficient" {
+ action a b
+ = map_binary corr a b
+ {
+ corr a b
+ = correlate a b,
+ a.width <= b.width && a.height <= b.height
+ = correlate b a;
+ }
+ }
+
+ Correlate_fast_item = class
+ Menuaction "_Simple Difference"
+ "calculate sum of squares of differences" {
+ action a b
+ = map_binary corr a b
+ {
+ corr a b
+ = correlate_fast a b,
+ a.width <= b.width && a.height <= b.height
+ = correlate_fast b a;
+ }
+ }
+}
+
+Filter_hough_item = class
+ Menupullright "_Hough Transform" "transform to parameter space" {
+ Line_item = class
+ Menuaction "_Line" "find straight line Hough transform" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ pspace_width = Expression "Parameter space width" 64;
+ pspace_height = Expression "Parameter space height" 64;
+
+ _result
+ = map_unary line a
+ {
+ line a
+ = hough_line
+ (to_real pspace_width) (to_real pspace_height) a;
+ }
+ }
+ }
+
+ Circle_item = class
+ Menuaction "_Circle" "find circle Hough transform" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ scale = Expression "Scale down parameter space by" 10;
+ min_radius = Expression "Minimum radius" 10;
+ max_radius = Expression "Maximum radius" 30;
+
+ _result
+ = map_unary circle a
+ {
+ circle a
+ = hough_circle (to_real scale) (to_real min_radius)
+ (to_real max_radius) a;
+ }
+ }
+ }
+}
+
+Filter_coordinate_item = class
+ Menupullright "_Coordinate Transform" "various coordinate transforms" {
+ // run a function which wants a complex arg on a non-complex two-band
+ // image
+ run_cmplx fn x
+ = re x' ++ im x'
+ {
+ x' = fn (x?0, x?1);
+ }
+
+ Polar_item = class
+ Menuaction "_Polar" "transform to polar coordinates" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary to_polar a
+ {
+ to_polar im
+ = mapim interp.value map' im
+ {
+ // xy image, origin in the centre, scaled to fit image to
+ // a circle
+ xy = make_xy im.width im.height;
+ xy' = xy - Vector [im.width / 2, im.height / 2];
+ scale = min [im.width, im.height] / im.width;
+ xy'' = 2 * xy' / scale;
+
+ // to polar, scale vertical axis to 360 degrees
+ map = run_cmplx polar xy'';
+ map' = map * Vector [1, im.height / 360];
+ }
+ }
+ }
+ }
+
+ Rectangular_item = class
+ Menuaction "_Rectangular" "transform to rectangular coordinates" {
+ action a = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary to_rect a
+ {
+ to_rect im
+ = mapim interp.value map'' im
+ {
+ // xy image, vertical scaled to 360 degrees
+ xy = make_xy im.width im.height;
+ xy' = xy * Vector [1, 360 / im.height];
+
+ // to rect, scale to image rect
+ map = run_cmplx rectangular xy';
+ scale = min [im.width, im.height] / im.width;
+ map' = map * scale / 2;
+
+ map'' = map' + Vector [im.width / 2, im.height / 2];
+ }
+ }
+ }
+ }
+}
+
+#separator
+
+Filter_tilt_item = class
+ Menupullright "Ti_lt Brightness" "tilt brightness" {
+ Left_right_item = class
+ Menuaction "_Left to Right" "linear left-right brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Left-right tilt" (-1) 1 0;
+
+ _result
+ = map_unary tilt_lr x
+ {
+ tilt_lr image
+ = image * scale
+ {
+ ramp = im_fgrey image.width image.height;
+ scale = (ramp - 0.5) * tilt + 1;
+ }
+ }
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "linear top-bottom brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Top-bottom tilt" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ ramp = rot90
+ (im_fgrey image.height image.width);
+ scale = (ramp - 0.5) * tilt + 1;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Left_right_cos_item = class
+ Menuaction "Cosine Left-_right" "cosine left-right brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Left-right tilt" (-1) 1 0;
+ shift = Scale "Shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_lr x
+ {
+ tilt_lr image
+ = image * scale
+ {
+ ramp = im_fgrey image.width image.height - 0.5 -
+ shift.value;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+
+ Top_bottom_cos_item = class
+ Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Top-bottom tilt" (-1) 1 0;
+ shift = Scale "Shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
+ shift.value;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Circular_item = class
+ Menuaction "_Circular" "circular brighten" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tilt = Scale "Tilt" (-1) 1 0;
+ hshift = Scale "Horizontal shift by" (-1) 1 0;
+ vshift = Scale "Vertical shift by" (-1) 1 0;
+
+ _result
+ = map_unary tilt_tb x
+ {
+ tilt_tb image
+ = image * scale
+ {
+ hramp = im_fgrey image.width image.height - 0.5 -
+ hshift.value;
+ vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
+ vshift.value;
+ ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
+ scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
+ }
+ }
+ }
+ }
+}
+
+Filter_blend_item = class
+ Menupullright "_Blend" "blend objects together" {
+ Scale_blend_item = class
+ Menuaction "_Scale" "blend two objects together with a scale" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ p = Scale "Blend position" 0 1 0.5;
+
+ _result
+ = map_binary process a b
+ {
+ process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
+ }
+ }
+ }
+
+ Image_blend_item = class
+ Menuaction "_Image" "use an image to blend two objects" {
+ action a b c = class
+ _result {
+ _vislevel = 3;
+
+ i = Toggle "Invert mask" false;
+
+ _result
+ = map_trinary process a b c
+ {
+ process a b c
+ = blend condition in1 in2, !i
+ = blend (invert condition) in1 in2
+ {
+ compare a b
+ // prefer image as the condition
+ = false,
+ !has_image a && has_image b
+ // prefer mono images as the condition
+ = false,
+ has_bands a && has_bands b &&
+ get_bands a > 1 && get_bands b == 1
+ // prefer uchar as the condition
+ = false,
+ has_format a && has_format b &&
+ get_format a > Image_format.UCHAR &&
+ get_format b == Image_format.UCHAR
+ = true;
+ [condition, in1, in2] = sortc compare [a, b, c];
+ }
+ }
+ }
+ }
+
+ Line_blend_item = class
+ Menuaction "_Along Line"
+ "blend between image a and image b along a line" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Left to Right",
+ "Top to Bottom"
+ ] 0;
+ blend_position = Scale "Blend position" 0 1 0.5;
+ blend_width = Scale "Blend width" 0 1 0.05;
+
+ _result
+ = map_binary process a b
+ {
+ process a b
+ = blend (Image condition) b a
+ {
+ output_width = max_pair a.width b.width;
+ output_height = max_pair a.height b.height;
+ range
+ = output_width, orientation == 0
+ = output_height;
+ blend_position'
+ = floor (range * blend_position.value);
+ blend_width'
+ = 1, blend_width.value == 0
+ = floor (range * blend_width.value);
+ start = blend_position' - blend_width' / 2;
+
+ background = (make_xy output_width output_height) >=
+ blend_position';
+ ramp
+ = im_grey blend_width' output_height, orientation == 0
+ = rot90 (im_grey blend_width' output_width);
+ condition
+ = insert_noexpand start 0 ramp background?0,
+ orientation == 0
+ = insert_noexpand 0 start ramp background?1;
+ }
+ }
+ }
+ }
+
+ Blend_alpha_item = class
+ Menuaction "Blend _Alpha" "blend images with optional alpha channels" {
+ // usage: layerit foreground background
+ // input images must be either 1 or 3 bands, optionally + 1 band
+ // which is used as the alpha channel
+ // rich lott
+
+ scale_mask im opacity
+ = (unsigned char) (to_real opacity / 255 * im);
+
+ // to mono
+ intensity = colour_transform_to Image_type.B_W;
+
+ // All the blend functions
+ // I am grateful to this page
+ // http://www.pegtop.net/delphi/blendmodes/
+ // for most of the formulae.
+
+ blend_normal mask opacity fg bg
+ = blend (scale_mask mask opacity) fg bg;
+
+ blend_iflighter mask opacity fg bg
+ = blend (if fg' > bg' then mask' else 0) fg bg
+ {
+ fg' = intensity fg;
+ bg' = intensity bg;
+ mask' = scale_mask mask opacity ;
+ }
+
+ blend_ifdarker mask opacity fg bg
+ = blend (if fg' < bg' then mask' else 0) fg bg
+ {
+ fg' = intensity fg ;
+ bg' = intensity bg ;
+ mask' = scale_mask mask opacity ;
+ }
+
+ blend_multiply mask opacity fg bg
+ = blend (scale_mask mask opacity) fg' bg
+ {
+ fg' = fg / 255 * bg;
+ }
+
+ blend_add mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = opacity / 255 * fg + bg;
+ }
+
+ blend_subtract mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = bg - opacity / 255 * fg;
+ }
+
+ blend_screen mask opacity fg bg
+ = blend mask fg' bg
+ {
+ fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
+ }
+
+ blend_burn mask opacity fg bg
+ = blend mask fg'' bg
+ {
+ // fades to white which has no effect.
+ fg' = (255 - opacity) + opacity * fg / 255;
+ fg'' = 255 - 255 * (255 - bg) / fg';
+ }
+
+ blend_softlight mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
+ }
+
+ blend_hardlight mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg'
+ = 2 / 255 * fg * bg, bg < 129
+ = 255 - 2 * (255 - bg) * (255 - fg) / 255;
+ }
+
+ blend_lighten mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = if bg < fg then fg else bg;
+ }
+
+ blend_darken mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = if bg > fg then fg else bg;
+ }
+
+ blend_dodge mask opacity fg bg
+ = blend mask fg'' bg
+ {
+ // one added to avoid divide by zero
+ fg' = 1 + 255 - (opacity / 255 * fg);
+ fg'' = bg * 255 / fg';
+ }
+
+ blend_reflect mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = bg * bg / (255 - fg);
+ }
+
+ blend_freeze mask opacity fg bg
+ = blend mask' fg' bg
+ {
+ mask' = scale_mask mask opacity;
+ fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
+ }
+
+ blend_or mask opacity fg bg
+ = bg | (unsigned char) fg'
+ {
+ mask' = scale_mask mask opacity;
+ fg' = fg * mask' / 255;
+ }
+
+ blend_and mask opacity fg bg
+ = bg & (unsigned char) fg'
+ {
+ mask' = scale_mask mask opacity;
+ fg' = fg * mask' / 255;
+ }
+
+ // blend types
+ NORMAL = 0;
+ IFLIGHTER = 1;
+ IFDARKER = 2;
+ MULTIPLY = 3;
+ ADD = 4;
+ SUBTRACT = 5;
+ SCREEN = 6;
+ BURN = 7;
+ DODGE = 8;
+ HARDLIGHT = 9;
+ SOFTLIGHT = 10;
+ LIGHTEN = 11;
+ DARKEN = 12;
+ REFLECT = 13;
+ FREEZE = 14;
+ OR = 15;
+ AND = 16;
+
+ // names we show the user for blend types
+ names = Enum [
+ _ "Normal" => NORMAL,
+ _ "If Lighter" => IFLIGHTER,
+ _ "If Darker" => IFDARKER,
+ _ "Multiply" => MULTIPLY,
+ _ "Add" => ADD,
+ _ "Subtract" => SUBTRACT,
+ _ "Screen" => SCREEN,
+ _ "Burn" => BURN,
+ _ "Soft Light" => SOFTLIGHT,
+ _ "Hard Light" => HARDLIGHT,
+ _ "Lighten" => LIGHTEN,
+ _ "Darken" => DARKEN,
+ _ "Dodge" => DODGE,
+ _ "Reflect" => REFLECT,
+ _ "Freeze" => FREEZE,
+ _ "Bitwise OR" => OR,
+ _ "Bitwise AND" => AND
+ ];
+
+ // functions we call for each blend type
+ actions = Table [
+ [NORMAL, blend_normal],
+ [IFLIGHTER, blend_iflighter],
+ [IFDARKER, blend_ifdarker],
+ [MULTIPLY, blend_multiply],
+ [ADD, blend_add],
+ [SUBTRACT, blend_subtract],
+ [SCREEN, blend_screen],
+ [BURN, blend_burn],
+ [SOFTLIGHT, blend_softlight],
+ [HARDLIGHT, blend_hardlight],
+ [LIGHTEN, blend_lighten],
+ [DARKEN, blend_darken],
+ [DODGE, blend_dodge],
+ [REFLECT, blend_reflect],
+ [FREEZE, blend_freeze],
+ [OR, blend_or],
+ [AND, blend_and]
+ ];
+
+ // make sure im has an alpha channel (set opaque if it hasn't)
+ put_alpha im
+ = im, im.bands == 4 || im.bands == 2
+ = im ++ 255;
+
+ // make sure im has no alpha channel
+ lose_alpha im
+ = extract_bands 0 3 im, im.bands == 4
+ = im?0, im.bands == 2
+ = im;
+
+ // does im have al alpha channel?
+ has_alpha im = im.bands == 2 || im.bands == 4;
+
+ // get the alpha (set opaque if no alpha)
+ get_alpha img
+ = img'?3, img.bands == 4
+ = img'?1
+ {
+ img' = put_alpha img;
+ }
+
+ // add an alpha ... cast the alpha image to match the main image
+ append_alpha im alpha
+ = im ++ clip2fmt im.format alpha;
+
+ // makes fg the same size as bg, displaced with u, v pixel offset
+ moveit fg bg u v
+ = insert_noexpand u v fg bg'
+ {
+ bg' = image_new bg.width bg.height
+ fg.bands fg.format fg.coding fg.type 0 0 0;
+ }
+
+ action bg fg = class
+ _value {
+ _vislevel = 3;
+
+ method = Option_enum "Blend mode" names "Normal";
+ opacity = Scale "Opacity" 0 255 255;
+ hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0;
+ vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0;
+
+ _value
+ = append_alpha blended merged_alpha, has_alpha bg
+ = blended
+ {
+ // displace and resize fg (need to displace alpha too)
+ fg' = moveit (put_alpha fg) bg hmove vmove;
+
+ // transform to sRGB
+ fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg');
+ bg' = colour_transform_to Image_type.sRGB (lose_alpha bg);
+
+ // alphas merged
+ merged_alpha = get_alpha bg | get_alpha fg';
+
+ // blend together
+ blended = (actions.lookup 0 1 method.value_thing)
+ (get_alpha fg') opacity.value fg'' bg';
+ }
+ }
+ }
+}
+
+Filter_overlay_header_item = class
+ Menuaction "_Overlay"
+ "make a colour overlay of two monochrome images" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ colour = Option "Colour overlay as" [
+ _ "Green over Red",
+ _ "Blue over Red",
+ _ "Red over Green",
+ _ "Red over Blue",
+ _ "Blue over Green",
+ _ "Green over Blue"
+ ] 0;
+
+ _result
+ = map_binary overlay a b
+ {
+ overlay a b
+ = image_set_type Image_type.sRGB
+ [(a' ++ b' ++ 0),
+ (a' ++ 0 ++ b'),
+ (b' ++ a' ++ 0),
+ (b' ++ 0 ++ a'),
+ (0 ++ a' ++ b'),
+ (0 ++ b' ++ a')]?colour
+ {
+ a' = colour_transform_to Image_type.B_W a;
+ b' = colour_transform_to Image_type.B_W b;
+ }
+ }
+ }
+}
+
+Filter_colourize_item = class
+ Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ tint = Scale "Tint" 0 1 0.6;
+
+ _result
+ = map_binary tintit a b
+ {
+ tintit a b
+ = colour_transform_to (get_type colour) colourized'
+ {
+ // get the mono thing first
+ [mono, colour] =
+ sortc (const (is_colour_type @ get_type)) [a, b];
+
+ colour' = tint * colour_transform_to Image_type.LAB colour;
+ mono' = colour_transform_to Image_type.B_W mono;
+ colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
+ colourized' = image_set_type Image_type.LAB colourized;
+ }
+ }
+ }
+}
+
+Filter_browse_multiband_item = class
+ Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
+ Bandwise_item = class
+ Menuaction "B_andwise" "browse through the bands of a multiband image" {
+ action image = class
+ _result {
+ _vislevel = 3;
+
+ band = Scale "Band" 0 (image.bands - 1) 0;
+ display = Option "Display as" [
+ _ "Grey",
+ _ "Green over Red",
+ _ "Blue over Red",
+ _ "Red over Green",
+ _ "Red over Blue",
+ _ "Blue over Green",
+ _ "Green over Blue"
+ ] 0;
+
+ _result
+ = output
+ {
+ down = (int) band.value;
+ up = down + 1;
+ remainder = band.value - down;
+
+ fade x a
+ = Vector [0], x == 0
+ = a * x;
+
+ a = fade remainder image?up;
+ b = fade (1 - remainder) image?down;
+
+ output = [
+ a + b,
+ a ++ b ++ 0,
+ a ++ 0 ++ b,
+ b ++ a ++ 0,
+ b ++ 0 ++ a,
+ 0 ++ a ++ b,
+ 0 ++ b ++ a
+ ] ? display;
+ }
+ }
+ }
+
+ Bitwise_item = class
+ Menuaction "Bi_twise" "browse through the bits of an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bit
+ = Islider "Bit" 0 (nbits - 1) (nbits - 1)
+ {
+ nbits
+ = x.bits, is_Image x
+ = 8;
+ Islider c f t v = class
+ scope.Scale c f t ((int) v) {
+ Scale = Islider;
+ }
+ }
+
+ _result
+ = map_unary process x
+ {
+ process im = (im & (0x1 << bit.value)) != 0;
+ }
+ }
+ }
+}
+
+#separator
+
+Filter_negative_item = class
+ Menuaction "Photographic _Negative" "swap black and white" {
+ action x
+ = map_unary invert x
+ {
+ invert in
+ = clip2fmt in.format (colour_transform_to (get_type in) rgb')
+ {
+ rgb = colour_transform_to Image_type.sRGB in;
+ rgb' = 255 - rgb;
+ }
+ }
+}
+
+Filter_solarize_item = class
+ Menuaction "_Solarise" "invert colours above a threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ kink = Scale "Kink" 0 1 0.5;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = hist_map tab'''' image
+ {
+ // max pixel value for this format
+ mx = Image_format.maxval image.format;
+
+ // make a LUT ... just 8 and 16 bit
+ tab
+ = im_identity_ushort image.bands mx,
+ image.format ==
+ Image_format.USHORT
+ = im_identity image.bands;
+ tab' = Image tab;
+
+ // make basic ^ shape
+ tab''
+ = tab' * (1 / kink), tab' < mx * kink
+ = (mx - tab') / (1 - kink);
+ tab''' = clip2fmt image.format tab'';
+
+ // smooth a bit
+ mask = matrix_blur (tab'''.width / 8);
+ tab'''' = convsep mask tab''';
+ }
+ }
+ }
+}
+
+Filter_diffuse_glow_item = class
+ Menuaction "_Diffuse Glow" "add a halo to highlights" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ r = Scale "Radius" 0 50 5;
+ highlights = Scale "Highlights" 0 100 95;
+ glow = Scale "Glow" 0 1 0.5;
+ colour = Colour_new_item.Widget_colour_item.action;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = image'
+ {
+ mono = (unsigned char) (colour_transform_to
+ Image_type.B_W image);
+ thresh = hist_thresh (highlights.value / 100) mono;
+ mask = mono > thresh;
+ blur = convsep (matrix_gaussian_blur r.value) mask;
+ colour' = colour_transform_to image.type colour;
+ image' = image + colour' * glow * (blur / 255);
+ }
+ }
+ }
+}
+
+Filter_drop_shadow_item = class
+ Menuaction "Drop S_hadow" "add a drop shadow to an image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sx = Scale "Horizontal shadow" (-50) 50 5;
+ sy = Scale "Vertical shadow" (-50) 50 5;
+ ss = Scale "Shadow softness" 0 20 5;
+ bg_colour = Expression "Background colour" 255;
+ sd_colour = Expression "Shadow colour" 128;
+ alpha = Toggle "Shadow in alpha channel" false;
+ transparent = Toggle "Zero pixels are transparent" false;
+
+ _result
+ = map_unary shadow x
+ {
+ shadow image
+ = Image final
+ {
+ blur_size = ss.value * 2 + 1;
+
+ // matrix we blur with to soften shadows
+ blur_matrix = matrix_gaussian_blur blur_size;
+ matrix_size = blur_matrix.width;
+ matrix_radius = (int) (matrix_size / 2) + 1;
+
+ // position and size of shadow image in input cods
+ // before and after fuzzing
+ shadow_rect = Rect sx.value sy.value
+ image.width image.height;
+ fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;
+
+ // size and pos of final image, in input cods
+ final_rect = image.rect.union fuzzy_shadow_rect;
+
+ // hard part of shadow in output cods
+ shadow_rect' = Rect
+ (shadow_rect.left - final_rect.left)
+ (shadow_rect.top - final_rect.top)
+ shadow_rect.width shadow_rect.height;
+
+ // make the shadow mask ... true for parts which cast
+ // a shadow
+ mask
+ = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
+ transparent
+ = image_new image.width image.height 1 Image_format.UCHAR
+ Image_coding.NOCODING Image_type.B_W 255 0 0;
+ mask' = embed 0 shadow_rect'.left shadow_rect'.top
+ final_rect.width final_rect.height mask;
+ mask'' = convsep blur_matrix mask';
+
+ // use mask to fade between bg and shadow colour
+ mk_background colour = image_new
+ final_rect.width final_rect.height
+ image.bands image.format image.coding image.type
+ colour 0 0;
+
+ bg_image = mk_background bg_colour.expr;
+ shadow_image = mk_background sd_colour.expr;
+ bg = blend mask'' shadow_image bg_image;
+
+ // make a full size mask
+ fg_mask = embed 0
+ (image.rect.left - final_rect.left)
+ (image.rect.top - final_rect.top)
+ final_rect.width final_rect.height mask;
+
+ // wrap up the input image ... put the shadow colour
+ // around it, so if we are outputting a separate
+ // alpha the shadow colour will be set correctly
+ fg = insert (image.rect.left - final_rect.left)
+ (image.rect.top - final_rect.top)
+ image.value shadow_image;
+
+ final
+ // make a separate alpha
+ = fg ++ mask'', alpha
+
+ // paste image over shadow
+ = if fg_mask then fg else bg;
+ }
+ }
+ }
+}
+
+Filter_paint_text_item = class
+ Menuaction "_Paint Text" "paint text into an image" {
+ action x
+ = paint_position, is_Group x
+ = paint_area
+ {
+ paint_area = class
+ _result {
+ _check_args = [
+ [x, "x", check_Image]
+ ];
+ _vislevel = 3;
+
+ text = String "Text to paint" "Hello world!";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ align = Option "Alignment" ["Left", "Centre", "Right"] 0;
+ dpi = Expression "DPI" 300;
+ colour = Expression "Text colour" 255;
+ place = Region x (x.width / 4) (x.height / 4)
+ (x.width / 2) (x.height / 2);
+
+ _result
+ = insert_noexpand place.left place.top (blend txt' fg place) x
+ {
+ fg = image_new place.width place.height x.bands x.format
+ x.coding x.type colour.expr 0 0;
+ txt = Image (im_text text.value font.value
+ place.width align.value (to_real dpi));
+ bg = im_black place.width place.height 1;
+ txt' = insert_noexpand 0 0 txt bg;
+ }
+ }
+
+ paint_position = class
+ _result {
+ _vislevel = 3;
+
+ text = Pattern_images_item.Text_item.action;
+ colour = Expression "Text colour" 255;
+ position = Option "Position" [
+ _ "North-west",
+ _ "North",
+ _ "North-east",
+ _ "West",
+ _ "Centre",
+ _ "East",
+ _ "South-west",
+ _ "South",
+ _ "South-east",
+ _ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_unary paint x
+ {
+ paint image
+ = insert_noexpand x' y' place' image
+ {
+ xr = image.width - text.width;
+ yr = image.height - text.height;
+ x
+ = left.expr, position == 9
+ = [0, xr / 2, xr]?(position % 3);
+ y
+ = top.expr, position == 9
+ = [0, yr / 2, yr]?(position / 3);
+ x' = range 0 x (image.width - 1);
+ y' = range 0 y (image.height - 1);
+ w' = range 1 text.width (image.width - x');
+ h' = range 1 text.height (image.height - y');
+
+ place = extract_area x' y' w' h' image;
+ text' = insert_noexpand 0 0 text (im_black w' h' 1);
+ fg = image_new w' h' image.bands image.format
+ image.coding image.type colour.expr 0 0;
+ place' = blend text' fg place;
+ }
+ }
+ }
+ }
+}
+
+Autotrace_item = class
+ Menuaction "_Trace" "convert a bitmap to an SVG file" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ despeckle = Scale "Despeckle level" 1 20 1;
+ line = Scale "Line threshold" 1 20 1;
+ center = Toggle "Trace centreline" false;
+ scale = Scale "SVG scale" 0.1 10 1;
+
+ command
+ = "autotrace %s " ++ join_sep " "
+ [ofmt, ofile, desp, lint, cent]
+ {
+ prog = search_for_error "autotrace";
+ ofmt = "-output-format svg";
+ ofile = "-output-file %s";
+ desp = "-despeckle-level " ++ print despeckle.value;
+ lint = "-line-threshold " ++ print line.value;
+ cent = if center then "-centerline " else "";
+ }
+
+ _result
+ = Image output
+ {
+ [output] = vips_call "system"
+ [command]
+ [$in => [x.value],
+ $in_format => "%s.ppm",
+ $out => true,
+ $out_format => "%s.svg[scale=" ++ print scale.value ++ "]"
+ ];
+ }
+ }
+}
+
diff --git a/share/nip2/compat/8.6/Histogram.def b/share/nip2/compat/8.6/Histogram.def
new file mode 100644
index 00000000..27c61b00
--- /dev/null
+++ b/share/nip2/compat/8.6/Histogram.def
@@ -0,0 +1,341 @@
+Hist_new_item = class
+ Menupullright "_New" "new histogram" {
+ Hist_item = class
+ Menuaction "_Identity" "make an identity histogram" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ d = Option "Depth" ["8 bit", "16 bit"] 0;
+ _result = Plot [] ([im_identity 1, im_identity_ushort 1 65536]?d);
+ }
+ }
+
+ Hist_new_from_matrix = Matrix_buildlut_item;
+
+ Hist_from_image_item = class
+ Menuaction "Ta_g Image As Histogram" "set image Type to Histogram" {
+ action x = hist_tag x;
+ }
+
+ Tone_item = class
+ Menuaction "_Tone Curve" "make a new tone mapping curve" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ d = Option "Depth" ["8 bit", "16 bit"] 0;
+ b = Scale "Black point" 0 100 0;
+ w = Scale "White point" 0 100 100;
+
+ sp = Scale "Shadow point" 0.1 0.3 0.2;
+ mp = Scale "Mid-tone point" 0.4 0.6 0.5;
+ hp = Scale "Highlight point" 0.7 0.9 0.8;
+
+ sa = Scale "Shadow adjust" (-15) 15 0;
+ ma = Scale "Mid-tone adjust" (-30) 30 0;
+ ha = Scale "Highlight adjust" (-15) 15 0;
+
+ _result
+ = tone_build fmt b w sp mp hp sa ma ha
+ {
+ fmt = [Image_format.UCHAR, Image_format.USHORT]?d;
+ }
+ }
+ }
+}
+
+Hist_convert_to_hist_item = class
+ Menuaction "Con_vert to Histogram" "convert anything to a histogram" {
+ action x = hist_tag (to_image x);
+}
+
+Hist_find_item = class
+ Menupullright "_Find" "find a histogram" {
+ Oned_item = class
+ Menuaction "_One Dimension"
+ "for a n-band image, make an n-band 1D histogram" {
+ action x = map_unary hist_find x;
+ }
+
+ Nd_item = class
+ Menuaction "_Many Dimensions"
+ "for a n-band image, make an n-dimensional histogram" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // default to something small-ish
+ bins = Expression "Number of bins in each dimension" 8;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = hist_find_nD bins in;
+ }
+ }
+ }
+
+ Indexed_item = class
+ Menuaction "_Indexed"
+ "use a 1-band index image to pick bins for an n-band image" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ combine = Combine_picker Combine_type.SUM;
+
+ _result
+ = map_binary map x y
+ {
+ map a b
+ = hist_find_indexed combine.value index im
+ {
+ [im, index] = sortc (const is_index) [a, b];
+
+ is_index x
+ = has_image x && b == 1 &&
+ (f == Image_format.UCHAR || f == Image_format.USHORT)
+ {
+ im = get_image x;
+ b = get_bands x;
+ f = get_format x;
+ }
+ }
+ }
+ }
+ }
+}
+
+Hist_map_item = class
+ Menuaction "_Map" "map an image through a histogram" {
+ action x y
+ = map_binary map x y
+ {
+ map a b
+ = hist_map hist im
+ {
+ [im, hist] = sortc (const is_hist) [a, b];
+ }
+ }
+}
+
+Hist_eq_item = Filter_enhance_item.Hist_equal_item;
+
+#separator
+
+Hist_cum_item = class
+ Menuaction "_Integrate"
+ "form cumulative histogram" {
+ action x = map_unary hist_cum x;
+}
+
+Hist_diff_item = class
+ Menuaction "_Differentiate"
+ "find point-to-point differences (inverse of Integrate)" {
+ action x = map_unary hist_diff x;
+}
+
+Hist_norm_item = class
+ Menuaction "N_ormalise" "normalise a histogram" {
+ action x = map_unary hist_norm x;
+}
+
+Hist_inv_item = class
+ Menuaction "In_vert" "invert a histogram" {
+ action x = map_unary hist_inv x;
+}
+
+Hist_match_item = class
+ Menuaction "Ma_tch"
+ "find LUT which will match first histogram to second" {
+ action in ref = map_binary hist_match in ref;
+}
+
+Hist_zerox_item = class
+ Menuaction "_Zero Crossings" "find zero crossings" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Direction" [
+ "Positive-going",
+ "Negative-going"
+ ] 0;
+
+ _result
+ = map_unary (zerox (if edge == 0 then -1 else 1)) x;
+ }
+}
+
+Hist_entropy_item = class Menuaction "Entropy" "calculate histogram entropy" {
+ action x = hist_entropy x;
+}
+
+#separator
+
+Hist_profile_item = class
+ Menuaction "Find _Profile"
+ "search from image edges for non-zero pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Search from" [
+ "Top edge down",
+ "Left edge to right",
+ "Bottom edge up",
+ "Right edge to left"
+ ] 2;
+
+ _result
+ = map_unary profile x
+ {
+ profile image
+ = (Plot_histogram @ hist_tag) [
+ profilemb 0 image.value,
+ profilemb 1 image.value,
+ profilemb 0 (fliptb image.value),
+ profilemb 1 (fliplr image.value)
+ ]?edge;
+
+ // im_profile only does 1 band images :-(
+ profilemb d = bandjoin @ map (converse im_profile d) @ bandsplit;
+ }
+ }
+}
+
+Hist_project_item = class
+ Menuaction "Find Pro_jections"
+ "find horizontal and vertical projections" {
+ action x = class {
+ _vislevel = 2;
+
+ _result = map_unary project x;
+
+ // extract the result ... could be a group
+ extr n
+ = Plot_histogram _result?n, is_list _result
+ = Group (map (Plot_histogram @ converse subscript n) _result.value);
+
+ horizontal = extr 0;
+ vertical = extr 1;
+ centre = (gravity horizontal, gravity vertical);
+ }
+}
+
+#separator
+
+Hist_graph_item = class
+ Menuaction "P_lot Slice" "plot a slice along a guide or arrow" {
+ action x = class
+ _value {
+ _vislevel = 3;
+
+ width = Scale "Width" 1 40 1;
+ displace = Scale "Horizontal displace" (-50) 50 0;
+ vdisplace = Scale "Vertical displace" (-50) 50 0;
+
+ _value
+ = map_unary graph x
+ {
+ graph arrow
+ = hist_tag area'
+ {
+ area = extract_arrow
+ displace.value vdisplace.value width.value arrow;
+
+ // squish vertically to get an average
+ area' = resize Kernel_linear 1 (1 / width.value) area;
+ }
+ }
+ }
+}
+
+Extract_arrow_item = class
+ Menuaction "Extract _Arrow" "extract the area around an arrow" {
+ action x = class
+ _value {
+ _vislevel = 3;
+
+ width = Scale "Width" 1 40 1;
+ displace = Scale "Horizontal displace" (-50) 50 0;
+ vdisplace = Scale "Vertical displace" (-50) 50 0;
+
+ _value
+ = map_unary (extract_arrow
+ displace.value vdisplace.value width.value) x;
+ }
+}
+
+Hist_plot_item = class
+ Menuaction "Plot _Object"
+ "plot an object as a bar, point or line graph" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ caption = Expression "Chart caption" "none";
+ format = Option_enum "Format" Plot_format.names "YYYY";
+ style = Option_enum "Style" Plot_style.names "Line";
+
+ auto = Toggle "Auto Range" true;
+ xmin = Expression "X range minimum" 0;
+ xmax = Expression "X range maximum" 1;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+ xcaption = Expression "X axis caption" "none";
+ ycaption = Expression "Y axis caption" "none";
+ series_captions = Expression "Series captions" ["Band 0"];
+
+ _result
+ = Plot options (image x)
+ {
+ options
+ = [$style => style.value, $format => format.value] ++
+ range ++ captions;
+ range
+ = [], auto
+ = [$xmin => xmin.expr, $xmax => xmax.expr,
+ $ymin => ymin.expr, $ymax => ymax.expr];
+
+ captions
+ = concat (map test caption_options) ++
+ [$series_captions => series_captions.expr]
+ {
+ caption_options = [
+ $caption => caption.expr,
+ $xcaption => xcaption.expr,
+ $ycaption => ycaption.expr
+ ];
+ test x
+ = [], value == "none"
+ = [option_name => value]
+ {
+ [option_name, value] = x;
+ }
+ }
+
+ image x
+ = image (extract_arrow 0 0 1 x), is_Arrow x
+ = get_image x, has_image x
+ = x2b im, b == 1
+ = im
+ {
+ im = get_image (to_image x);
+ w = get_width im;
+ h = get_height im;
+ b = get_bands im;
+
+ // matrix to image makes a 1-band mxn image
+ // we need to put columns into bands
+ x2b im
+ = bandjoin (map extract_col [0 .. w - 1])
+ {
+ extract_col x = extract_area x 0 1 h im;
+ }
+ }
+ }
+ }
+}
diff --git a/share/nip2/compat/8.6/Image.def b/share/nip2/compat/8.6/Image.def
new file mode 100644
index 00000000..a010d989
--- /dev/null
+++ b/share/nip2/compat/8.6/Image.def
@@ -0,0 +1,2329 @@
+Image_new_item = class Menupullright "_New" "make new things" {
+ Image_black_item = class Menuaction "_Image" "make a new image" {
+ format_names = [
+ "8-bit unsigned int - UCHAR", // 0
+ "8-bit signed int - CHAR", // 1
+ "16-bit unsigned int - USHORT", // 2
+ "16-bit signed int - SHORT", // 3
+ "32-bit unsigned int - UINT", // 4
+ "32-bit signed int - INT", // 5
+ "32-bit float - FLOAT", // 6
+ "64-bit complex - COMPLEX", // 7
+ "64-bit float - DOUBLE", // 8
+ "128-bit complex - DPCOMPLEX" // 9
+ ];
+
+ action = class
+ Image _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ nbands = Expression "Image bands" 1;
+ format_option = Option "Image format" format_names 0;
+ type_option = Option_enum "Image type"
+ Image_type.type_names "B_W";
+ pixel = Expression "Pixel value" 0;
+
+ _result
+ = image_new (to_real nwidth) (to_real nheight) (to_real nbands)
+ (to_real format_option) Image_coding.NOCODING
+ type_option.value_thing pixel.expr 0 0;
+ }
+ }
+
+ Image_new_from_image_item = class
+ Menuaction "_From Image" "make a new image based on image x" {
+ action x = class
+ Image _result {
+ _vislevel = 3;
+
+ pixel = Expression "Pixel value" 0;
+
+ _result
+ = image_new x.width x.height x.bands
+ x.format x.coding x.type pixel.expr x.xoffset x.yoffset;
+ }
+ }
+
+ Image_region_item = class
+ Menupullright "_Region on Image" "make a new region on an image" {
+ Region_item = class
+ Menuaction "_Region" "make a region on an image" {
+ action image = scope.Region_relative image 0.25 0.25 0.5 0.5;
+ }
+
+ Mark_item = class
+ Menuaction "_Point" "make a point on an image" {
+ action image = scope.Mark_relative image 0.5 0.5;
+ }
+
+ Arrow_item = class
+ Menuaction "_Arrow" "make an arrow on an image" {
+ action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5;
+ }
+
+ HGuide_item = class
+ Menuaction "_Horizontal Guide"
+ "make a horizontal guide on an image" {
+ action image = scope.HGuide image 0.5;
+ }
+
+ VGuide_item = class
+ Menuaction "_Vertical Guide" "make a vertical guide on an image" {
+ action image = scope.VGuide image 0.5;
+ }
+
+ sep1 = Menuseparator;
+
+ Move_item = class
+ Menuaction "From Region"
+ "new region on image using existing region as a guide" {
+ action a b
+ = map_binary process a b
+ {
+ process a b
+ = x.Region target x.left x.top x.width x.height,
+ is_Region x
+ = x.Arrow target x.left x.top x.width x.height,
+ is_Arrow x
+ = error "bad arguments to region-from-region"
+ {
+ // prefer image then region
+ compare a b
+ = false,
+ !is_Image a && is_Image b
+ = false,
+ is_Region a && !is_Region b
+ = true;
+
+ [target, x] = sortc compare [a, b];
+ }
+ }
+ }
+ }
+}
+
+Image_convert_to_image_item = class
+ Menuaction "Con_vert to Image" "convert anything to an image" {
+ action x = to_image x;
+}
+
+Image_number_format_item = class
+ Menupullright "_Format" "convert numeric format" {
+
+ U8_item = class
+ Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" {
+ action x = map_unary cast_unsigned_char x;
+ }
+
+ U16_item = class
+ Menuaction "1_6 bit unsigned"
+ "convert to unsigned 16 bit [0, 65535]" {
+ action x = map_unary cast_unsigned_short x;
+ }
+
+ U32_item = class
+ Menuaction "_32 bit unsigned"
+ "convert to unsigned 32 bit [0, 4294967295]" {
+ action x = map_unary cast_unsigned_int x;
+ }
+
+ sep1 = Menuseparator;
+
+ S8_item = class
+ Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" {
+ action x = map_unary cast_signed_char x;
+ }
+
+ S16_item = class
+ Menuaction "16 b_it signed"
+ "convert to signed 16 bit [-32768, 32767]" {
+ action x = map_unary cast_signed_short x;
+ }
+
+ S32_item = class
+ Menuaction "32 bi_t signed"
+ "convert to signed 32 bit [-2147483648, 2147483647]" {
+ action x = map_unary cast_signed_int x;
+ }
+
+ sep2 = Menuseparator;
+
+ Float_item = class
+ Menuaction "_Single precision float"
+ "convert to IEEE 32 bit float" {
+ action x = map_unary cast_float x;
+ }
+
+ Double_item = class
+ Menuaction "_Double precision float"
+ "convert to IEEE 64 bit float" {
+ action x = map_unary cast_double x;
+ }
+
+ sep3 = Menuseparator;
+
+ Scmplxitem = class
+ Menuaction "Single _precision complex"
+ "convert to 2 x IEEE 32 bit float" {
+ action x = map_unary cast_complex x;
+ }
+
+ Dcmplx_item = class
+ Menuaction "Double p_recision complex"
+ "convert to 2 x IEEE 64 bit float" {
+ action x = map_unary cast_double_complex x;
+ }
+}
+
+Image_header_item = class
+ Menupullright "_Header" "do stuff to the image header" {
+
+ Image_get_item = class
+ Menupullright "_Get" "get header fields" {
+
+ // the header fields we can get
+ fields = class {
+ type = 0;
+ width = 1;
+ height = 2;
+ format = 3;
+ bands = 4;
+ xres = 5;
+ yres = 6;
+ xoffset = 7;
+ yoffset = 8;
+ coding = 9;
+
+ field_names = Enum [
+ $width => width,
+ $height => height,
+ $bands => bands,
+ $format => format,
+ $type => type,
+ $xres => xres,
+ $yres => yres,
+ $xoffset => xoffset,
+ $yoffset => yoffset,
+ $coding => coding
+ ];
+
+ field_option name = Option_enum (_ "Field") field_names name;
+
+ field_funcs = Table [
+ [type, get_type],
+ [width, get_width],
+ [height, get_height],
+ [format, get_format],
+ [bands, get_bands],
+ [xres, get_xres],
+ [yres, get_yres],
+ [xoffset, get_xoffset],
+ [yoffset, get_yoffset],
+ [coding, get_coding]
+ ];
+ }
+
+ get_field field_name x = class
+ _result {
+ _vislevel = 3;
+
+ field = fields.field_option field_name;
+
+ _result
+ = map_unary (Real @
+ fields.field_funcs.lookup 0 1 field.value_thing) x;
+ }
+
+ Width_item = class
+ Menuaction "_Width" "get width" {
+ action x = get_field "width" x;
+ }
+
+ Height_item = class
+ Menuaction "_Height" "get height" {
+ action x = get_field "height" x;
+ }
+
+ Bands_item = class
+ Menuaction "_Bands" "get bands" {
+ action x = get_field "bands" x;
+ }
+
+ Format_item = class
+ Menuaction "_Format" "get format" {
+ action x = get_field "format" x;
+ }
+
+ Type_item = class
+ Menuaction "_Type" "get type" {
+ action x = get_field "type" x;
+ }
+
+ Xres_item = class
+ Menuaction "_Xres" "get X resolution" {
+ action x = get_field "xres" x;
+ }
+
+ Yres_item = class
+ Menuaction "_Yres" "get Y resolution" {
+ action x = get_field "yres" x;
+ }
+
+ Xoffset_item = class
+ Menuaction "X_offset" "get X offset" {
+ action x = get_field "xoffset" x;
+ }
+
+ Yoffset_item = class
+ Menuaction "Yo_ffset" "get Y offset" {
+ action x = get_field "yoffset" x;
+ }
+
+ Coding_item = class
+ Menuaction "_Coding" "get coding" {
+ action x = get_field "coding" x;
+ }
+
+ sep1 = Menuseparator;
+
+ Custom_item = class
+ Menuaction "C_ustom" "get any header field" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ field = String "Field" "Xsize";
+ parse = Option "Parse" [
+ "No parsing",
+ "Parse string as integer",
+ "Parse string as real",
+ "Parse string as hh:mm:ss"
+ ] 0;
+
+ _result
+ = map_unary (wrap @ process @ get_header field.value) x
+ {
+ parse_str parse str = parse (split is_space str)?0;
+
+ parse_field name cast parse x
+ = cast x, is_number x
+ = parse_str parse x, is_string x
+ = error ("not " ++ name);
+
+ get_int = parse_field "int"
+ cast_signed_int parse_int;
+ get_float = parse_field "float"
+ cast_float parse_float;
+ get_time = parse_field "hh:mm:ss"
+ cast_signed_int parse_time;
+
+ wrap x
+ = Real x, is_real x
+ = Vector x, is_real_list x
+ = Image x, is_image x
+ = Bool x, is_bool x
+ = Matrix x, is_matrix x
+ = String "String" x, is_string x
+ = List x, is_list x
+ = x;
+
+ process = [
+ id,
+ get_int,
+ get_float,
+ get_time
+ ]?parse;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Image_set_meta_item = class
+ Menuaction "_Set" "set image metadata" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ fname = String "Field" "field-name";
+ val = Expression "Value" 42;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = set_header fname.value val.expr image;
+ }
+ }
+ }
+
+ Image_edit_header_item = class
+ Menuaction "_Edit" "change advisory header fields of image" {
+ type_names = Image_type.type_names;
+ all_names = sort (map (extract 0) type_names.value);
+
+ get_prop has get def x
+ = get x, has x
+ = def;
+
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ nxres = Expression "Xres" (get_prop has_xres get_xres 1 x);
+ nyres = Expression "Yres" (get_prop has_yres get_yres 1 x);
+ nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x);
+ nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x);
+
+ type_option
+ = Option_enum "Image type" Image_type.type_names
+ (Image_type.type_names.get_name type)
+ {
+ type
+ = x.type, is_Image x
+ = Image_type.MULTIBAND;
+ }
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = Image (im_copy_set image.value type_option.value_thing
+ (to_real nxres) (to_real nyres)
+ (to_real nxoff) (to_real nyoff));
+ }
+ }
+ }
+}
+
+Image_cache_item = class
+ Menuaction "C_ache" "cache calculated image pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tile_width = Number "Tile width" 128;
+ tile_height = Number "Tile height" 128;
+ max_tiles = Number "Maximum number of tiles to cache" (-1);
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = cache (to_real tile_width) (to_real tile_height)
+ (to_real max_tiles) image;
+ }
+ }
+}
+
+#separator
+
+Image_levels_item = class
+ Menupullright "_Levels" "change image levels" {
+ Scale_item = class
+ Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" {
+ action x = map_unary scale x;
+ }
+
+ Linear_item = class
+ Menuaction "_Linear" "linear transform of image levels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ scale = Scale "Scale" 0.001 3 1;
+ offset = Scale "Offset" (-128) 128 0;
+
+ _result
+ = map_unary adj x
+ {
+ adj x
+ // only force back to input type if this is a thing
+ // with a type ... so we work for Colour / Matrix etc.
+ = clip2fmt x.format x', has_member "format" x
+ = x'
+ {
+ x' = x * scale + offset;
+ }
+ }
+ }
+ }
+
+ Gamma_item = class
+ Menuaction "_Power" "power transform of image levels (gamma)" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gamma = Scale "Gamma" 0.001 4 1;
+ image_maximum_hint = "You may need to change image_maximum if " ++
+ "this is not an 8 bit image";
+ im_mx
+ = Expression "Image maximum" mx
+ {
+ mx
+ = Image_format.maxval x.format, has_format x
+ = 255;
+ }
+
+ _result
+ = map_unary gam x
+ {
+ gam x
+ = clip2fmt (get_format x) x', has_format x
+ = x'
+ {
+ x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma;
+ }
+ }
+ }
+ }
+
+ Tone_item = class
+ Menuaction "_Tone Curve" "adjust tone curve" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ b = Scale "Black point" 0 100 0;
+ w = Scale "White point" 0 100 100;
+
+ sp = Scale "Shadow point" 0.1 0.3 0.2;
+ mp = Scale "Mid-tone point" 0.4 0.6 0.5;
+ hp = Scale "Highlight point" 0.7 0.9 0.8;
+
+ sa = Scale "Shadow adjust" (-15) 15 0;
+ ma = Scale "Mid-tone adjust" (-30) 30 0;
+ ha = Scale "Highlight adjust" (-15) 15 0;
+
+ curve = tone_build x.format b w sp mp hp sa ma ha;
+
+ _result = map_unary (hist_map curve) x;
+ }
+ }
+}
+
+Image_transform_item = class
+ Menupullright "_Transform" "transform images" {
+ Rotate_item = class
+ Menupullright "Ro_tate" "rotate image" {
+ Fixed_item = class
+ Menupullright "_Fixed" "clockwise rotation by fixed angles" {
+ rotate_widget default x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Option "Rotate by" [
+ "Don't rotate",
+ "90 degrees clockwise",
+ "180 degrees",
+ "90 degrees anticlockwise"
+ ] default;
+
+ _result
+ = map_unary process x
+ {
+ process = [
+ // we can't use id here since we want to "declass"
+ // the members of x ... consider if x is a crop class,
+ // for example, we don't want to inherit from crop, we
+ // want to make a new image class
+ rot180 @ rot180,
+ rot90,
+ rot180,
+ rot270
+ ] ? angle;
+ }
+ }
+
+ Rot90_item = class
+ Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" {
+ action x = rotate_widget 1 x;
+ }
+
+ Rot180_item = class
+ Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" {
+ action x = rotate_widget 2 x;
+ }
+
+ Rot270_item = class
+ Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" {
+ action x = rotate_widget 3 x;
+ }
+ }
+
+ Free_item = class
+ Menuaction "_Free" "clockwise rotation by any angle" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "Angle" (-180) 180 0;
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = rotate interp angle image;
+ }
+ }
+ }
+
+ Straighten_item = class
+ Menuaction "_Straighten"
+ ("smallest rotation that makes an arrow either horizontal " ++
+ "or vertical") {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_unary straighten x
+ {
+ straighten arrow
+ = rotate interp angle'' arrow.image
+ {
+ x = arrow.width;
+ y = arrow.height;
+
+ angle = im (polar (x, y));
+
+ angle'
+ = angle - 360, angle > 315
+ = angle - 180, angle > 135
+ = angle;
+
+ angle''
+ = -angle', angle' >= (-45) && angle' < 45
+ = 90 - angle';
+ }
+ }
+ }
+ }
+ }
+
+ Flip_item = class
+ Menupullright "_Flip" "mirror left/right or up/down" {
+ Left_right_item = class
+ Menuaction "_Left Right" "mirror object left/right" {
+ action x = map_unary fliplr x;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top Bottom" "mirror object top/bottom" {
+ action x = map_unary fliptb x;
+ }
+ }
+
+ Resize_item = class
+ Menupullright "_Resize" "change image size" {
+ Scale_item = class
+ Menuaction "_Scale" "scale image size by a factor" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ xfactor = Expression "Horizontal scale factor" 1;
+ yfactor = Expression "Vertical scale factor" 1;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = resize kernel xfactor yfactor image;
+ }
+ }
+ }
+
+ Size_item = class
+ Menuaction "_Size To" "resize to a fixed size" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ which = Option "Resize axis" [
+ "Shortest",
+ "Longest",
+ "Horizontal",
+ "Vertical"
+ ] 0;
+ size = Expression "Resize to (pixels)" 128;
+ aspect = Toggle "Break aspect ratio" false;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = resize kernel h v image, aspect
+ = resize kernel fac fac image
+ {
+ xfac = to_real size / image.width;
+ yfac = to_real size / image.height;
+ max_factor
+ = [xfac, 1], xfac > yfac
+ = [1, yfac];
+ min_factor
+ = [xfac, 1], xfac < yfac
+ = [1, yfac];
+ [h, v] = [
+ max_factor,
+ min_factor,
+ [xfac, 1],
+ [1, yfac]]?which;
+
+ fac
+ = h, v == 1
+ = v;
+ }
+ }
+ }
+ }
+
+ Size_within_item = class
+ Menuaction "Size _Within" "size to fit within a rectangle" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // the rects we size to fit within
+ _rects = [
+ [2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050],
+ [1280, 1024], [1024, 768], [800, 600], [640, 480]
+ ];
+
+ within = Option "Fit within (pixels)" (
+ [print w ++ " x " ++ print h :: [w, h] <- _rects] ++
+ ["Custom"]
+ ) 4;
+ custom_width = Expression "Custom width" 1000;
+ custom_height = Expression "Custom height" 1000;
+ size = Option "Page size" [
+ "Full page", "Half page", "Quarter page"
+ ] 0;
+ kernel = Kernel_picker Kernel_type.LINEAR;
+
+ _result
+ = map_unary process x
+ {
+ xdiv = [1, 2, 2]?size;
+ ydiv = [1, 1, 2]?size;
+ allrect = _rects ++ [
+ [custom_width.expr, custom_height.expr]
+ ];
+ [width, height] = allrect?within;
+
+ process x
+ = resize kernel fac fac x, fac < 1
+ = x
+ {
+ xfac = (width / xdiv) / x.width;
+ yfac = (height / ydiv) / x.height;
+ fac = min_pair xfac yfac;
+ }
+ }
+ }
+ }
+
+ Resize_canvas_item = class
+ Menuaction "_Canvas" "change size of surrounding image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // try to guess a sensible size for the new image
+ _guess_size
+ = x.rect, is_Image x
+ = Rect 0 0 100 100;
+
+ nwidth = Expression "New width (pixels)" _guess_size.width;
+ nheight = Expression "New height (pixels)" _guess_size.height;
+ bgcolour = Expression "Background colour" 0;
+
+ position = Option "Position" [
+ "North-west",
+ "North",
+ "North-east",
+ "West",
+ "Centre",
+ "East",
+ "South-west",
+ "South",
+ "South-east",
+ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = insert_noexpand xp yp image background
+ {
+ width = image.width;
+ height = image.height;
+ coding = image.coding;
+ bands
+ = 3, coding == Image_coding.LABPACK
+ = image.bands;
+ format
+ = Image_format.FLOAT, coding == Image_coding.LABPACK
+ = image.format;
+ type = image.type;
+
+ // placement vectors ... left, centre, right
+ xposv = [0, to_real nwidth / 2 - width / 2,
+ to_real nwidth - width];
+ yposv = [0, to_real nheight / 2 - height / 2,
+ to_real nheight - height];
+ xp
+ = left, position == 9
+ = xposv?((int) (position % 3));
+ yp
+ = top, position == 9
+ = yposv?((int) (position / 3));
+
+ background = image_new nwidth nheight
+ bands format coding type bgcolour.expr 0 0;
+ }
+ }
+ }
+ }
+ }
+
+ Image_map_item = class
+ Menuaction "_Map" "map an image through a 2D transform image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ interp = Interpolate_picker Interpolate_type.BILINEAR;
+
+ _result
+ = map_binary trans a b
+ {
+ trans a b
+ = mapim interp.value in index
+ {
+ // get the index image first
+ [index, in] = sortc (const is_twocomponent) [a, b];
+
+ // is a two-component image, ie. one band complex, or
+ // two-band non-complex
+ is_twocomponent x
+ = is_nonc x || is_c x;
+ is_nonc x
+ = has_bands x && get_bands x == 2 &&
+ has_format x && !is_complex_format (get_format x);
+ is_c x
+ = has_bands x && get_bands x == 1 &&
+ has_format x && is_complex_format (get_format x);
+ is_complex_format f
+ = f == Image_format.COMPLEX ||
+ f == Image_format.DPCOMPLEX;
+ }
+ }
+ }
+ }
+
+ Image_perspective_item = Perspective_item;
+
+ Image_rubber_item = class
+ Menupullright "Ru_bber Sheet"
+ "automatically warp images to superposition" {
+ rubber_interp = Option "Interpolation" ["Nearest", "Bilinear"] 1;
+ rubber_order = Option "Order" ["0", "1", "2", "3"] 1;
+ rubber_wrap = Toggle "Wrap image edges" false;
+
+ // a transform ... a matrix, plus the size of the image the
+ // matrix was made for
+ Transform matrix image_width image_height = class
+ matrix {
+ // scale a transform ... if it worked for a m by n image, make
+ // it work for a (m * xfac) by (y * yfac) image
+ rescale xfac yfac
+ = Transform (Matrix (map2 (map2 multiply) matrix.value facs))
+ (image_width * xfac) (image_height * yfac)
+ {
+ facs = [
+ [xfac, yfac],
+ [1, 1],
+ [1, 1],
+ [1 / xfac, 1 / yfac],
+ [1 / xfac, 1 / yfac],
+ [1 / xfac, 1 / yfac]
+ ];
+ }
+ }
+
+ // yuk!!!! fix is_instanceof to not need absolute names
+ is_Transform = is_instanceof
+ "Image_transform_item.Image_rubber_item.Transform";
+
+ Find_item = class
+ Menuaction "_Find"
+ ("find a transform which will map sample image onto " ++
+ "reference") {
+ action reference sample = class
+ _trn {
+ _vislevel = 3;
+
+ // controls
+ order = rubber_order;
+ interp = rubber_interp;
+ wrap = rubber_wrap;
+ max_err = Expression "Maximum error" 0.3;
+ max_iter = Expression "Maximum iterations" 10;
+
+ // transform
+ [sample', trn, err] = transform_search
+ max_err max_iter order interp wrap
+ sample reference;
+ transformed_image = Image sample';
+ _trn = Transform trn reference.width reference.height;
+ final_error = err;
+ }
+ }
+
+ Apply_item = class
+ Menuaction "_Apply" "apply a transform to an image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ // controls
+ interp = rubber_interp;
+ wrap = rubber_wrap;
+
+ _result
+ = map_binary trans a b
+ {
+ trans a b
+ = transform interp wrap t' i
+ {
+ // get the transform arg first
+ [i, t] = sortc (const is_Transform) [a, b];
+ t' = t.rescale (i.width / t.image_width)
+ (i.height / t.image_height);
+ }
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Match_item = class
+ Menuaction "_Linear Match"
+ "rotate and scale one image to match another" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+ _b = find_image y;
+
+ ap1 = Mark_relative _a 0.5 0.25;
+ bp1 = Mark_relative _b 0.5 0.25;
+ ap2 = Mark_relative _a 0.5 0.75;
+ bp2 = Mark_relative _b 0.5 0.75;
+
+ refine = Toggle "Refine selected tie-points" false;
+ lock = Toggle "No resize" false;
+
+ _result
+ = map_binary process x y
+ {
+ process a b
+ = Image b'''
+ {
+ _prefs = Workspaces.Preferences;
+ window = _prefs.MOSAIC_WINDOW_SIZE;
+ object = _prefs.MOSAIC_OBJECT_SIZE;
+
+ a' = a.value;
+ b' = b.value;
+
+ b'' = clip2fmt a.format b';
+
+ // return p2 ... if lock is set, return a p2 a standard
+ // distance along the vector joining p1 and p2
+ norm p1 p2
+ = Rect left' top' 0 0, lock
+ = p2
+ {
+ v = (p2.left - p1.left, p2.top - p1.top);
+ // 100000 to give precision since we pass points as
+ // ints to match
+ n = 100000 * sign v;
+ left' = p1.left + re n;
+ top' = p1.top + im n;
+ }
+
+ ap2'' = norm ap1 ap2;
+ bp2'' = norm bp1 bp2;
+
+ b'''
+ = im_match_linear_search a' b''
+ ap1.left ap1.top bp1.left bp1.top
+ ap2''.left ap2''.top bp2''.left bp2''.top
+ object window,
+ // we can't search if lock is on
+ refine && !lock
+ = im_match_linear a' b''
+ ap1.left ap1.top bp1.left bp1.top
+ ap2''.left ap2''.top bp2''.left bp2''.top;
+ }
+ }
+ }
+ }
+
+ Image_perspective_match_item = Perspective_match_item;
+}
+
+Image_band_item = class
+ Menupullright "_Band" "manipulate image bands" {
+ // like extract_bands, but return [] for zero band image
+ // makes compose a bit simpler
+ exb b n x
+ = [], to_real n == 0
+ = extract_bands b n x;
+
+ Extract_item = class Menuaction "_Extract" "extract bands from image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from band" 0;
+ number = Expression "Extract this many bands" 1;
+
+ _result = map_unary (exb first number) x;
+ }
+ }
+
+ Insert_item = class Menuaction "_Insert" "insert bands into image" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at position" 0;
+
+ _result
+ = map_binary process x y
+ {
+ process im1 im2
+ = exb 0 f im1 ++ im2 ++ exb f (b - f) im1
+ {
+ f = to_real first;
+ b = im1.bands;
+ }
+ }
+ }
+ }
+
+ Delete_item = class Menuaction "_Delete" "delete bands from image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from band" 0;
+ number = Expression "Delete this many bands" 1;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = exb 0 f im ++ exb (f + n) (b - (f + n)) im
+ {
+ f = to_real first;
+ n = to_real number;
+ b = im.bands;
+ }
+ }
+ }
+ }
+
+ Bandwise_item = Image_join_item.Bandwise_item;
+
+ sep1a = Menuseparator;
+
+ Bandand_item = class
+ Menuaction "Bitwise Band AND" "bitwise AND of image bands" {
+ action x = bandand x;
+ }
+
+ Bandor_item = class
+ Menuaction "Bitwise Band OR" "bitwise OR of image bands" {
+ action x = bandor x;
+ }
+
+ sep2 = Menuseparator;
+
+ To_dimension_item = class
+ Menuaction "To D_imension" "convert bands to width or height" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = foldl1 [join_lr, join_tb]?orientation (bandsplit im);
+ }
+ }
+ }
+
+ To_bands_item = class
+ Menuaction "To B_ands" "turn width or height to bands" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = bandjoin (map extract_column [0 .. im.width - 1]),
+ orientation == 0
+ = bandjoin (map extract_row [0 .. im.height - 1])
+ {
+ extract_column n
+ = extract_area n 0 1 im.height im;
+ extract_row n
+ = extract_area 0 n im.width 1 im;
+ }
+ }
+ }
+ }
+}
+
+Image_alpha_item = class
+ Menupullright "_Alpha" "manipulate image alpha" {
+
+ Add_item = class Menuaction "_Add" "add alpha" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ opacity = Expression "Opacity (255 == solid)" 255;
+
+ _result = x ++ to_real opacity;
+ }
+ }
+
+ Flatten_item = class Menuaction "_Flatten" "flatten alpha out of image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bg = Expression "Background" 0;
+
+ _result = map_unary (flattenimage bg) x;
+ }
+ }
+
+ Extract_item = class Menuaction "_Extract" "extract alpha" {
+ action x
+ = map_unary exb x
+ {
+ exb x = extract_bands (x.bands - 1) 1 x;
+ }
+ }
+
+ Drop_item = class Menuaction "_Drop" "drop alpha" {
+ action x
+ = map_unary exb x
+ {
+ exb x = extract_bands 0 (x.bands - 1) x;
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Premultiply_item = class Menuaction "_Premultiply" "premultiply alpha" {
+ action x = premultiply x;
+ }
+
+ Unpremultiply_item = class
+ Menuaction "_Unpremultiply" "unpremultiply alpha" {
+ action x = unpremultiply x;
+ }
+
+ sep2 = Menuseparator;
+
+ Composite2_item = class
+ Menuaction "_Composite two" "composite a pair of images" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ blend = Option_enum (_ "Blend mode") modes "over"
+ {
+ modes = Blend_type.types;
+ }
+ compositing_space = Option_enum (_ "Compositing space") spaces "sRGB"
+ {
+ spaces = Image_type.image_colour_spaces;
+ }
+ premultiplied = Toggle (_ "Premultiplied") false;
+
+ _result
+ = Image output
+ {
+ [output] = vips_call "composite"
+ [[y.value, x.value], blend.value]
+ [$compositing_space => compositing_space.value_thing,
+ $premultiplied => premultiplied.value
+ ];
+ }
+ }
+ }
+
+ Composite3_item = class
+ Menuaction "_Composite three" "composite three images" {
+ action x y z = class
+ _result {
+ _vislevel = 3;
+
+ blend1 = Option_enum (_ "Blend mode") modes "over"
+ {
+ modes = Blend_type.types;
+ }
+ blend2 = Option_enum (_ "Blend mode") modes "over"
+ {
+ modes = Blend_type.types;
+ }
+ compositing_space = Option_enum (_ "Compositing space") spaces "sRGB"
+ {
+ spaces = Image_type.image_colour_spaces;
+ }
+ premultiplied = Toggle (_ "Premultiplied") false;
+
+ _result
+ = Image output
+ {
+ [output] = vips_call "composite"
+ [[z.value, y.value, x.value], [blend1.value, blend2.value]]
+ [$compositing_space => compositing_space.value_thing,
+ $premultiplied => premultiplied.value
+ ];
+ }
+ }
+ }
+}
+
+Image_crop_item = class
+ Menuaction "_Crop" "extract a rectangular area from an image" {
+ action x
+ = crop x [l, t, w, h]
+ {
+ fields = [
+ [has_left, get_left, 0],
+ [has_top, get_top, 0],
+ [has_width, get_width, 100],
+ [has_height, get_height, 100]
+ ];
+
+ [l, t, w, h]
+ = map get_default fields
+ {
+ get_default line
+ = get x, has x
+ = default
+ {
+ [has, get, default] = line;
+ }
+ }
+ }
+
+ crop x geo = class
+ _result {
+ _vislevel = 3;
+
+ l = Expression "Crop left" ((int) (geo?0 + geo?2 / 4));
+ t = Expression "Crop top" ((int) (geo?1 + geo?3 / 4));
+ w = Expression "Crop width" (max_pair 1 ((int) (geo?2 / 2)));
+ h = Expression "Crop height" (max_pair 1 ((int) (geo?3 / 2)));
+
+ _result
+ = map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr]
+ {
+ extract im l t w h
+ = extract_area left' top' width' height' im
+ {
+ width' = min_pair (to_real w) im.width;
+ height' = min_pair (to_real h) im.height;
+ left' = range 0 (to_real l) (im.width - width');
+ top' = range 0 (to_real t) (im.height - height');
+ }
+ }
+ }
+}
+
+Image_insert_item = class
+ Menuaction "_Insert" "insert a small image into a large image" {
+ action a b
+ = insert_position, is_Group a || is_Group b
+ = insert_area
+ {
+ insert_area = class
+ _result {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _vislevel = 3;
+
+ // sort to get smallest first
+ _pred x y = x.width * x.height < y.width * y.height;
+ [_a', _b'] = sortc _pred [a, b];
+
+ place
+ = Area _b' left top width height
+ {
+ // be careful in case b is smaller than a
+ left = max_pair 0 ((_b'.width - _a'.width) / 2);
+ top = max_pair 0 ((_b'.height - _a'.height) / 2);
+ width = min_pair _a'.width _b'.width;
+ height = min_pair _a'.height _b'.height;
+ }
+
+ _result
+ = insert_noexpand place.left place.top
+ (clip2fmt _b'.format a'') _b'
+ {
+ a'' = extract_area 0 0 place.width place.height _a';
+ }
+ }
+
+ insert_position = class
+ _result {
+ _vislevel = 3;
+
+ position = Option "Position" [
+ "North-west",
+ "North",
+ "North-east",
+ "West",
+ "Centre",
+ "East",
+ "South-west",
+ "South",
+ "South-east",
+ "Specify in pixels"
+ ] 4;
+ left = Expression "Pixels from left" 0;
+ top = Expression "Pixels from top" 0;
+
+ _result
+ = map_binary insert a b
+ {
+ insert a b
+ = insert_noexpand left top (clip2fmt b.format a) b,
+ position == 9
+ = insert_noexpand xp yp (clip2fmt b.format a) b
+ {
+ xr = b.width - a.width;
+ yr = b.height - a.height;
+ xp = [0, xr / 2, xr]?((int) (position % 3));
+ yp = [0, yr / 2, yr]?((int) (position / 3));
+ }
+ }
+ }
+ }
+}
+
+Image_select_item = Select_item;
+
+Image_draw_item = class
+ Menupullright "_Draw" "draw lines, circles, rectangles, floods" {
+ Line_item = class Menuaction "_Line" "draw line on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ x1 = Expression "Start x" 0;
+ y1 = Expression "Start y" 0;
+ x2 = Expression "End x" 100;
+ y2 = Expression "End y" 100;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary line x
+ {
+ line im
+ = draw_line x1 y1 x2 y2 i.expr im;
+ }
+ }
+ }
+
+ Rect_item = class Menuaction "_Rectangle" "draw rectangle on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rx = Expression "Left" 50;
+ ry = Expression "Top" 50;
+ rw = Expression "Width" 100;
+ rh = Expression "Height" 100;
+
+ f = Toggle "Fill" true;
+
+ t = Scale "Line thickness" 1 50 3;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary rect x
+ {
+ rect im
+ = draw_rect_width rx ry rw rh f t i.expr im;
+ }
+ }
+ }
+
+ Circle_item = class Menuaction "_Circle" "draw circle on image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ cx = Expression "Centre x" 100;
+ cy = Expression "Centre y" 100;
+ r = Expression "Radius" 50;
+
+ f = Toggle "Fill" true;
+
+ i = Expression "Ink" [0];
+
+ _result
+ = map_unary circle x
+ {
+ circle im
+ = draw_circle cx cy r f i.expr im;
+ }
+ }
+ }
+
+ Flood_item = class Menuaction "_Flood" "flood bounded area of image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ sx = Expression "Start x" 0;
+ sy = Expression "Start y" 0;
+
+ e = Option "Flood while" [
+ "Not equal to ink",
+ "Equal to start point"
+ ] 0;
+
+ // pick a default ink that won't flood, if we can
+ i
+ = Expression "Ink" default_ink
+ {
+ default_ink
+ = [0], ! has_image x
+ = pixel;
+ pixel = map mean (bandsplit (extract_area sx sy 1 1 im));
+ im = get_image x;
+ }
+
+ _result
+ = map_unary flood x
+ {
+ flood im
+ = draw_flood sx sy i.expr im, e == 0
+ = draw_flood_blob sx sy i.expr im;
+ }
+ }
+ }
+
+ Draw_scalebar_item = class Menuaction "_Scale" "draw scale bar" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ px = Expression "Left" 50;
+ py = Expression "Top" 50;
+ wid = Expression "Width" 100;
+ thick = Scale "Line thickness" 1 50 3;
+ text = String "Dimension text" "50μm";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ pos = Option "Position Text" ["Above", "Below"] 1;
+ vp = Option "Dimension by" [
+ "Inner Vertical Edge",
+ "Centre of Vertical",
+ "Outer Vertical Edge"
+ ] 1;
+ dpi = Expression "DPI" 100;
+ ink = Colour "Lab" [50,0,0];
+
+ _result
+ = map_unary process x
+ {
+ process im
+ = blend (Image scale) ink' im
+ {
+ // make an ink compatible with the image
+ ink' = colour_transform_to (get_type im) ink;
+
+ x = to_real px;
+ y = to_real py;
+ w = to_real wid;
+ d = to_real dpi;
+
+ t = floor thick;
+
+ bg = image_new (get_width im) (get_height im) (get_bands im)
+ (get_format im) (get_coding im) (get_type im) 0 0 0;
+ draw_block x y w t im =
+ draw_rect_width x y w t true 1 [255] im;
+ label = im_text text.value font.value w 1 d;
+ lw = get_width label;
+ lh = get_height label;
+ ly = [y - lh - t, y + 2 * t]?pos;
+ vx = [
+ [x - t, x + w],
+ [x - t / 2, x + w - t / 2],
+ [x, x + w - t]
+ ]?vp;
+
+ scale = (draw_block x y w t @
+ draw_block vx?0 (y - 2 * t) t (t * 5) @
+ draw_block vx?1 (y - 2 * t) t (t * 5) @
+ insert_noexpand (x + w / 2 - lw / 2) ly label)
+ bg;
+ }
+ }
+ }
+ }
+}
+
+Image_join_item = class
+ Menupullright "_Join" "join two or more images together" {
+ Bandwise_item = class
+ Menuaction "_Bandwise Join" "join two images bandwise" {
+ action a b = join a b;
+ }
+
+ sep1 = Menuseparator;
+
+ join_lr shim bg align a b
+ = im2
+ {
+ w = a.width + b.width + shim;
+ h = max_pair a.height b.height;
+
+ back = image_new w h a.bands a.format a.coding a.type bg 0 0;
+
+ ya = [0, max_pair 0 ((b.height - a.height)/2),
+ max_pair 0 (b.height - a.height)];
+ yb = [0, max_pair 0 ((a.height - b.height)/2),
+ max_pair 0 (a.height - b.height)];
+
+ im1 = insert_noexpand 0 ya?align a back;
+ im2 = insert_noexpand (a.width + shim) yb?align b im1;
+ }
+
+ join_tb shim bg align a b
+ = im2
+ {
+ w = max_pair a.width b.width;
+ h = a.height + b.height + shim;
+
+ back = image_new w h a.bands a.format a.coding a.type bg 0 0;
+
+ xa = [0, max_pair 0 ((b.width - a.width)/2),
+ max_pair 0 (b.width - a.width)];
+ xb = [0, max_pair 0 ((a.width - b.width)/2),
+ max_pair 0 (a.width - b.width)];
+
+ im1 = insert_noexpand xa?align 0 a back;
+ im2 = insert_noexpand xb?align (a.height + shim) b im1;
+ }
+
+ halign_names = ["Top", "Centre", "Bottom"];
+ valign_names = ["Left", "Centre", "Right"];
+
+ Left_right_item = class
+ Menuaction "_Left to Right" "join two images left-right" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ shim = Scale "Spacing" 0 100 0;
+ bg_colour = Expression "Background colour" 0;
+ align = Option "Alignment" halign_names 1;
+
+ _result = map_binary
+ (join_lr shim.value bg_colour.expr align.value) a b;
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "join two images top-bottom" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ shim = Scale "Spacing" 0 100 0;
+ bg_colour = Expression "Background colour" 0;
+ align = Option "Alignment" valign_names 1;
+
+ _result = map_binary
+ (join_tb shim.value bg_colour.expr align.value) a b;
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Array_item = class
+ Menuaction "_Array"
+ "join a list of lists of images into a single image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ hshim = Scale "Horizontal spacing" (-100) (100) 0;
+ vshim = Scale "Vertical spacing" (-100) (100) 0;
+ bg_colour = Expression "Background colour" 0;
+ halign = Option "Horizontal alignment" valign_names 1;
+ valign = Option "Vertical alignment" halign_names 1;
+
+ // we can't use map_unary since chop-into-tiles returns a group of
+ // groups and we want to be able to reassemble that
+ // TODO: chop-into-tiles should return an array class which
+ // displays as group but does not have the looping behaviour?
+ _result
+ = (image_set_origin 0 0 @
+ foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
+ map (foldl1 (join_lr hshim.value
+ bg_colour.expr valign.value))) (to_list (to_list x));
+ }
+ }
+
+ ArrayFL_item = class
+ Menuaction "_Array from List"
+ "join a list of images into a single image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ ncol = Number "Max. Number of Columns" 1;
+ hshim = Scale "Horizontal spacing" (-100) (100) 0;
+ vshim = Scale "Vertical spacing" (-100) (100) 0;
+ bg_colour = Expression "Background colour" 0;
+ halign = Option "Horizontal alignment" valign_names 1;
+ valign = Option "Vertical alignment" halign_names 1;
+ snake = Toggle "Reverse the order of every other row" false;
+
+ _l
+ = split_lines ncol.value x.value, is_Group x
+ = split_lines ncol.value x;
+
+ _l'
+ = map2 reverse_if_odd [0..] _l, snake
+ = _l
+ {
+ reverse_if_odd n x
+ = reverse x, n % 2 == 1
+ = x;
+ }
+
+ _result
+ = (image_set_origin 0 0 @
+ foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
+ map (foldl1 (join_lr hshim.value
+ bg_colour.expr valign.value))) (to_list (to_list _l'));
+ }
+ }
+}
+
+Image_tile_item = class
+ Menupullright "Til_e" "tile an image across and down" {
+ tile_widget default_type x = class
+ _result {
+ _vislevel = 3;
+
+ across = Expression "Tiles across" 2;
+ down = Expression "Tiles down" 2;
+ repeat = Option "Tile type"
+ ["Replicate", "Four-way mirror"] default_type;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = tile across down image, repeat == 0
+ = tile across down image''
+ {
+ image' = insert image.width 0 (fliplr image) image;
+ image'' = insert 0 image.height (fliptb image') image';
+ }
+ }
+ }
+
+ Replicate_item = class
+ Menuaction "_Replicate" "replicate image across and down" {
+ action x = tile_widget 0 x;
+ }
+
+ Fourway_item = class
+ Menuaction "_Four-way Mirror" "four-way mirror across and down" {
+ action x = tile_widget 1 x;
+ }
+
+ Chop_item = class
+ Menuaction "_Chop Into Tiles" "slice an image into tiles" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ tile_width = Expression "Tile width" 100;
+ tile_height = Expression "Tile height" 100;
+ hoverlap = Expression "Horizontal overlap" 0;
+ voverlap = Expression "Vertical overlap" 0;
+
+ _result
+ = map_unary (Group @ map Group @ process) x
+ {
+ process x
+ = imagearray_chop tile_width tile_height
+ hoverlap voverlap x;
+ }
+ }
+ }
+}
+
+#separator
+
+Pattern_images_item = class
+ Menupullright "_Patterns" "make a variety of useful patterns" {
+ Grey_item = class
+ Menuaction "Grey _Ramp" "make a smooth grey ramp" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ orientation = Option "Orientation" [
+ "Horizontal",
+ "Vertical"
+ ] 0;
+ foption = Option "Format" ["8 bit", "float"] 0;
+
+ _result
+ = Image im
+ {
+ gen
+ = im_grey, foption == 0
+ = im_fgrey;
+ w = to_real nwidth;
+ h = to_real nheight;
+ im
+ = gen w h, orientation == 0
+ = rot90 (gen h w);
+ }
+ }
+ }
+
+ Xy_item = class
+ Menuaction "_XY Image"
+ "make a two band image whose pixel values are their coordinates" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+
+ _result = Image (make_xy nwidth nheight);
+ }
+ }
+
+ Noise_item = class
+ Menupullright "_Noise" "various noise generators" {
+ Gaussian_item = class
+ Menuaction "_Gaussian" "make an image of gaussian noise" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ mean = Scale "Mean" 0 255 128;
+ deviation = Scale "Deviation" 0 128 50;
+
+ _result = Image (gaussnoise nwidth nheight
+ mean.value deviation.value);
+ }
+ }
+
+ Fractal_item = class
+ Menuaction "_Fractal" "make a fractal noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ dimension = Scale "Dimension" 2.001 2.999 2.001;
+
+ _result = Image (im_fractsurf (to_real nsize) dimension.value);
+ }
+ }
+
+ Perlin_item = class
+ Menuaction "_Perlin" "Perlin noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ cell_size = Expression "Cell size (pixels)" 8;
+ eight = Toggle "Eight bit output" true;
+
+ _result
+ = 128 * im + 128, eight
+ = im
+ {
+ im = perlin cell_size nwidth nheight;
+ }
+ }
+ }
+
+ Worley_item = class
+ Menuaction "_Worley" "Worley noise image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 512;
+ nheight = Expression "Image height (pixels)" 512;
+ cell_size = Expression "Cell size (pixels)" 256;
+
+ _result
+ = worley cell_size nwidth nheight;
+ }
+ }
+
+ }
+
+ Checkerboard_item = class
+ Menuaction "_Checkerboard" "make a checkerboard image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ hpsize = Expression "Horizontal patch size" 8;
+ vpsize = Expression "Vertical patch size" 8;
+ hpoffset = Expression "Horizontal patch offset" 0;
+ vpoffset = Expression "Vertical patch offset" 0;
+
+ _result
+ = Image (xstripes ^ ystripes)
+ {
+ pixels = make_xy nwidth nheight;
+ xpixels = pixels?0 + to_real hpoffset;
+ ypixels = pixels?1 + to_real vpoffset;
+
+ make_stripe pix swidth = pix % (swidth * 2) >= swidth;
+
+ xstripes = make_stripe xpixels (to_real hpsize);
+ ystripes = make_stripe ypixels (to_real vpsize);
+ }
+ }
+ }
+
+ Grid_item = class
+ Menuaction "Gri_d" "make a grid" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ hspace = Expression "Horizontal line spacing" 8;
+ vspace = Expression "Vertical line spacing" 8;
+ thick = Expression "Line thickness" 1;
+ hoff = Expression "Horizontal grid offset" 4;
+ voff = Expression "Vertical grid offset" 4;
+
+ _result
+ = Image (xstripes | ystripes)
+ {
+ pixels = make_xy nwidth nheight;
+ xpixels = pixels?0 + to_real hoff;
+ ypixels = pixels?1 + to_real voff;
+
+ make_stripe pix swidth = pix % swidth < to_real thick;
+
+ xstripes = make_stripe xpixels (to_real hspace);
+ ystripes = make_stripe ypixels (to_real vspace);
+ }
+ }
+ }
+
+ Text_item = class
+ Menuaction "_Text" "make a bitmap of some text" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ text = String "Text to paint" "Hello world!";
+ font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
+ wrap = Expression "Wrap text at" 500;
+ align = Option "Alignment" [
+ "Left",
+ "Centre",
+ "Right"
+ ] 0;
+ dpi = Expression "DPI" 300;
+
+ _result = Image (im_text text.value font.value
+ (to_real wrap) align.value (to_real dpi));
+ }
+ }
+
+ New_CIELAB_slice_item = class
+ Menuaction "CIELAB _Slice" "make a slice through CIELAB space" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ L = Scale "L value" 0 100 50;
+
+ _result = Image (lab_slice (to_real nsize) L.value);
+ }
+ }
+
+ sense_option = Option "Sense" [
+ "Pass",
+ "Reject"
+ ] 0;
+
+ build fn size
+ = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
+ (im_create_fmask size size);
+
+ New_ideal_item = class
+ Menupullright "_Ideal Fourier Mask"
+ "make various ideal Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f sense.value fc.value 0 0 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 6) fc.value rw.value 0 0 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "ideal Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 12) fcx.value fcy.value
+ r.value 0 0;
+ }
+ }
+ }
+ }
+
+ New_gaussian_item = class
+ Menupullright "_Gaussian Fourier Mask"
+ "make various Gaussian Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 4) fc.value ac.value 0 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 10) fc.value rw.value
+ ac.value 0 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "Gaussian Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 16) fcx.value fcy.value
+ r.value ac.value 0;
+ }
+ }
+ }
+ }
+
+ New_butterworth_item = class
+ Menupullright "_Butterworth Fourier Mask"
+ "make various Butterworth Fourier filter masks" {
+ High_low_item = class
+ Menuaction "_High or Low Pass"
+ ("make a mask image for a highpass/lowpass " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 2) order.value fc.value
+ ac.value 0 0;
+ }
+ }
+ }
+
+ Ring_item = class
+ Menuaction "_Ring Pass or Ring Reject"
+ ("make a mask image for an ring pass/reject " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ rw = Scale "Ring width" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 8) order.value fc.value
+ rw.value ac.value 0;
+ }
+ }
+ }
+
+ Band_item = class
+ Menuaction "_Band Pass or Band Reject"
+ ("make a mask image for a band pass/reject " ++
+ "Butterworth Fourier filter") {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+ sense = sense_option;
+ fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
+ fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
+ r = Scale "Radius" 0.01 0.99 0.5;
+ ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
+ order = Scale "Order" 1 10 2;
+
+ _result
+ = build param (to_real nsize)
+ {
+ param f = f (sense.value + 14) order.value fcx.value
+ fcy.value r.value ac.value;
+ }
+ }
+ }
+ }
+}
+
+Test_images_item = class
+ Menupullright "Test I_mages" "make a variety of test images" {
+ Eye_item = class
+ Menuaction "_Spatial Response"
+ "image for testing the eye's spatial response" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ nheight = Expression "Image height (pixels)" 64;
+ factor = Scale "Factor" 0.001 1 0.2;
+
+ _result = Image (im_eye (to_real nwidth) (to_real nheight)
+ factor.value);
+ }
+ }
+
+ Zone_plate = class
+ Menuaction "_Zone Plate" "make a zone plate" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nsize = Expression "Image size (pixels)" 64;
+
+ _result = Image (im_zone (to_real nsize));
+ }
+ }
+
+ Frequency_test_chart_item = class
+ Menuaction "_Frequency Testchart"
+ "make a black/white frequency test pattern" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ nwidth = Expression "Image width (pixels)" 64;
+ sheight = Expression "Strip height (pixels)" 10;
+ waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2];
+
+ _result
+ = imagearray_assemble 0 0 (transpose [strips])
+ {
+ freq_slice wave = Image (sin (grey / wave) > 0);
+ strips = map freq_slice waves.expr;
+ grey = im_fgrey (to_real nwidth) (to_real sheight) *
+ 360 * (to_real nwidth);
+ }
+ }
+ }
+
+ CRT_test_chart_item = class
+ Menuaction "CRT _Phosphor Chart"
+ "make an image for measuring phosphor colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ brightness = Scale "Brightness" 0 255 200;
+ psize = Expression "Patch size (pixels)" 32;
+
+ _result
+ = Image (imagearray_assemble 0 0 [[green, red], [blue, white]])
+ {
+
+ black = image_new (to_real psize) (to_real psize) 1
+ Image_format.FLOAT Image_coding.NOCODING
+ Image_type.B_W 0 0 0;
+ notblack = black + brightness;
+
+ green = black ++ notblack ++ black;
+ red = notblack ++ black ++ black;
+ blue = black ++ black ++ notblack;
+ white = notblack ++ notblack ++ notblack;
+ }
+ }
+ }
+
+ Greyscale_chart_item = class
+ Menuaction "_Greyscale" "make a greyscale" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ pwidth = Expression "Patch width" 8;
+ pheight = Expression "Patch height" 8;
+ npatches = Expression "Number of patches" 16;
+
+ _result
+ = Image (image_set_type Image_type.B_W
+ (clip2fmt Image_format.UCHAR wedge))
+ {
+ wedge
+ = 255 / (to_real npatches - 1) *
+ (int) (strip?0 / to_real pwidth)
+ {
+ strip = make_xy (to_real pwidth * to_real npatches) pheight;
+ }
+ }
+ }
+ }
+
+ CMYK_test_chart_item = class
+ Menuaction "_CMYK Wedges" "make a set of CMYK wedges" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ pwidth = Expression "Patch width" 8;
+ pheight = Expression "Patch height" 8;
+ npatches = Expression "Number of patches" 16;
+
+ _result
+ = Image (image_set_type Image_type.CMYK
+ (clip2fmt Image_format.UCHAR strips))
+ {
+ wedge
+ = 255 / (to_real npatches - 1) *
+ (int) (strip?0 / to_real pwidth)
+ {
+ strip = make_xy (to_real pwidth * to_real npatches) pheight;
+ }
+
+ black = wedge * 0;
+
+ C = wedge ++ black ++ black ++ black;
+ M = black ++ wedge ++ black ++ black;
+ Y = black ++ black ++ wedge ++ black;
+ K = black ++ black ++ black ++ wedge;
+
+ strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]];
+ }
+ }
+ }
+
+ Colour_atlas_item = class
+ Menuaction "_Colour Atlas"
+ "make a grid of patches grouped around a colour" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ start = Colour_picker "Lab" [50,0,0];
+ nstep = Expression "Number of steps" 9;
+ ssize = Expression "Step size" 10;
+ psize = Expression "Patch size" 32;
+ sepsize = Expression "Separator size" 4;
+
+ _result
+ = colour_transform_to (get_type start) blocks'''
+ {
+ size = (to_real nstep * 2 + 1) * to_real psize -
+ to_real sepsize;
+ xy = make_xy size size;
+
+ xy_grid = (xy % to_real psize) <
+ (to_real psize - to_real sepsize);
+ grid = xy_grid?0 & xy_grid?1;
+
+ blocks = (int) (to_real ssize * ((int) (xy / to_real psize)));
+ lab_start = colour_transform_to Image_type.LAB start;
+ blocks' = blocks - to_real nstep * to_real ssize +
+ Vector (tl lab_start.value);
+ blocks'' = hd lab_start.value ++ Image blocks';
+ blocks'''
+ = image_set_type Image_type.LAB blocks'', Image grid
+ = 0;
+ }
+ }
+ }
+}
+
diff --git a/share/nip2/compat/8.6/Magick.def b/share/nip2/compat/8.6/Magick.def
new file mode 100644
index 00000000..ab6cdb35
--- /dev/null
+++ b/share/nip2/compat/8.6/Magick.def
@@ -0,0 +1,2423 @@
+/*
+
+ ImageMagick operations edited by Alan Gibson (aka "snibgo"; snibgo at earthling dot net).
+ 1-Apr-2014
+ Minor corrections to Geometry_widget and Alpha.
+ Added loads of widgets and Menuactions.
+ Not fully tested.
+ 5-Apr-2014
+ Many more menu actions.
+ Reorganised Magick menu.
+ 10-Apr-2014
+ Many more menu actions.
+ 11-Apr-2014 jcupitt
+ Split to separate Magick.def
+ 13-Apr-2014 snibgo
+ Put "new image" items into sub-menu.
+ New class VirtualPixlBack.
+ 17-Apr-2014 snibgo
+ Many small changes.
+ A few new menu options.
+ Created sub-menu for multi-input operations.
+ 3-May-2014 jcupitt
+ Put quotes around ( in shadow to help unix
+
+ Last update: 17-Apr-2014.
+
+ For details of ImageMagick operations, see http://www.imagemagick.org/script/command-line-options.php etc.
+*/
+
+// We don't need Noop.
+/*===
+Magick_noop_item = class
+ Menuaction "_Noop" "no operation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+===*/
+
+Magick_testPar_item = class
+ Menuaction "_TestPar" "no operation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "( +clone ) +append ",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+/* Removed Read_item and Write_item, much better to use nip2 load/save image.
+ * Plus they can load all libMagick formats anyway.
+ */
+
+
+// Put "new image" items into sub-menu
+Magick_NewImageMenu_item = class
+ Menupullright "_New image" "make a new image" {
+
+ Magick_newcanvas_item = class
+ Menuaction "_Solid colour" "make image of solid colour" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ colour = Magick.generalcol_widget;
+ command = Magick.command [
+ size._flag,
+ "\"canvas:" ++ colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_builtin_item = class
+ Menuaction "_Built-in image" "create a built-in image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ builtin = Magick.builtin_widget;
+ command = Magick.command [
+ builtin._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_gradient_item = class
+ Menuaction "_Gradient" "make a linear gradient between two colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ topColour = Magick.generalcol_widget;
+ bottomColour = Magick.generalcol_widget;
+
+ command = Magick.command [
+ size._flag,
+ concat ["\"gradient:",
+ topColour._flag, "-", bottomColour._flag, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_hald_item = class
+ Menuaction "_Hald-clut image" "create an identity hald-clut image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ order = Expression "order" 8;
+ command = Magick.command [
+ "hald:" ++ print order.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_pattern_item = class
+ Menuaction "_Pattern" "create pattern" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ pattern = Magick.pattern_widget;
+ command = Magick.command [
+ size._flag,
+ pattern._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_plasma_item = class
+ Menuaction "_Plasma image" "create plasma image" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ // FIXME? ColourA-ColourB.
+ // FIXME? Allow plasma:fractal?
+
+ command = Magick.command [
+ size._flag,
+ "plasma:",
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+
+ Magick_radialgradient_item = class
+ Menuaction "_Radial gradient"
+ "make a radial gradient between two colours" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+ innerColour = Magick.generalcol_widget;
+ outerColour = Magick.generalcol_widget;
+
+ command = Magick.command [
+ size._flag,
+ concat ["\"radial-gradient:",
+ innerColour._flag, "-", outerColour._flag, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system0 command;
+ }
+ }
+} // end Magick_NewImageMenu_item
+
+
+Magick_MultiMenu_item = class
+ Menupullright "_Multiple inputs" "make an image from multiple images" {
+
+ Magick_composite_item = class
+ Menuaction "_Composite" "composite two images (without mask)" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.compose_widget;
+ offsets = Magick.OffsetGeometry_widget;
+ gravity = Magick.gravity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-geometry", offsets._flag,
+ gravity._flag,
+ method._flag,
+ "-composite",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+ Magick_compositeMask_item = class
+ Menuaction "_Composite masked" "composite two images (with mask)" {
+ action x y z = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.compose_widget;
+ offsets = Magick.OffsetGeometry_widget;
+ gravity = Magick.gravity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "\"%s\"",
+ "-geometry", offsets._flag,
+ gravity._flag,
+ method._flag,
+ "-composite",
+ "\"%s\""
+ ];
+
+ _result = Magick.system3 command x y z;
+ }
+ }
+
+ // FIXME: other operations like remap that take another image as arguments are:
+ // mask (pointless?), texture, tile (pointless?)
+
+ // FIXME: operations that take a filename that isn't an image:
+ // cdl, profile
+
+ Magick_clut_item = class
+ Menuaction "_Clut" "replace values using second image as colour look-up table" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // FIXME: uses -intensity "when mapping greyscale CLUT image to alpha channel if set by -channels"
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-clut",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+ Magick_haldclut_item = class
+ Menuaction "_Hald clut" "replace values using second image as Hald colour look-up table" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "\"%s\"",
+ "-hald-clut",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+
+ // Encipher and decipher: key files can be text or image files.
+
+ Magick_encipher_item = class
+ Menuaction "_Encipher/Decipher" "encipher or decipher an image image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pathname = Pathname "Read key file" "";
+ isDecipher = Toggle "Decipher" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ if pathname.value == "" then "" else (
+ ( if isDecipher then "-decipher " else "-encipher ") ++
+ ( "\"" ++ pathname.value ++ "\"" )
+ ),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_profile_item = class
+ Menuaction "_Profile" "assigns/applies an ICC profile" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ pathname1 = Pathname "Read profile file" "";
+ pathname2 = Pathname "Read profile file" "";
+
+ command = Magick.command [
+ "\"%s\"",
+ if pathname1.value == "" then "" else (
+ "-profile " ++
+ "\"" ++ pathname1.value ++ "\""),
+ if pathname2.value == "" then "" else (
+ "-profile " ++
+ "\"" ++ pathname2.value ++ "\""),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_remap_item = class
+ Menuaction "_Remap" "reduce colours to those in another image" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-remap",
+ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system2 command x y;
+ }
+ }
+
+} // end Magick_MultiMenu_item
+
+
+Magick_image_type_item = class
+ Menuaction "_Image Type" "change image type" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ imagetype = Magick.imagetype_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ imagetype._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+sep2 = Menuseparator;
+
+Magick_alpha_item = class
+ Menuaction "_Alpha" "add/remove alpha channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ alpha = Magick.alpha_widget;
+ command = Magick.command [
+ "\"%s\"",
+ alpha._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_annotate_item = class
+ Menuaction "_Annotate" "add text annotation" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ text = Magick.text_widget;
+ font = Magick.Font_widget;
+ geometry = Magick.AnnotGeometry_widget;
+ gravity = Magick.gravity_widget;
+ foreground = Magick.foreground_widget;
+ undercol = Magick.undercol_widget;
+ antialias = Magick.antialias_widget;
+ command = Magick.command [
+ "\"%s\"",
+ font._flag,
+ antialias._flag,
+ gravity._flag,
+ foreground._flag,
+ undercol._flag,
+ "-annotate",
+ geometry._flag,
+ "\"" ++ text.value ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_autoGamma_item = class
+ Menuaction "_AutoGamma" "automatic gamma" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-auto-gamma",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_autoLevel_item = class
+ Menuaction "_AutoLevel" "automatic level" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-auto-level",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_blurSharpMenu_item = class
+ Menupullright "_Blur/Sharpen" "blur and sharpen" {
+
+ Magick_adaptive_blur_item = class
+ Menuaction "_Adaptive Blur" "blur less near edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ // note: adaptive-blur doesn't regard VP.
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-adaptive-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_blur_item = class
+ Menuaction "_Blur" "blur" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_gaussianBlur_item = class
+ Menuaction "_Gaussian Blur" "blur with a Gaussian operator" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-gaussian-blur",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_motionBlur_item = class
+ Menuaction "_Motion Blur" "simulate motion blur" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ angle = Scale "angle" (-360) 360 0;
+ channels = Magick.ch_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-motion-blur",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_rotationalBlur_item = class
+ Menuaction "_RotationalBlur" "blur around the centre" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ angle = Scale "angle (degrees)" (-360) 360 20;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-radial-blur",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_selectiveBlur_item = class
+ Menuaction "_Selective Blur" "blur where contrast is less than or equal to threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ virtpixback._flag,
+ channels._flag,
+ "-selective-blur",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print threshold.value ++ "%%",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Magick_adaptive_sharpen_item = class
+ Menuaction "_Adaptive Sharpen" "sharpen more near edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-adaptive-sharpen",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_sharpen_item = class
+ Menuaction "_Sharpen" "sharpen" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-sharpen",
+ print radius.value ++ "x" ++ print sigma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_unsharpen_item = class
+ Menuaction "_Unsharp" "sharpen with unsharp mask" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ channels = Magick.channels_widget;
+ gain = Scale "Gain" (-10) 10 1;
+ threshold = Scale "Threshold" 0 1 0.05;
+ virtpixback = Magick.VirtualPixelBack_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ channels._flag,
+ "-unsharp",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print gain.value ++ "+" ++
+ print threshold.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end BlurSharpMenu_item
+
+
+Magick_border_item = class
+ Menuaction "_Border" "add border of given colour" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ compose = Magick.compose_widget;
+ width = Expression "Width" 3;
+ bordercol = Magick.bordercol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ bordercol._flag,
+ "-border",
+ print width.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_brightCont_item = class
+ Menuaction "_Brightness-contrast" "adjust the brightness and/or contrast" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ bri = Scale "brightness" (-100) 100 0;
+ con = Scale "contrast" (-100) 100 0;
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-brightness-contrast",
+ print bri.value ++ "x" ++ print con.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// Note: canny requires ImageMagick 6.8.9-0 or later.
+
+Magick_canny_item = class
+ Menuaction "_Canny" "detect a wide range of edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ lowPc = Scale "lower percent" 0 100 10;
+ highPc = Scale "lower percent" 0 100 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-canny",
+ concat ["\"",
+ print radius.value ++ "x" ++
+ print sigma.value ++ "+" ++
+ print lowPc.value ++ "%%+" ++
+ print highPc.value ++ "%%" ++ "\""
+ ],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
+Magick_charcoal_item = class
+ Menuaction "_Charcoal" "simulate a charcoal drawing" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ factor = Scale "factor" 0 50 1;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-charcoal",
+ print factor.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_chop_item = class
+ Menuaction "_Chop" "remove pixels from the interior" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ gravity._flag,
+ "-chop",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colorize_item = class
+ Menuaction "_Colorize" "colorize by given amount" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ val = Scale "value" 0 100 100;
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-colorize",
+ print val.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colors_item = class
+ Menuaction "_Colors" "reduce number of colors" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ treedepth = Expression "Treedepth" 8;
+ dither = Magick.dither_widget;
+ quantize = Magick.colorspace_widget;
+ colors = Expression "Colours" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-quantize", quantize._flag,
+ "-treedepth",
+ print treedepth.expr,
+ dither._flag,
+ "-colors",
+ print colors.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: color-matrix?
+
+Magick_colorspace_item = class
+ Menuaction "_Colourspace" "convert to arbitrary colourspace" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colsp = Magick.colorspace_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-colorspace",
+ colsp._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_colorspaceGray_item = class
+ Menuaction "_Colourspace gray" "convert to gray using given intensity method" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ "-colorspace gray",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_contrast_item = class
+ Menuaction "_Contrast" "increase or reduce the contrast" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ isReduce = Toggle "reduce contrast" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ (if isReduce then "+" else "-") ++ "contrast",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_contrastStretch_item = class
+ Menuaction "_Contrast stretch" "stretches tones, making some black/white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+ blk = Scale "percent to make black" 0 100 0;
+ wht = Scale "percent to make white" 0 100 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-contrast-stretch",
+ "\"" ++ print blk.value ++ "x" ++ print wht.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: convolve (bias, kernel)
+
+Magick_crop_item = class
+ Menuaction "_Crop" "cut out a rectangular region" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ gravity._flag,
+ "-crop",
+ geometry._flag,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_deskew_item = class
+ Menuaction "_Deskew" "straighten the image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ threshold = Scale "Threshold (percent)" 0 100 80;
+
+ // FIXME: toggle auto-crop?
+
+ command = Magick.command [
+ "\"%s\"",
+ "-deskew",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_despeckle_item = class
+ Menuaction "_Despeckle" "reduce the speckles" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-despeckle",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_distort_item = class
+ Menuaction "_Distort" "distort using a method and arguments" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ virtpixback = Magick.VirtualPixelBack_widget;
+ distort = Magick.distort_widget;
+ args = String "Arguments" "1,0";
+ isPlus = Toggle "Extend to show entire image" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ (if isPlus then "+" else "-") ++ "distort",
+ distort._flag,
+ args.value,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_draw_item = class
+ Menuaction "_Draw" "annotate with one or more graphic primitives" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ args = String "Arguments" "line 0,0 9,9 rectangle 10,10 20,20";
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-draw",
+ concat ["\"", args.value, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_edge_item = class
+ Menuaction "_Edge" "detect edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "Radius" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-edge",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_emboss_item = class
+ Menuaction "_Emboss" "emboss" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "Radius" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-emboss",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_enhance_item = class
+ Menuaction "_Enhance" "enhance a noisy image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-enhance",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_equalize_item = class
+ Menuaction "_Equalize" "equalize the histogram" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-equalize",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_evaluate_item = class
+ Menuaction "_Evaluate" "evaluate an expression on each pixel channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ operation = Magick.evaluate_widget;
+ val = Expression "value" 5;
+ isPc = Toggle "Value is percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ operation._flag,
+ print val.expr ++ if isPc then "%%" else "",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_extent_item = class
+ Menuaction "_Extent" "set the image size and offset" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ background = Magick.background_widget;
+ compose = Magick.compose_widget;
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ background._flag,
+ gravity._flag,
+ "-extent",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
+Magick_FlipFlopMenu_item = class
+ Menupullright "_Flip/flop" "flip/flop/transverse/transpose" {
+
+ Magick_flip_item = class
+ Menuaction "_Flip vertically" "mirror upside-down" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-flip",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_flop_item = class
+ Menuaction "_Flop horizontally" "mirror left-right" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-flop",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_transpose_item = class
+ Menuaction "_Transpose" "mirror along the top-left to bottom-right diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-transpose +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_transverse_item = class
+ Menuaction "_Transverse" "mirror along the bottom-left to top-right diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-transverse +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end Magick_FlipFlopMenu_item
+
+
+Magick_floodfill_item = class
+ Menuaction "_Floodfill" "recolour neighbours that match" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ fuzz = Magick.fuzz_widget;
+ coordinate = Magick.coordinate_widget;
+
+ // -draw "color x,y floodfill"
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-fuzz",
+ "\"" ++ print fuzz.value ++ "%%\"",
+ "-draw \" color",
+ coordinate._flag,
+ "floodfill \"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_frame_item = class
+ Menuaction "_Frame" "surround with border or beveled frame" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ border = Magick.bordercol_widget;
+ compose = Magick.compose_widget;
+ matte = Magick.mattecol_widget;
+ geometry = Magick.FrameGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ compose._flag,
+ border._flag,
+ matte._flag,
+ "-frame",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_function_item = class
+ Menuaction "_Function" "evaluate a function on each pixel channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ function = Magick.function_widget;
+ // FIXME: explain values; use sensible defaults.
+ values = String "values" "0,0,0,0";
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-function",
+ function._flag,
+ values.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_fx_item = class
+ Menuaction "_Fx" "apply a mathematical expression" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ interpolate = Magick.interpolate_widget;
+ virtpixback = Magick.VirtualPixelBack_widget;
+ args = String "Expression" "u*1/2";
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ interpolate._flag,
+ virtpixback._flag,
+ "-fx",
+ concat ["\"", args.value, "\""],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gamma_item = class
+ Menuaction "_Gamma" "apply a gamma correction" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ gamma = Magick.gamma_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-gamma",
+ print gamma.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gradient_item = class
+ Menuaction "_Gradient" "apply a linear gradient" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colourA = Magick.generalcol_widget;
+ colourB = Magick.generalcol_widget;
+
+ position = Option "colourA is at" [
+ "top", "bottom",
+ "left", "right",
+ "top-left", "top-right",
+ "bottom-left", "bottom-right"] 0;
+ _baryArg
+ = concat ["0,0,", colourA._flag, " 0,%%[fx:h-1],", colourB._flag], position.value == 0
+ = concat ["0,0,", colourB._flag, " 0,%%[fx:h-1],", colourA._flag], position.value == 1
+ = concat ["0,0,", colourA._flag, " %%[fx:w-1],0,", colourB._flag], position.value == 2
+ = concat ["0,0,", colourB._flag, " %%[fx:w-1],0,", colourA._flag], position.value == 3
+ = concat ["0,0,", colourA._flag, " %%[fx:w-1],%%[fx:h-1],", colourB._flag], position.value == 4
+ = concat ["%%[fx:w-1],0,", colourA._flag, " 0,%%[fx:h-1],", colourB._flag], position.value == 5
+ = concat ["%%[fx:w-1],0,", colourB._flag, " 0,%%[fx:h-1],", colourA._flag], position.value == 6
+ = concat ["0,0,", colourB._flag, " %%[fx:w-1],%%[fx:h-1],", colourA._flag], position.value == 7
+ = "dunno";
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sparse-color barycentric \"" ++ _baryArg ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_gradientCorn_item = class
+ Menuaction "_Gradient corners"
+ "apply a bilinear gradient between the corners" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ colour_top_left = Magick.generalcol_widget;
+ colour_top_right = Magick.generalcol_widget;
+ colour_bottom_left = Magick.generalcol_widget;
+ colour_bottom_right = Magick.generalcol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sparse-color bilinear \"" ++
+ "0,0," ++ colour_top_left._flag ++
+ ",%%[fx:w-1],0" ++ colour_top_right._flag ++
+ ",0,%%[fx:h-1]" ++ colour_bottom_left._flag ++
+ ",%%[fx:w-1],%%[fx:h-1]" ++ colour_bottom_right._flag ++ "\"",
+ "+depth",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_histogram_item = class
+ Menuaction "_Histogram" "make a histogram image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-define histogram:unique-colors=false histogram:" ++
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_implode_item = class
+ Menuaction "_Implode" "implode pixels about the center" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ factor = Scale "factor" 0 20 1;
+ interpolate = Magick.interpolate_widget;
+ // FIXME: virtual-pixel?
+
+ command = Magick.command [
+ "\"%s\"",
+ interpolate._flag,
+ "-implode",
+ print factor.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_level_item = class
+ Menuaction "_Level" "adjust the level of channels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ blk = Scale "black point" (-100) 200 0;
+ wht = Scale "white point" (-100) 200 100;
+ gam = Scale "gamma" 0 30 1;
+ isPc = Toggle "Levels are percent" true;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "level",
+ "\"" ++ print blk.value ++ "," ++
+ print wht.value ++ (if isPc then "%%" else "") ++ "," ++
+ print gam.value ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_levelCols_item = class
+ Menuaction "_Level colors" "adjust levels to given colours" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ colour_black = Magick.generalcol_widget;
+ colour_white = Magick.generalcol_widget;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "level-colors",
+ "\"" ++ colour_black._flag ++ "," ++ colour_white._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_linearStretch_item = class
+ Menuaction "_Linear stretch" "stretches tones, making some black/white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ blk = Scale "percent to make black" 0 100 0;
+ wht = Scale "percent to make white" 0 100 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-linear-stretch",
+ "\"" ++ print blk.value ++ "x" ++ print wht.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_magnify_item = class
+ Menuaction "_Magnify" "double the size of the image with pixel art scaling" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-magnify",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_modulate_item = class
+ Menuaction "_Modulate" "modulate brightness, saturation and hue" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ modcolsp = Magick.ModColSp_widget;
+ bright = Scale "brightness" 0 200 100;
+ sat = Scale "saturation" 0 200 100;
+ hue = Scale "hue" 0 200 100;
+
+ command = Magick.command [
+ "\"%s\"",
+ modcolsp._flag,
+ "-modulate",
+ print bright.value ++ "," ++ print sat.value ++ "," ++
+ print hue.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_monochrome_item = class
+ Menuaction "_Monochrome" "transform to black and white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // FIXME: also intensity?
+
+ intensity = Magick.intensity_widget;
+ treedepth = Expression "Treedepth" 8;
+ dither = Magick.dither_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ dither._flag,
+ "-treedepth",
+ print treedepth.expr,
+ "-monochrome",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_morphology_item = class
+ // See http://www.imagemagick.org/Usage/morphology/
+ Menuaction "_Morphology" "apply a morphological method" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ method = Magick.morphmeth_widget;
+ iter = Expression "Iterations (-1=repeat until done)" 1;
+
+ kernel = Magick.kernel_widget;
+ // FIXME: custom kernel eg "3x1+2+0:1,0,0"
+ // width x height + offsx + offsy : {w*h values}
+ // each value is 0.0 to 1.0 or "NaN" or "-"
+
+ // kernel args, mostly float radius,scale. radius=0=default. default scale = 1.0
+ // but
+ // ring takes: radius1, radius2, scale
+ // rectangle and comet take: width x height + offsx + offsy
+ // blur takes: radius x sigma
+ // FIXME: for now, simply allow any string input.
+ kernel_arg = String "Kernel arguments" "";
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-morphology",
+ method._flag ++ ":" ++ print iter.expr,
+ kernel._flag ++
+ (if kernel_arg.value == "" then "" else ":") ++ kernel_arg.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_negate_item = class
+ Menuaction "_Negate" "negate" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-negate",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_addNoise_item = class
+ Menuaction "_add Noise" "add noise" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ attenuate = Scale "attenuate" 0 1.0 1.0;
+ noise = Magick.noise_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-attenuate",
+ print attenuate.value,
+ noise._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_normalize_item = class
+ Menuaction "_Normalize" "normalize" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-normalize",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_opaque_item = class
+ Menuaction "_Opaque" "change this colour to the fill colour" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ fill = Magick.foreground_widget;
+ changeColour = Magick.changeCol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ fill._flag,
+ channels._flag,
+ "-fuzz",
+ "\"" ++ print changeColour.fuzz.value ++ "%%\"",
+ (if changeColour.nonMatch then "+" else "-") ++ "opaque",
+ "\"" ++ changeColour.colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_paint_item = class
+ Menuaction "_Paint" "simulate an oil painting" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rad = Expression "radius" 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-paint",
+ print rad.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+/*=== FIXME Bug; remove for now.
+Polaroid_item = class
+ Menuaction "_Polaroid" "simulate a polaroid picture" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "angle" (-90) 90 20;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-polaroid",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+===*/
+
+Magick_posterize_item = class
+ Menuaction "_Posterize" "reduce to (n) levels per channel" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ levels = Expression "levels" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-posterize",
+ print levels.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_raise_item = class
+ Menuaction "_Raise" "lighten or darken image edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ thk = Expression "Thickness" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-raise",
+ print thk.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_resize_item = class
+ Menuaction "_Resize" "resize to given width and height" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ filter = Magick.filter_widget;
+ type = Magick.ResizeType_widget;
+ width = Scale "Width" 1 100 10;
+ height = Scale "Height" 1 100 10;
+ isPc = Toggle "Width and height are percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ filter._flag,
+ "-" ++ type._flag,
+ "\"" ++ print width.value ++ "x" ++ print height.value ++
+ (if isPc then "%%" else "!") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_roll_item = class
+ Menuaction "_Roll" "roll an image horizontally or vertically" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ rollx = Expression "X" 3;
+ rolly = Expression "Y" 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-roll",
+ (if rollx.expr >= 0 then "+" else "") ++ print rollx.expr ++
+ (if rolly.expr >= 0 then "+" else "") ++ print rolly.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_rotate_item = class
+ Menuaction "_Rotate" "rotate" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Scale "angle (degrees)" (-360) 360 20;
+ virtpixback = Magick.VirtualPixelBack_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ "+distort",
+ "SRT",
+ print angle.value,
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: -segment, but cluster-threshold should be percentage of image area.
+
+Magick_sepia_item = class
+ Menuaction "_Sepia tone" "simulate a sepia-toned photo" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ threshold = Scale "Threshold (percent)" 0 100 80;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sepia-tone",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shade_item = class
+ Menuaction "_Shade" "shade with a distant light source" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ azimuth = Scale "Azimuth (degrees)" (-360) 360 0;
+ elevation = Scale "Elevation (degrees)" 0 90 45;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-shade",
+ print azimuth.value ++ "x" ++ print elevation.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shadow_item = class
+ Menuaction "_Shadow" "simulate a shadow" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ shadowCol = Magick.generalcol_widget;
+ opacity = Scale "Opacity (percent)" 0 100 75;
+ sigma = Scale "Sigma" 0 30 2;
+ // FIXME: make offsets a single widget?
+ offsx = Scale "X-offset" (-20) 20 4;
+ offsy = Scale "Y-offset" (-20) 20 4;
+ arePc = Toggle "offsets are percentages" false;
+
+ // FIXME: raw operation creates page offset, which vips dislikes.
+ // So we take this futher, compositing with source.
+ command = Magick.command [
+ "\"%s\"",
+ "\"(\" +clone",
+ "-background", "\"" ++ shadowCol._flag ++ "\"",
+ "-shadow",
+ concat [
+ "\"",
+ print opacity.value, "x", print sigma.value,
+ (if offsx.value >= 0 then "+" else ""), print offsx.value,
+ (if offsy.value >= 0 then "+" else ""), print offsy.value,
+ (if arePc then "%%" else ""),
+ "\""
+ ],
+ "\")\" +swap -background None -layers merge",
+ "+repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shave_item = class
+ Menuaction "_Shave" "shave pixels from the edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Scale "Width" 0 50 10;
+ height = Scale "Height" 0 50 10;
+ isPc = Toggle "Width and height are percent" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-shave",
+ "\"" ++ print width.value ++ "x" ++ print height.value ++
+ (if isPc then "%%" else "") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_shear_item = class
+ Menuaction "_Shear" "shear along the x-axis and/or y-axis" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ shearX = Expression "shear X (degrees)" 0;
+ shearY = Expression "shear Y (degrees)" 0;
+ background = Magick.background_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ "-shear",
+ print shearX.expr ++ "x" ++ print shearY.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_sigmoid_item = class
+ Menuaction "_Sigmoid" "increase or decrease mid-tone contrast sigmoidally" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ contrast = Scale "contrast" 0 30 3;
+ midpoint = Scale "mid-point (percent)" 0 100 50;
+ isInv = Toggle "Invert effect" false;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ (if isInv then "+" else "-") ++ "sigmoidal-contrast",
+ "\"" ++ print contrast.value ++ "x" ++
+ print midpoint.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_sketch_item = class
+ Menuaction "_Sketch" "simulate a pencil sketch" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ angle = Scale "angle" (-360) 360 0;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-sketch",
+ print radius.value ++ "x" ++ print sigma.value ++
+ (if angle >= 0 then ("+" ++ print angle.value) else ""),
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_solarize_item = class
+ Menuaction "_Solarize" "negate all pixels above a threshold level" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-solarize",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+// FIXME: -sparse-color needs abitrary list of {x,y,colour}.
+
+Magick_splice_item = class
+ Menuaction "_Splice" "splice a colour into the image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ background = Magick.background_widget;
+ gravity = Magick.gravity_widget;
+ geometry = Magick.WhxyGeometry_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ gravity._flag,
+ "-splice",
+ geometry._flag,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_spread_item = class
+ Menuaction "_Spread" "displace pixels by random amount" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ virtpixback = Magick.VirtualPixelBack_widget;
+ amount = Expression "Amount (pixels)" 5;
+
+ command = Magick.command [
+ "\"%s\"",
+ virtpixback._flag,
+ "-spread",
+ print amount.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_statistic_item = class
+ Menuaction "_Statistic" "replace each pixel with statistic from neighbourhood" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Expression "Width" 5;
+ height = Expression "Height" 5;
+ statisticType = Magick.StatType_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-statistic",
+ statisticType._flag,
+ print width.expr ++ "x" ++ print height.expr,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_swirl_item = class
+ Menuaction "_Swirl" "swirl around the centre" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ angle = Magick.angle_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-swirl",
+ print angle.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_thresholdMenu_item = class
+ Menupullright "_Threshold" "make black or white" {
+
+ Magick_threshold_item = class
+ Menuaction "_Threshold" "apply black/white threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ intensity = Magick.intensity_widget;
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ intensity._flag,
+ channels._flag,
+ "-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_blackThreshold_item = class
+ Menuaction "_Black threshold" "where below threshold set to black" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-black-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_whiteThreshold_item = class
+ Menuaction "_White threshold" "where above threshold set to white" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ threshold = Scale "Threshold (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-white-threshold",
+ "\"" ++ print threshold.value ++ "%%\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_latThreshold_item = class
+ Menuaction "_Local Adaptive Threshold" "where above average plus offset set to white, otherwise black" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ width = Expression "Width" 10;
+ height = Expression "Height" 10;
+ offset = Scale "Offset (percent)" (-100) 100 0;
+ // note: "-lat" doesn't respond to channels
+
+ command = Magick.command [
+ "\"%s\"",
+ "-lat",
+ concat ["\"", print width.expr, "x", print height.expr,
+ (if offset.value >= 0 then "+" else ""), print offset.value,
+ "%%\""
+ ],
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+ Magick_randThreshold_item = class
+ Menuaction "_Random Threshold" "between specified limits, apply random threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ channels = Magick.channels_widget;
+ low = Scale "Low threshold" 0 100 10;
+ high = Scale "High threshold" 0 100 90;
+ isPc = Toggle "Thresholds are percent" true;
+
+ command = Magick.command [
+ "\"%s\"",
+ channels._flag,
+ "-random-threshold",
+ "\"" ++ print low.value ++ "x" ++ print high.value ++
+ (if isPc then "%%" else "") ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+ }
+
+} // end ThresholdMenu_item
+
+
+// Note: alternatives include:
+// convert in.tif -write mpr:TILE +delete -size 200x200 -background XXXX -tile-offset +30+30 tile:mpr:TILE out.tif
+
+Magick_tile_item = class
+ Menuaction "_Tile" "fill given size with tiled image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ size = Magick.Size_widget;
+
+ command = Magick.command [
+ size._flag,
+ "tile:" ++ "\"%s\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_tint_item = class
+ Menuaction "_Tint" "apply a tint" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ foreground = Magick.foreground_widget;
+ amount = Scale "amount (percent)" 0 100 50;
+
+ command = Magick.command [
+ "\"%s\"",
+ foreground._flag,
+ "-tint",
+ // snibgo note: although the amount is a percentage, it doesn't need "%" character.
+ print amount.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_transparent_item = class
+ Menuaction "_Transparent" "make this colour transparent" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ changeColour = Magick.changeCol_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-fuzz",
+ "\"" ++ print changeColour.fuzz.value ++ "%%\"",
+ (if changeColour.nonMatch then "+" else "-") ++ "transparent",
+ "\"" ++ changeColour.colour._flag ++ "\"",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_trim_item = class
+ Menuaction "_Trim" "trims away border" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ fuzz = Magick.fuzz_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-fuzz",
+ "\"" ++ print fuzz.value ++ "%%\"",
+ "-trim +repage",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_uniqueCols_item = class
+ Menuaction "_Unique colours" "discard all but one of any pixel color" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-unique-colors",
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_vignette_item = class
+ Menuaction "_Vignette" "soften the edges in vignette style" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ radius = Magick.blur_rad_widget;
+ sigma = Magick.sigma_widget;
+ rx = Scale "Rolloff x (percent)" 0 100 10;
+ ry = Scale "Rolloff y (percent)" 0 100 10;
+ background = Magick.background_widget;
+
+ command = Magick.command [
+ "\"%s\"",
+ background._flag,
+ "-vignette",
+ print radius.value ++ "x" ++ print sigma.value ++
+ (if rx.value >= 0 then "+" else "") ++ print rx.value ++
+ (if ry.value >= 0 then "+" else "") ++ print ry.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+Magick_wave_item = class
+ Menuaction "_Wave" "shear the columns into a sine wave" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ amplitude = Scale "Amplitude (pixels)" 0 100 10;
+ wavelength = Scale "Wavelength (pixels)" 0 100 10;
+
+ command = Magick.command [
+ "\"%s\"",
+ "-wave",
+ print amplitude.value ++ "x" ++ print wavelength.value,
+ "\"%s\""
+ ];
+
+ _result = Magick.system command x;
+ }
+}
+
+
diff --git a/share/nip2/compat/8.6/Makefile.am b/share/nip2/compat/8.6/Makefile.am
new file mode 100644
index 00000000..ce260fa8
--- /dev/null
+++ b/share/nip2/compat/8.6/Makefile.am
@@ -0,0 +1,26 @@
+startdir = $(pkgdatadir)/compat/8.6
+
+start_DATA = \
+ Colour.def \
+ _convert.def \
+ Filter.def \
+ _generate.def \
+ Histogram.def \
+ Image.def \
+ _joe_extra.def \
+ _joe_utilities.def \
+ _list.def \
+ _magick.def \
+ Magick.def \
+ Math.def \
+ Matrix.def \
+ _Object.def \
+ Object.def \
+ _predicate.def \
+ _stdenv.def \
+ Tasks.def \
+ _types.def \
+ Widgets.def
+
+EXTRA_DIST = $(start_DATA)
+
diff --git a/share/nip2/compat/8.6/Math.def b/share/nip2/compat/8.6/Math.def
new file mode 100644
index 00000000..34a40a66
--- /dev/null
+++ b/share/nip2/compat/8.6/Math.def
@@ -0,0 +1,588 @@
+Math_arithmetic_item = class
+ Menupullright "_Arithmetic" "basic arithmetic for objects" {
+ Add_item = class
+ Menuaction "_Add" "add a and b" {
+ action a b = map_binary add a b;
+ }
+
+ Subtract_item = class
+ Menuaction "_Subtract" "subtract b from a" {
+ action a b = map_binary subtract a b;
+ }
+
+ Multiply_item = class
+ Menuaction "_Multiply" "multiply a by b" {
+ action a b = map_binary multiply a b;
+ }
+
+ Divide_item = class
+ Menuaction "_Divide" "divide a by b" {
+ action a b = map_binary divide a b;
+ }
+
+ Remainder_item = class
+ Menuaction "_Remainder"
+ "remainder after integer division of a by b" {
+ action a b = map_binary remainder a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Absolute_value_item = class
+ Menuaction "A_bsolute Value" "absolute value of x" {
+ action x = map_unary abs x;
+ }
+
+ Absolute_value_vector_item = class
+ Menuaction "Absolute Value _Vector"
+ "like Absolute Value, but treat pixels as vectors" {
+ action x = map_unary abs_vec x;
+ }
+
+ Sign_item = class
+ Menuaction "S_ign" "unit vector" {
+ action x = map_unary sign x;
+ }
+
+ Negate_item = class
+ Menuaction "_Negate" "multiply by -1" {
+ action x = map_unary unary_minus x;
+ }
+}
+
+Math_trig_item = class
+ Menupullright "_Trigonometry" "trigonometry operations (all in degrees)" {
+ Sin_item = class
+ Menuaction "_Sine" "calculate sine x" {
+ action x = map_unary sin x;
+ }
+
+ Cos_item = class
+ Menuaction "_Cosine" "calculate cosine x" {
+ action x = map_unary cos x;
+ }
+
+ Tan_item = class
+ Menuaction "_Tangent" "calculate tangent x" {
+ action x = map_unary tan x;
+ }
+
+ sep1 = Menuseparator;
+
+ Asin_item = class
+ Menuaction "Arc S_ine" "calculate arc sine x" {
+ action x = map_unary asin x;
+ }
+
+ Acos_item = class
+ Menuaction "Arc C_osine" "calculate arc cosine x" {
+ action x = map_unary acos x;
+ }
+
+ Atan_item = class
+ Menuaction "Arc T_angent" "calculate arc tangent x" {
+ action x = map_unary atan x;
+ }
+
+ sep2 = Menuseparator;
+
+ Rad_item = class
+ Menuaction "_Degrees to Radians" "convert degrees to radians" {
+ action x = map_unary rad x;
+ }
+
+ Deg_item = class
+ Menuaction "_Radians to Degrees" "convert radians to degrees" {
+ action x = map_unary deg x;
+ }
+
+ sep3 = Menuseparator;
+
+ Angle_range_item = class
+ Menuaction "Angle i_n Range"
+ "is angle within t degrees of r, mod 360" {
+ action t r angle
+ = clock (max - angle) < 2*r
+ {
+ max = clock (t + r);
+
+ clock a
+ = a + 360, a < 0;
+ = a - 360, a >= 360;
+ = a;
+ }
+ }
+}
+
+Math_log_item = class
+ Menupullright "_Log" "logarithms and anti-logs" {
+ Exponential_item = class
+ Menuaction "_Exponential" "calculate e ** x" {
+ action x = map_unary (power e) x;
+ }
+
+ Log_natural_item = class
+ Menuaction "Natural _Log" "log base e of x" {
+ action x = map_unary log x;
+ }
+
+ sep1 = Menuseparator;
+
+ Exponential10_item = class
+ Menuaction "E_xponential base 10" "calculate 10 ** x" {
+ action x = map_unary (power 10) x;
+ }
+
+ Log10_item = class
+ Menuaction "L_og Base 10" "log base 10 of x" {
+ action x = map_unary log10 x;
+ }
+
+ sep2 = Menuseparator;
+
+ Raise_to_power_item = class
+ Menuaction "_Raise to Power" "calculate x ** y" {
+ action x y = map_binary power x y;
+ }
+}
+
+Math_complex_item = class
+ Menupullright "_Complex" "operations on complex numbers and images" {
+ Complex_extract = class
+ Menupullright "_Extract" "extract fields from complex" {
+ Real_item = class
+ Menuaction "_Real"
+ "extract real part of complex" {
+ action in = map_unary re in;
+ }
+
+ Imaginary_item = class
+ Menuaction "_Imaginary"
+ "extract imaginary part of complex" {
+ action in = map_unary im in;
+ }
+ }
+
+ Complex_build_item = class
+ Menuaction "_Build" "join a and b to make a complex" {
+ action a b = map_binary comma a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Polar_item = class
+ Menuaction "_Polar"
+ "convert real and imag to amplitude and phase" {
+ action a = map_unary polar a;
+ }
+
+ Rectangular_item = class
+ Menuaction "_Rectagular"
+ ("convert (amplitude, phase) image to rectangular " ++
+ "coordinates") {
+ action x = map_unary rectangular x;
+ }
+
+ sep2 = Menuseparator;
+
+ Conjugate_item = class
+ Menuaction "_Conjugate" "invert imaginary part" {
+ action x = map_unary conj x;
+ }
+}
+
+Math_boolean_item = class
+ Menupullright "_Boolean" "bitwise boolean operations for integer objects" {
+ And_item = class
+ Menuaction "_AND" "bitwise AND of a and b" {
+ action a b = map_binary bitwise_and a b;
+ }
+
+ Or_item = class
+ Menuaction "_OR" "bitwise OR of a and b" {
+ action a b = map_binary bitwise_or a b;
+ }
+
+ Eor_item = class
+ Menuaction "_XOR" "bitwise exclusive or of a and b" {
+ action a b = map_binary eor a b;
+ }
+
+ Not_item = class
+ Menuaction "_NOT" "invert a" {
+ action a = map_unary not a;
+ }
+
+ sep1 = Menuseparator;
+
+ Right_shift_item = class
+ Menuaction "Shift _Right" "shift a right by b bits" {
+ action a b = map_binary right_shift a b;
+ }
+
+ Left_shift_item = class
+ Menuaction "Shift _Left" "shift a left by b bits" {
+ action a b = map_binary left_shift a b;
+ }
+
+ sep2 = Menuseparator;
+
+ If_then_else_item = class
+ Menuaction "_If Then Else"
+ "b where a is non-zero, c elsewhere" {
+ action a b c
+ = map_trinary ite a b c
+ {
+ // can't use if_then_else, we need a true trinary
+ ite a b c = if a then b else c;
+ }
+ }
+
+ Bandand_item = Image_band_item.Bandand_item;
+
+ Bandor_item = Image_band_item.Bandor_item;
+}
+
+Math_relational_item = class
+ Menupullright "R_elational" "comparison operations" {
+ Equal_item = class
+ Menuaction "_Equal to" "test a equal to b" {
+ action a b = map_binary equal a b;
+ }
+
+ Not_equal_item = class
+ Menuaction "_Not Equal to" "test a not equal to b" {
+ action a b = map_binary not_equal a b;
+ }
+
+ sep1 = Menuseparator;
+
+ More_item = class
+ Menuaction "_More Than" "test a strictly greater than b" {
+ action a b = map_binary more a b;
+ }
+
+ Less_item = class
+ Menuaction "_Less Than" "test a strictly less than b" {
+ action a b = map_binary less a b;
+ }
+
+ sep2 = Menuseparator;
+
+ More_equal_item = class
+ Menuaction "M_ore Than or Equal to"
+ "test a greater than or equal to b" {
+ action a b = map_binary more_equal a b;
+ }
+
+ Less_equal_item = class
+ Menuaction "L_ess Than or Equal to"
+ "test a less than or equal to b" {
+ action a b = map_binary less_equal a b;
+ }
+}
+
+Math_list_item = class
+ Menupullright "L_ist" "operations on lists" {
+ Head_item = class
+ Menuaction "_Head" "first element in list" {
+ action x = map_unary hd x;
+ }
+
+ Tail_item = class
+ Menuaction "_Tail" "list without the first element" {
+ action x = map_unary tl x;
+ }
+
+ Last_item = class
+ Menuaction "_Last" "last element in list" {
+ action x = map_unary last x;
+ }
+
+ Init_item = class
+ Menuaction "_Init" "list without the last element" {
+ action x = map_unary init x;
+ }
+
+ sep1 = Menuseparator;
+
+ Reverse_item = class
+ Menuaction "_Reverse" "reverse order of elements in list" {
+ action x = map_unary reverse x;
+ }
+
+ Sort_item = class
+ Menuaction "_Sort" "sort list into ascending order" {
+ action x = map_unary sort x;
+ }
+
+ Make_set_item = class
+ Menuaction "_Make Set" "remove duplicates from list" {
+ action x = map_unary mkset equal x;
+ }
+
+ Transpose_list_item = class
+ Menuaction "Tr_anspose"
+ "exchange rows and columns in a list of lists" {
+ action x = map_unary transpose x;
+ }
+
+ Concat_item = class
+ Menuaction "_Concat"
+ "flatten a list of lists into a single list" {
+ action l = map_unary concat l;
+ }
+
+ sep2 = Menuseparator;
+
+ Length_item = class
+ Menuaction "L_ength" "find the length of list" {
+ action x = map_unary len x;
+ }
+
+ Subscript_item = class
+ Menuaction "S_ubscript"
+ "return element n from list (index from zero)" {
+ action n x = map_binary subscript n x;
+ }
+
+ Take_item = class
+ Menuaction "_Take" "take the first n elements of list x" {
+ action n x = map_binary take n x;
+ }
+
+ Drop_item = class
+ Menuaction "_Drop" "drop the first n elements of list x" {
+ action n x = map_binary drop n x;
+ }
+
+ sep3 = Menuseparator;
+
+ Join_item = class
+ Menuaction "_Join" "join two lists end to end" {
+ action a b = map_binary join a b;
+ }
+
+ Difference_item = class
+ Menuaction "_Difference" "difference of two lists" {
+ action a b = map_binary difference a b;
+ }
+
+ Cons_item = class
+ Menuaction "C_ons" "put element a on the front of list x" {
+ action a x = map_binary cons a x;
+ }
+
+ Zip_item = class
+ Menuaction "_Zip" "join two lists, pairwise" {
+ action a b = map_binary zip2 a b;
+ }
+}
+
+Math_round_item = class
+ Menupullright "_Round" "various rounding operations" {
+ /* smallest integral value not less than x
+ */
+ Ceil_item = class
+ Menuaction "_Ceil" "smallest integral value not less than x" {
+ action x = map_unary ceil x;
+ }
+
+ Floor_item = class
+ Menuaction "_Floor"
+ "largest integral value not greater than x" {
+ action x = map_unary floor x;
+ }
+
+ Rint_item = class
+ Menuaction "_Round to Nearest" "round to nearest integer" {
+ action x = map_unary rint x;
+ }
+}
+
+Math_fourier_item = class
+ Menupullright "_Fourier" "Fourier transform" {
+ Forward_item = class
+ Menuaction "_Forward" "fourier transform of image" {
+ action a = map_unary (rotquad @ fwfft) a;
+ }
+
+ Reverse_item = class
+ Menuaction "_Reverse" "inverse fourier transform of image" {
+ action a = map_unary (invfft @ rotquad) a;
+ }
+
+ Rotate_quadrants_item = class
+ Menuaction "Rotate _Quadrants" "rotate quadrants" {
+ action a = map_unary rotquad a;
+ }
+}
+
+Math_stats_item = class
+ Menupullright "_Statistics" "measure various statistics of objects" {
+ Value_item = class
+ Menuaction "_Value" "value of point in object" {
+ action a = class _result {
+ _vislevel = 3;
+
+ position = Expression "Coordinate" (0, 0);
+
+ _result = map_binary point position.expr a;
+ }
+ }
+
+ Mean_item = class
+ Menuaction "_Mean" "arithmetic mean value" {
+ action a = map_unary mean a;
+ }
+
+ Gmean_item = class
+ Menuaction "_Geometric Mean" "geometric mean value" {
+ action a = map_unary meang a;
+ }
+
+ Zmean_item = class
+ Menuaction "_Zero-excluding Mean" "mean value of non-zero elements" {
+ action a = map_unary meanze a;
+ }
+
+ Deviation_item = class
+ Menuaction "_Standard Deviation" "standard deviation of object" {
+ action a = map_unary deviation a;
+ }
+
+ Zdeviation_item = class
+ Menuaction "Z_ero-excluding Standard Deviation"
+ "standard deviation of non-zero elements" {
+ action a = map_unary deviationze a;
+ }
+
+ Skew_item = class
+ Menuaction "S_kew" "skew of image or list or vector" {
+ action a = map_unary skew a;
+ }
+
+ Kurtosis_item = class
+ Menuaction "Kurtosis" "kurtosis of image or list or vector" {
+ action a = map_unary kurtosis a;
+ }
+
+ Stats_item = class
+ Menuaction "Ma_ny Stats" "calculate many stats in a single pass" {
+ action a = map_unary stats a;
+ }
+
+ sep1 = Menuseparator;
+
+ Max_item = class
+ Menuaction "M_aximum" "maximum of object" {
+ action a = map_unary max a;
+ }
+
+ Min_item = class
+ Menuaction "M_inimum" "minimum of object" {
+ action a = map_unary min a;
+ }
+
+ Maxpos_item = class
+ Menuaction "_Position of Maximum" "position of maximum in object" {
+ action a = map_unary maxpos a;
+ }
+
+ Minpos_item = class
+ Menuaction "P_osition of Minimum" "position of minimum in object" {
+ action a = map_unary minpos a;
+ }
+
+ Gravity_item = class
+ Menuaction "Centre of _Gravity"
+ "position of centre of gravity of histogram" {
+ action a = map_unary gravity a;
+ }
+
+ sep2 = Menuseparator;
+
+ Count_set_item = class
+ Menuaction "_Non-zeros" "number of non-zero elements in object" {
+ action a
+ = map_unary cset a
+ {
+ cset i = (mean (i != 0) * i.width * i.height) / 255;
+ }
+ }
+
+ Count_clear_item = class
+ Menuaction "_Zeros" "number of zero elements in object" {
+ action a
+ = map_unary cclear a
+ {
+ cclear i = (mean (i == 0) * i.width * i.height) / 255;
+ }
+ }
+
+ Count_edges_item = class
+ Menuaction "_Edges"
+ "count average edges across or down image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ edge = Option "Count" [
+ "Horizontal lines",
+ "Vertical lines"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process image = Number (edge.labels?edge)
+ (im_cntlines image.value edge.value);
+ }
+ }
+ }
+
+ sep3 = Menuseparator;
+
+ Linear_regression_item = class
+ Menuaction "_Linear Regression" "fit a line to a set of points" {
+ action xes yes = linreg xes yes;
+ }
+
+ Weighted_linear_regression_item = class
+ Menuaction "_Weighted Linear Regression"
+ "fit a line to a set of points and deviations" {
+ action xes yes devs = linregw xes yes devs;
+ }
+
+ Cluster_item = class
+ Menuaction "_Cluster" "cluster a list of numbers" {
+ action l = class {
+ _vislevel = 3;
+
+ thresh = Expression "Threshold" 10;
+
+ [_r, _w] = cluster thresh.expr l;
+
+ result = _r;
+ weights = _w;
+ }
+ }
+}
+
+Math_base_item = class
+ Menupullright "Bas_e" "convert number bases" {
+ Hexadecimal_item = class
+ Menuaction "_Hexadecimal" "convert to hexadecimal (base 16)" {
+ action a = map_unary (print_base 16) a;
+ }
+
+ Binary_item = class
+ Menuaction "_Binary" "convert to binary (base 2)" {
+ action a = map_unary (print_base 2) a;
+ }
+
+ Octal_item = class
+ Menuaction "_Octal" "convert to octal (base 8)" {
+ action a = map_unary (print_base 8) a;
+ }
+}
diff --git a/share/nip2/compat/8.6/Matrix.def b/share/nip2/compat/8.6/Matrix.def
new file mode 100644
index 00000000..944e8d77
--- /dev/null
+++ b/share/nip2/compat/8.6/Matrix.def
@@ -0,0 +1,537 @@
+
+Matrix_build_item = class
+ Menupullright "_New" "make a new matrix of some sort" {
+
+ Plain_item = class
+ Menuaction "_Plain" "make a new plain matrix widget" {
+ action = Matrix (identity_matrix 3);
+ }
+
+ Convolution_item = class
+ Menuaction "_Convolution" "make a new convolution matrix widget" {
+ action = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
+ }
+
+ Recombination_item = class
+ Menuaction "_Recombination"
+ "make a new recombination matrix widget" {
+ action = Matrix_rec (identity_matrix 3);
+ }
+
+ Morphology_item = class
+ Menuaction "_Morphology" "make a new morphology matrix widget" {
+ action = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
+ }
+
+ sep1 = Menuseparator;
+
+ Matrix_identity_item = class
+ Menuaction "_Identity" "make an identity matrix" {
+ action = identity (identity_matrix 5);
+
+ identity v = class
+ _result {
+ _vislevel = 3;
+
+ s = Expression "Size" (len v);
+
+ _result
+ = Matrix (identity_matrix (to_real s)), to_real s != len v;
+ = Matrix v;
+
+ Matrix_vips value scale offset filename display = identity value;
+ }
+ }
+
+ Matrix_series_item = class
+ Menuaction "_Series" "make a series" {
+ action = series (mkseries 0 1 5);
+
+ mkseries s t e
+ = transpose [[to_real s, to_real s + to_real t .. to_real e]];
+
+ series v = class
+ _result {
+ _vislevel = 3;
+
+ _s = v?0?0;
+ _t = v?1?0 - v?0?0;
+ _e = (last v)?0;
+
+ s = Expression "Start value" _s;
+ t = Expression "Step by" _t;
+ e = Expression "End value" _e;
+
+ _result
+ = Matrix (mkseries s t e),
+ to_real s != _s || to_real t != _t || to_real e != _e
+ = Matrix v;
+
+ Matrix_vips value scale offset filename display = series value;
+ }
+ }
+
+ Matrix_square_item = class
+ Menuaction "_Square" "make a square matrix" {
+ action = square (mksquare 5);
+
+ mksquare s = replicate s (take s [1, 1 ..]);
+
+ square v = class
+ _result {
+ _vislevel = 3;
+
+ s = Expression "Size" (len v);
+
+ _result
+ = Matrix_con (sum v) 0 v, len v == to_real s
+ = Matrix_con (sum m) 0 m
+ {
+ m = mksquare (to_real s);
+ }
+
+ Matrix_vips value scale offset filename display = square value;
+ }
+ }
+
+ Matrix_circular_item = class
+ Menuaction "_Circular" "make a circular matrix" {
+ action = circle (mkcircle 3);
+
+ mkcircle r
+ = map2 (map2 pyth) xes yes
+ {
+ line = [-r .. r];
+ xes = replicate (2 * r + 1) line;
+ yes = transpose xes;
+ pyth a b
+ = 1, (a**2 + b**2) ** 0.5 <= r
+ = 0;
+ }
+
+ circle v = class
+ _result {
+ _vislevel = 3;
+
+ r = Expression "Radius" ((len v - 1) / 2);
+
+ _result
+ = Matrix_con (sum v) 0 v, len v == r.expr * 2 + 1
+ = Matrix_con (sum m) 0 m
+ {
+ m = mkcircle (to_real r);
+ }
+
+ Matrix_vips value scale offset filename display = circle value;
+ }
+ }
+
+ Matrix_gaussian_item = class
+ Menuaction "_Gaussian" "make a gaussian matrix" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ s = Scale "Sigma" 0.001 10 1;
+ ma = Scale "Minimum amplitude" 0 1 0.2;
+ integer = Toggle "Integer" false;
+
+ _result
+ = fn s.value ma.value
+ {
+ fn
+ = im_gauss_imask, integer
+ = im_gauss_dmask;
+ }
+ }
+ }
+
+ Matrix_laplacian_item = class
+ Menuaction "_Laplacian" "make the Laplacian of a Gaussian matrix" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ s = Scale "Sigma" 0.001 10 1.5;
+ ma = Scale "Minimum amplitude" 0 1 0.1;
+ integer = Toggle "Integer" false;
+
+ _result
+ = fn s.value ma.value
+ {
+ fn
+ = im_log_imask, integer
+ = im_log_dmask;
+ }
+ }
+ }
+
+}
+
+Matrix_to_matrix_item = class
+ Menuaction "Con_vert to Matrix" "convert anything to a matrix" {
+ action x = to_matrix x;
+}
+
+#separator
+
+Matrix_extract_item = class
+ Menupullright "_Extract" "extract rows or columns from a matrix" {
+ Rows_item = class
+ Menuaction "_Rows" "extract rows" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from row" 0;
+ number = Expression "Extract this many rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = extract_area 0 first (get_width x) number x;
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "extract columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Extract from column" 0;
+ number = Expression "Extract this many columns" 1;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = extract_area first 0 number (get_height x) x;
+ }
+ }
+ }
+
+ Area_item = class
+ Menuaction "_Area" "extract area" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ left = Expression "First column" 0;
+ top = Expression "First row" 0;
+ width = Expression "Number of columns" 1;
+ height = Expression "Number of rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = extract_area left top width height x;
+ }
+ }
+ }
+
+ Diagonal_item = class
+ Menuaction "_Diagonal" "extract diagonal" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ which = Option "Extract" [
+ "Leading Diagonal",
+ "Trailing Diagonal"
+ ] 0;
+
+ _result
+ = map_unary process x
+ {
+ process mat
+ = mat.Matrix_base (map2 extr [0..] mat.value),
+ which == 0
+ = mat.Matrix_base (map2 extr
+ [mat.width - 1, mat.width - 2 .. 0] mat.value);
+ extr n l = [l?n];
+ }
+ }
+ }
+}
+
+Matrix_insert_item = class
+ Menupullright "_Insert" "insert rows or columns into a matrix" {
+ // make a new 8-bit uchar image of wxh with pixels set to p
+ // use to generate new cells
+ newim w h p
+ = image_new w h 1
+ Image_format.UCHAR Image_coding.NOCODING Image_type.B_W p 0 0;
+
+ Rows_item = class
+ Menuaction "_Rows" "insert rows" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at row" 0;
+ number = Expression "Insert this many rows" 1;
+ item = Expression "Set new cells to" 0;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_tb (concat [top, new, bottom])
+ {
+ top
+ = [extract_area 0 0 w f x], f > 0
+ = [];
+ new = [(if is_Matrix x then to_matrix else id)
+ (newim w number item.expr)];
+ bottom
+ = [extract_area 0 f w (h - f) x], f < h
+ = [];
+
+ f = to_real first;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "insert columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Insert at column" 0;
+ number = Expression "Insert this many columns" 1;
+ item = Expression "Set new cells to" 0;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_lr (concat [left, new, right])
+ {
+ left
+ = [extract_area 0 0 f h x], f > 0
+ = [];
+ new = [(if is_Matrix x then to_matrix else id)
+ (newim number h item.expr)];
+ right
+ = [extract_area f 0 (w - f) h x], f < w
+ = [];
+
+ f = to_real first;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+}
+
+Matrix_delete_item = class
+ Menupullright "_Delete" "delete rows or columns from a matrix" {
+ // remove number of items, starting at first
+ delete first number l = take (to_real first) l ++
+ drop (to_real first + to_real number) l;
+
+ Rows_item = class
+ Menuaction "_Rows" "delete rows" {
+
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from row" 0;
+ number = Expression "Delete this many rows" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_tb (concat [top, bottom])
+ {
+ top
+ = [extract_area 0 0 w f x], f > 0
+ = [];
+ bottom
+ = [extract_area 0 b w (h - b) x], b < h
+ = [];
+
+ f = to_real first;
+ n = to_real number;
+ b = f + n;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+
+ Columns_item = class
+ Menuaction "_Columns" "delete columns" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ first = Expression "Delete from column" 0;
+ number = Expression "Delete this many columns" 1;
+
+ _result
+ = map_unary process x
+ {
+ process x
+ = foldl1 join_lr (concat [left, right])
+ {
+ left
+ = [extract_area 0 0 f h x], f > 0
+ = [];
+ right
+ = [extract_area r 0 (w - r) h x], r < w
+ = [];
+
+ f = to_real first;
+ n = to_real number;
+ r = f + n;
+ w = get_width x;
+ h = get_height x;
+ }
+ }
+ }
+ }
+}
+
+Matrix_join = class
+ Menupullright "_Join" "join two matricies" {
+ Left_right_item = class
+ Menuaction "_Left to Right" "join two matricies left-right" {
+ action a b = map_binary join_lr a b;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom" "joiin two matricies top-bottom" {
+ action a b = map_binary join_tb a b;
+ }
+}
+
+Matrix_rotate_item = class
+ Menupullright "_Rotate" "clockwise rotation by fixed angles" {
+
+ rot90 = Image_transform_item.Rotate_item.Fixed_item.Rot90_item;
+
+ rot180 = Image_transform_item.Rotate_item.Fixed_item.Rot180_item;
+
+ rot270 = Image_transform_item.Rotate_item.Fixed_item.Rot270_item;
+
+ Matrix_rot45_item = class
+ Menuaction "_45 Degrees"
+ "45 degree rotate (square, odd-length-sides only)" {
+ action x = map_unary rot45 x;
+ }
+}
+
+Matrix_flip_item = Image_transform_item.Flip_item;
+
+Matrix_sort_item = class
+ Menuaction "_Sort" "sort by column or row" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ o = Option (_ "Orientation") [
+ _ "Sort by column",
+ _ "Sort by row"
+ ] 0;
+ i = Expression (_ "Sort on index") 0;
+ d = Option (_ "Direction") [
+ _ "Ascending",
+ _ "Descending"
+ ] 0;
+
+ _result
+ = map_unary sort x
+ {
+ idx = to_real i;
+ pred a b
+ = a?idx <= b?idx, d == 0
+ = a?idx >= b?idx;
+ sort x
+ = (x.Matrix_base @ sortc pred) x.value,
+ o == 0
+ = (x.Matrix_base @ transpose @ sortc pred @ transpose) x.value;
+ }
+ }
+}
+
+#separator
+
+Matrix_invert_item = class
+ Menuaction "In_vert" "calculate inverse matrix" {
+ action x = map_unary (converse power (-1)) x;
+}
+
+Matrix_transpose_item = class
+ Menuaction "_Transpose" "swap rows and columns" {
+ action x = map_unary transpose x;
+}
+
+#separator
+
+Matrix_plot_scatter_item = class
+ Menuaction "_Plot Scatter"
+ "plot a scatter graph of a matrix of [x,y1,y2,..] coordinates" {
+ action x = class
+ _result {
+ _check_args = [
+ [x, "x", check_Matrix]
+ ];
+ _vislevel = 3;
+
+ auto = Toggle "Auto Range" true;
+ xmin = Expression "X range minimum" 0;
+ xmax = Expression "X range maximum" 1;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+
+ _result
+ = Plot options ((x2b @ get_image @ to_image) x)
+ {
+ options
+ = [$style => Plot_style.POINT, $format => Plot_format.XYYY] ++
+ range;
+ range
+ = [], auto
+ = [$xmin => xmin.expr, $xmax => xmax.expr,
+ $ymin => ymin.expr, $ymax => ymax.expr];
+
+ // matrix to image makes a 1-band mxn image
+ // we need to put columns into bands
+ x2b im
+ = bandjoin (map extract_col [0 .. w - 1])
+ {
+ w = get_width im;
+ h = get_height im;
+ b = get_bands im;
+ extract_col x = extract_area x 0 1 h im;
+ }
+ }
+ }
+}
+
+Matrix_plot_item = Hist_plot_item;
+
+Matrix_buildlut_item = class
+ Menuaction "_Build LUT From Scatter"
+ "make a lookup table from a matrix of [x,y1,y2..] coordinates" {
+ action x = class
+ _result {
+ _check_args = [
+ [x, "x", check_Matrix]
+ ];
+ _result = buildlut x;
+ }
+}
diff --git a/share/nip2/compat/8.6/Object.def b/share/nip2/compat/8.6/Object.def
new file mode 100644
index 00000000..b1585186
--- /dev/null
+++ b/share/nip2/compat/8.6/Object.def
@@ -0,0 +1,49 @@
+Object_duplicate_item = class
+ Menuaction "_Duplicate" "take a copy of an object" {
+ action x = map_unary copy x;
+}
+
+#separator
+
+Object_list_to_group_item = class
+ Menuaction "_List to Group" "turn a list of objects into a group" {
+ action x = to_group x;
+}
+
+Object_group_to_list_item = class
+ Menuaction "_Group to List" "turn a group into a list of objects" {
+ action x = to_list x;
+}
+
+#separator
+
+Object_break_item = class
+ Menuaction "_Break Up Object"
+ "break an object into a list of components" {
+ action x
+ = map_unary break x
+ {
+ break x
+ = bandsplit x, is_Image x
+ = map Vector x.value, is_Matrix x
+ = x.value, is_Vector x || is_Real x
+ = error "Breakup: not Image/Matrix/Vector/Real";
+ }
+}
+
+Object_assemble_item = class
+ Menuaction "_Assemble Objects"
+ "assemble a list of objects into a single object" {
+ action x
+ = map_unary ass x
+ {
+ ass x
+ = [], x == []
+ = Vector x, is_real_list x
+ = Matrix x, is_matrix x
+ = bandjoin x, is_listof is_Image x
+ = Vector (map get_value x), is_listof is_Real x
+ = Matrix (map get_value x), is_listof is_Vector x
+ = error "Assemble: not list of Image/Vector/Real/image/real";
+ }
+}
diff --git a/share/nip2/compat/8.6/Preferences.ws b/share/nip2/compat/8.6/Preferences.ws
new file mode 100644
index 00000000..522c8714
--- /dev/null
+++ b/share/nip2/compat/8.6/Preferences.ws
@@ -0,0 +1,919 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/share/nip2/compat/8.6/Tasks.def b/share/nip2/compat/8.6/Tasks.def
new file mode 100644
index 00000000..d0c6d711
--- /dev/null
+++ b/share/nip2/compat/8.6/Tasks.def
@@ -0,0 +1,952 @@
+Tasks_capture_item = class
+ Menupullright "_Capture"
+ "useful stuff for capturing and preprocessing images" {
+
+ Csv_import_item = class
+ Menuaction "_CSV Import" "read a file of comma-separated values" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ path = Pathname "File to load" "empty";
+ start_line = Expression "Start at line" 1;
+ rows = Expression "Lines to read (-1 for whole file)" (-1);
+ whitespace = String "Whitespace characters" " \"";
+ separator = String "Separator characters" ",;\t";
+
+ _result
+ = Image blank, path.value == "empty"
+ = Image (im_csv2vips filename)
+ {
+ filename = search (expand path.value) ++ ":" ++
+ "skip:" ++ print (start_line.expr - 1) ++ "," ++
+ "whi:" ++ escape whitespace.value ++ "," ++
+ "sep:" ++ escape separator.value ++ "," ++
+ "line:" ++ print rows.expr;
+
+ // prefix any ',' with a '\' in the separators line
+ escape x
+ = foldr prefix [] x
+ {
+ prefix x l
+ = '\\' : x : l, x == ','
+ = x : l;
+ }
+
+ blank = image_new 1 1 1
+ Image_format.DOUBLE Image_coding.NOCODING Image_type.B_W
+ 0 0 0;
+ }
+ }
+ }
+
+ Raw_import_item = class
+ Menuaction "_Raw Import" "read a file of binary values" {
+ action = class
+ _result {
+ _vislevel = 3;
+
+ path = Pathname "File to load" "empty";
+ across = Expression "Pixels across" 100;
+ down = Expression "Pixels down" 100;
+ bytes = Expression "Bytes per pixel" 3;
+ skip = Expression "Skip over initial bytes" 0;
+
+ _result
+ = Image blank, path.value == "empty"
+ = Image (im_binfile path.value
+ across.expr down.expr bytes.expr skip.expr)
+ {
+ blank = image_new 1 1 1
+ Image_format.DOUBLE Image_coding.NOCODING Image_type.B_W
+ 0 0 0;
+ }
+ }
+ }
+
+ // interpret Analyze header for layout and calibration
+ Analyze7_header_item = class
+ Menuaction "_Interpret Analyze 7 Header"
+ "examine the Analyze header and set layout and value" {
+ action x
+ = x'''
+ {
+ // read bits of header
+ dim n = get_header ("dsr-image_dimension.dim[" ++ print n ++ "]");
+ dim0 = dim 0 x;
+ dim1 = dim 1 x;
+ dim2 = dim 2 x;
+ dim3 = dim 3 x;
+ dim4 = dim 4 x;
+ dim5 = dim 5 x;
+ dim6 = dim 6 x;
+ dim7 = dim 7 x;
+ glmax = get_header "dsr-image_dimension.glmax" x;
+ cal_max = get_header "dsr-image_dimension.cal_max" x;
+
+ // oops, now a nop
+ x' = x;
+
+ // lay out higher dimensions width-ways
+ x''
+ = grid dim2 dim3 1 x', dim0 == 3
+ = grid dim2 dim3 dim4 x', dim0 == 4
+ = grid (dim2 * dim4) dim5 1 (grid dim2 dim3 dim4) x', dim0 == 5
+ = grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4) x', dim0 == 6
+ = grid (dim2 * dim4 * dim6) dim7 1
+ (grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4)) x',
+ dim0 == 7
+ = error (_ "unsupported dimension " ++ dim0);
+
+ // multiply by scale factor to get kBeq
+ x''' = x'' * (cal_max / glmax);
+ }
+ }
+
+ Video_item = class
+ Menuaction "Capture _Video Frame" "capture a frame of still video" {
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+
+ action = class
+ _result {
+ _vislevel = 3;
+
+ device = prefs.VIDEO_DEVICE;
+ channel = Option "Input channel" [
+ "TV",
+ "Composite 1",
+ "Composite 2",
+ "Composite 3"
+ ] prefs.VIDEO_CHANNEL;
+ b = Scale "Brightness" 0 32767 prefs.VIDEO_BRIGHTNESS;
+ col = Scale "Colour" 0 32767 prefs.VIDEO_COLOUR;
+ con = Scale "Contrast" 0 32767 prefs.VIDEO_CONTRAST;
+ hue = Scale "Hue" 0 32767 prefs.VIDEO_HUE;
+ frames = Scale "Frames to average" 0 100 prefs.VIDEO_FRAMES;
+ mono = Toggle "Monochrome grab" prefs.VIDEO_MONO;
+ crop = Toggle "Crop image" prefs.VIDEO_CROP;
+
+ // grab, but hide it ... if we let the crop edit
+ _raw_grab = Image (im_video_v4l1 device channel.value
+ b.value col.value con.value
+ hue.value frames.value);
+
+ edit_crop
+ = Region _raw_grab left top width height
+ {
+ left = prefs.VIDEO_CROP_LEFT;
+ top = prefs.VIDEO_CROP_TOP;
+ width = min_pair
+ prefs.VIDEO_CROP_WIDTH (_raw_grab.width + left);
+ height = min_pair
+ prefs.VIDEO_CROP_HEIGHT (_raw_grab.height + top);
+ }
+
+ aspect_ratio = Expression "Stretch vertically by"
+ prefs.VIDEO_ASPECT;
+
+ _result
+ = frame'
+ {
+ frame
+ = edit_crop, crop
+ = _raw_grab;
+
+ frame'
+ = colour_transform_to Image_type.B_W frame, mono
+ = frame;
+ }
+ }
+ }
+
+ Smooth_image_item = class
+ Menuaction "_Smooth" "remove small features from image" {
+ action in = class
+ _result {
+ _vislevel = 3;
+
+ feature = Scale "Minimum feature size" 1 50 20;
+
+ _result = map_unary (smooth feature.value) in;
+ }
+ }
+
+ Light_correct_item = class
+ Menuaction "_Flatfield" "use white image w to flatfield image i" {
+ action w i
+ = map_binary wc w i
+ {
+ wc w i
+ = clip2fmt i.format (w' * i)
+ {
+ fac = mean w / max w;
+ w' = fac * (max w / w);
+ }
+ }
+ }
+
+ Image_rank_item = Filter_rank_item.Image_rank_item;
+
+ Tilt_item = Filter_tilt_item;
+
+ sep1 = Menuseparator;
+
+ White_balance_item = class
+ Menuaction "_White Balance"
+ "use average of small image to set white of large image" {
+ action a b = class
+ _result {
+ _vislevel = 3;
+
+ white_hint = "Set image white to:";
+ white = Colour_picker "Lab" [100, 0, 0];
+
+ _result
+ = map_binary wb a b
+ {
+ wb a b
+ = colour_transform_to (get_type image) image_xyz'
+ {
+ area x = x.width * x.height;
+ larger x y = area x > area y;
+ [image, patch] = sortc larger [a, b];
+ to_xyz = colour_transform_to Image_type.XYZ;
+
+ // white balance in XYZ
+ patch_xyz = to_colour (to_xyz patch);
+ white_xyz = to_xyz white;
+
+ facs = (mean patch_xyz / mean white_xyz) *
+ (white_xyz / patch_xyz);
+
+ image_xyz = to_xyz image;
+ image_xyz' = image_xyz * facs;
+ }
+ }
+ }
+ }
+
+ Gamma_item = Image_levels_item.Gamma_item;
+
+ Tone_item = Image_levels_item.Tone_item;
+
+ sep2 = Menuseparator;
+
+ Crop_item = Image_crop_item;
+
+ Rotate_item = Image_transform_item.Rotate_item;
+
+ Flip_item = Image_transform_item.Flip_item;
+
+ Resize_item = Image_transform_item.Resize_item;
+
+ Rubber_item = Image_transform_item.Image_rubber_item;
+
+ sep3 = Menuseparator;
+
+ ICC_item = Colour_icc_item;
+
+ Temp_item = Colour_temperature_item;
+
+ Find_calib_item = class
+ Menuaction "Find _Colour Calibration"
+ "find an RGB -> XYZ transform from an image of a colour chart" {
+ action image = class
+ _result {
+ _check_args = [
+ [image, "image", check_Image]
+ ];
+ _vislevel = 3;
+
+ measure = Scale (_ "Measure area (%)") 1 100 50;
+
+ sample = measure_draw 6 4 (to_real measure) image;
+
+ // get macbeth data file to use
+ macbeth = Pathname "Pick a Macbeth data file"
+ "$VIPSHOME/share/$PACKAGE/data/macbeth_lab_d65.mat";
+
+ mode = Option "Input LUT" [
+ "Linearize from chart greyscale",
+ "Fit intercept from chart greyscale",
+ "Linear input, set brightness from chart",
+ "Linear input"
+ ] 0;
+
+ // get max of input image
+ _max_value = Image_format.maxval image.format;
+
+ // measure chart image
+ _camera = measure_sample 6 4 (to_real measure) image;
+
+ // load true values
+ _true_Lab = Matrix_file macbeth.value;
+ _true_XYZ = colour_transform
+ Image_type.LAB Image_type.XYZ _true_Lab;
+
+ // get Ys of greyscale
+ _true_grey_Y = map (extract 1) (drop 18 _true_XYZ.value);
+
+ // camera greyscale (all bands)
+ _camera_grey = drop 18 _camera.value;
+
+ // normalise both to 0-1 and combine
+ _camera_grey' = map (map (multiply (1 / _max_value))) _camera_grey;
+ _true_grey_Y' = map (multiply (1 / 100)) _true_grey_Y;
+ _comb
+ = Matrix (map2 cons _true_grey_Y' _camera_grey'), mode == 0
+ = Matrix [0: intercepts, replicate (_camera.width + 1) 1],
+ mode == 1
+ = Matrix [[0, 0], [1, 1]]
+ {
+ intercepts = [(linreg _true_grey_Y' cam).intercept ::
+ cam <- transpose _camera_grey'];
+ }
+
+ // make a linearising lut ... zero on left
+ _linear_lut = im_invertlut _comb (_max_value + 1);
+
+ // and display it
+ // plot from 0 explicitly so we see the effect of mode1 (intercept
+ // from greyscale)
+ linearising_lut = Plot [$ymin => 0] _linear_lut;
+
+ // map an image though the lineariser
+ linear x
+ = hist_map linearising_lut.value x, mode == 0 || mode == 1
+ = x;
+
+ // map the chart measurements though the lineariser
+ _camera' = (to_matrix @ linear @ to_image) _camera;
+
+ // solve for RGB -> XYZ
+ // normalise: the 2nd row is what makes Y, so divide by that to
+ // get Y in 0-1.
+ _pinv = (transpose _camera' * _camera') ** -1;
+ _full_M = transpose (_pinv * (transpose _camera' * _true_XYZ));
+ M = _full_M / scale;
+ scale = sum _full_M.value?1;
+
+ // now turn the camera to LAB and calculate dE76
+ _camera'' = (to_matrix @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ recomb M @
+ multiply scale @
+ to_image) _camera';
+
+ _dEs = map abs_vec (_camera'' - _true_Lab).value;
+ avg_dE76 = mean _dEs;
+ _max_dE = foldr max_pair 0 _dEs;
+ _worst = index (equal _max_dE) _dEs;
+ worst_patch
+ = name _worst ++ " (patch " ++
+ print (_worst + 1) ++ ", " ++
+ print _max_dE ++ " dE)"
+ {
+ name i
+ = macbeth_names?i, i >= 0 && i < len macbeth_names
+ = "Unknown";
+ }
+
+ // normalise brightness ... in linear mode, we optionally don't
+ // set the brightness from the Macbeth chart
+ norm x
+ = x * scale, mode != 3
+ = x;
+
+ // convert RGB camera to Lab
+ _result = (Image @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ norm @
+ recomb M @
+ cast_float @
+ linear) image.value;
+ }
+ }
+
+ Apply_calib_item = class
+ Menuaction "_Apply Colour Calibration"
+ "apply an RGB -> LAB transform to an image" {
+ action a b = class
+ (map_binary process a b) {
+ process a b
+ = result, is_instanceof calib_name calib && is_Image image
+ = error (_ "bad arguments to " ++ "Calibrate_image")
+ {
+ // the name of the calib object we need
+ calib_name = "Tasks_capture_item.Find_calib_item.action";
+
+ // get the Calibrate_chart arg first
+ [image, calib] = sortc (const (is_instanceof calib_name))
+ [a, b];
+
+ result = (Image @
+ colour_transform Image_type.XYZ Image_type.LAB @
+ calib.norm @
+ recomb calib.M @
+ cast_float @
+ calib.linear) image.value;
+ }
+ }
+ }
+
+ sep4 = Menuseparator;
+
+ Graph_hist_item = Hist_find_item;
+
+ Graph_bands_item = class
+ Menuaction "Plot _Bands" "show image bands as a graph" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ style = Option_enum "Style" Plot_style.names "Line";
+
+ auto = Toggle "Auto Range" true;
+ ymin = Expression "Y range minimum" 0;
+ ymax = Expression "Y range maximum" 1;
+
+ _result
+ = Plot options (to_image (bands (image x))).value
+ {
+ options
+ = [$style => style.value] ++
+ if auto then [] else
+ [$ymin => ymin.expr, $ymax => ymax.expr];
+
+ // try to make something image-like from it
+ image x
+ = extract_area x.left x.top 1 1 x.image, is_Mark x
+ = get_image x, has_image x
+ = get_image (to_image x);
+
+ // get as [[1],[2],[3]]
+ bands x
+ = transpose [map mean (bandsplit x)];
+ }
+ }
+ }
+}
+
+Tasks_mosaic_item = class
+ Menupullright "_Mosaic" "build image mosaics" {
+ /* Check and group a point list by image.
+ */
+ mosaic_sort_test l
+ = error "mosaic: not all points",
+ !is_listof is_Mark l
+ = error "mosaic: points not on two images",
+ !is_list_len 2 images
+ = error "mosaic: images do not match in format and coding",
+ !all_equal (map get_format l) || !all_equal (map get_coding l)
+ = error "mosaic: not same number of points on each image",
+ !foldr1 equal (map len l')
+ = l'
+ {
+ // test for all elements of a list equal
+ all_equal l = all (map (equal (hd l)) (tl l));
+
+ // all the different images
+ images = mkset pointer_equal (map get_image l);
+
+ // find all points defined on image
+ test_image image p = (get_image p) === image;
+ find l image = filter (test_image image) l;
+
+ // group point list by image
+ l' = map (find l) images;
+ }
+
+ /* Sort a point group to get right before left, and within each group to
+ * get above before below.
+ */
+ mosaic_sort_lr l
+ = l''
+ {
+ // sort to get upper point first
+ above a b = a.top < b.top;
+ l' = map (sortc above) l;
+
+ // sort to get right group before left group
+ right a b = a?0.left > b?0.left;
+ l'' = sortc right l';
+ }
+
+ /* Sort a point group to get top before bottom, and within each group to
+ * get left before right.
+ */
+ mosaic_sort_tb l
+ = l''
+ {
+ // sort to get upper point first
+ left a b = a.left < b.left;
+ l' = map (sortc left) l;
+
+ // sort to get right group before left group
+ below a b = a?0.top > b?0.top;
+ l'' = sortc below l';
+ }
+
+ /* Put 'em together! Group by image, sort vertically (or horizontally) with
+ * one of the above, transpose to get pairs matched up, and flatten again.
+ */
+ mosaic_sort fn = concat @ transpose @ fn @ mosaic_sort_test;
+
+ Mosaic_1point_item = class
+ Menupullright "_One Point" "join two images with a single tie point" {
+ check_ab_args a b = [
+ [a, "a", check_Mark],
+ [b, "b", check_Mark]
+ ];
+
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+ search_area = prefs.MOSAIC_WINDOW_SIZE;
+ object_size = prefs.MOSAIC_OBJECT_SIZE;
+ blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
+ refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
+
+ lr_mos _refine a b = class
+ Image _result {
+ _check_args = check_ab_args a b;
+
+ bw = blend_width_widget;
+ refine = _refine;
+
+ _result
+ = im_lrmosaic a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ (object_size / 2) (search_area / 2) 0 bw.value,
+ refine
+ = im_lrmerge a'.image.value b'.image.value
+ (b'.left - a'.left) (b'.top - a'.top) bw.value
+ {
+ [a', b'] = mosaic_sort mosaic_sort_lr [a, b];
+ }
+ }
+
+ tb_mos _refine a b = class
+ Image _result {
+ _check_args = check_ab_args a b;
+
+ bw = blend_width_widget;
+ refine = _refine;
+
+ _result
+ = im_tbmosaic a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ (object_size / 2) (search_area / 2) 0 bw.value,
+ refine
+ = im_tbmerge a'.image.value b'.image.value
+ (b'.left - a'.left) (b'.top - a'.top) bw.value
+ {
+ [a', b'] = mosaic_sort mosaic_sort_tb [a, b];
+ }
+ }
+
+ Left_right_item = class
+ Menuaction "_Left to Right"
+ "join two images left-right with a single tie point" {
+ action a b = lr_mos refine_widget a b;
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom"
+ "join two images top-bottom with a single tie point" {
+ action a b = tb_mos refine_widget a b;
+ }
+
+ sep1 = Menuseparator;
+
+ Left_right_manual_item = class
+ Menuaction "Manual L_eft to Right"
+ "join left-right, no auto-adjust of tie points" {
+ action a b = lr_mos false a b;
+ }
+
+ Top_bottom_manual_item = class
+ Menuaction "Manual T_op to Bottom"
+ "join top-bottom, no auto-adjust of tie points" {
+ action a b = tb_mos false a b;
+ }
+ }
+
+ Mosaic_2point_item = class
+ Menupullright "_Two Point" "join two images with two tie points" {
+ check_abcd_args a b c d = [
+ [a, "a", check_Mark],
+ [b, "b", check_Mark],
+ [c, "c", check_Mark],
+ [d, "d", check_Mark]
+ ];
+
+ // shortcut to prefs
+ prefs = Workspaces.Preferences;
+ search_area = prefs.MOSAIC_WINDOW_SIZE;
+ object_size = prefs.MOSAIC_OBJECT_SIZE;
+ blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
+ refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
+
+ Left_right_item = class
+ Menuaction "_Left to Right"
+ "join two images left-right with a pair of tie points" {
+ action a b c d = class
+ Image _result {
+ _check_args = check_abcd_args a b c d;
+
+ bw = blend_width_widget;
+ refine = refine_widget;
+
+ _result
+ = im_lrmosaic1 a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ (object_size / 2) (search_area / 2)
+ 0 bw.value,
+ refine
+ = im_lrmerge1 a'.image.value b'.image.value
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ bw.value
+ {
+ [a', b', c', d'] = mosaic_sort mosaic_sort_lr [a, b, c, d];
+ }
+ }
+ }
+
+ Top_bottom_item = class
+ Menuaction "_Top to Bottom"
+ "join two images top-bottom with a pair of tie points" {
+ action a b c d = class
+ Image _result {
+ _check_args = check_abcd_args a b c d;
+
+ bw = blend_width_widget;
+ refine = refine_widget;
+
+ _result
+ = im_tbmosaic1 a'.image.value b'.image.value 0
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ (object_size / 2) (search_area / 2)
+ 0 bw.value,
+ refine
+ = im_tbmerge1 a'.image.value b'.image.value
+ a'.left a'.top b'.left b'.top
+ c'.left c'.top d'.left d'.top
+ bw.value
+ {
+ [a', b', c', d'] = mosaic_sort mosaic_sort_tb [a, b, c, d];
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Balance_item = class
+ Menuaction "Mosaic _Balance"
+ "disassemble mosaic, scale brightness to match, reassemble" {
+ action x
+ = map_unary balance x
+ {
+ balance x
+ = oo_unary_function balance_op x, is_class x
+ = im_global_balancef x
+ Workspaces.Preferences.MOSAIC_BALANCE_GAMMA,
+ is_image x
+ = error (_ "bad arguments to " ++ "balance")
+ {
+ balance_op = Operator "balance" balance
+ Operator_type.COMPOUND_REWRAP false;
+ }
+ }
+ }
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Manual_balance_item = class
+ Menupullright "Manual B_alance" "balance tonality of user defined areas" {
+ prefs = Workspaces.Preferences;
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_find_item = class
+ Menuaction "_Find Values"
+ "calculates values required to scale and offset balance user defined areas in a given image"
+ /* Outputs a matrix of scale and offset values. Eg. Values required to balance the secondary
+ * structure in an X-ray image. Takes an X-ray image an 8-bit control mask and a list of
+ * 8-bit reference masks, where the masks are white on a black background.
+ */
+ {
+ action im_in m_control m_group = class
+ Matrix values{
+ _vislevel = 1;
+
+ _control_im = if m_control then im_in else 0;
+ _control_meanmax = so_meanmax _control_im;
+ _group_check = is_Group m_group;
+ _m_list = m_group.value, _group_check
+ = m_group;
+
+ process m_current mat_in = mat_out
+ {so_values = so_calculate _control_meanmax im_in m_current;
+ mat_out = join [so_values] mat_in;}
+
+ values = (foldr process [] _m_list);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_check_item = class
+ Menuaction "_Check Values"
+ "allows calculated set of scale and offset values to be checked and adjusted if required"
+ /* Outputs adjusted matrix of scale and offset values and scale and offset image maps.
+ * Eg. Check values required to balance the secondary structure in an X-ray image.
+ * Takes an X-ray image an 8-bit control mask and a list of 8-bit reference masks,
+ * where the masks are white on a black background.
+ */
+ {
+ action im_in m_matrix m_group = class
+ Image value
+ {
+ _vislevel = 3;
+
+ blur = Scale "Blur" 1 10 1;
+ _blur = (blur.value/2 + 0.5), blur.value > 1
+ = 1;
+
+ _group_check = is_Group m_group;
+ _m_list = m_group.value, _group_check
+ = m_group;
+
+ adjust = Matrix_rec mat_a
+ {
+ no_masks = len _m_list;
+ mat_a = replicate no_masks [0, 0];
+ }
+
+ // Apply the user defined adjustments to the inputted matrix of scale and offset values
+ _adjusted = map2 fn_adjust m_matrix.value adjust.value;
+ fn_adjust a b = [(a?0 + b?0), (a?1 + (a?1 * b?1))];
+
+ _scaled_ims = map (fn_so_apply im_in) _adjusted;
+ fn_so_apply im so = map_unary adj im
+ {adj im = im * (so?0) + (so?1);}
+ _im_pairs = zip2 _m_list _scaled_ims;
+
+ // Prepare black images as starting point. ////////////
+ _blank = image_new (_m_list?0).width (_m_list?0).height 1 6 Image_coding.NOCODING 1 0 0 0;
+ _pair_start = [(_blank + 1), _blank];
+
+ Build = Toggle "Build Scale and Offset Correction Images" false;
+
+ Output = class
+ {
+ _vislevel = 1;
+ scale_im = _build?0;
+ offset_im = _build?1;
+ so_values = Matrix _adjusted;
+
+ _build = [Image so_images?0, Image so_images?1], Build
+ = ["Scale image not built.", "Offset image not built."]
+ {
+ m_list' = transpose [_m_list];
+ m_all = map2 join m_list' _adjusted;
+ so_images = foldr process_2 _pair_start m_all;
+ }
+ }
+
+ value = (foldr process_1 im_in_b _im_pairs).value
+ {im_in_b = map_unary cast_float im_in;}
+
+ process_1 m_current im_start
+ = im_out
+ {
+ bl_mask = convsep (matrix_blur _blur) (get_image m_current?0);
+ blended_im = im_blend bl_mask (m_current?1).value im_start.value;
+ im_out = Image (clip2fmt im_start.format blended_im);
+ }
+
+ // Process for building scale and offset image.
+ process_2 current p_start
+ = p_out
+ {
+ im_s = if ((current?0) > 128) then current?1 else _blank;
+ im_o = if ((current?0) > 128) then current?2 else _blank;
+
+ im_s' = convsep (matrix_blur _blur) (im_s != 0);
+ im_o' = convsep (matrix_blur _blur) (im_o != 0);
+
+ im_s'' = im_blend im_s'.value im_s.value p_start?0;
+ im_o'' = im_blend im_o'.value im_o.value p_start?1;
+
+ p_out = [im_s'', im_o''];
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Balance_apply_item = class
+ Menuaction "_Apply Values"
+ "apply scale and offset corrections, defined as image maps, to a given image"
+ /* Outputs the balanced image. Eg. Balance the secondary structure in an X-ray image. Takes an
+ * X-ray image an 32-bit float scale image and a 32-bit offset image.
+ */
+ {
+ action im_in scale_im offset_im = class
+ Image value
+ {
+ _vislevel = 1;
+
+ xfactor = im_in.width/scale_im.width;
+ yfactor = im_in.height/scale_im.height;
+
+ _scale_im = resize Kernel_linear xfactor yfactor scale_im;
+ _offset_im = resize Kernel_linear xfactor yfactor offset_im;
+
+ value = get_image ( clip2fmt im_in.format ( ( im_in * _scale_im ) + _offset_im ) );
+ }
+ }
+}
+
+ Tilt_item = Filter_tilt_item;
+
+ sep2 = Menuseparator;
+
+ Rebuild_item = class
+ Menuaction "_Rebuild"
+ "disassemble mosaic, substitute image files and reassemble" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ old = String "In each filename, replace" "foo";
+ new = String "With" "bar";
+
+ _result
+ = map_unary remosaic x
+ {
+ remosaic image
+ = Image (im_remosaic image.value old.value new.value);
+ }
+ }
+ }
+
+ sep3 = Menuseparator;
+
+Clone_area_item = class
+ Menuaction "_Clone Area"
+ "replace dark or light section of im1 with pixels from im2"
+ {
+ action im1 im2 = class
+ _result {
+ _check_args = [
+ [im1, "im1", check_Image],
+ [im2, "im2", check_Image]
+ ];
+ _vislevel = 3; /* Region on first
+image placed in the top left hand corner,
+ * positioned and size relative to the height and width of im1.
+ */
+ r1 = Region_relative im1 0.05 0.05 0.05 0.05;
+ /* Mark on second image placed in the top left hand corner,
+ * positioned relative to the height and width of im2. Used to
+ * define _r2, the region from which the section of image is cloned
+ * from.
+ */
+ p2 = Mark_relative im2 0.05 0.05; _r2 = Region im2 p2.left
+p2.top r1.width r1.height;
+ mask = [r1 <= Options.sc, r1 >=
+Options.sc]?(Options.replace);
+ Options = class
+ {
+ _vislevel = 3;
+ pause = Toggle "Pause process" true;
+ /* Option toggle used to define whether the user is
+ * replacing a dark or a light area.
+ */
+ replace = Option "Replace" [ "A Dark Area", "A Light Area" ] 1;
+
+ // Used to select the area to be replaced.
+ sc = Scale "Scale cutoff" 0.01 mx (mx / 2)
+ {mx = Image_format.maxval im1.format;}
+ //Allows replacement with scale&offset balanced gaussian noise.
+ balance = Toggle "Balance cloned data to match surroundings." true;
+ //Allows replacement with scale&offset balanced
+ //gaussian noise.
+ process = Toggle "Replace area with Gaussian noise." false;
+ }
+ _result = im1, Options.pause
+ = Image (im_insert im1.value patch r1.left r1.top)
+ { r2 = Region im2 p2.left p2.top r1.width r1.height;
+ ref_meanmax = so_meanmax (if mask then 0 else r1);
+ mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255],
+[255, 255, 255]];
+ mask_a = map_unary (dilate mask8) mask;
+ mask_b = convsep (matrix_blur 2) mask_a;
+ patch = so_balance ref_meanmax r1 r2 mask_b
+Options.process, Options.balance
+ = so_balance ref_meanmax r1 r2 mask_b Options.process,
+Options.process
+ = im_blend (get_image mask_b) (get_image r2) (get_image r1);
+ }
+ }
+ }
+}
+
+Tasks_frame_item = Frame_item;
+
+Tasks_print_item = class
+ Menupullright "_Print" "useful stuff for image output" {
+
+ Rotate_item = Image_transform_item.Rotate_item;
+
+ Flip_item = Image_transform_item.Flip_item;
+
+ Resize_item = Image_transform_item.Resize_item;
+
+ Tone_item = Image_levels_item.Tone_item;
+
+ Sharpen_item = class
+ Menuaction "_Sharpen"
+ "unsharp filter tuned for typical inkjet printers" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ target_dpi = Option "Sharpen for print at" [
+ "400 dpi",
+ "300 dpi",
+ "150 dpi",
+ "75 dpi"
+ ] 1;
+
+ _result
+ = map_unary process x
+ {
+ process image
+ = sharpen params?0 params?1
+ params?2 params?3 params?4 params?5
+ (colour_transform_to Image_type.LABQ image)
+ {
+ // sharpen params for various dpi
+ // just change the size of the area we search
+ param_table = [
+ [7, 2.5, 40, 20, 0.5, 1.5],
+ [5, 2.5, 40, 20, 0.5, 1.5],
+ [3, 2.5, 40, 20, 0.5, 1.5],
+ [11, 2.5, 40, 20, 0.5, 1.5]
+ ];
+ params = param_table?target_dpi;
+ }
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Temp_item = Colour_temperature_item;
+
+ ICC_item = Colour_icc_item;
+}
diff --git a/share/nip2/compat/8.6/Widgets.def b/share/nip2/compat/8.6/Widgets.def
new file mode 100644
index 00000000..c7ade22f
--- /dev/null
+++ b/share/nip2/compat/8.6/Widgets.def
@@ -0,0 +1,46 @@
+Widget_slider_item = class
+ Menuaction "_Scale" "make a new scale widget" {
+ icon = "nip-slider-16.png";
+ action = Scale "untitled scale" 0 255 128;
+}
+
+Widget_toggle_item = class
+ Menuaction "_Toggle" "make a new toggle widget" {
+ action = Toggle "untitled toggle" false;
+}
+
+Widget_option_item = class
+ Menuaction "_Option" "make a new option widget" {
+ action = Option "untitled option" ["option0", "option1"] 0;
+}
+
+Widget_string_item = class
+ Menuaction "St_ring" "make a new string widget" {
+ action = String "Enter a string" "sample text";
+}
+
+Widget_number_item = class
+ Menuaction "_Number" "make a new number widget" {
+ action = Number "Enter a number" 42;
+}
+
+Widget_expression_item = class
+ Menuaction "_Expression" "make a new expression widget" {
+ action = Expression "Enter an expression" 42;
+}
+
+Widget_pathname_item = class
+ Menuaction "_File Chooser" "make a new file chooser widget" {
+ action = Pathname "Pick a file"
+ "$VIPSHOME/share/$PACKAGE/data/print_test_image.v";
+}
+
+Widget_font_item = class
+ Menuaction "F_ont Chooser" "make a new font chooser widget" {
+ action = Fontname "Pick a font" Workspaces.Preferences.PAINTBOX_FONT;
+}
+
+Widget_clock_item = class
+ Menuaction "_Clock" "make a new clock widget" {
+ action = Clock 1 1;
+}
diff --git a/share/nip2/compat/8.6/_Object.def b/share/nip2/compat/8.6/_Object.def
new file mode 100644
index 00000000..9562f9b5
--- /dev/null
+++ b/share/nip2/compat/8.6/_Object.def
@@ -0,0 +1,364 @@
+/* Lots of little arg checks. Global for convenience.
+ */
+
+check_any = [(const true), _ "any"];
+check_bool = [is_bool, _ "boolean"];
+check_real = [is_real, _ "real"];
+check_ureal = [is_ureal, _ "unsigned real"];
+check_preal = [is_preal, _ "positive real"];
+check_list = [is_list, _ "list"];
+check_real_list = [is_real_list, _ "list of real"];
+check_string = [is_string, _ "string"];
+check_string_list = [is_string_list, _ "list of string"];
+check_int = [is_int, _ "integer"];
+check_uint = [is_uint, _ "unsigned integer"];
+check_pint = [is_pint, _ "positive integer"];
+check_matrix = [is_matrix, _ "rectangular array of real"];
+check_matrix_display = [Matrix_display.is_display, _ "0|1|2|3"];
+check_image = [is_image, _ "image"];
+check_xy_list = [is_xy_list, _ "list of form [[1, 2], [3, 4], [5, 6], ...]"];
+check_instance name = [is_instanceof name, name];
+check_Image = check_instance "Image";
+check_Matrix = [is_Matrix, _ "Matrix"];
+check_colour_space = [is_colour_space,
+ join_sep "|" Image_type.colour_spaces.names];
+check_rectangular = [is_rectangular, _ "rectangular [[*]]"];
+check_Guide = [is_Guide, _ "HGuide|VGuide"];
+check_Colour = check_instance (_ "Colour");
+check_Mark = check_instance (_ "Mark");
+
+/* Check a set of args to a class. Two members to look at: _check_args and
+ * _check_all.
+ *
+ * - each line in _check_args is [arg, "arg name", [test_fn, "arg type"]]
+ * same number of lines as there are args
+ *
+ * stuff like "arg 2 must be real"
+ *
+ * - each line in _check_all is [test, "description"]
+ * any number of lines
+ *
+ * stuff like "to must be greater than from"
+ *
+ * generate an error dialog with a helpful message on failure.
+ *
+ * Have as a separate function to try to keep the size of _Object down.
+ */
+check_args x
+ = error message, badargs != [] || badalls != []
+ = x
+{
+ argcheck = x._check_args;
+ allcheck = x._check_all;
+
+ // indent string
+ indent = " ";
+
+ // test for a condition in a check line fails
+ test_fail x = ! x?0;
+
+ // set of failed argcheck indexes
+ badargs = map (extract 1)
+ (filter test_fail (zip2 (map testarg argcheck) [0..]))
+ {
+ testarg x = x?2?0 x?0;
+ }
+
+ // set of failed allcheck indexes
+ badalls = map (extract 1)
+ (filter test_fail (zip2 (map hd allcheck) [0..]));
+
+ // the error message
+ message = _ "bad properties for " ++ "\"" ++ x.name ++ "\"\n" ++
+ argmsg ++ allmsg ++ "\n" ++
+ _ "where" ++ "\n" ++ arg_types ++ extra;
+
+ // make the failed argcheck messages ... eg. ""value" should be
+ // real, you passed " etc.
+ argmsg = concat (map fmt badargs)
+ {
+ fmt n = indent ++ "\"" ++ argcheck?n?1 ++ "\"" ++
+ _ " should be of type " ++ argcheck?n?2?1 ++ ", " ++
+ _ "you passed" ++ ":\n" ++
+ indent ++ indent ++ print argcheck?n?0 ++ "\n";
+ }
+
+ // make the failed allcheck messages ... eg "condition failed:
+ // x < y" ... don't make a message if any typechecks have
+ // failed, as we'll probably error horribly
+ allmsg
+ = [], badargs != []
+ = concat (map fmt badalls) ++
+ _ "you passed" ++ "\n" ++
+ concat (map fmt_arg argcheck)
+ {
+ fmt n = _ "condition failed" ++ ": " ++ allcheck?n?1 ++ "\n";
+ fmt_arg l = indent ++ l?1 ++ " = " ++ print l?0 ++ "\n";
+ }
+
+ // make arg type notes
+ arg_types = join_sep "\n" (map fmt argcheck)
+ {
+ fmt l = indent ++ l?1 ++ " is of type " ++ l?2?1;
+ }
+
+ // extra bit at the bottom, if we have any conditions
+ extra
+ = [], allcheck == []
+ = "\n" ++ _ "and" ++ "\n" ++ all_desc;
+
+ // make a list of all the allcheck descriptions, with a few
+ // spaces in front
+ all_desc_list = map (join indent @ extract 1) allcheck;
+
+ // join em up to make a set of condition notes
+ all_desc = join_sep "\n" all_desc_list;
+}
+
+/* Operator overloading stuff.
+ */
+
+Operator_type = class {
+ ARITHMETIC = 1; // eg. add
+ RELATIONAL = 2; // eg. less
+ COMPOUND = 3; // eg. max/mean/etc.
+ COMPOUND_REWRAP = 4; // eg. transpose
+}
+
+Operator op_name fn type symmetric = class {
+}
+
+/* Form the converse of an Operator.
+ */
+oo_converse op
+ = Operator (converse_name op.op_name)
+ (converse op.fn) op.type op.symmetric
+{
+ converse_name x
+ = init x, last x == last "'"
+ = x ++ "'";
+}
+
+/* Given an operator name, look up the definition.
+ */
+oo_binary_lookup op_name
+ = matches?0, matches != []
+ = error (_ "unknown binary operator" ++ ": " ++ print op_name)
+{
+ operator_table = [
+ Operator "add" add
+ Operator_type.ARITHMETIC true,
+ Operator "subtract" subtract
+ Operator_type.ARITHMETIC false,
+ Operator "remainder" remainder
+ Operator_type.ARITHMETIC false,
+ Operator "power" power
+ Operator_type.ARITHMETIC false,
+ Operator "subscript" subscript
+ Operator_type.ARITHMETIC false,
+ Operator "left_shift" left_shift
+ Operator_type.ARITHMETIC false,
+ Operator "right_shift" right_shift
+ Operator_type.ARITHMETIC false,
+ Operator "divide" divide
+ Operator_type.ARITHMETIC false,
+ Operator "join" join
+ Operator_type.ARITHMETIC false,
+ Operator "multiply" multiply
+ Operator_type.ARITHMETIC true,
+ Operator "logical_and" logical_and
+ Operator_type.ARITHMETIC true,
+ Operator "logical_or" logical_or
+ Operator_type.ARITHMETIC true,
+ Operator "bitwise_and" bitwise_and
+ Operator_type.ARITHMETIC true,
+ Operator "bitwise_or" bitwise_or
+ Operator_type.ARITHMETIC true,
+ Operator "eor" eor
+ Operator_type.ARITHMETIC true,
+ Operator "comma" comma
+ Operator_type.ARITHMETIC false,
+ Operator "if_then_else" if_then_else
+ Operator_type.ARITHMETIC false,
+ Operator "equal" equal
+ Operator_type.RELATIONAL true,
+ Operator "not_equal" not_equal
+ Operator_type.RELATIONAL true,
+ Operator "less" less
+ Operator_type.RELATIONAL false,
+ Operator "less_equal" less_equal
+ Operator_type.RELATIONAL false
+ ];
+
+ matches = filter test_name operator_table;
+ test_name x = x.op_name == op_name;
+}
+
+/* Given an operator name, look up a function that implements that
+ * operator.
+ */
+oo_unary_lookup op_name
+ = matches?0, matches != []
+ = error (_ "unknown unary operator" ++ ": " ++ print op_name)
+{
+ operator_table = [
+ /* Operators.
+ */
+ Operator "cast_signed_char" cast_signed_char
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_char" cast_unsigned_char
+ Operator_type.ARITHMETIC false,
+ Operator "cast_signed_short" cast_signed_short
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_short" cast_unsigned_short
+ Operator_type.ARITHMETIC false,
+ Operator "cast_signed_int" cast_signed_int
+ Operator_type.ARITHMETIC false,
+ Operator "cast_unsigned_int" cast_unsigned_int
+ Operator_type.ARITHMETIC false,
+ Operator "cast_float" cast_float
+ Operator_type.ARITHMETIC false,
+ Operator "cast_double" cast_double
+ Operator_type.ARITHMETIC false,
+ Operator "cast_complex" cast_complex
+ Operator_type.ARITHMETIC false,
+ Operator "cast_double_complex" cast_double_complex
+ Operator_type.ARITHMETIC false,
+ Operator "unary_minus" unary_minus
+ Operator_type.ARITHMETIC false,
+ Operator "negate" negate
+ Operator_type.RELATIONAL false,
+ Operator "complement" complement
+ Operator_type.ARITHMETIC false,
+ Operator "unary_plus" unary_plus
+ Operator_type.ARITHMETIC false,
+
+ /* Built in projections.
+ */
+ Operator "re" re Operator_type.ARITHMETIC false,
+ Operator "im" im Operator_type.ARITHMETIC false,
+ Operator "hd" hd Operator_type.ARITHMETIC false,
+ Operator "tl" tl Operator_type.ARITHMETIC false,
+
+ /* Maths builtins.
+ */
+ Operator "sin" sin Operator_type.ARITHMETIC false,
+ Operator "cos" cos Operator_type.ARITHMETIC false,
+ Operator "tan" tan Operator_type.ARITHMETIC false,
+ Operator "asin" asin Operator_type.ARITHMETIC false,
+ Operator "acos" acos Operator_type.ARITHMETIC false,
+ Operator "atan" atan Operator_type.ARITHMETIC false,
+ Operator "log" log Operator_type.ARITHMETIC false,
+ Operator "log10" log10 Operator_type.ARITHMETIC false,
+ Operator "exp" exp Operator_type.ARITHMETIC false,
+ Operator "exp10" exp10 Operator_type.ARITHMETIC false,
+ Operator "ceil" ceil Operator_type.ARITHMETIC false,
+ Operator "floor" floor Operator_type.ARITHMETIC false
+ ];
+
+ matches = filter test_name operator_table;
+ test_name x = x.op_name == op_name;
+}
+
+/* Find the matching methods in a method table.
+ */
+oo_method_lookup table = map (extract 0) (filter (extract 1) table);
+
+/* A binary op: a is a class, b may be a class ... eg. "add" a b
+
+ two obvious ways to find a method:
+
+ - a.oo_binary_search "add" (+) b
+ - b.oo_binary_search "add'" (converse (+)) a, is_class b
+
+ if these fail but op is a symmetric operator (eg. a + b == b + a), we can
+ also try reversing the args
+
+ - a.oo_binary_search "add'" (converse (+)) b
+ - b.oo_binary_search "add" (+) a, is_class b
+
+ if those fail as well, but this is ==, do pointer equals as a fallback
+
+ */
+oo_binary_function op a b
+ = matches1?0,
+ matches1 != []
+ = matches2?0,
+ is_class b && matches2 != []
+ = matches3?0,
+ op.symmetric && matches3 != []
+ = matches4?0,
+ op.symmetric && is_class b && matches4 != []
+ = pointer_equal a b,
+ op.op_name == "equal" || op.op_name == "equal'"
+ = not_pointer_equal a b,
+ op.op_name == "not_equal" || op.op_name == "not_equal'"
+ = error (_ "No method found for binary operator." ++ "\n" ++
+ _ "left" ++ " = " ++ print a ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "right" ++ " = " ++ print b)
+{
+ matches1 = oo_method_lookup (a.oo_binary_table op b);
+ matches2 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
+ matches3 = oo_method_lookup (a.oo_binary_table (oo_converse op) b);
+ matches4 = oo_method_lookup (b.oo_binary_table op a);
+}
+
+/* A binary op: a is not a class, b is a class ... eg. "subtract" a b
+
+ only one way to find a method:
+
+ - b.oo_binary_search "subtract'" (converse (-)) a
+
+ if this fails but op is a symmetric operator (eg. a + b == b + a), we can
+ try reversing the args
+
+ - b.oo_binary_search "add" (+) a, is_class b
+
+ if that fails as well, but this is ==, do pointer equals as a fallback
+
+ */
+oo_binary'_function op a b
+ = matches1?0, matches1 != []
+ = matches2?0, op.symmetric && matches2 != []
+ = pointer_equal a b,
+ op.op_name == "equal" || op.op_name == "equal'"
+ = not_pointer_equal a b,
+ op.op_name == "not_equal" || op.op_name == "not_equal'"
+ = error (_ "No method found for binary operator." ++ "\n" ++
+ _ "left" ++ " = " ++ print a ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "right" ++ " = " ++ print b)
+{
+ matches1 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
+ matches2 = oo_method_lookup (b.oo_binary_table op a);
+}
+
+oo_unary_function op x
+ = matches?0, matches != []
+ = error (_ "No method found for unary operator." ++ "\n" ++
+ _ "operator" ++ " = " ++ op.op_name ++ "\n" ++
+ _ "argument" ++ " = " ++ print x)
+{
+ matches = oo_method_lookup (x.oo_unary_table op);
+}
+
+/* Base class for nip's built-in classes ... base check function, base
+ * operator overload functions.
+ */
+_Object = class {
+ check = check_args this;
+
+ // these should always be defined
+ _check_args = [];
+ _check_all = [];
+
+ /* Operator overloading stuff.
+ */
+ oo_binary op x = oo_binary_function (oo_binary_lookup op) this x;
+ oo_binary' op x = oo_binary'_function (oo_binary_lookup op) x this;
+ oo_unary op = oo_unary_function (oo_unary_lookup op) this;
+
+ oo_binary_table op x = [];
+ oo_unary_table op = [];
+}
diff --git a/share/nip2/compat/8.6/_convert.def b/share/nip2/compat/8.6/_convert.def
new file mode 100644
index 00000000..c63f78ae
--- /dev/null
+++ b/share/nip2/compat/8.6/_convert.def
@@ -0,0 +1,739 @@
+
+/* Try to make a Matrix ... works for Vector/Image/Real, plus image/real
+ */
+to_matrix x
+ = to_matrix x.expr, is_Expression x
+ = x, is_Matrix x
+ = oo_unary_function to_matrix_op x, is_class x
+ = tom x
+{
+ to_matrix_op = Operator "to_matrix" tom Operator_type.COMPOUND false;
+
+ tom x
+ = Matrix (itom x), is_image x
+ = Matrix [[x]], is_real x
+ = Matrix [x], is_real_list x
+ = Matrix x, is_matrix x
+ = error (_ "bad arguments to " ++ "to_matrix");
+
+ itom i
+ = (im_vips2mask ((double) i)).value, is_image i
+ = error (_ "not image");
+}
+
+/* Try to make a Vector ... works for Vector/Image/Real, plus image/real
+ */
+to_vector x
+ = to_vector x.expr, is_Expression x
+ = x, is_Vector x
+ = oo_unary_function to_vector_op x, is_class x
+ = tov x
+{
+ to_vector_op = Operator "to_vector" tov Operator_type.COMPOUND false;
+
+ tov x
+ = Vector (itov x), is_image x
+ = Vector [x], is_real x
+ = Vector x, is_real_list x
+ = Vector x?0, is_matrix x && len x == 1
+ = Vector (transpose x)?0, is_matrix x && len x?0 == 1
+ = error (_ "bad arguments to " ++ "to_vector");
+
+ itov i
+ = v, is_image i
+ = error (_ "not image")
+ {
+ m = im_vips2mask ((double) i);
+ v
+ = m.value?0, m.height == 1
+ = (transpose m.value)?0, m.width == 1
+ = error (_ "image is not 1xN or Nx1");
+ }
+}
+
+/* Try to make an Image ... works for Vector/Matrix/Real, plus image/real
+ * Special case for Colour ... pull out the colour_space and set Type in the
+ * image.
+ */
+to_image x
+ = to_image x.expr, is_Expression x
+ = Image x.value, is_Plot x
+ = x, is_Image x
+ = Image (image_set_type
+ (Image_type.colour_spaces.lookup 0 1 x.colour_space)
+ (mtoi [x.value])),
+ is_Colour x
+ = oo_unary_function to_image_op x, is_class x
+ = toi x
+{
+ to_image_op = Operator "to_image" toi Operator_type.COMPOUND false;
+
+ toi x
+ = Image x, is_image x
+ = Image (mtoi [[x]]), is_real x
+ = Image (mtoi [x]), is_real_list x
+ = Image (mtoi x), is_matrix x
+ = error (_ "bad arguments to " ++ "to_image");
+
+ // [[real]] -> image
+ mtoi m
+ = im_mask2vips (Matrix m), width != 3
+ = joinup (im_mask2vips (Matrix m))
+ {
+ width = len m?0;
+ height = len m;
+ joinup i
+ = b1 ++ b2 ++ b3
+ {
+ b1 = extract_area 0 0 1 height i;
+ b2 = extract_area 1 0 1 height i;
+ b3 = extract_area 2 0 1 height i;
+ }
+ }
+}
+
+// like to_image, but we do 1x1 pixel + x, then embed it up
+// always make an unwrapped image for speed ... this gets used by ifthenelse
+// and stuff like that
+// format can be NULL, meaning set format from x
+to_image_size width height bands format x
+ = x, is_image x
+ = x.value, is_Image x
+ = im''
+{
+ // we want x to set the target format if we don't have one, so we
+ // can't use image_new
+ im = im_black 1 1 bands + x;
+ im'
+ = clip2fmt format im, format != NULL
+ = im;
+ im'' = embed 1 0 0 width height im';
+}
+
+/* Try to make a Colour.
+ */
+to_colour x
+ = to_colour x.expr, is_Expression x
+ = x, is_Colour x
+ = to_colour (extract_area x.left x.top 1 1 x.image), is_Mark x
+ = oo_unary_function to_colour_op x, is_class x
+ = toc x
+{
+ to_colour_op = Operator "to_colour" toc Operator_type.COMPOUND false;
+
+ toc x
+ = Colour (colour_space (get_type x))
+ (map mean (bandsplit (get_image x))),
+ has_image x && has_type x
+ = Colour "sRGB" [x, x, x], is_real x // since Colour can't do mono
+ = Colour "sRGB" x, is_real_list x && is_list_len 3 x
+ = map toc x, is_matrix x
+ = error (_ "bad arguments to " ++ "to_colour");
+
+ colour_space type
+ = table.get_name type, table.has_name type
+ = error (_ "unable to make Colour from " ++ table.get_name type ++
+ _ " image")
+ {
+ table = Image_type.colour_spaces;
+ }
+}
+
+/* Try to make a real. (not a Real!)
+ */
+to_real x
+ = to_real x.expr, is_Expression x
+ = oo_unary_function to_real_op x, is_class x
+ = tor x
+{
+ to_real_op = Operator "to_real" tor Operator_type.COMPOUND false;
+
+ tor x
+ = x, is_real x
+ = abs x, is_complex x
+ = 1, is_bool x && x
+ = 0, is_bool x && !x
+ = error (_ "bad arguments to " ++ "to_real");
+}
+
+to_int x = (int) (to_real x);
+
+/* Try to make a list ... ungroup, basically. We remove the innermost layer of
+ * Groups.
+ */
+to_list x
+ = x.value, is_Group x && !contains_Group x.value
+ = Group (map to_list x.value), is_Group x
+ = x;
+
+/* Try to make a group. The outermost list objects become Group()'d.
+ */
+to_group x
+ = Group x, is_list x
+ = Group (map to_group x.value), is_Group x
+ = x;
+
+/* Parse a positive integer.
+ */
+parse_pint l
+ = foldl acc 0 l
+{
+ acc sofar ch = sofar * 10 + parse_c ch;
+
+ /* Turn a char digit to a number.
+ */
+ parse_c ch
+ = error (_ "not a digit"), !is_digit ch
+ = (int) ch - (int) '0';
+}
+
+/* Parse an integer, with an optional sign character.
+ */
+parse_int l
+ = error (_ "badly formed number"), !is_list_len 2 parts
+ = sign * n
+{
+ parts = splitpl [member "+-", is_digit] l;
+
+ n = parse_pint parts?1;
+ sign
+ = 1, parts?0 == [] || parts?0 == "+"
+ = -1;
+}
+
+/* Parse a float.
+ * [+-]?[0-9]*([.][0-9]*)?(e[0-9]+)?
+ */
+parse_float l
+ = err, !is_list_len 4 parts
+ = sign * (abs ipart + fpart) * 10 ** exp
+{
+ err = error (_ "badly formed number");
+
+ parts = splitpl [
+ member "+-0123456789",
+ member ".0123456789",
+ member "eE",
+ member "+-0123456789"
+ ] l;
+
+ ipart = parse_int parts?0;
+ sign
+ = 1, ipart >= 0
+ = -1;
+ fpart
+ = 0, parts?1 == [];
+ = err, parts?1?0 != '.'
+ = parse_pint (tl parts?1) / 10 ** (len parts?1 - 1);
+ exp
+ = 0, parts?2 == [] && parts?3 == []
+ = err, parts?2 == []
+ = parse_int parts?3;
+
+}
+
+/* Parse a time in "hh:mm:ss" into seconds.
+
+We could do this in one line :)
+
+ = (sum @ map2 multiply (iterate (multiply 60) 1) @ reverse @ map
+ parse_pint @ map (subscript (splitpl [is_digit, equal ':', is_digit,
+ equal ':', is_digit] l))) [0,2,4];
+
+but it's totally unreadable.
+
+ */
+parse_time l
+ = error (_ "badly formed time"), !is_list_len 5 parts
+ = s + 60 * m + 60 * 60 * h
+{
+ parts = splitpl [is_digit, equal ':', is_digit, equal ':', is_digit] l;
+ h = parse_int parts?0;
+ m = parse_int parts?2;
+ s = parse_int parts?4;
+}
+
+/* matrix to convert D65 XYZ to D50 XYZ ... direct conversion, found by
+ * measuring a macbeth chart in D50 and D65 and doing a LMS to get a matrix
+ */
+D652D50_direct = Matrix
+ [[ 1.13529, -0.0604663, -0.0606321 ],
+ [ 0.0975399, 0.935024, -0.0256156 ],
+ [ -0.0336428, 0.0414702, 0.994135 ]];
+
+D502D65_direct = D652D50_direct ** -1;
+
+/* Convert normalised XYZ to bradford RGB.
+ */
+XYZ2RGBbrad = Matrix
+ [[0.8951, 0.2664, -0.1614],
+ [-0.7502, 1.7135, 0.0367],
+ [0.0389, -0.0685, 1.0296]];
+
+/* Convert bradford RGB to normalised XYZ.
+ */
+RGBbrad2XYZ = XYZ2RGBbrad ** -1;
+
+D93_whitepoint = Vector [89.7400, 100, 130.7700];
+D75_whitepoint = Vector [94.9682, 100, 122.5710];
+D65_whitepoint = Vector [95.0470, 100, 108.8827];
+D55_whitepoint = Vector [95.6831, 100, 92.0871];
+D50_whitepoint = Vector [96.4250, 100, 82.4680];
+A_whitepoint = Vector [109.8503, 100, 35.5849]; // 2856K
+B_whitepoint = Vector [99.0720, 100, 85.2230]; // 4874K
+C_whitepoint = Vector [98.0700, 100, 118.2300]; // 6774K
+E_whitepoint = Vector [100, 100, 100]; // ill. free
+D3250_whitepoint = Vector [105.6590, 100, 45.8501];
+
+Whitepoints = Enum [
+ $D93 => D93_whitepoint,
+ $D75 => D75_whitepoint,
+ $D65 => D65_whitepoint,
+ $D55 => D55_whitepoint,
+ $D50 => D50_whitepoint,
+ $A => A_whitepoint,
+ $B => B_whitepoint,
+ $C => C_whitepoint,
+ $E => E_whitepoint,
+ $D3250 => D3250_whitepoint
+];
+
+/* Convert D50 XYZ to D65 using the bradford chromatic adaptation approx.
+ */
+im_D502D65 xyz
+ = xyz'''
+{
+ xyz' = xyz / D50_whitepoint;
+
+ rgb = recomb XYZ2RGBbrad xyz';
+
+ // move white in bradford RGB
+ rgb' = rgb / Vector [0.94, 1.02, 1.33];
+
+ xyz'' = recomb RGBbrad2XYZ rgb';
+
+ // back to D65
+ xyz''' = xyz'' * D65_whitepoint;
+}
+
+/* Convert D65 XYZ to D50 using the bradford approx.
+ */
+im_D652D50 xyz
+ = xyz'''
+{
+ xyz' = xyz / D65_whitepoint;
+
+ rgb = recomb XYZ2RGBbrad xyz';
+
+ // move white in bradford RGB
+ rgb' = rgb * Vector [0.94, 1.02, 1.33];
+
+ xyz'' = recomb RGBbrad2XYZ rgb';
+
+ xyz''' = xyz'' * D50_whitepoint;
+}
+
+/* Convert D50 XYZ to Lab.
+ */
+im_D50XYZ2Lab xyz
+ = im_XYZ2Lab_temp xyz
+ D50_whitepoint.value?0
+ D50_whitepoint.value?1
+ D50_whitepoint.value?2;
+im_D50Lab2XYZ lab
+ = im_Lab2XYZ_temp lab
+ D50_whitepoint.value?0
+ D50_whitepoint.value?1
+ D50_whitepoint.value?2;
+
+/* ... and mono conversions
+ */
+im_sRGB2mono in
+ = (image_set_type Image_type.B_W @
+ clip2fmt (get_header "BandFmt" in) @
+ recomb (Matrix [[.3, .6, .1]])) in;
+im_mono2sRGB in
+ = image_set_type Image_type.sRGB (in ++ in ++ in);
+
+im_sRGB2Lab = im_XYZ2Lab @ im_sRGB2XYZ;
+
+im_Lab2sRGB = im_XYZ2sRGB @ im_Lab2XYZ;
+
+// from the 16 bit RGB and GREY formats
+im_1628 x = im_clip (x >> 8);
+im_162f x = x / 256;
+
+im_8216 x = (im_clip2us x) << 8;
+im_f216 x = im_clip2us (x * 256);
+
+im_RGB162GREY16 in
+ = (image_set_type Image_type.GREY16 @
+ clip2fmt (get_header "BandFmt" in) @
+ recomb (Matrix [[.3, .6, .1]])) in;
+im_GREY162RGB16 in
+ = image_set_type Image_type.RGB16 (in ++ in ++ in);
+
+/* The vips8 scRGB functions.
+ */
+
+im_sRGB2scRGB in
+ = out
+{
+ [out] = vips_call "sRGB2scRGB" [in] [];
+}
+
+im_scRGB2sRGB in
+ = out
+{
+ [out] = vips_call "scRGB2sRGB" [in] [];
+}
+
+im_scRGB2XYZ in
+ = out
+{
+ [out] = vips_call "scRGB2XYZ" [in] [];
+}
+
+im_XYZ2scRGB in
+ = out
+{
+ [out] = vips_call "XYZ2scRGB" [in] [];
+}
+
+/* apply a func to an image ... make it 1 or 3 bands, and reapply other bands
+ * on the way out. Except if it's LABPACK.
+ */
+colour_apply fn x
+ = fn x, b == 1 || b == 3 || c == Image_coding.LABPACK
+ = x''
+{
+ b = get_bands x;
+ c = get_coding x;
+
+ first
+ = extract_bands 0 3 x, b > 3
+ = extract_bands 0 1 x;
+ tail
+ = extract_bands 3 (b - 3) x, b > 3
+ = extract_bands 1 (b - 1) x;
+ x' = fn first;
+ x'' = x' ++ clip2fmt (get_format x') tail;
+}
+
+/* Any 1-ary colour op, applied to Vector/Image/Matrix or image
+ */
+colour_unary fn x
+ = oo_unary_function colour_op x, is_class x
+ = colour_apply fn x, is_image x
+ = colour_apply fn [x], is_real x
+ = error (_ "bad arguments to " ++ "colour_unary")
+{
+ // COMPOUND_REWRAP ... signal to the colour class to go to image and
+ // back
+ colour_op = Operator "colour_unary"
+ colour_object Operator_type.COMPOUND_REWRAP false;
+
+ colour_object x
+ = colour_real_list x, is_real_list x
+ = map colour_real_list x, is_matrix x
+ = colour_apply fn x, is_image x
+ = error (_ "bad arguments to " ++ "colour_unary");
+
+ colour_real_list l
+ = (to_matrix (fn (float) (to_image (Vector l)).value)).value?0;
+}
+
+/* Any symmetric 2-ary colour op, applied to Vector/Image/Matrix or image ...
+ * name is op name for error messages etc.
+ */
+colour_binary name fn x y
+ = oo_binary_function colour_op x y, is_class x
+ = oo_binary'_function colour_op x y, is_class y
+ = fn x y, is_image x && is_image y
+ = error (_ "bad arguments to " ++ name)
+{
+ colour_op = Operator name
+ colour_object Operator_type.COMPOUND_REWRAP true;
+
+ colour_object x y
+ = fn x y, is_image x && is_image y
+ = colour_real_list fn x y, is_real_list x && is_real_list y
+ = map (colour_real_list fn x) y, is_real_list x && is_matrix y
+ = map (colour_real_list (converse fn) y) x,
+ is_matrix x && is_real_list y
+ = map2 (colour_real_list fn) x y, is_matrix x && is_matrix y
+ = error (_ "bad arguments to " ++ name);
+
+ colour_real_list fn l1 l2
+ = (to_matrix (fn i1 i2)).value?0
+ {
+ i1 = (float) (to_image (Vector l1)).value;
+ i2 = (float) (to_image (Vector l2)).value;
+ }
+}
+
+_colour_conversion_table = [
+ /* Lines are [space-from, space-to, conversion function]. Could do
+ * this as a big array, but table lookup feels safer.
+ */
+ [B_W, B_W, image_set_type B_W],
+ [B_W, XYZ, im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, LAB, im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_mono2sRGB @ im_clip],
+ [B_W, sRGB, im_mono2sRGB @ im_clip],
+ [B_W, scRGB, im_sRGB2scRGB @ im_mono2sRGB @ im_clip],
+ [B_W, RGB16, image_set_type RGB16 @ im_8216 @ im_mono2sRGB],
+ [B_W, GREY16, image_set_type GREY16 @ im_8216],
+ [B_W, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_mono2sRGB @ im_clip],
+ [B_W, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab @
+ im_mono2sRGB @ im_clip],
+
+ [XYZ, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_clip2f],
+ [XYZ, XYZ, image_set_type XYZ],
+ [XYZ, YXY, im_XYZ2Yxy @ im_clip2f],
+ [XYZ, LAB, im_XYZ2Lab @ im_clip2f],
+ [XYZ, LCH, im_Lab2LCh @ im_XYZ2Lab],
+ [XYZ, UCS, im_XYZ2UCS @ im_clip2f],
+ [XYZ, RGB, im_XYZ2disp @ im_clip2f],
+ [XYZ, sRGB, im_XYZ2sRGB @ im_clip2f],
+ [XYZ, scRGB, im_XYZ2scRGB @ im_clip2f],
+ [XYZ, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
+ [XYZ, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_clip2f],
+
+ [YXY, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, XYZ, im_Yxy2XYZ @ im_clip2f],
+ [YXY, YXY, image_set_type YXY],
+ [YXY, LAB, im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LCH, im_Lab2LCh @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, UCS, im_XYZ2UCS @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, RGB, im_XYZ2disp @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, sRGB, im_XYZ2sRGB @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, scRGB, im_XYZ2scRGB @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @ im_clip2f],
+ [YXY, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_XYZ2Lab @ im_Yxy2XYZ @
+ im_clip2f],
+
+ [LAB, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_Lab2XYZ @ im_clip2f],
+ [LAB, XYZ, im_Lab2XYZ @ im_clip2f],
+ [LAB, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_clip2f],
+ [LAB, LAB, image_set_type LAB @ im_clip2f],
+ [LAB, LCH, im_Lab2LCh @ im_clip2f],
+ [LAB, UCS, im_Lab2UCS @ im_clip2f],
+ [LAB, RGB, im_Lab2disp @ im_clip2f],
+ [LAB, sRGB, im_Lab2sRGB @ im_clip2f],
+ [LAB, scRGB, im_XYZ2scRGB @ im_Lab2XYZ @ im_clip2f],
+ [LAB, LABQ, im_Lab2LabQ @ im_clip2f],
+ [LAB, LABS, im_Lab2LabS @ im_clip2f],
+
+ [LCH, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
+ [LCH, XYZ, im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
+ [LCH, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
+ [LCH, LAB, im_LCh2Lab @ im_clip2f],
+ [LCH, LCH, image_set_type LCH],
+ [LCH, UCS, im_LCh2UCS @ im_clip2f],
+ [LCH, RGB, im_Lab2disp @ im_LCh2Lab @ im_clip2f],
+ [LCH, sRGB, im_Lab2sRGB @ im_LCh2Lab @ im_clip2f],
+ [LCH, scRGB, im_XYZ2scRGB @ im_Lab2XYZ @ im_LCh2Lab @ im_clip2f],
+ [LCH, LABQ, im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
+ [LCH, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_LCh2Lab @ im_clip2f],
+
+ [UCS, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_UCS2XYZ @ im_clip2f],
+ [UCS, XYZ, im_UCS2XYZ @ im_clip2f],
+ [UCS, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_UCS2Lab @ im_clip2f],
+ [UCS, LAB, im_UCS2Lab @ im_clip2f],
+ [UCS, LCH, im_UCS2LCh @ im_clip2f],
+ [UCS, UCS, image_set_type UCS],
+ [UCS, RGB, im_Lab2disp @ im_UCS2Lab @ im_clip2f],
+ [UCS, sRGB, im_Lab2sRGB @ im_UCS2Lab @ im_clip2f],
+ [UCS, scRGB, im_XYZ2scRGB @ im_Lab2XYZ @ im_UCS2Lab @ im_clip2f],
+ [UCS, LABQ, im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
+ [UCS, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_UCS2Lab @ im_clip2f],
+
+ [RGB, B_W, im_sRGB2mono @ im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
+ [RGB, XYZ, im_disp2XYZ @ im_clip],
+ [RGB, YXY, im_XYZ2Yxy @ im_disp2XYZ @ im_clip],
+ [RGB, LAB, im_disp2Lab @ im_clip],
+ [RGB, LCH, im_Lab2LCh @ im_disp2Lab @ im_clip],
+ [RGB, UCS, im_Lab2UCS @ im_disp2Lab @ im_clip],
+ [RGB, RGB, image_set_type RGB],
+ [RGB, sRGB, im_XYZ2sRGB @ im_disp2XYZ @ im_clip],
+ [RGB, scRGB, im_XYZ2scRGB @ im_disp2XYZ @ im_clip],
+ [RGB, RGB16, image_set_type RGB16 @ im_8216],
+ [RGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
+ [RGB, LABQ, im_Lab2LabQ @ im_disp2Lab @ im_clip],
+ [RGB, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_disp2Lab @ im_clip],
+
+ [sRGB, B_W, im_sRGB2mono],
+ [sRGB, XYZ, im_sRGB2XYZ @ im_clip],
+ [sRGB, YXY, im_XYZ2Yxy @ im_sRGB2XYZ @ im_clip],
+ [sRGB, LAB, im_sRGB2Lab @ im_clip],
+ [sRGB, LCH, im_Lab2LCh @ im_sRGB2Lab @ im_clip],
+ [sRGB, UCS, im_XYZ2UCS @ im_sRGB2XYZ @ im_clip],
+ [sRGB, RGB, im_XYZ2disp @ im_sRGB2XYZ @ im_clip],
+ [sRGB, sRGB, image_set_type sRGB],
+ [sRGB, scRGB, im_sRGB2scRGB @ im_clip],
+ [sRGB, RGB16, image_set_type RGB16 @ im_8216],
+ [sRGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono],
+ [sRGB, LABQ, im_Lab2LabQ @ im_sRGB2Lab @ im_clip],
+ [sRGB, LABS, im_Lab2LabS @ im_sRGB2Lab @ im_clip],
+
+ [scRGB, B_W, im_sRGB2mono @ im_scRGB2sRGB],
+ [scRGB, XYZ, im_scRGB2XYZ],
+ [scRGB, YXY, im_XYZ2Yxy @ im_scRGB2XYZ],
+ [scRGB, LAB, im_XYZ2Lab @ im_scRGB2XYZ],
+ [scRGB, LCH, im_Lab2LCh @ im_XYZ2Lab @ im_scRGB2XYZ],
+ [scRGB, UCS, im_XYZ2UCS @ im_scRGB2XYZ],
+ [scRGB, RGB, im_XYZ2disp @ im_scRGB2XYZ],
+ [scRGB, sRGB, im_scRGB2sRGB],
+ [scRGB, scRGB, image_set_type scRGB],
+ [scRGB, RGB16, image_set_type RGB16 @ im_8216 @ im_scRGB2sRGB],
+ [scRGB, GREY16, image_set_type GREY16 @ im_8216 @ im_sRGB2mono @
+ im_scRGB2sRGB],
+ [scRGB, LABQ, im_Lab2LabQ @ im_XYZ2Lab @ im_scRGB2XYZ],
+ [scRGB, LABS, im_Lab2LabS @ im_XYZ2Lab @ im_scRGB2XYZ],
+
+ [RGB16, B_W, im_1628 @ im_sRGB2mono],
+ [RGB16, RGB, image_set_type RGB @ im_1628],
+ [RGB16, sRGB, image_set_type sRGB @ im_1628],
+ [RGB16, scRGB, im_sRGB2scRGB],
+ [RGB16, RGB16, image_set_type RGB16],
+ [RGB16, GREY16, im_RGB162GREY16],
+ [RGB16, LABS, im_LabQ2LabS @ im_Lab2LabQ @ im_sRGB2Lab],
+
+ [GREY16, B_W, image_set_type B_W @ im_1628],
+ [GREY16, RGB, im_mono2sRGB @ im_1628],
+ [GREY16, sRGB, im_mono2sRGB @ im_1628],
+ [GREY16, scRGB, im_sRGB2scRGB @ im_mono2sRGB],
+ [GREY16, RGB16, im_GREY162RGB16],
+ [GREY16, GREY16, image_set_type GREY16],
+
+ [LABQ, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab],
+ [LABQ, XYZ, im_Lab2XYZ @ im_LabQ2Lab],
+ [LABQ, YXY, im_XYZ2Yxy @ im_Lab2XYZ @ im_LabQ2Lab],
+ [LABQ, LAB, im_LabQ2Lab],
+ [LABQ, LCH, im_Lab2LCh @ im_LabQ2Lab],
+ [LABQ, UCS, im_Lab2UCS @ im_LabQ2Lab],
+ [LABQ, RGB, im_LabQ2disp],
+ [LABQ, sRGB, im_Lab2sRGB @ im_LabQ2Lab],
+ [LABQ, scRGB, im_XYZ2scRGB @ im_Lab2XYZ @ im_LabQ2Lab],
+ [LABQ, LABQ, image_set_type LABQ],
+ [LABQ, LABS, im_LabQ2LabS],
+
+ [LABS, B_W, im_sRGB2mono @ im_Lab2sRGB @ im_LabQ2Lab @
+ im_LabS2LabQ @ im_clip2s],
+ [LABS, XYZ, im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, YXY, im_XYZ2Yxy @
+ im_Lab2XYZ @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, LAB, im_LabS2Lab],
+ [LABS, LCH, im_Lab2LCh @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, UCS, im_Lab2UCS @ im_LabQ2Lab @ im_LabS2LabQ @ im_clip2s],
+ [LABS, RGB, im_LabQ2disp @ im_LabS2LabQ @ im_clip2s],
+ [LABS, sRGB, im_XYZ2sRGB @ im_Lab2XYZ @ im_LabS2Lab @ im_clip2s],
+ [LABS, scRGB, im_XYZ2scRGB @ im_Lab2XYZ @ im_LabS2Lab @ im_clip2s],
+ [LABS, LABQ, im_LabS2LabQ @ im_clip2s],
+ [LABS, LABS, image_set_type LABS]
+]
+{
+ /* From Image_type ... repeat here for brevity. Use same ordering as
+ * in Colour menu for consistency.
+ */
+ B_W = 1;
+ XYZ = 12;
+ YXY = 23;
+ LAB = 13;
+ LCH = 19;
+ UCS = 18;
+ RGB = 17;
+ sRGB = 22;
+ scRGB = 28;
+ RGB16 = 25;
+ GREY16 = 26;
+ LABQ = 16;
+ LABS = 21;
+}
+
+/* Transform between two colour spaces.
+ */
+colour_transform from to in
+ = colour_unary _colour_conversion_table?i?2 in, i >= 0
+ = error (_ "unable to convert " ++ Image_type.type_names.get_name from ++
+ _ " to " ++ Image_type.type_names.get_name to)
+{
+ match x = x?0 == from && x?1 == to;
+ i = index match _colour_conversion_table;
+}
+
+/* Transform to a colour space, assuming the type field in the input is
+ * correct
+ */
+colour_transform_to to in = colour_transform (get_type in) to in;
+
+/* String for path separator on this platform.
+ */
+path_separator = expand "$SEP";
+
+/* Form a relative pathname.
+ * path_relative ["home", "john"] == "home/john"
+ * path_relative [] == ""
+ */
+path_relative l = join_sep path_separator l;
+
+/* Form an absolute pathname.
+ * path_absolute ["home", "john"] == "/home/john"
+ * path_absolute [] == "/"
+ * If the first component looks like 'A:', don't add an initial separator.
+ */
+path_absolute l
+ = path_relative l,
+ len l?0 > 1 && is_letter l?0?0 && l?0?1 == ':'
+ = path_separator ++ path_relative l;
+
+/* Parse a pathname.
+ * path_parse "/home/john" == ["home", "john"]
+ * path_parse "home/john" == ["home", "john"]
+ */
+path_parse str
+ = split (equal path_separator?0) str;
+
+/* Return $PATH, reformatted as [["comp1", "comp2"], ["comp1", "comp2"] ..]
+ */
+system_search_path
+ = [vipsbin] ++
+ map path_parse (split (equal path_sep) (expand "$PATH"))
+{
+ /* On some platforms we ship vips with a few extra progs. Search
+ * $VIPSHOME/bin first.
+ */
+ vipsbin = path_parse (expand "$VIPSHOME") ++ ["bin"];
+
+ path_sep
+ = ':', expand "$SEP" == "/"
+ = ';';
+}
+
+/* Search $PATH for the first occurence of name, or "".
+ */
+search_for name
+ = hits?0, hits != []
+ = ""
+{
+ exe_name = name ++ expand "$EXEEXT";
+ form_path p = path_absolute (p ++ [exe_name]);
+ paths = map form_path system_search_path;
+ hits = dropwhile (equal []) (map search paths);
+}
+
+/* Search $PATH for the first occurence of name, error on failure.
+ */
+search_for_error name
+ = path, path != ""
+ = error (exe_name ++ " not found on your search path. " ++
+ "Check you have installed the program and it is on your PATH.")
+{
+ exe_name = name ++ expand "$EXEEXT";
+ path = search_for name;
+}
+
diff --git a/share/nip2/compat/8.6/_generate.def b/share/nip2/compat/8.6/_generate.def
new file mode 100644
index 00000000..1ce3af2a
--- /dev/null
+++ b/share/nip2/compat/8.6/_generate.def
@@ -0,0 +1,155 @@
+
+/* make an image of size x by y whose pixels are their coordinates.
+ */
+make_xy x y = im_make_xy (to_real x) (to_real y);
+
+/* make an image with the specified properties ... pixel is (eg.)
+ * Vector [0, 0, 0], or 12. If coding == labq, we ignore bands, format and
+ * type, generate a 3 band float image, and lab2labq it before handing it
+ * back.
+ */
+image_new w h b fmt coding type pixel xoff yoff
+ = embed 1 0 0 w h im''''
+{
+ b'
+ = 3, coding == Image_coding.LABPACK
+ = b;
+ fmt'
+ = Image_format.FLOAT, coding == Image_coding.LABPACK
+ = fmt;
+ type'
+ = Image_type.LAB, coding == Image_coding.LABPACK
+ = type;
+
+ im = im_black 1 1 (to_real b') + pixel;
+
+ im' = clip2fmt fmt' im;
+
+ im''
+ = im_Lab2LabQ im', coding == Image_coding.LABPACK;
+ = im';
+
+ im''' = image_set_type type' im'';
+ im'''' = image_set_origin xoff yoff im''';
+}
+
+mkim options x y b
+ = Image (image_new x y b
+ (opt $format) (opt $coding) (opt $type)
+ (opt $pixel)
+ (opt $xoffset) (opt $yoffset))
+{
+ opt = get_option options [
+ $format => Image_format.UCHAR,
+ $coding => Image_coding.NOCODING,
+ $type => Image_type.sRGB,
+ $pixel => 0,
+ $xoffset => 0,
+ $yoffset => 0
+ ];
+}
+
+/* generate a slice of LAB space size x size pixels for L* == l
+ */
+lab_slice size l
+ = image_set_type Image_type.LAB im
+{
+ L = image_new size size 1
+ Image_format.FLOAT Image_coding.NOCODING Image_type.B_W l 0 0;
+ A1 = im_fgrey (to_real size) (to_real size);
+
+ /* im_fgrey always makes 0-1, so these ranges can be wired in.
+ */
+ A2 = A1 * 256 - 128;
+
+ A4 = im_rot90 A2;
+ im = image_set_origin (size / 2) (size / 2) (L ++ A2 ++ A4);
+}
+
+/* Look at Image, try to make a Colour (failing that, a Vector) which is white
+ * for that image type.
+ */
+image_white im
+ = colour_transform_to type white_lab,
+ bands == 3 && coding == Image_coding.NOCODING &&
+ colour_spaces.present 1 type
+ = white_lab,
+ coding == Image_coding.LABPACK
+ = Vector (replicate bands (max_value.lookup 1 0 format))
+{
+ bands = im.bands;
+ type = im.type;
+ format = im.format;
+ coding = im.coding;
+ colour_spaces = Image_type.colour_spaces;
+
+ // white as LAB
+ white_lab = Colour "Lab" [100, 0, 0];
+
+ // maximum value for this numeric type
+ max_value = Table [
+ [255, Image_format.DPCOMPLEX],
+ [255, Image_format.DOUBLE],
+ [255, Image_format.COMPLEX],
+ [255, Image_format.FLOAT],
+ [2 ** 31 - 1, Image_format.INT],
+ [2 ** 32 - 1, Image_format.UINT],
+ [2 ** 15 - 1, Image_format.SHORT],
+ [2 ** 16 - 1, Image_format.USHORT],
+ [2 ** 7 - 1, Image_format.CHAR],
+ [2 ** 8 - 1, Image_format.UCHAR]
+ ];
+}
+
+/* Make a seperable gaussian mask.
+ */
+matrix_gaussian_blur radius
+ = im_gauss_imask_sep (radius / 3) 0.2;
+
+/* Make a seperable square mask.
+ */
+matrix_blur radius
+ = Matrix_con (sum mask_sq_line) 0 [mask_sq_line]
+{
+ mask_sq_line = replicate (2 * radius - 1) 1;
+}
+
+/* Make a colour from a temperature.
+ */
+colour_from_temp T
+ = error (_ "T out of range"), T < 1667 || T > 25000
+ = Colour "Yxy" [50, x, y]
+{
+ // Kim et all approximation
+ // see eg. http://en.wikipedia.org/wiki/Planckian_locus#Approximation
+ x
+ = -0.2661239 * 10 ** 9 / T ** 3 - 0.2343580 * 10 ** 6 / T ** 2 +
+ 0.8776956 * 10 ** 3 / T + 0.179910, T < 4000
+ = -3.0258469 * 10 ** 9 / T ** 3 + 2.1070379 * 10 ** 6 / T ** 2 +
+ 0.2226347 * 10 ** 3 / T + 0.240390;
+
+ y
+ = -1.1063814 * x ** 3 - 1.34811020 * x ** 2 +
+ 2.18555832 * x - 0.20219638, T < 2222
+ = -0.9549476 * x ** 3 - 1.37418593 * x ** 2 +
+ 2.09137015 * x - 0.16748867, T < 4000
+ = 3.0817580 * x ** 3 - 5.87338670 * x ** 2 +
+ 3.75112997 * x - 0.37001483;
+}
+
+temp_from_colour z
+ = T
+{
+ c = colour_transform_to Image_type.YXY (to_colour z);
+ x = c.value?1;
+ y = c.value?2;
+
+ // McCamy's approximation, see eg.
+ // http://en.wikipedia.org/wiki/Color_temperature#Approximation
+
+ xe = 0.332;
+ ye = 0.1858;
+ n = (x - xe) / (y - ye);
+ T = -449 * n ** 3 + 3525 * n ** 2 - 6823.3 * n + 5520.33;
+}
+
diff --git a/share/nip2/compat/8.6/_joe_extra.def b/share/nip2/compat/8.6/_joe_extra.def
new file mode 100644
index 00000000..1e7ac4aa
--- /dev/null
+++ b/share/nip2/compat/8.6/_joe_extra.def
@@ -0,0 +1,505 @@
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Frame_item = class
+ Menupullright "Picture _Frame" "working with images of frames"
+ {
+ ////////////////////////////////////////////////////////////////////////////////////
+ Build_frame_item = class
+ Menupullright "_Build Frame From" "builds a new frame from image a and places it around image b"
+ {
+ ////////////////////////////////////////////////////////////////////////////////////
+ Frame_corner_item = class
+ Menuaction "_Frame Corner"
+ "copies and extends a frame corner, a, to produce a complete frame to fit round a given image, b"
+ {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 0;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ //Scale frame image if required.
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.mount_colour.value, mount_options.apply
+ = 0;
+
+ //Produce scaled and resized frame.
+ frame = corner_frame _a _im_w _im_h _ov _cs _ms _bf;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Simple_frame_item = class
+ Menuaction "_Simple Frame"
+ "extends or shortens the central sections of a simple frame, a, to fit round a given image, b"
+ {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 0;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ //Scale frame image if required.
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.mount_colour.value, mount_options.apply
+ = 0;
+
+ //Produce scaled and resized frame.
+ frame = simple_frame _a _im_w _im_h _ov _cs _ms _bf variables.option;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ Complex_frame_item = class
+ Menuaction "_Complex Frame"
+ "extends or shortens the central sections of a frame a, preserving any central edge details, to fit image b" {
+ prefs = Workspaces.Preferences;
+
+ action a b = class
+ _result
+ {
+ _check_args = [
+ [a, "a", check_Image],
+ [b, "b", check_Image]
+ ];
+ _check_all = [
+ [a.coding == b.coding && a.bands == b.bands,
+ "a.coding == b.coding && a.bands == b.bands"]
+ ];
+ _vislevel = 3;
+
+ ppcm = Expression "Number of pixels per cm" 25;
+
+ /* Given the value of ppcm, distance between the inner edge of the frame
+ * and the outer edge of the image. +ve values mean the frame overlaps
+ * the image.
+ */
+ overlap = Expression "Size of frame overlap in cm" 0;
+ variables = Frame_variables 1;
+
+ _type = Image_type.colour_spaces.get_name b.type;
+
+ //If applied the count colour be seen for -ve values of overlap
+ mount_options = Mount_options _type ppcm.expr;
+
+ _cs = variables.corner_section.value;
+ _es = variables.edge_section.value;
+ _ms = variables.middle_section.value;
+ _ov = ppcm.expr * overlap.expr;
+ _sf = variables.scale_factor.expr;
+ _bf = variables.blend_fraction.value;
+
+ _a = a, _sf == 1;
+ = a, _sf == 0;
+ = Image (resize Kernel_linear _sf _sf a.value);
+
+ _im_w = b.width;
+ _im_h = b.height + mount_options._los, mount_options.apply
+ = b.height;
+ _os = mount_options._los, mount_options.apply
+ = 0;
+ _cl = Vector mount_options.colour.value, mount_options.apply
+ = 0;
+
+
+ //Produce scaled and resized frame.
+ frame = complex_frame _a _im_w _im_h _ov _cs _es _ms _bf variables.option;
+ //Resize image canvas and applied mount colour as required.
+ _pos_im = frame_position_image b frame _os _cl;
+ //Wrap frame round image.
+ _result = if (frame == 0) then _pos_im else frame;
+ }
+ }
+ }
+////////////////////////////////////////////////////////////////////////////////////
+ Straighten_frame_item = class
+ Menuaction "_Straighten Frame" "uses four points to square up distorted images of frames" {
+ action a = Perspective_item.action a;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Select_item = class
+ Menupullright "_Select"
+ "select user defined areas of an image" {
+ prefs = Workspaces.Preferences;
+
+ /* Option toggle used to define whether the user is replacing a
+ * dark or a light area.
+ */
+ _control = Option "Make" [
+ "Selection Brighter",
+ "Selection Darker",
+ "Selection Black",
+ "Selection White",
+ "Background Black",
+ "Background White",
+ "Mask"
+ ] 4;
+
+ control_selection mask im no
+ = [
+ if mask then im * 1.2 else im * 1,
+ if mask then im * 0.8 else im * 1,
+ if mask then 0 else im,
+ if mask then 255 else im,
+ if mask then im else 0,
+ if mask then im else 255,
+ mask
+ ]?no;
+
+ Rectangle = class
+ Menuaction "_Rectangle"
+ "use an Arrow or Region x to define a rectangle"
+ {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ im = x.image;
+ mask = Image m
+ {
+ rx
+ = x.region_rect, is_Region x
+ = x;
+ b = image_new im.width im.height 1 0 0 1 0 0 0;
+ w = image_new rx.nwidth rx.nheight 1 0 0 1 255 0 0;
+ m = insert_noexpand rx.nleft rx.ntop w b;
+ }
+ }
+ }
+ }
+
+ Elipse = class
+ Menuaction "_Ellipse"
+ "use a line/arrow x to define the center point radius and direction of an ellipse"
+ {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+ width = Scale "Width" 0.01 1 0.5;
+
+ _result = control_selection mask im control
+ {
+ mask = select_ellipse x width.value;
+ im = x.image;
+ }
+ }
+ }
+
+ Tetragon = class
+ Menuaction "_Tetragon"
+ "selects the convex area defined by four points"
+ {
+ action a b c d = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ mask = select_tetragon a b c d;
+ im = get_image a;
+ }
+ }
+
+ }
+
+ Polygon = class
+ Menuaction "_Polygon"
+ "selects a polygon from an ordered group of points"
+ {
+ action pt_list = class
+ _result {
+ _vislevel = 3;
+
+ control = _control;
+
+ _result = control_selection mask im control
+ {
+ mask = select_polygon pt_list;
+ im = get_image ((pt_list.value)?0);
+ }
+ }
+ }
+
+ sep1 = Menuseparator;
+
+ Threshold_item = class
+ Menuaction "Thres_hold" "simple image threshold" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ t
+ = Scale "Threshold" 0 mx (mx / 2)
+ {
+ mx
+ = Image_format.maxval x.format, is_Image x
+ = 255;
+ }
+
+ _result = map_unary (more t.value) x;
+ }
+ }
+
+ Threshold_percent_item = class
+ Menuaction "Per_cent Threshold" "threshold at a percentage of pixels" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ t = Scale "Percentage of pixels" 0 100 50;
+
+ _result
+ = map_unary (more (hist_thresh (t.value / 100) x)) x;
+ }
+ }
+
+ sep2 = Menuseparator;
+
+ Segment_item = class
+ Menuaction "_Segment" "break image into disjoint regions" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ segments
+ = Expression "Number of disjoint regions"
+ (map_unary (get_header "n-segments") _result);
+
+ _result = map_unary segment x;
+ }
+ }
+
+ Fill_item = class
+ Menuaction "_Fill" "fill zero pixels with the nearest non-zero" {
+ action x = class
+ Image _result {
+ _vislevel = 3;
+
+ distance = Image _distance;
+
+ [_result, _distance] = vips_call "fill_nearest" [x.value] [
+ "distance" => true
+ ];
+ }
+ }
+
+fill_nearest x
+ = oo_unary_function nearest_op x, is_class x
+ = near x, is_image x
+ = error (_ "bad arguments to " ++ "fill_nearest")
+{
+ nearest_op = Operator "fill_nearest"
+ fill_nearest Operator_type.COMPOUND_REWRAP false;
+
+ near x
+ = [out, distance]
+ {
+ [out, distance] = vips_call "fill_nearest" [x] [
+ "distance" => true
+ ];
+ }
+}
+
+
+
+
+
+ };
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+
+Perspective_match_item = class
+ Menuaction "_Perspective Match"
+ "rotate, scale and skew one image to match another" {
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+ _b = find_image y;
+
+ ap1 = Mark_relative _a 0.1 0.1;
+ ap2 = Mark_relative _a 0.9 0.1;
+ ap3 = Mark_relative _a 0.1 0.9;
+ ap4 = Mark_relative _a 0.9 0.9;
+
+ bp1 = Mark_relative _b 0.1 0.1;
+ bp2 = Mark_relative _b 0.9 0.1;
+ bp3 = Mark_relative _b 0.1 0.9;
+ bp4 = Mark_relative _b 0.9 0.9;
+
+ _result = map_binary process x y
+ {
+ f1 = _a.width / _b.width;
+ f2 = _a.height / _b.height;
+
+ rl = sort_pts_clockwise [ap1, ap2, ap3, ap4];
+ pl = sort_pts_clockwise [bp1, bp2, bp3, bp4];
+
+ to = [
+ rl?0.left, rl?0.top,
+ rl?1.left, rl?1.top,
+ rl?2.left, rl?2.top,
+ rl?3.left, rl?3.top
+ ];
+
+ from = [
+ pl?0.left * f1, pl?0.top * f2,
+ pl?1.left * f1, pl?1.top * f2,
+ pl?2.left * f1, pl?2.top * f2,
+ pl?3.left * f1, pl?3.top * f2
+ ];
+
+ trans = perspective_transform to from;
+
+ process a b = transform 1 0 trans b2
+ {
+ b2 = resize Kernel_linear f1 f2 b, (f1 >= 1 && f2 >= 1) || (f1 >= 1 && f2 >= 1)
+ = resize Kernel_linear f1 1 b1
+ {b1 = resize Kernel_linear 1 f2 b;}
+ }
+ }
+ }
+ };
+
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+Perspective_item = class
+ Menuaction "Pe_rspective Distort"
+ "rotate, scale and skew an image with respect to defined points" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ // try to find an image ... for a group, get the first item
+ find_image x
+ = x, is_Image x
+ = find_image x?0, is_list x
+ = find_image x.value, is_class x && has_value x
+ = error "unable to find image";
+
+ _a = find_image x;
+
+ dir = Option "Select distort direction" [ "Distort to points", "Distort to corners" ] 1;
+ ap1 = Mark_relative _a 0.1 0.1;
+ ap2 = Mark_relative _a 0.9 0.1;
+ ap3 = Mark_relative _a 0.9 0.9;
+ ap4 = Mark_relative _a 0.1 0.9;
+
+ _result = map_unary process x
+ {
+ trans = [perspective_transform to from, perspective_transform from to]?(dir.value)
+ {
+ rl = sort_pts_clockwise [ap1, ap2, ap3, ap4];
+ to = [(rl?0).left, (rl?0).top, (rl?1).left, (rl?1).top,
+ (rl?2).left, (rl?2).top, (rl?3).left, (rl?3).top];
+ from=[0, 0, (_a.width - 1), 0,
+ (_a.width - 1), (_a.height - 1), 0, (_a.height - 1)];
+ }
+
+ process a = transform 1 0 trans a;
+ }
+ }
+ };
+
diff --git a/share/nip2/compat/8.6/_joe_utilities.def b/share/nip2/compat/8.6/_joe_utilities.def
new file mode 100644
index 00000000..be7931df
--- /dev/null
+++ b/share/nip2/compat/8.6/_joe_utilities.def
@@ -0,0 +1,705 @@
+/* ******Functions included in start/_NG_utilities.def:******
+ *
+ * so_balance ref_meanmax im1 im2 mask blur gauss *
+ * nonzero_mean im = no_out *
+ * so_meanmax im = result *
+ * so_calculate ref_meanmax im mask = result *
+ * simple_frame frame im_w im_h ov cs ms bf option = result *
+ * corner_frame frame im_w im_h ov cs ms bf = result *
+ * build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result *
+ * complex_frame frame im_w im_h ov cs es ms bf option= result *
+ * complex_edge ra rb t bl d = rc *
+ * frame_lr_min r_l r_r target bw = result *
+ * frame_tb_min r_t r_b target bw = result *
+ * frame_position_image im ref os colour= result *
+ * merge_array bw arr = result *
+ * merge_to_scale im target blend dir = result *
+ * select_ellipse line width = mask *
+ * select_tetragon p1 p2 p3 p4 = mask *
+ * select_polygon pt_list = mask *
+ * perspective_transform to from = trans'' *
+ * sort_pts_clockwise l = l'' *
+ */
+
+/* Called from:
+* _NG_Extra.def Clone_area_item
+*/
+so_balance ref_meanmax im1 im2 mask gauss
+ = result
+ {
+ //ref_meanmax = so_meanmax im1;
+ so_values = so_calculate ref_meanmax im2 mask;
+ im2_cor_a = clip2fmt im2.format im2'', has_member "format" im2
+ = im2''
+ {im2'' = im2 * (so_values?0) + (so_values?1);}
+ // Option to convert replacement image to scaled gaussian noise
+ im2_cor = im2_cor_a, gauss == false
+ = clip2fmt im2_cor_a.format gauss_im
+ {gauss_im =
+ gaussnoise im2_cor_a.width im2_cor_a.height ref_meanmax?0
+(deviation im2_cor_a);}
+ result = im_blend (get_image mask) (get_image
+im2_cor) (get_image im1);
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the mean of the non zero pixels.
+ *
+ * Called from:
+ * _NG_utilities so_meanmax
+ */
+nonzero_mean im = no_out
+ {
+ zero_im = (im == 0);
+ zero_mean = mean zero_im;
+ no_mean = mean im;
+ no_out = no_mean/(1 - (zero_mean/255));
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the max and nonzero mean of an image
+ *
+ * Called from:
+ * _NG_utilities so_balance
+ * _NG_utilities so_calculate
+ * _NG_Extra.def Clone_area_item
+ * _NG_Extra.def Balance_item.Balance_find_item
+ */
+so_meanmax im = result
+ {
+ mean_of_im = nonzero_mean im;
+ adjusted_im = im - mean_of_im;
+ max_of_im = max adjusted_im;
+
+ result = [mean_of_im, max_of_im];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Calculates the scale and offset required to match a reference mean and max
+ *
+ * Called from:
+ * _NG_utilities so_balance
+ * _NG_Extra.def Balance_item.Balance_find_item
+ */
+so_calculate ref_meanmax im mask = result
+ {
+ im' = if mask then im else 0;
+ im_values = so_meanmax im';
+
+ mean_of_ref = ref_meanmax?0;
+ mean_of_im = im_values?0;
+
+ max_of_ref = ref_meanmax?1;
+ max_of_im = im_values?1;
+
+ scale = (max_of_ref)/(max_of_im);
+ offset = mean_of_ref - (mean_of_im * scale);
+ result = [ scale, offset ];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Extends or shortens the central sections of a simple frame to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Simple_frame_item
+ */
+simple_frame frame im_w im_h ov cs ms bf option = result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+ ms'' = (1 - cs);
+
+ //Regions
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl, option == true
+ = Region_relative frame cs' 0 cs cs;
+ r_bl = Region_relative frame 0 cs' cs cs;
+ r_br = fliplr r_bl, option == true
+ = Region_relative frame cs' cs' cs cs;
+
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = Region_relative frame ms' ms'' ms cs;
+ r_ml = Region_relative frame 0 ms' cs ms;
+ r_mr = fliplr r_ml, option == true
+ = Region_relative frame ms'' ms' cs ms;
+
+ result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Copies and extends a simple frame corner to produce a complete frame to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Frame_corner_item
+ */
+corner_frame frame im_w im_h ov cs ms bf = result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+
+ //Regions
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl;
+ r_bl = fliptb r_tl;
+ r_br = fliplr r_bl;
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = fliptb r_mt;
+ r_ml = Region_relative frame 0 ms' cs ms;;
+ r_mr = fliplr r_ml;
+ result = build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Completes the frame building process for simple_frame and corner_frame.
+ *
+ * _NG_utilities simple_frame
+ * _NG_utilities corner_frame
+ */
+build_frame r_tl r_tr r_bl r_br r_mt r_mb r_ml r_mr im_w im_h ov bf = result
+ {
+ //Find pixel thickness of frames section
+ s_width = r_ml.width - mean (im_profile (map_unary fliplr (r_ml.value)?0) 1);
+ s_height = r_mt.height - mean (im_profile (map_unary fliptb (r_mt.value)?0) 0);
+
+ w_target = im_w + (2 * (s_width - ov));
+ h_target = im_h + (2 * (s_height - ov));
+
+ blend = bf * r_tl.width;
+
+ cw_target = w_target - (2 * r_tl.width) + (2 * blend), w_target > (2 * r_tl.width)
+ = w_target;
+ ch_target = h_target - (2 * r_tl.height) + (2 * blend), h_target > (2 * r_tl.height)
+ = h_target;
+
+ //Use regions to produce sections
+ top = merge_to_scale r_mt cw_target blend 0;
+ bottom = merge_to_scale r_mb cw_target blend 0;
+ left = merge_to_scale r_ml ch_target blend 1;
+ right = merge_to_scale r_mr ch_target blend 1;
+ middle = Image
+ (image_new cw_target ch_target left.bands left.format left.coding left.type 0 0 0);
+
+ //Build sections into full frame.
+ row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_tl, top, r_tr]];
+ row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[left, middle, right]];
+ row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_bl, bottom, r_br]];
+
+ result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
+ = merge_array blend [[row_1], [row_2], [row_3]];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Extends or shortens the central sections of a frame, preserving any central details on each
+ * edge, to fit round a given image.
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Complex_frame_item
+ */
+complex_frame frame im_w im_h ov cs es ms bf option= result
+ {
+ cs' = (1 - cs);
+ ms' = (0.5 - (ms/2));
+ es' = (0.25 - (es/2));
+
+ r_tl = Region_relative frame 0 0 cs cs;
+ r_tr = fliplr r_tl, option == true
+ = Region_relative frame cs' 0 cs cs;
+ r_bl = Region_relative frame 0 cs' cs cs;
+ r_br = fliplr r_bl, option == true
+ = Region_relative frame cs' cs' cs cs;
+
+ r_mt = Region_relative frame ms' 0 ms cs;
+ r_mb = Region_relative frame ms' cs' ms cs;
+ r_ml = Region_relative frame 0 ms' cs ms;
+ r_mr = fliplr r_ml, option == true
+ = Region_relative frame cs' ms' cs ms;
+
+ r_et = Region_relative frame es' 0 es cs;
+ r_eb = Region_relative frame es' cs' es cs;
+ r_el = Region_relative frame 0 es' cs es;
+ r_er = fliplr r_el, option == true
+ = Region_relative frame cs' es' cs es;
+
+ //Find pixel thickness of frames section
+ s_width = r_el.width - mean (im_profile (map_unary fliplr (r_el.value)?0) 1);
+ s_height = r_et.height - mean (im_profile (map_unary fliptb (r_et.value)?0) 0);
+
+ w_target = im_w + (2 * (s_width - ov));
+ h_target = im_h + (2 * (s_height - ov));
+ min_size = foldr1 min_pair [r_tl.width, r_tl.height,
+ r_mt.width, r_mt.height,
+ r_et.width, r_et.height];
+ blend = bf * min_size;
+
+ cw_target = w_target - (2 * r_tl.width) + (2 * blend);
+ ch_target = h_target - (2 * r_tl.height) + (2 * blend);
+
+ top = complex_edge r_mt r_et cw_target blend 0;
+ bottom = complex_edge r_mb r_eb cw_target blend 0;
+ left = complex_edge r_ml r_el ch_target blend 1;
+ right = complex_edge r_mr r_er ch_target blend 1;
+ middle = Image
+ (image_new top.width left.height left.bands left.format left.coding left.type 0 0 0);
+
+ //Build regions into full frame.
+ row_1 = frame_lr_min r_tl r_tr w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_tl, top, r_tr]];
+ row_2 = frame_lr_min left right w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[left, middle, right]];
+ row_3 = frame_lr_min r_bl r_br w_target blend, ( w_target < (r_tl.width * 2))
+ = merge_array blend [[r_bl, bottom, r_br]];
+ result = frame_tb_min row_1 row_3 h_target blend, (h_target < (r_tl.height * 2))
+ = merge_array blend [[row_1], [row_2], [row_3]];
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+/* Function called by complex frame, used to produce section
+ *
+ * Called from:
+ * _NG_utilities.def complex_frame
+ */
+complex_edge ra rb t bl d = rc
+ {
+ e1 = ceil (ra.width - t)/2, d == 0
+ = 0;
+ e2 = 0, d == 0
+ = ceil (ra.height - t)/2;
+ e3 = t, d == 0
+ = ra.width;
+ e4 = ra.height, d == 0
+ = t;
+
+ check = ra.width, d == 0;
+ = ra.height;
+
+ rai = get_image ra;
+
+ t2 = (t - ra.width + (2 * bl))/2, d == 0
+ = (t - ra.height + (2 * bl))/2;
+
+ rc = ra , t <= 0
+ = Image (im_extract_area rai e1 e2 e3 e4), t <= check
+ = merge_array bl [[rb',ra,rb']], d == 0
+ = merge_array bl [[rb'],[ra],[rb']]
+ {rb' = merge_to_scale rb t2 bl d;}
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Blends two images left/right to produce an image a specific width.
+ *
+ * _NG_utilities build_frame
+ * _NG_utilities complex_frame
+ */
+frame_lr_min r_l r_r target bw = result
+ {
+ //Calculating the new widh required for each image.
+ no = (target/2 + bw);
+ n_w = no, (r_l.width > no)
+ = r_l.width;
+
+ //Removing excess from what will be the middle of the final image.
+ n_l = im_extract_area r_l.value 0 0 n_w r_l.height;
+ n_r = im_extract_area r_r.value (r_r.width - n_w) 0 n_w r_l.height;
+
+ //Merge the two image together with a bw*2 pixel overlap.
+ result = Image (im_lrmerge n_l n_r ((bw*2) - n_w) 0 bw);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Blends two images top/bottom to produce an image a specific width.
+ *
+ * _NG_utilities build_frame
+ * _NG_utilities complex_frame
+ */
+frame_tb_min r_t r_b target bw = result
+ {
+ //Calculating the new height required for each image.
+ no = (target/2 + bw);
+ n_h = no, (r_t.height > no)
+ = r_t.height;
+
+ //Removing excess from what will be the middle of the final image.
+ n_t = im_extract_area r_t.value 0 0 r_t.width n_h;
+ n_b = im_extract_area r_b.value 0 (r_b.height - n_h) r_b.width n_h;
+
+ //Merge the two image together with a 50 pixel overlap.
+ result = Image (im_tbmerge n_t n_b 0 ((bw*2) -n_h) bw);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Resixe canvas of an image to accomodate a frame and possible mount
+ *
+ * Called from:
+ * _NG_Extra.def Frame_item.Frame_corner_item
+ * _NG_Extra.def Frame_item.Simple_frame_item
+ * _NG_Extra.def Frame_item.Complex_frame_item
+ */
+frame_position_image im ref os colour= result
+ {
+ background = image_new ref.width ref.height
+ im.bands im.format im.coding im.type colour 0 0;
+
+ result = insert_noexpand xp yp im background
+ {
+ xp = (ref.width - im.width)/2;
+ yp = (ref.height - im.height - os)/2;
+ }
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Merges an array of images together according to blend width bw
+ *
+ * Called from:
+ * _NG_Utilites.def build_frame
+ * _NG_Utilites.def complex_frame
+ * _NG_Utilites.def complex_edge
+ */
+merge_array bw arr = result
+ {
+ merge_lr bw im1 im2 = im3
+ {
+ bw' = get_header "Xsize" (get_image im1);
+ bw'' = -(bw' - bw);
+ im3 = im_lrmerge (get_image im1) (get_image im2) bw'' 0 bw;
+ }
+ merge_tb bw im1 im2 = im3
+ {
+ bw' = get_header "Ysize" (get_image im1);
+ bw'' = -(bw' - bw);
+ im3 = im_tbmerge (get_image im1) (get_image im2) 0 bw'' bw;
+ }
+
+ im_out = (image_set_origin 0 0 @
+ foldl1 (merge_tb bw) @
+ map (foldl1 (merge_lr bw))) arr;
+ result = Image im_out;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Repeatably top/bottom add clones of im, with a defined overlap, until final height > target
+ *
+ * Called from:
+ * _NG_Utilites.def build_frame
+ * _NG_Utilites.def complex_edge
+ */
+merge_to_scale im target blend dir = result
+ {
+ blend' = floor blend;
+
+ //allow fir lr or tb process
+ var_a = im.width, dir == 0
+ = im.height;
+
+ var_w = im.width, dir == 1
+ = target, target > blend'
+ = blend';
+ var_h = im.height, dir == 0
+ = target, target > blend'
+ = blend';
+
+ //total numner of copies of im requires, taking overlap into account.
+ no_loops = ceil ((log ((target - blend')/(var_a - blend')))/(log 2));
+
+ process im no = result
+ {
+ pr_a = get_header "Xsize" (get_image im), dir == 0
+ = get_header "Ysize" (get_image im);
+ pr_b = -(pr_a - blend' + 1);
+
+ im' = im_lrmerge (get_image im) (get_image im) pr_b 0 blend', dir == 0
+ = im_tbmerge (get_image im) (get_image im) 0 pr_b blend';
+ no' = no - 1;
+
+ result = im', no' < 1
+ = process im' no';
+ }
+
+ im_tmp = im.value, var_a > target
+ = process im no_loops;
+
+ result = Image (im_extract_area (get_image im_tmp) 0 0 var_w var_h);
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects an elispe based on a line and a width
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Elipse
+ */
+select_ellipse line width = mask
+ {
+ im = Image (get_image line);
+
+ //Make a 2 band image whose value equals its coordinates.
+ im_coor = Image (make_xy im.width im.height);
+
+ //Adjust the values to center tham on (line.left, line.top)
+ im_cent = im_coor - Vector [line.left,line.top];
+
+ w = line.width;
+ h = line.height;
+
+ angle = 270, w == 0 && h < 0
+ = 90, w == 0 && h >= 0
+ = 360 + atan (h/w), w > 0 && h < 0
+ = atan (h/w), w > 0 && h >= 0
+ = 180 + atan (h/w);
+
+ a = ( (h ** 2) + (w ** 2) )**0.5;
+ b = a * width;
+
+ x' = ( (cos angle) * im_cent?0) + ( (sin angle) * im_cent?1);
+ y' = ( (cos angle) * im_cent?1) - ( (sin angle) * im_cent?0);
+
+ mask = ( (b**2) * (x'**2) ) + ( (a**2) * (y'**2) ) <= (a * b)**2;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Tetragon
+ * _NG_Extra.def Perspective_item
+ */
+select_tetragon p1 p2 p3 p4 = mask
+ {
+ //Put points in clockwise order starting at the top left.
+ pt_list = sort_pts_clockwise [p1, p2, p3, p4];
+
+ pair_list = [
+ [ pt_list?0, pt_list?1 ],
+ [ pt_list?1, pt_list?2 ],
+ [ pt_list?2, pt_list?3 ],
+ [ pt_list?3, pt_list?0 ] ];
+
+ //Make xy image the same size as p1.image;
+ im_xy = Image (make_xy p1.image.width p1.image.height);
+ white = Image (image_new p1.image.width p1.image.height 1 0 Image_coding.NOCODING 1 255 0 0);
+
+ mask = foldl process white pair_list;
+
+ /* Treat each pair of point as a vector going from p1 to p2,
+ * then select all to right of line. This is done for each pair,
+ * the results are all combined to select the area defined by
+ * the four points.
+ */
+ process im_in pair = im_out
+ {
+ x = (pair?0).left;
+ y = (pair?0).top;
+ x'= (pair?1).left;
+ y'= (pair?1).top;
+
+ w = x' - x;
+ h = y' - y;
+
+ m = 0, x == x'
+ = (y-y')/(x-x');
+ c = 0, x == x'
+ = ((y*x') - (y'*x))/(x' - x);
+
+ mask= im_xy?1 - (im_xy?0 * m) >= c, w > 0
+ = im_xy?1 - (im_xy?0 * m) <= c, w < 0
+ = im_xy?0 <= x, w == 0 && h > 0
+ = im_xy?0 >= x;
+
+ im_out = im_in & mask;
+ }
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Select_item.Polygon
+ */
+select_polygon pt_list = mask
+ {
+ group_check = is_Group pt_list;
+ pt_l = pt_list.value, group_check
+ = pt_list;
+
+ im = Image (get_image (pt_l?0));
+ im_xy = Image (make_xy im.width im.height);
+ black = Image (image_new im_xy.width im_xy.height 1 0 Image_coding.NOCODING 1 0 0 0);
+
+ x = im_xy?0;
+ y = im_xy?1;
+
+ pt_l' = grp_trip pt_l;
+
+ mask = foldl process black pt_l';
+
+ /*Takes a group adds the first two the end and then creates a lists of
+ *lists [[a, b, c], [b, c, d] .... [x, a, b]]
+ */
+ grp_trip l = l''
+ {
+ px = take 2 l;
+ l' = join l px;
+ start = [(take 3 l')];
+ rest = drop 3 l';
+
+ process a b = c
+ {
+ x = (last a)?1;
+ x'= (last a)?2;
+ x'' = [[x, x', b]];
+ c = join a x'';
+ }
+
+ l'' = foldl process start rest;
+ };
+
+ process im_in triplet = im_out
+ {
+ p1 = triplet?0;
+ p2 = triplet?1;
+ p3 = triplet?2;
+
+ //check for change in x direction between p1-p2 and p2 -p3
+ dir_1 = sign (p2.left - p1.left);
+ dir_2 = sign (p3.left - p2.left);
+ dir = dir_1 + dir_2;
+
+ //define min x limit.
+ min_x = p1.left, p1.left < p2.left
+ = p2.left + 1, dir != 0
+ = p2.left;
+
+ //define max x limit.
+ max_x = p1.left, p1.left > p2.left
+ = p2.left - 1, dir != 0
+ = p2.left;
+
+ //equation of line defined by p1 and p2
+ m = line_m p1 p2;
+ c = line_c p1 p2;
+
+ //Every thing below the line
+ im_test = ((y >= (m * x) + c) & (x >= min_x) & (x <= max_x));
+
+ im_out = im_in ^ im_test;
+ }
+
+ line_c p1 p2 = c
+ {m = line_m p1 p2;
+ c = p1.top - (m * p1.left);};
+
+ line_m p1 p2 = (p2.top - p1.top)/(p2.left - p1.left), p2.left != p1.left
+ = 0;
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Selects a tetragon based on four points.
+ *
+ * Called from:
+ * _NG_Extra.def Perspective_match_item
+ * _NG_Extra.def Perspective_item
+ */
+perspective_transform to from = trans''
+ {
+ /*
+ * Tramsformation matrix is calculated on the bases of the following functions:
+ * x' = c0x + c1y + c2xy + c3
+ * y' = c4x + c5y + c6xy + c7
+ *
+ * The functions used in vips im_transform works based on the functions:
+ * x = x' + b0 + b2x' + b4y' + b6x'y'
+ * y = y' + b1 + b3x' + b5y' + b7x'y'
+ *
+ * and is applied in the form of the matrix:
+ *
+ * [[b0, b1],
+ * [b2, b3],
+ * [b4, b5],
+ * [b6, b7]]
+ *
+ * Therefore our required calculated matrix will be
+ *
+ * [[ c3 , c7],
+ * [(c0 - 1) , c4],
+ * [ c1 , (c5 - 1)],
+ * [ c2 , c6]]
+ *
+ * to = [x1, y1, x2, y2, x3, y3, x4, y4]
+ * from = [x1', y1', x2', y2', x3', y3', x4', y4']
+ * trans = [[c0], [c1], [c2], [c3], [c4], [c5], [c6], [c7]]
+ *
+ */
+
+ to' = Matrix
+ [[to?0, to?1, ((to?0)*(to?1)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?0, to?1, ((to?0)*(to?1)), 1],
+ [to?2, to?3, ((to?2)*(to?3)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?2, to?3, ((to?2)*(to?3)), 1],
+ [to?4, to?5, ((to?4)*(to?5)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?4, to?5, ((to?4)*(to?5)), 1],
+ [to?6, to?7, ((to?6)*(to?7)), 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, to?6, to?7, ((to?6)*(to?7)), 1]];
+
+ from' = Matrix (transpose [from]);
+
+ to'' = to' ** (-1);
+
+ trans = to'' * from';
+ trans' = trans.value;
+ trans''= Matrix [[(trans'?3)?0, (trans'?7)?0 ],
+ [((trans'?0)?0 - 1), (trans'?4)?0 ],
+ [(trans'?1)?0, ((trans'?5)?0 - 1)],
+ [(trans'?2)?0, (trans'?6)?0 ]];
+ };
+
+//////////////////////////////////////////////////////////////////////////////
+/* Sort a list of points into clockwise order.
+ *
+ * Called from:
+ * _NG_utilities.def select_tetragon
+ * _NG_Extra.def Perspective_match_item
+ * _NG_Extra.def Perspective_item
+ */
+sort_pts_clockwise l = l''
+ {
+ // sort functions:
+ f_top a b = a.top < b.top;
+ f_left a b = a.left < b.left;
+ f_right a b = a.left > b.left;
+
+ l' = sortc f_top l;
+ l'_a = take 2 l';
+ l'_b = drop 2 l';
+
+ l''_a = sortc f_left l'_a;
+ l''_b = sortc f_right l'_b;
+ l'' = join l''_a l''_b;
+ };
+
+Mount_options _ctype _ppcm = class
+ {
+ _vislevel = 3;
+ apply = Toggle "Apply mount options" false;
+ ls = Expression "Lower mount section bigger by (cm)" 0;
+ mount_colour = Colour _ctype [0, 0, 0];
+ _los = ls.expr * _ppcm;
+ };
+
+Frame_variables comp = class
+ {
+ _vislevel = 3;
+
+ scale_factor = Expression "scale the size of the frame by" 1;
+
+ /* These sliders define the fraction of the frames width or height is extracted
+ * to produce each of the particular regions.
+ */
+ corner_section = Scale "Corner section" 0.1 1 0.5;
+ edge_section = Scale "Edge section" 0.1 1 0.2, comp > 0
+ = "Only required for complex frames";
+ middle_section = Scale "Middle section" 0.1 1 0.2;
+ blend_fraction = Scale "Blend fraction" 0.1 0.9 0.1;
+ option = Toggle "Use mirror of left-side to make right" true;
+ };
+
diff --git a/share/nip2/compat/8.6/_list.def b/share/nip2/compat/8.6/_list.def
new file mode 100644
index 00000000..d2ef4a1f
--- /dev/null
+++ b/share/nip2/compat/8.6/_list.def
@@ -0,0 +1,482 @@
+/* any l: or all the elements of list l together
+ *
+ * any (map (equal 0) list) == true, if any element of list is zero.
+ * any :: [bool] -> bool
+ */
+any = foldr logical_or false;
+
+/* all l: and all the elements of list l together
+ *
+ * all (map (==0) list) == true, if every element of list is zero.
+ * all :: [bool] -> bool
+ */
+all = foldr logical_and true;
+
+/* concat l: join a list of lists together
+ *
+ * concat ["abc","def"] == "abcdef".
+ * concat :: [[*]] -> [*]
+ */
+concat l = foldr join [] l;
+
+/* delete eq x l: delete the first x from l
+ *
+ * delete equal 'b' "abcdb" == "acdb"
+ * delete :: (* -> bool) -> * -> [*] -> [*]
+ */
+delete eq a l
+ = [], l == []
+ = y, eq a b
+ = b : delete eq a y
+{
+ b:y = l;
+}
+
+/* difference eq a b: delete b from a
+ *
+ * difference equal "asdf" "ad" == "sf"
+ * difference :: (* -> bool) -> [*] -> [*] -> [*]
+ */
+difference = foldl @ converse @ delete;
+
+/* drop n l: drop the first n elements from list l
+ *
+ * drop 3 "abcd" == "d"
+ * drop :: num -> [*] -> [*]
+ */
+drop n l
+ = l, n <= 0 || l == []
+ = drop (n - 1) (tl l);
+
+/* dropwhile fn l: drop while fn is true
+ *
+ * dropwhile is_digit "1234pigs" == "pigs"
+ * dropwhile :: (* -> bool) -> [*] -> [*]
+ */
+dropwhile fn l
+ = [], l == []
+ = dropwhile fn x, fn a
+ = l
+{
+ a:x = l;
+}
+
+/* extract n l: extract element at index n from list l
+ */
+extract = converse subscript;
+
+/* filter fn l: return all elements of l for which predicate fn holds
+ *
+ * filter is_digit "1one2two3three" = "123"
+ * filter :: (* -> bool) -> [*] -> [*]
+ */
+filter fn l
+ = foldr addif [] l
+{
+ addif x l
+ = x : l, fn x;
+ = l;
+}
+
+/* flatten x: flatten a list of lists of things into a simple list
+ *
+ * flatten :: [[*]] -> [*]
+ */
+flatten x
+ = foldr flat [] x, is_list x
+ = x
+{
+ flat x sofar
+ = foldr flat sofar x, is_list x
+ = x : sofar;
+}
+
+/* foldl fn st l: fold list l from the left with function fn and start st
+ *
+ * Start from the left hand end of the list (unlike foldr, see below).
+ * foldl is less useful (and much slower).
+ *
+ * foldl fn start [a,b .. z] = ((((st fn a) fn b) ..) fn z)
+ * foldl :: (* -> ** -> *) -> * -> [**] -> *
+ */
+foldl fn st l
+ = st, l == []
+ = foldl fn (fn st x) xs
+{
+ x:xs = l;
+}
+
+/* foldl1 fn l: like foldl, but use the 1st element as the start value
+ *
+ * foldl1 fn [1,2,3] == ((1 fn 2) fn 3)
+ * foldl1 :: (* -> * -> *) -> [*] -> *
+ */
+foldl1 fn l
+ = [], l == []
+ = foldl fn x xs
+{
+ x:xs = l;
+}
+
+/* foldr fn st l: fold list l from the right with function fn and start st
+ *
+ * foldr fn st [a,b..z] = (a fn (b fn (.. (z fn st))))
+ * foldr :: (* -> ** -> **) -> ** -> [*] -> **
+ */
+foldr fn st l
+ = st, l == []
+ = fn x (foldr fn st xs)
+{
+ x:xs = l;
+}
+
+/* foldr1 fn l: like foldr, but use the last element as the start value
+ *
+ * foldr1 fn [1,2,3,4] == (1 fn (2 fn (3 fn 4)))
+ * foldr1 :: (* -> * -> *) -> [*] -> *
+ */
+foldr1 fn l
+ = [], l == []
+ = x, xs == []
+ = fn x (foldr1 fn xs)
+{
+ x:xs = l;
+}
+
+/* Search a list for an element, returning its index (or -1)
+ *
+ * index (equal 12) [13,12,11] == 1
+ * index :: (* -> bool) -> [*] -> real
+ */
+index fn list
+ = search list 0
+{
+ search l n
+ = -1, l == []
+ = n, fn x
+ = search xs (n + 1)
+ {
+ x:xs = l;
+ }
+}
+
+/* init l: remove last element of list l
+ *
+ * The dual of tl.
+ * init [1,2,3] == [1,2]
+ * init :: [*] -> [*]
+ */
+init l
+ = error "init of []", l == [];
+ = [], tl l == [];
+ = x : init xs
+{
+ x:xs = l;
+}
+
+/* iterate f x: repeatedly apply f to x
+ *
+ * return the infinite list [x, f x, f (f x), ..].
+ * iterate (multiply 2) 1 == [1, 2, 4, 8, 16, 32, 64 ... ]
+ * iterate :: (* -> *) -> * -> [*]
+ */
+iterate f x = x : iterate f (f x);
+
+/* join_sep sep l: join a list with a separator
+ *
+ * join_sep ", " (map print [1 .. 4]) == "1, 2, 3, 4"
+ * join_sep :: [*] -> [[*]] -> [*]
+ */
+join_sep sep l
+ = foldl1 fn l
+{
+ fn a b = a ++ sep ++ b;
+}
+
+/* last l: return the last element of list l
+ *
+ * The dual of hd. last [1,2,3] == 3
+ * last :: [*] -> [*]
+ */
+last l
+ = error "last of []", l == []
+ = x, xs == []
+ = last xs
+{
+ x:xs = l;
+}
+
+/* len l: length of list l
+ * (see also is_list_len and friends in predicate.def)
+ *
+ * len :: [*] -> num
+ */
+len l
+ = 0, l == []
+ = 1 + len (tl l);
+
+/* limit l: return the first element of l which is equal to its predecessor
+ *
+ * useful for checking for convergence
+ * limit :: [*] -> *
+ */
+limit l
+ = error "incorrect use of limit",
+ l == [] || tl l == [] || tl (tl l) == []
+ = a, a == b
+ = limit (b : x)
+{
+ a:b:x = l;
+}
+
+/* Turn a function of n args into a function which takes a single arg of an
+ * n-element list.
+ */
+list_1ary fn x = fn x?0;
+list_2ary fn x = fn x?0 x?1;
+list_3ary fn x = fn x?0 x?1 x?2;
+list_4ary fn x = fn x?0 x?1 x?2 x?3;
+list_5ary fn x = fn x?0 x?1 x?2 x?3 x?4;
+list_6ary fn x = fn x?0 x?1 x?2 x?3 x?4 x?5;
+list_7ary fn x = fn x?0 x?1 x?2 x?3 x?4 x?5 x?6;
+
+/* map fn l: map function fn over list l
+ *
+ * map :: (* -> **) -> [*] -> [**]
+ */
+map f l
+ = [], l == [];
+ = f (hd l) : map f (tl l);
+
+/* map2 fn l1 l2: map two lists together with fn
+ *
+ * map2 :: (* -> ** -> ***) -> [*] -> [**] -> [***]
+ */
+map2 fn l1 l2 = map (list_2ary fn) (zip2 l1 l2);
+
+/* map3 fn l1 l2 l3: map three lists together with fn
+ *
+ * map3 :: (* -> ** -> *** -> ****) -> [*] -> [**] -> [***] -> [****]
+ */
+map3 fn l1 l2 l3 = map (list_3ary fn) (zip3 l1 l2 l3);
+
+/* member l x: true if x is a member of list l
+ *
+ * is_digit == member "0123456789"
+ * member :: [*] -> * -> bool
+ */
+member l x = any (map (equal x) l);
+
+/* merge b l r: merge two lists based on a bool list
+ *
+ * merge :: [bool] -> [*] -> [*] -> [*]
+ */
+merge p l r
+ = [], p == [] || l == [] || r == []
+ = a : merge z x y, c
+ = b : merge z x y
+{
+ a:x = l;
+ b:y = r;
+ c:z = p;
+}
+
+/* mkset eq l: remove duplicates from list l using equality function
+ *
+ * mkset :: (* -> bool) -> [*] -> [*]
+ */
+mkset eq l
+ = [], l == []
+ = a : filter (not @ eq a) (mkset eq x)
+{
+ a:x = l;
+}
+
+/* postfix l r: add r to the end of list l
+ *
+ * The dual of ':'.
+ * postfix :: [*] -> ** -> [*,**]
+ */
+postfix l r = l ++ [r];
+
+/* repeat x: make an infinite list of xes
+ *
+ * repeat :: * -> [*]
+ */
+repeat x = map (const x) [1..];
+
+/* replicate n x: make n copies of x in a list
+ *
+ * replicate :: num -> * -> [*]
+ */
+replicate n x = take n (repeat x);
+
+/* reverse l: reverse list l
+ *
+ * reverse :: [*] -> [*]
+ */
+reverse l = foldl (converse cons) [] l;
+
+/* scanl fn st l: apply (foldl fn r) to every initial segment of a list
+ *
+ * scanl add 0 [1,2,3] == [1,3,6]
+ * scanl :: (* -> ** -> *) -> * -> [**] -> [*]
+ */
+scanl fn st l
+ = st, l == []
+ = st' : scanl fn st' xs
+{
+ x:xs = l;
+ st' = fn st x;
+}
+
+/* sort l: sort list l into ascending order
+ *
+ * sort :: [*] -> [*]
+ */
+sort l = sortc less_equal l;
+
+/* sortc comp l: sort list l into order using a comparision function
+ *
+ * Uses merge sort (n log n behaviour)
+ * sortc :: (* -> * -> bool) -> [*] -> [*]
+ */
+sortc comp l
+ = l, n <= 1
+ = merge (sortc comp (take n2 l)) (sortc comp (drop n2 l))
+{
+ n = len l;
+ n2 = (int) (n / 2);
+
+ /* merge l1 l2: merge sorted lists l1 and l2 to make a single
+ * sorted list
+ */
+ merge l1 l2
+ = l2, l1 == []
+ = l1, l2 == []
+ = a : merge x (b : y), comp a b
+ = b : merge (a : x) y
+ {
+ a:x = l1;
+ b:y = l2;
+ }
+}
+
+/* sortpl pl l: sort by a list of predicates
+ *
+ * sortpl :: (* -> bool) -> [*] -> [*]
+ */
+sortpl pl l
+ = sortc (test pl) l
+{
+ /* Comparision function ... put true before false, if equal move on to
+ * the next predicate.
+ */
+ test pl a b
+ = true, pl == []
+ = ta, ta != tb
+ = test (tl pl) a b
+ {
+ ta = pl?0 a;
+ tb = pl?0 b;
+ }
+}
+
+/* sortr l: sort list l into descending order
+ *
+ * sortr :: [*] -> [*]
+ */
+sortr l = sortc more l;
+
+/* split fn l: break a list into sections separated by many fn
+ *
+ * split is_space " hello world " == ["hello", "world"]
+ * split is_space " " == []
+ * split :: (* -> bool) -> [*] -> [[*]]
+ */
+split fn l
+ = [], l == [] || l' == []
+ = head : split fn tail
+{
+ nfn = not @ fn;
+
+ l' = dropwhile fn l;
+ head = takewhile nfn l';
+ tail = dropwhile nfn l';
+}
+
+/* splits fn l: break a list into sections separated by a single fn
+ *
+ * split (equal ',') ",,1" == ["", "", "1"]
+ * split :: (* -> bool) -> [*] -> [[*]]
+ */
+splits fn l
+ = [], l == []
+ = head : splits fn tail
+{
+ fn' = not @ fn;
+ dropif x
+ = [], x == []
+ = tl x;
+
+ head = takewhile fn' l;
+ tail = dropif (dropwhile fn' l);
+}
+
+/* splitpl fnl l: split a list up with a list of predicates
+ *
+ * splitpl [is_digit, is_letter, is_digit] "123cat" == ["123", "cat", []]
+ * splitpl :: [* -> bool] -> [*] -> [[*]]
+ */
+splitpl fnl l
+ = l, fnl == []
+ = head : splitpl (tl fnl) tail
+{
+ head = takewhile (hd fnl) l;
+ tail = dropwhile (hd fnl) l;
+}
+
+/* split_lines n l: split a list into equal length lines
+ *
+ * split_lines 4 "1234567" == ["1234", "567"]
+ * splitl :: int -> [*] -> [[*]]
+ */
+split_lines n l
+ = [], l == []
+ = take n l : split_lines n (drop n l);
+
+/* take n l: take the first n elements from list l
+ * take :: num -> [*] -> [*]
+ */
+take n l
+ = [], n <= 0
+ = [], l == []
+ = hd l : take (n-1) (tl l);
+
+/* takewhile fn l: take from the front of a list while predicate fn holds
+ *
+ * takewhile is_digit "123onetwothree" == "123"
+ * takewhile :: (* -> bool) -> [*] -> [*]
+ */
+takewhile fn l
+ = [], l == []
+ = hd l : takewhile fn (tl l), fn (hd l)
+ = [];
+
+/* zip2 l1 l2: zip two lists together
+ *
+ * zip2 [1,2] ['a', 'b', 'c'] == [[1,'a'],[2,'b']]
+ * zip2 :: [*] -> [**] -> [[*,**]]
+ */
+zip2 l1 l2
+ = [], l1 == [] || l2 == []
+ = [hd l1, hd l2] : zip2 (tl l1) (tl l2);
+
+/* zip3 l1 l2 l3: zip three lists together
+ *
+ * zip3 [1,2] ['a', 'b', 'c'] [true] == [[1,'a',true]]
+ * zip3 :: [*] -> [**] ->[***] -> [[*,**,***]]
+ */
+zip3 l1 l2 l3
+ = [], l1 == [] || l2 == [] || l3 == []
+ = [hd l1, hd l2, hd l3] : zip3 (tl l1) (tl l2) (tl l3);
diff --git a/share/nip2/compat/8.6/_magick.def b/share/nip2/compat/8.6/_magick.def
new file mode 100644
index 00000000..01c22b18
--- /dev/null
+++ b/share/nip2/compat/8.6/_magick.def
@@ -0,0 +1,1107 @@
+/*
+
+ ImageMagick operations edited by Alan Gibson (aka "snibgo"; snibgo at earthling dot net).
+
+ 1-Apr-2014
+ Minor corrections to Geometry_widget and Alpha.
+ Added loads of widgets and Menuactions.
+ Not fully tested.
+ 5-Apr-2014
+ Many more menu actions.
+ Reorganised Magick menu.
+ 10-Apr-2014
+ Many more menu actions.
+ 11-Apr-2014 jcupitt
+ Split to separate _magick.def
+ Add 0-ary and 2-ary system
+ Put utility funcs into a Magick class
+ 11-Apr-2014 snibgo
+ Added VirtualPixelBack for cases where background is only relevant when VP=Background
+ 17-Apr-2014 snibgo
+ Many small changes.
+ 2-May-2014 jcupitt
+ Added Magick.version
+ 30-June-2014
+ Put single-quotes around command exe to help win
+ 1-July-2014
+ Automatically fall back to gm if we can't find convert
+ 17-July-2014
+ better GM support
+
+
+ Last update: 17-July-2014.
+
+ For details of ImageMagick operations, see http://www.imagemagick.org/script/command-line-options.php etc.
+
+*/
+
+/* Put these in a class to avoid filling the main namespace with IM stuff.
+ */
+
+Magick = class {
+
+ // first gm on path, or ""
+ gm_path = search_for "gm";
+
+ // first convert on $PATH, or ""
+ // we check for the convert we ship first
+ convert_path
+ = vips_convert, vips_convert != ""
+ = search_for "convert"
+ {
+ // the convert we ship with the vips binary on some platforms, or ""
+ vips_convert
+ = search (path_absolute convert)
+ {
+ vipshome = path_parse (expand "$VIPSHOME");
+ convert = vipshome ++ ["bin", "convert" ++ expand "$EXEEXT"];
+ }
+ }
+
+ use_gm_pref = Workspaces.Preferences.USE_GRAPHICSMAGICK;
+
+ // Are we in GM or IM mode?
+ use_gm
+ = true, use_gm_pref && gm_path != ""
+ = false, !use_gm_pref && convert_path != ""
+ = false, convert_path != ""
+ = true, gm_path != ""
+ = error "neither IM nor GM executable found";
+
+ command_path
+ = gm_path, use_gm
+ = convert_path;
+
+ // try to get the version as eg. [6, 7, 7, 10]
+ // GM versions are smaller, typically [1, 3, 18]
+ version
+ = map parse_int (split (member ".-") version_string)
+ {
+ [output] = vips_call "system"
+ ["'" ++ command_path ++ "' -version"] [$log=>true];
+ version_string
+ = (split (equal ' ') output)?1, use_gm
+ = (split (equal ' ') output)?2;
+ }
+
+ // make a command-line ... args is a [str] we join with spaces
+ command args
+ = "'" ++ command_path ++ "' " ++ join_sep " " args'
+ {
+ args'
+ = ["convert"] ++ args, use_gm
+ = args;
+ }
+
+ // capabilities ... different versions support different features, we
+ // turn features on and off based on these
+
+ // would probably be better to test for caps somehow
+ has_intensity
+ = false, use_gm
+ = version?0 > 6 || version?1 > 7;
+ has_channel
+ = false, use_gm
+ = version?0 > 6 || version?1 > 7;
+
+ system0 cmd = system_image0 cmd;
+ system cmd x = map_unary (system_image cmd) x;
+ system2 cmd x y = map_binary (system_image2 cmd) x y;
+ system3 cmd x y z = map_trinary (system_image3 cmd) x y z;
+
+ radius_widget = Scale "Radius" 0 100 10;
+ sigma_widget = Scale "Sigma" 0.1 10 1;
+ angle_widget = Scale "Angle (degrees)" (-360) 360 0;
+ text_widget = String "Text to draw" "AaBbCcDdEe";
+
+ gamma_widget = Scale "Gamma" 0 10 1;
+ colors_widget = Scale "Colors" 1 10 3;
+ resize_widget = Scale "Resize (percent)" 0 500 100;
+ fuzz_widget = Scale "Fuzz (percent)" 0 100 0;
+ blur_rad_widget = Scale "Radius (0=auto)" 0 100 0;
+
+ // a colour with no enclosing quotes ... use this if we know there are
+ // some quotes at an outer level
+ print_colour_nq triple
+ = concat ["#", concat (map fmt triple)]
+ {
+ fmt x = reverse (take 2 (reverse (print_base 16 (x + 256))));
+ }
+
+ // we need the quotes because # is the comment character in *nix
+ print_colour triple = "\"" ++ print_colour_nq triple ++ "\"";
+
+ Foreground triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-fill " ++ print_colour triple;
+
+ Colour_edit space triple = this.Foreground triple;
+ }
+ foreground_widget = Foreground [0, 0, 0];
+
+ GeneralCol triple = class
+ Colour "sRGB" triple {
+
+ _flag = print_colour_nq triple;
+
+ Colour_edit space triple = this.GeneralCol triple;
+ }
+ generalcol_widget = GeneralCol [0, 0, 0];
+
+ Background triple = class
+ Colour "sRGB" triple {
+
+ isNone = Toggle "None (transparent black)" false;
+
+ _flag = "-background " ++ if isNone then "None" else print_colour triple;
+
+ Colour_edit space triple = this.Background triple;
+ }
+ background_widget = Background [255, 255, 255];
+
+ Bordercol triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-bordercolor " ++ print_colour triple;
+
+ Colour_edit space triple = this.Bordercol triple;
+ }
+ bordercol_widget = Bordercol [0, 0, 0];
+
+ Mattecol triple = class
+ Colour "sRGB" triple {
+
+ _flag = "-mattecolor " ++ print_colour triple;
+
+ Colour_edit space triple = this.Mattecol triple;
+ }
+ mattecol_widget = Mattecol [189, 189, 189];
+
+ // FIXME: Undercolour, like many others, can have alpha channel.
+ // How does user input this? With a slider?
+ Undercol triple = class
+ Colour "sRGB" triple {
+
+ isNone = Toggle "None (transparent black)" true;
+
+ _flag = if isNone then "" else ("-undercolor " ++ print_colour triple);
+
+ Colour_edit space triple = this.Undercol triple;
+ }
+ undercol_widget = Undercol [0, 0, 0];
+
+ changeCol_widget = class {
+ _vislevel = 3;
+
+ colour = GeneralCol [0, 0, 0];
+ fuzz = fuzz_widget;
+ nonMatch = Toggle "change non-matching colours" false;
+ }
+
+ Alpha alpha = class
+ Option_string "Alpha" [
+ "On",
+ "Off",
+ "Set",
+ "Opaque",
+ "Transparent",
+ "Extract",
+ "Copy",
+ "Shape",
+ "Remove",
+ "Background"
+ ] alpha {
+
+ _flag = "-alpha " ++ alpha;
+
+ Option_edit caption labels value = this.Alpha labels?value;
+ }
+ alpha_widget = Alpha "On";
+
+ Antialias value = class
+ Toggle "Antialias" value {
+
+ _flag
+ = "-antialias", value
+ = "+antialias";
+
+ Toggle_edit caption value = this.Antialias value;
+ }
+ antialias_widget = Antialias true;
+
+ Builtin builtin = class
+ Option_string "Builtin" [
+ // See http://www.imagemagick.org/script/formats.php
+ "rose:",
+ "logo:",
+ "wizard:",
+ "granite:",
+ "netscape:"
+ ] builtin {
+
+ _flag = builtin;
+
+ Option_edit caption labels value = this.Builtin labels?value;
+ }
+ builtin_widget = Builtin "rose:";
+
+
+ channels_widget = class {
+ // FIXME? Can we grey-out alpha when we have no alpha channel,
+ // show CMY(K) instead of RGB(K) etc?
+ // Yes, perhaps we can create different widgets for RGB, RGBA, CMY, CMYK, CMYA, CMYKA.
+ ChanR valueR = class
+ Toggle "Red" valueR {
+
+ _flag
+ = "R", valueR
+ = "";
+
+ Toggle_edit caption valueR = this.ChanR valueR;
+ }
+ channelR = ChanR true;
+
+ ChanG valueG = class
+ Toggle "Green" valueG {
+
+ _flag
+ = "G", valueG
+ = "";
+
+ Toggle_edit caption valueG = this.ChanG valueG;
+ }
+ channelG = ChanG true;
+
+ ChanB valueB = class
+ Toggle "Blue" valueB {
+
+ _flag
+ = "B", valueB
+ = "";
+
+ Toggle_edit caption valueB = this.ChanB valueB;
+ }
+ channelB = ChanB true;
+
+ ChanK valueK = class
+ Toggle "Black" valueK {
+
+ _flag
+ = "K", valueK
+ = "";
+
+ Toggle_edit caption valueK = this.ChanK valueK;
+ }
+ channelK = ChanK true;
+
+ ChanA valueA = class
+ Toggle "Alpha" valueA {
+
+ _flag
+ = "A", valueA
+ = "";
+
+ Toggle_edit caption valueA = this.ChanA valueA;
+ }
+ channelA = ChanA false;
+
+ ChanSy valueSy = class
+ Toggle "Sync" valueSy {
+
+ _flag
+ = ",sync", valueSy
+ = "";
+
+ Toggle_edit caption valueSy = this.ChanSy valueSy;
+ }
+ channelSy = ChanSy true;
+
+ _rgbka = concat [channelR._flag,
+ channelG._flag,
+ channelB._flag,
+ channelK._flag,
+ channelA._flag
+ ];
+
+ _flag
+ = "", _rgbka == "" || !has_channel
+ = concat [ "-channel ",
+ _rgbka,
+ channelSy._flag
+ ];
+ }
+
+ ch_widget = channels_widget;
+
+ Colorspace colsp = class
+ Option_string "Colorspace" [
+ "CIELab",
+ "CMY",
+ "CMYK",
+ "Gray",
+ "HCL",
+ "HCLp",
+ "HSB",
+ "HSI",
+ "HSL",
+ "HSV",
+ "HWB",
+ "Lab",
+ "LCH",
+ "LCHab",
+ "LCHuv",
+ "LMS",
+ "Log",
+ "Luv",
+ "OHTA",
+ "Rec601Luma",
+ "Rec601YCbCr",
+ "Rec709Luma",
+ "Rec709YCbCr",
+ "RGB",
+ "scRGB",
+ "sRGB",
+ "Transparent",
+ "XYZ",
+ "YCbCr",
+ "YDbDr",
+ "YCC",
+ "YIQ",
+ "YPbPr",
+ "YUV"
+ ] colsp {
+
+ _flag = colsp;
+
+ Option_edit caption labels value = this.Colorspace labels?value;
+ }
+ colorspace_widget = Colorspace "sRGB";
+
+ Compose comp = class
+ Option_string "Compose method" [
+ "Atop",
+ "Blend",
+ "Blur",
+ "Bumpmap",
+ "ChangeMask",
+ "Clear",
+ "ColorBurn",
+ "ColorDodge",
+ "Colorize",
+ "CopyBlack",
+ "CopyBlue",
+ "CopyCyan",
+ "CopyGreen",
+ "Copy",
+ "CopyMagenta",
+ "CopyOpacity",
+ "CopyRed",
+ "CopyYellow",
+ "Darken",
+ "DarkenIntensity",
+ "DivideDst",
+ "DivideSrc",
+ "Dst",
+ "Difference",
+ "Displace",
+ "Dissolve",
+ "Distort",
+ "DstAtop",
+ "DstIn",
+ "DstOut",
+ "DstOver",
+ "Exclusion",
+ "HardLight",
+ "Hue",
+ "In",
+ "Lighten",
+ "LightenIntensity",
+ "LinearBurn",
+ "LinearDodge",
+ "LinearLight",
+ "Luminize",
+ "Mathematics",
+ "MinusDst",
+ "MinusSrc",
+ "Modulate",
+ "ModulusAdd",
+ "ModulusSubtract",
+ "Multiply",
+ "None",
+ "Out",
+ "Overlay",
+ "Over",
+ "PegtopLight",
+ "PinLight",
+ "Plus",
+ "Replace",
+ "Saturate",
+ "Screen",
+ "SoftLight",
+ "Src",
+ "SrcAtop",
+ "SrcIn",
+ "SrcOut",
+ "SrcOver",
+ "VividLight",
+ "Xor"
+ ] comp {
+
+ _flag = "-compose " ++ comp;
+
+ Option_edit caption labels value = this.Compose labels?value;
+ }
+ compose_widget = Compose "Over";
+ // FIXME: Some compose mehods (Displace, Distort, Mathematics) need a string.
+
+ // FIXME: we could use a class that does both -compose and -intensity, for methods LightenIntensity, DarkenIntensity, CopyOpacity, CopyBlack
+
+ coordinate_widget = class {
+ _vislevel = 3;
+
+ x = Expression "X" 0;
+ y = Expression "Y" 0;
+
+ _flag = concat [print x.expr, ",", print y.expr];
+ };
+
+ Distort distort = class
+ Option_string "Distort" [
+ "Affine",
+ "AffineProjection",
+ "ScaleRotateTranslate",
+ "SRT",
+ "Perspective",
+ "PerspectiveProjection",
+ "BilinearForward",
+ "BilinearReverse",
+ "Polynomial",
+ "Arc",
+ "Polar",
+ "DePolar",
+ "Barrel",
+ "BarrelInverse",
+ "Shepards",
+ "Resize"
+ ] distort {
+
+ _flag = distort;
+
+ Option_edit caption labels value = this.Distort labels?value;
+ }
+ distort_widget = Distort "SRT";
+
+ Dither dither = class
+ Option_string "Dither" [
+ "None",
+ "FloydSteinberg",
+ "Riemersma"
+ ] dither {
+
+ _flag = "-dither " ++ dither;
+
+ Option_edit caption labels value = this.Dither labels?value;
+ }
+ dither_widget = Dither "FloydSteinberg";
+
+ Evaluate eval = class
+ Option_string "Evaluate operation" [
+ "Abs",
+ "Add",
+ "AddModulus",
+ "And",
+ "Cos",
+ "Cosine",
+ "Divide",
+ "Exp",
+ "Exponential",
+ "GaussianNoise",
+ "ImpulseNoise",
+ "LaplacianNoise",
+ "LeftShift",
+ "Log",
+ "Max",
+ "Mean",
+ "Median",
+ "Min",
+ "MultiplicativeNoise",
+ "Multiply",
+ "Or",
+ "PoissonNoise",
+ "Pow",
+ "RightShift",
+ "Set",
+ "Sin",
+ "Sine",
+ "Subtract",
+ "Sum",
+ "Threshold",
+ "ThresholdBlack",
+ "ThresholdWhite",
+ "UniformNoise",
+ "Xor"
+ ] eval {
+
+ _flag = "-evaluate " ++ eval;
+
+ Option_edit caption labels value = this.Evaluate labels?value;
+ }
+ evaluate_widget = Evaluate "Add";
+
+ Filter filt = class
+ Option_string "Filter" [
+ "default",
+ "Bartlett",
+ "Blackman",
+ "Bohman",
+ "Box",
+ "Catrom",
+ "Cosine",
+ "Cubic",
+ "Gaussian",
+ "Hamming",
+ "Hann",
+ "Hermite",
+ "Jinc",
+ "Kaiser",
+ "Lagrange",
+ "Lanczos",
+ "Lanczos2",
+ "Lanczos2Sharp",
+ "LanczosRadius",
+ "LanczosSharp",
+ "Mitchell",
+ "Parzen",
+ "Point",
+ "Quadratic",
+ "Robidoux",
+ "RobidouxSharp",
+ "Sinc",
+ "SincFast",
+ "Spline",
+ "Triangle",
+ "Welch"
+ ] filt {
+
+ _flag = if filt == "default" then "" else "-filter " ++ filt;
+
+ Option_edit caption labels value = this.Filter labels?value;
+ }
+ filter_widget = Filter "default";
+
+ Function func = class
+ Option_string "Function" [
+ "Polynomial",
+ "Sinusoid",
+ "Arcsin",
+ "Arctan"
+ ] func {
+
+ _flag = func;
+
+ Option_edit caption labels value = this.Function labels?value;
+ }
+ function_widget = Function "Polynomial";
+
+// "Polynomial (a[n], a[n-1], ... a[1], a[0])",
+// "Sinusoid (freq, phase, amp, bias)",
+// "Arcsin (width, centre, range, bias)",
+// "Arctan (slope, centre, range, bias)"
+
+ Gravity gravity = class
+ Option_string "Gravity" [
+ "None",
+ "Center",
+ "East",
+ "Forget",
+ "NorthEast",
+ "North",
+ "NorthWest",
+ "SouthEast",
+ "South",
+ "SouthWest",
+ "West",
+ "Static"
+ ] gravity {
+
+ _flag = "-gravity " ++ gravity;
+
+ Option_edit caption labels value = this.Gravity labels?value;
+ }
+ gravity_widget = Gravity "Center";
+
+ ImageType imagetype = class
+ Option_string "Image type" [
+ "Bilevel",
+ "ColorSeparation",
+ "ColorSeparationAlpha",
+ "ColorSeparationMatte",
+ "Grayscale",
+ "GrayscaleAlpha",
+ "GrayscaleMatte",
+ "Optimize",
+ "Palette",
+ "PaletteBilevelAlpha",
+ "PaletteBilevelMatte",
+ "PaletteAlpha",
+ "PaletteMatte",
+ "TrueColorAlpha",
+ "TrueColorMatte",
+ "TrueColor"
+ ] imagetype {
+
+ _flag = "-type " ++ imagetype;
+
+ Option_edit caption labels value = this.ImageType labels?value;
+ }
+ imagetype_widget = ImageType "TrueColor";
+
+ Intensity intensity = class
+ Option_string "Intensity (gray conversion)" [
+ "Average",
+ "Brightness",
+ "Lightness",
+ "MS",
+ "Rec601Luma",
+ "Rec601Luminance",
+ "Rec709Luma",
+ "Rec709Luminance",
+ "RMS"
+ ] intensity {
+
+ _flag
+ = "-intensity " ++ intensity, has_intensity
+ = "";
+
+ Option_edit caption labels value = this.Intensity labels?value;
+ }
+ intensity_widget = Intensity "Rec709Luminance";
+
+ Interpolate interp = class
+ Option_string "Interpolate" [
+ "default",
+ "Average",
+ "Average4",
+ "Average9",
+ "Average16",
+ "Background",
+ "Bilinear",
+ "Blend",
+ "Integer",
+ "Mesh",
+ "Nearest",
+ "NearestNeighbor",
+ "Spline"
+ ] interp {
+
+ _flag = if interp == "default" then "" else "-interpolate " ++ interp;
+
+ Option_edit caption labels value = this.Interpolate labels?value;
+ }
+ interpolate_widget = Interpolate "default";
+
+ Kernel kernel = class
+ Option_string "Kernel" [
+ "Unity",
+ "Gaussian",
+ "DoG",
+ "LoG",
+ "Blur",
+ "Comet",
+ "Binomial",
+ "Laplacian",
+ "Sobel",
+ "FreiChen",
+ "Roberts",
+ "Prewitt",
+ "Compass",
+ "Kirsch",
+ "Diamond",
+ "Square",
+ "Rectangle",
+ "Disk",
+ "Octagon",
+ "Plus",
+ "Cross",
+ "Ring",
+ "Peaks",
+ "Edges",
+ "Corners",
+ "Diagonals",
+ "LineEnds",
+ "LineJunctions",
+ "Ridges",
+ "ConvexHull",
+ "ThinSe",
+ "Skeleton",
+ "Chebyshev",
+ "Manhattan",
+ "Octagonal",
+ "Euclidean"
+ // FIXME: custom kernel
+ ] kernel {
+
+ _flag = kernel;
+
+ Option_edit caption labels value = this.Kernel labels?value;
+ }
+ kernel_widget = Kernel "Unity";
+
+ ModColSp msp = class
+ Option_string "modulate colorspace" [
+ "HCL",
+ "HCLp",
+ "HSB",
+ "HSI",
+ "HSL",
+ "HSV",
+ "HWB",
+ "LCH"
+ ] msp {
+
+ _flag = "-set option:modulate:colorspace " ++ msp;
+
+ Option_edit caption labels value = this.ModColSp labels?value;
+ }
+ ModColSp_widget = ModColSp "HSL";
+
+ MorphMeth morph = class
+ Option_string "Method" [
+ "Correlate",
+ "Convolve",
+ "Dilate",
+ "Erode",
+ "Close",
+ "Open",
+ "DilateIntensity",
+ "ErodeIntensity",
+ "CloseIntensity",
+ "OpenIntensity",
+ "Smooth",
+ "EdgeOut",
+ "EdgeIn",
+ "Edge",
+ "TopHat",
+ "BottomHat",
+ "HitAndMiss",
+ "Thinning",
+ "Thicken",
+ "Distance",
+ "IterativeDistance"
+ ] morph {
+
+ _flag = morph;
+
+ Option_edit caption labels value = this.MorphMeth labels?value;
+ }
+ morphmeth_widget = MorphMeth "Dilate";
+
+ Noise noise = class
+ Option_string "Noise" [
+ "Gaussian",
+ "Impulse",
+ "Laplacian",
+ "Multiplicative",
+ "Poisson",
+ "Random",
+ "Uniform"
+ ] noise {
+
+ _flag = "+noise " ++ noise;
+
+ Option_edit caption labels value = this.Noise labels?value;
+ }
+ noise_widget = Noise "Gaussian";
+
+ Pattern pattern = class
+ Option_string "Noise" [
+ // See http://www.imagemagick.org/script/formats.php
+ "bricks",
+ "checkerboard",
+ "circles",
+ "crosshatch",
+ "crosshatch30",
+ "crosshatch45",
+ "gray0",
+ "gray5",
+ "gray10",
+ "gray15",
+ "gray20",
+ "gray25",
+ "gray30",
+ "gray35",
+ "gray40",
+ "gray45",
+ "gray50",
+ "gray55",
+ "gray60",
+ "gray65",
+ "gray70",
+ "gray75",
+ "gray80",
+ "gray85",
+ "gray90",
+ "gray95",
+ "gray100",
+ "hexagons",
+ "horizontal",
+ "horizontal2",
+ "horizontal3",
+ "horizontalsaw",
+ "hs_bdiagonal",
+ "hs_cross",
+ "hs_diagcross",
+ "hs_fdiagonal",
+ "hs_horizontal",
+ "hs_vertical",
+ "left30",
+ "left45",
+ "leftshingle",
+ "octagons",
+ "right30",
+ "right45",
+ "rightshingle",
+ "smallfishscales",
+ "vertical",
+ "vertical2",
+ "vertical3",
+ "verticalbricks",
+ "verticalleftshingle",
+ "verticalrightshingle",
+ "verticalsaw"
+ ] pattern {
+
+ _flag = "pattern:" ++ pattern;
+
+ Option_edit caption labels value = this.Pattern labels?value;
+ }
+ pattern_widget = Pattern "bricks";
+
+ ResizeType resizet = class
+ Option_string "Resize type" [
+ "resize",
+ "scale",
+ "sample",
+ "adaptive-resize"
+ ] resizet {
+
+ _flag = resizet;
+
+ Option_edit caption labels value = this.ResizeType labels?value;
+ }
+ ResizeType_widget = ResizeType "resize";
+
+ Size_widget = class {
+ _vislevel = 3;
+
+ width = Expression "Width (pixels)" 64;
+ height = Expression "Height (pixels)" 64;
+
+ _flag = "-size " ++
+ print width.expr ++ "x" ++ print height.expr;
+
+ };
+
+ StatType statt = class
+ Option_string "Statistic type" [
+ "Gradient",
+ "Maximum",
+ "Mean",
+ "Median",
+ "Minimum",
+ "Mode",
+ "Nonpeak",
+ "StandardDeviation"
+ ] statt {
+
+ _flag = statt;
+
+ Option_edit caption labels value = this.StatType labels?value;
+ }
+ StatType_widget = StatType "Mean";
+
+ VirtualPixel vp = class
+ Option_string "Virtual pixel" [
+ "Background",
+ "Black",
+ "CheckerTile",
+ "Dither",
+ "Edge",
+ "Gray",
+ "HorizontalTile",
+ "HorizontalTileEdge",
+ "Mirror",
+ "None",
+ "Random",
+ "Tile",
+ "Transparent",
+ "VerticalTile",
+ "VerticalTileEdge",
+ "White"
+ ] vp {
+
+ _flag = "-virtual-pixel " ++ vp;
+
+ _isBackground = (vp == "Background");
+
+ Option_edit caption labels value = this.VirtualPixel labels?value;
+ }
+ VirtualPixel_widget = VirtualPixel "Edge";
+
+ VirtualPixelBack_widget = class {
+ virtpix = Magick.VirtualPixel_widget;
+ background = Magick.background_widget;
+ _flag = (if virtpix._isBackground then (background._flag ++ " ") else "")
+ ++ virtpix._flag;
+ }
+
+ Geometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "X" 0;
+ y = Expression "Y" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ AnnotGeometry_widget = class {
+ _vislevel = 3;
+
+ shearX = Expression "shear X (degrees)" 0;
+ shearY = Expression "shear Y (degrees)" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print shearX.expr, "x", print shearY.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ OffsetGeometry_widget = class {
+ _vislevel = 3;
+
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag = concat [format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ WhxyGeometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "Width" 0;
+ y = Expression "Height" 0;
+ hoffset = Expression "Horizontal offset" 0;
+ voffset = Expression "Vertical offset" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format hoffset, format voffset]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ FrameGeometry_widget = class {
+ _vislevel = 3;
+
+ x = Expression "Width" 0;
+ y = Expression "Height" 0;
+ outbev = Expression "Outer bevel thickness" 0;
+ inbev = Expression "Inner bevel thickness" 0;
+
+ _flag
+ = concat [print x.expr, "x", print y.expr,
+ format outbev, format inbev]
+ {
+ // print an offset ... we want '+' in front of +ve strings
+ format offset
+ = concat ["+", print offset.expr], offset.expr >= 0
+ = print offset.expr;
+ }
+ };
+
+ Font_widget = class {
+ _vislevel = 3;
+
+ family = Option_string "Family" [
+ "Arial",
+ "ArialBlack",
+ "AvantGarde",
+ "BitstreamCharter",
+ "Bookman",
+ "CenturySchoolbook",
+ "ComicSansMS",
+ "Courier",
+ "CourierNew",
+ "DejaVuSans",
+ "DejaVuSansMono",
+ "DejaVuSerif",
+ "Dingbats",
+ "FreeMono",
+ "FreeSans",
+ "FreeSerif",
+ "Garuda",
+ "Georgia",
+ "Helvetica",
+ "HelveticaNarrow",
+ "Impact",
+ "LiberationMono",
+ "LiberationSans",
+ "LiberationSerif",
+ "NewCenturySchlbk",
+ "Palatino",
+ "Purisa",
+ "Symbol",
+ "Times",
+ "TimesNewRoman",
+ "Ubuntu",
+ "Verdana",
+ "Webdings"
+ ] "Arial";
+ style = Option_string "Style" [
+ "Any", "Italic", "Normal", "Oblique"
+ ] "Normal";
+ weight = Scale "Weight" 1 800 400;
+ size = Scale "Point size" 1 100 12;
+ stretch = Option_string "Stretch" [
+ "Any", "Condensed", "Expanded", "ExtraCondensed", "ExtraExpanded",
+ "Normal", "SemiCondensed", "SemiExpanded", "UltraCondensed",
+ "UltraExpanded"
+ ] "Normal";
+
+ _flag = join_sep " " [
+ "-family", family.item,
+ "-weight", print weight.value,
+ "-pointsize", print size.value,
+ "-style", style.item,
+ "-stretch", stretch.item];
+ }
+}
+
diff --git a/share/nip2/compat/8.6/_predicate.def b/share/nip2/compat/8.6/_predicate.def
new file mode 100644
index 00000000..7fae24a8
--- /dev/null
+++ b/share/nip2/compat/8.6/_predicate.def
@@ -0,0 +1,530 @@
+
+/* is_colour_space str: is a string one of nip's colour space names
+ */
+is_colour_space str = Image_type.colour_spaces.present 0 str;
+
+/* is_colour_type n: is a number one of VIPS's colour spaces
+ */
+is_colour_type n = Image_type.colour_spaces.present 1 n;
+
+/* is_number: is a real or a complex number.
+ */
+is_number a = is_real a || is_complex a;
+
+/* is_int: is an integer
+ */
+is_int a = is_real a && a == (int) a;
+
+/* is_uint: is an unsigned integer
+ */
+is_uint a = is_int a && a >= 0;
+
+/* is_pint: is a positive integer
+ */
+is_pint a = is_int a && a > 0;
+
+/* is_preal: is a positive real
+ */
+is_preal a = is_real a && a > 0;
+
+/* is_ureal: is an unsigned real
+ */
+is_ureal a = is_real a && a >= 0;
+
+/* is_letter c: true if character c is an ASCII letter
+ *
+ * is_letter :: char -> bool
+ */
+is_letter c = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
+
+/* is_digit c: true if character c is an ASCII digit
+ *
+ * is_digit :: char->bool
+ */
+is_digit x = '0' <= x && x <= '9';
+
+/* A whitespace character.
+ *
+ * is_space :: char->bool
+ */
+is_space = member " \n\t";
+
+/* List str starts with section prefix.
+ *
+ * is_prefix "hell" "hello world!" == true
+ * is_prefix :: [*] -> [*] -> bool
+ */
+is_prefix prefix str = take (len prefix) str == prefix;
+
+/* List str ends with section suffix.
+ *
+ * is_suffix "ld!" "hello world!" == true
+ * is_suffix :: [*] -> [*] -> bool
+ */
+is_suffix suffix str = take (len suffix) (reverse str) == reverse suffix;
+
+/* List contains seqence.
+ *
+ * is_substr "llo" "hello world!" == true
+ * is_substr :: [*] -> [*] -> bool
+ */
+is_substr seq str = any (map (is_prefix seq) (iterate tl str));
+
+/* is_listof p s: true if finite list with p true for every element.
+ */
+is_listof p l = is_list l && all (map p l);
+
+/* is_string s: true if finite list of char.
+ */
+is_string s = is_listof is_char s;
+
+/* is_real_list l: is l a list of real numbers ... test each element,
+ * so no infinite lists pls.
+ */
+is_real_list l = is_listof is_real l;
+
+/* is_string_list l: is l a finite list of finite strings.
+ */
+is_string_list l = is_listof is_string l;
+
+/* Test list length ... quicker than len x == n for large lists.
+ */
+is_list_len n x
+ = true, x == [] && n == 0
+ = false, x == [] || n == 0
+ = is_list_len (n - 1) (tl x);
+
+is_list_len_more n x
+ = true, x != [] && n == 0
+ = false, x == [] || n == 0
+ = is_list_len_more (n - 1) (tl x);
+
+is_list_len_more_equal n x
+ = true, n == 0
+ = false, x == []
+ = is_list_len_more_equal (n - 1) (tl x);
+
+/* is_rectangular l: is l a rectangular data structure
+ */
+is_rectangular l
+ = true, !is_list l
+ = true, all (map is_obj l)
+ = true, all (map is_list l) &&
+ all (map (not @ is_obj) l) &&
+ all (map is_rectangular l) &&
+ is_list_len_more 0 l &&
+ all (map (is_list_len (len (hd l))) (tl l))
+ = false
+{
+ // treat strings as a base type, not [char]
+ is_obj x = !is_list x || is_string x;
+}
+
+/* is_matrix l: is l a list of lists of real numbers, all the same length
+ *
+ * [[]] is the empty matrix, [] is the empty list ... disallow []
+ */
+is_matrix l = l != [] && is_listof is_real_list l && is_rectangular l;
+
+/* is_square_matrix l: is l a matrix with width == height
+ */
+is_square_matrix l
+ = true, l == [[]]
+ = is_matrix l && is_list_len (len (hd l)) l;
+
+/* is_oddmatrix l: is l a matrix with odd-length sides
+ */
+is_oddmatrix l
+ = true, l == [[]]
+ = is_matrix l && len l % 2 == 1 && len l?0 % 2 == 1;
+
+/* is_odd_square_matrix l: is l a square_matrix with odd-length sides
+ */
+is_odd_square_matrix l = is_square_matrix l && len l % 2 == 1;
+
+/* Is an item in a column of a table?
+ */
+is_incolumn n table x = member (map (extract n) table) x;
+
+/* Is HGuide or VGuide.
+ */
+is_HGuide x = is_instanceof "HGuide" x;
+
+is_VGuide x = is_instanceof "VGuide" x;
+
+is_Guide x = is_HGuide x || is_VGuide x;
+
+is_Mark x = is_instanceof "Mark" x;
+
+is_Group x = is_instanceof "Group" x;
+
+is_NULL x = is_instanceof "NULL" x;
+
+is_List x = is_instanceof "List" x;
+
+is_Image x = is_instanceof "Image" x;
+
+is_Plot x = is_instanceof "Plot" x;
+
+is_Region x = is_instanceof "Region" x;
+
+is_Real x = is_instanceof "Real" x;
+
+is_Matrix x = is_instanceof "Matrix_base" x;
+
+is_Vector x = is_instanceof "Vector" x;
+
+is_Colour x = is_instanceof "Colour" x;
+
+is_Arrow x = is_instanceof "Arrow" x;
+
+is_Bool x = is_instanceof "Bool" x;
+
+is_Scale x = is_instanceof "Scale" x;
+
+is_Rect x = is_instanceof "Rect" x;
+
+is_Number x = is_instanceof "Number" x;
+
+is_Expression x = is_instanceof "Expression" x;
+
+is_String x = is_instanceof "String" x;
+
+/* A list of the form [[1,2],[3,4],[5,6]...]
+ */
+is_xy_list l
+ = is_list l && all (map xy l)
+{
+ xy l = is_real_list l && is_list_len 2 l;
+}
+
+// does a nested list structure contain a Group object?
+contains_Group l
+ = true, is_list l && any (map is_Group l)
+ = any (map contains_Group l), is_list l
+ = false;
+
+/* Does an object have a sensible VIPS type?
+ */
+has_type x = is_image x || is_Image x || is_Arrow x || is_Colour x;
+
+/* Try to get a VIPS image type from an object.
+ */
+get_type x
+ = get_type_im x, is_image x
+ = get_type_im x.value, is_Image x
+ = get_type_im x.image.value, is_Arrow x
+ = Image_type.colour_spaces.lookup 0 1 x.colour_space, is_Colour x
+ // slightly odd ... but our display is always 0-255, so it makes sense for
+ // a plain number to be in the same range
+ = Image_type.sRGB, is_real x
+ = oo_unary_function get_type_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_type")
+{
+ get_type_op = Operator "get_type" get_type
+ Operator_type.COMPOUND false;
+
+ // get the type from a VIPS image ... but only if it makes sense with
+ // the rest of the image
+
+ // we often have Type set wrong, hence the ugly guessing :-(
+ // can have alpha, hence we let bands be one more than you might think
+
+ get_type_im im
+ = Image_type.LABQ, coding == Image_coding.LABPACK
+ = Image_type.scRGB, coding == Image_coding.RAD
+ = Image_type.GREY16, type == Image_type.GREY16 && is_bands 1
+ = Image_type.HISTOGRAM, type == Image_type.HISTOGRAM &&
+ (width == 1 || height == 1)
+ = Image_type.B_W, is_bands 1
+ = Image_type.CMYK, type == Image_type.CMYK && is_bands 4
+ = type, is_colorimetric && is_bands 3
+ = Image_type.sRGB, !is_colorimetric && is_bands 3
+ = Image_type.MULTIBAND, !is_colorimetric && !is_bands 3
+ = type
+ {
+ type = get_header "Type" im;
+ coding = get_header "Coding" im;
+ bands = get_header "Bands" im;
+ width = get_header "Xsize" im;
+ height = get_header "Ysize" im;
+
+ // 3-band colorimetric types we allow ... the things which the
+ // Colour/Convert To menu can make, excluding mono.
+ ok_types = [
+ Image_type.sRGB,
+ Image_type.scRGB,
+ Image_type.RGB16,
+ Image_type.LAB,
+ Image_type.LABQ,
+ Image_type.LABS,
+ Image_type.LCH,
+ Image_type.XYZ,
+ Image_type.YXY,
+ Image_type.UCS
+ ];
+ is_colorimetric = member ok_types type;
+
+ // is bands n, with an optional alpha (ie. can be n + 1 too)
+ is_bands n = bands == n || bands == n + 1;
+ }
+}
+
+has_format x = has_member "format" x || is_Arrow x || is_image x;
+
+get_format x
+ = x.format, has_member "format" x
+ = x.image.format, is_Arrow x
+ = get_header "BandFmt" x, is_image x
+ = oo_unary_function get_format_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_format")
+{
+ get_format_op = Operator "get_format" get_format
+ Operator_type.COMPOUND false;
+}
+
+has_bits x = has_member "bits" x || is_Arrow x || is_image x;
+
+get_bits x
+ = x.bits, has_member "bits" x
+ = x.image.bits, is_Arrow x
+ = get_header "Bbits" x, is_image x
+ = oo_unary_function get_bits_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_bits")
+{
+ get_bits_op = Operator "get_bits" get_format
+ Operator_type.COMPOUND false;
+}
+
+has_bands x = is_image x || has_member "bands" x || is_Arrow x;
+
+get_bands x
+ = x.bands, has_member "bands" x
+ = x.image.bands, is_Arrow x
+ = get_header "Bands" x, is_image x
+ = 1, is_real x
+ = len x, is_real_list x
+ = oo_unary_function get_bands_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_bands")
+{
+ get_bands_op = Operator "get_bands" get_bands
+ Operator_type.COMPOUND false;
+}
+
+has_coding x = has_member "coding" x || is_Arrow x || is_image x;
+
+get_coding x
+ = x.coding, has_member "coding" x
+ = x.image.coding, is_Arrow x
+ = get_header "Coding" x, is_image x
+ = Image_coding.NOCODING, is_real x
+ = oo_unary_function get_coding_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_coding")
+{
+ get_coding_op = Operator "get_coding" get_coding
+ Operator_type.COMPOUND false;
+}
+
+has_xres x = has_member "xres" x || is_Arrow x || is_image x;
+
+get_xres x
+ = x.xres, has_member "xres" x
+ = x.image.xres, is_Arrow x
+ = get_header "Xres" x, is_image x
+ = oo_unary_function get_xres_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_xres")
+{
+ get_xres_op = Operator "get_xres" get_xres
+ Operator_type.COMPOUND false;
+}
+
+has_yres x = has_member "yres" x || is_Arrow x || is_image x;
+
+get_yres x
+ = x.yres, has_member "yres" x
+ = x.image.yres, is_Arrow x
+ = get_header "Yres" x, is_image x
+ = oo_unary_function get_yres_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_yres")
+{
+ get_yres_op = Operator "get_yres" get_yres
+ Operator_type.COMPOUND false;
+}
+
+has_xoffset x = has_member "xoffset" x || is_Arrow x || is_image x;
+
+get_xoffset x
+ = x.xoffset, has_member "xoffset" x
+ = x.image.xoffset, is_Arrow x
+ = get_header "Xoffset" x, is_image x
+ = oo_unary_function get_xoffset_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_xoffset")
+{
+ get_xoffset_op = Operator "get_xoffset" get_xoffset
+ Operator_type.COMPOUND false;
+}
+
+has_yoffset x = has_member "yoffset" x || is_Arrow x || is_image x;
+
+get_yoffset x
+ = x.yoffset, has_member "yoffset" x
+ = x.image.yoffset, is_Arrow x
+ = get_header "Yoffset" x, is_image x
+ = oo_unary_function get_yoffset_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_yoffset")
+{
+ get_yoffset_op = Operator "get_yoffset" get_yoffset
+ Operator_type.COMPOUND false;
+}
+
+has_value = has_member "value";
+
+get_value x = x.value;
+
+has_image x = is_image x || is_Image x || is_Arrow x;
+
+get_image x
+ = x.value, is_Image x
+ = x.image.value, is_Arrow x
+ = x, is_image x
+ = oo_unary_function get_image_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_image")
+{
+ get_image_op = Operator "get_image" get_image
+ Operator_type.COMPOUND false;
+}
+
+has_number x = is_number x || is_Real x;
+
+get_number x
+ = x.value, is_Real x
+ = x, is_number x
+ = oo_unary_function get_number_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_number")
+{
+ get_number_op = Operator "get_number" get_number
+ Operator_type.COMPOUND false;
+}
+
+has_real x = is_real x || is_Real x;
+
+get_real x
+ = x.value, is_Real x
+ = x, is_real x
+ = oo_unary_function get_real_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_real")
+{
+ get_real_op = Operator "get_real" get_real
+ Operator_type.COMPOUND false;
+}
+
+has_width x = has_member "width" x || is_image x;
+
+get_width x
+ = x.width, has_member "width" x
+ = get_header "Xsize" x, is_image x
+ = oo_unary_function get_width_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_width")
+{
+ get_width_op = Operator "get_width" get_width
+ Operator_type.COMPOUND false;
+}
+
+has_height x = has_member "height" x || is_image x;
+
+get_height x
+ = x.height, has_member "height" x
+ = get_header "Ysize" x, is_image x
+ = oo_unary_function get_height_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_height")
+{
+ get_height_op = Operator "get_height" get_height
+ Operator_type.COMPOUND false;
+}
+
+has_left x = has_member "left" x;
+
+get_left x
+ = x.left, has_member "left" x
+ = oo_unary_function get_left_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_left")
+{
+ get_left_op = Operator "get_left" get_left
+ Operator_type.COMPOUND false;
+}
+
+has_top x = has_member "top" x;
+
+get_top x
+ = x.top, has_member "top" x
+ = oo_unary_function get_top_op x, is_class x
+ = error (_ "bad arguments to " ++ "get_top")
+{
+ get_top_op = Operator "get_top" get_top
+ Operator_type.COMPOUND false;
+}
+
+// like has/get member, but first in a lst of objects
+has_member_list has objects
+ = filter has objects != [];
+
+// need one with the args swapped
+get_member = converse dot;
+
+// get a member from the first of a list of objects to have it
+get_member_list has get objects
+ = hd members, members != []
+ = error "unable to get property"
+{
+ members = map get (filter has objects);
+}
+
+is_hist x
+ = has_image x && (h == 1 || w == 1 || t == Image_type.HISTOGRAM)
+{
+ im = get_image x;
+ w = get_width im;
+ h = get_height im;
+ t = get_type im;
+}
+
+get_header field x
+ = oo_unary_function get_header_op x, is_class x
+ = get_header_image x, is_image x
+ = error (_ "bad arguments to " ++ "get_header")
+{
+ get_header_op = Operator "get_header" (get_header field)
+ Operator_type.COMPOUND false;
+ get_header_image im
+ = im_header_int field im, type == itype
+ = im_header_double field im, type == dtype
+ = im_header_string field im, type == stype1 || type == stype2
+ = error (_ "image has no field " ++ field), type == 0
+ = error (_ "unknown type for field " ++ field)
+ {
+ type = im_header_get_typeof field im;
+
+ itype = name2gtype "gint";
+ dtype = name2gtype "gdouble";
+ stype1 = name2gtype "VipsRefString";
+ stype2 = name2gtype "gchararray";
+ }
+}
+
+get_header_type field x
+ = oo_unary_function get_header_type_op x, is_class x
+ = im_header_get_typeof field x, is_image x
+ = error (_ "bad arguments to " ++ "get_header_type")
+{
+ get_header_type_op = Operator "get_header_type" (get_header_type field)
+ Operator_type.COMPOUND false;
+}
+
+set_header field value x
+ = oo_unary_function set_header_op x, is_class x
+ = im_copy_set_meta x field value, is_image x
+ = error (_ "bad arguments to " ++ "set_header")
+{
+ set_header_op = Operator "set_header" (set_header field value)
+ Operator_type.COMPOUND false;
+}
diff --git a/share/nip2/compat/8.6/_stdenv.def b/share/nip2/compat/8.6/_stdenv.def
new file mode 100644
index 00000000..0112d1f7
--- /dev/null
+++ b/share/nip2/compat/8.6/_stdenv.def
@@ -0,0 +1,2619 @@
+/* optional args to functions
+ */
+
+get_option options defaults f
+ = error (_ "unknown parameter " ++ f), hits == []
+ = hits?0
+{
+ hits = [v :: [n, v] <- options ++ defaults; n == f];
+}
+
+/* Various operators as functions.
+ */
+
+logical_and a b = a && b;
+logical_or a b = a || b;
+bitwise_and a b = a & b;
+bitwise_or a b = a | b;
+eor a b = a ^ b;
+left_shift a b = a << b;
+right_shift a b = a >> b;
+not a = !a;
+
+less a b = a < b;
+more a b = a > b;
+less_equal a b = a <= b;
+more_equal a b = a >= b;
+equal a b = a == b;
+not_equal a b = a != b;
+pointer_equal a b = a === b;
+not_pointer_equal a b = a !== b;
+
+add a b = a + b;
+subtract a b = a - b;
+multiply a b = a * b;
+divide a b = a / b;
+idivide a b = (int) ((int) a / (int) b);
+power a b = a ** b;
+square x = x * x;
+remainder a b = a % b;
+
+cons a b = a : b;
+dot a b = a . ( b );
+join a b = a ++ b;
+// 'difference' is defined in _list
+subscript a b = a ? b;
+
+generate s n f = [s, n .. f];
+comma r i = (r, i);
+
+compose f g = f @ g;
+
+// our only trinary operator is actually a binary operator
+if_then_else a x = if a then x?0 else x?1;
+
+cast_unsigned_char x = (unsigned char) x;
+cast_signed_char x = (signed char) x;
+cast_unsigned_short x = (unsigned short) x;
+cast_signed_short x = (signed short) x;
+cast_unsigned_int x = (unsigned int) x;
+cast_signed_int x = (signed int) x;
+cast_float x = (float) x;
+cast_double x = (double) x;
+cast_complex x = (complex) x;
+cast_double_complex x = (double complex) x;
+
+unary_minus x = -x;
+negate x = !x;
+complement x = ~x;
+unary_plus x = +x;
+
+// the function we call for "a -> v" expressions
+mksvpair s v
+ = [s, v], is_string s
+ = error "not str on lhs of ->";
+
+// the vector ops ... im is an image, vec is a real_list
+vec op_name im vec
+ = im_lintra_vec ones im vec,
+ op_name == "add" || op_name == "add'"
+ = im_lintra_vec ones (-1 * im) vec,
+ op_name == "subtract'"
+ = im_lintra_vec ones im inv,
+ op_name == "subtract"
+ = im_lintra_vec vec im zeros,
+ op_name == "multiply" || op_name == "multiply'"
+ = im_lintra_vec vec (1 / im) zeros,
+ op_name == "divide'"
+ = im_lintra_vec recip im zeros,
+ op_name == "divide"
+ = im_expntra_vec im vec,
+ op_name == "power'"
+ = im_powtra_vec im vec,
+ op_name == "power"
+ = im_remainderconst_vec im vec,
+ op_name == "remainder"
+ = im_andimage_vec im vec,
+ op_name == "bitwise_and" || op_name == "bitwise_and'"
+ = im_orimage_vec im vec,
+ op_name == "bitwise_or" || op_name == "bitwise_or'"
+ = im_eorimage_vec im vec,
+ op_name == "eor" || op_name == "eor'"
+ = im_equal_vec im vec,
+ op_name == "equal" || op_name == "equal'"
+ = im_notequal_vec im vec,
+ op_name == "not_equal" || op_name == "not_equal'"
+ = im_less_vec im vec,
+ op_name == "less"
+ = im_moreeq_vec im vec,
+ op_name == "less'"
+ = im_lesseq_vec im vec,
+ op_name == "less_equal"
+ = im_more_vec im vec,
+ op_name == "less_equal'"
+ = error ("unimplemented vector operation: " ++ op_name)
+{
+ zeros = replicate (len vec) 0;
+ ones = replicate (len vec) 1;
+ recip = map (divide 1) vec;
+ inv = map (multiply (-1)) vec;
+}
+
+// make a name value pair
+mknvpair n v
+ = [n, v], is_string n
+ = error "not [char] on LHS of =>";
+
+/* Macbeth chart patch names.
+ */
+macbeth_names = [
+ "Dark skin",
+ "Light skin",
+ "Blue sky",
+ "Foliage",
+ "Blue flower",
+ "Bluish green",
+ "Orange",
+ "Purplish blue",
+ "Moderate red",
+ "Purple",
+ "Yellow green",
+ "Orange yellow",
+ "Blue",
+ "Green",
+ "Red",
+ "Yellow",
+ "Magenta",
+ "Cyan",
+ "White (density 0.05)",
+ "Neutral 8 (density 0.23)",
+ "Neutral 6.5 (density 0.44)",
+ "Neutral 5 (density 0.70)",
+ "Neutral 3.5 (density 1.05)",
+ "Black (density 1.50)"
+];
+
+bandsplit x
+ = oo_unary_function bandsplit_op x, is_class x
+ = map (subscript x) [0 .. bands - 1], is_image x
+ = error (_ "bad arguments to " ++ "bandsplit")
+{
+ bands = get_header "Bands" x;
+ bandsplit_op = Operator "bandsplit" (map Image @ bandsplit)
+ Operator_type.COMPOUND false;
+}
+
+bandjoin l
+ = wrapper joined, has_wrapper
+ = joined, is_listof has_image l
+ = error (_ "bad arguments to " ++ "bandjoin")
+{
+ has_wrapper = has_member_list (has_member "Image") l;
+ wrapper = get_member_list (has_member "Image") (get_member "Image") l;
+ joined = im_gbandjoin (map get_image l);
+}
+
+bandand x
+ = oo_unary_function bandand_op x, is_class x
+ = foldr1 bitwise_and (bandsplit x), is_image x
+ = error (_ "bad arguments to " ++ "bandand")
+{
+ bandand_op = Operator "bandand" bandand Operator_type.COMPOUND_REWRAP false;
+}
+
+bandor x
+ = oo_unary_function bandor_op x, is_class x
+ = foldr1 bitwise_or (bandsplit x), is_image x
+ = error (_ "bad arguments to " ++ "bandor")
+{
+ bandor_op = Operator "bandor" bandor Operator_type.COMPOUND_REWRAP false;
+}
+
+sum x
+ = oo_unary_function sum_op x, is_class x
+ = im_avg x * (get_width x) * (get_height x) * (get_bands x), is_image x
+ = sum_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "sum")
+{
+ sum_op = Operator "sum" sum Operator_type.COMPOUND false;
+
+ // add elements in a nested-list thing
+ sum_list l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + sum x, is_list x
+ = total + x;
+ }
+}
+
+product x
+ = oo_unary_function product_op x, is_class x
+ = product_list x, is_list x
+ // (product image) doesn't make much sense :(
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "product")
+{
+ product_op = Operator "product" product Operator_type.COMPOUND false;
+
+ product_list l
+ = foldr prod 1 l
+ {
+ prod x total
+ = total * product x, is_list x
+ = total * x;
+ }
+}
+
+mean x
+ = oo_unary_function mean_op x, is_class x
+ = im_avg x, is_image x
+ = mean_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "mean")
+{
+ mean_op = Operator "mean" mean Operator_type.COMPOUND false;
+
+ mean_list l = sum l / size l;
+
+ // number of elements in some sort of nested-list thing
+ size l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + size x, is_list x
+ = total + 1;
+ }
+}
+
+meang x
+ = (appl (power e) @ mean @ appl log) x
+{
+ appl fn x
+ = map fn x, is_list x
+ = fn x;
+}
+
+skew x
+ = oo_unary_function skew_op x, is_class x
+ = sum ((x - m) ** 3) / ((N - 1) * s ** 3), is_image x
+ = sum ((Group x' - m) ** 3).value / ((N - 1) * s ** 3), is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "skew")
+{
+ skew_op = Operator "skew" skew Operator_type.COMPOUND false;
+
+ // squash any large matrix down to a flat list ... much simpler
+ x'
+ = x, is_image x;
+ = flatten x;
+
+ m = mean x';
+ s = deviation x';
+ w = get_width x';
+ h = get_height x';
+ b = get_bands x';
+
+ N
+ = w * h * b, is_image x'
+ = len x';
+}
+
+kurtosis x
+ = oo_unary_function kurtosis_op x, is_class x
+ = sum ((x - m) ** 4) / ((N - 1) * s ** 4), is_image x
+ = sum ((Group x' - m) ** 4).value / ((N - 1) * s ** 4), is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "kurtosis")
+{
+ kurtosis_op = Operator "kurtosis" kurtosis Operator_type.COMPOUND false;
+
+ // squash any large matrix down to a flat list ... much simpler
+ x'
+ = x, is_image x;
+ = flatten x;
+
+ m = mean x';
+ s = deviation x';
+ w = get_width x';
+ h = get_height x';
+ b = get_bands x';
+
+ N
+ = len x', is_list x';
+ = w * h * b;
+}
+
+// zero-excluding mean
+meanze x
+ = oo_unary_function meanze_op x, is_class x
+ = meanze_image_hist x, is_image x &&
+ (fmt == Image_format.UCHAR || fmt == Image_format.USHORT)
+ = meanze_image x, is_image x
+ = meanze_list x, is_list x
+ = error (_ "bad arguments (" ++ print x ++ ") to " ++ "meanze")
+{
+ fmt = get_format x;
+
+ meanze_op = Operator "meanze" meanze Operator_type.COMPOUND false;
+
+ meanze_list l = sum l / size l;
+
+ // number of non-zero elements in some sort of nested-list thing
+ size l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + size x, is_list x
+ = total + 1, x != 0;
+ = total;
+ }
+
+ // add elements in a nested-list thing
+ sum l
+ = foldr acc 0 l
+ {
+ acc x total
+ = total + sum x, is_list x
+ = total + x;
+ }
+
+ // image mean, for any image type
+ meanze_image i
+ = sum / N
+ {
+ w = get_width i;
+ h = get_height i;
+ b = get_bands i;
+
+ st = stats i;
+ sum = st.value?0?2;
+
+ // find non-zero pixels (not zero in all bands)
+ zp = im_notequal_vec i (replicate b 0);
+
+ // number of non-zero pixels
+ N = b * (mean zp * w * h) / 255;
+ }
+
+ // image mean for 8 and 16-bit unsigned images
+ // we can use a histogram, yay, and save a pass through the image
+ meanze_image_hist i
+ = sum / N
+ {
+ // histogram, knock out zeros
+ hist = hist_find i;
+ black = image_new 1 1 (get_bands hist)
+ (get_format hist) (get_coding hist) (get_type hist) 0 0 0;
+ histze = insert 0 0 black hist;
+
+ // matching identity
+ iden
+ = im_identity_ushort (get_bands hist) (get_width hist),
+ (get_width hist) > 256
+ = im_identity (get_bands hist);
+
+ // number of non-zero pixels
+ N = mean histze * 256;
+
+ // sum of pixels
+ sum = mean (hist * iden) * 256;
+ }
+}
+
+deviation x
+ = oo_unary_function deviation_op x, is_class x
+ = im_deviate x, is_image x
+ = deviation_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "deviation")
+{
+ deviation_op = Operator "deviation"
+ deviation Operator_type.COMPOUND false;
+
+ deviation_list l
+ = (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
+ {
+ [n, s, s2] = sum_sum2_list l;
+ }
+
+ // return n, sum, sum of squares for a list of reals
+ sum_sum2_list x
+ = foldr accumulate [0, 0, 0] x
+ {
+ accumulate x sofar
+ = [n + 1, x + s, x * x + s2], is_real x
+ = [n + n', s + s', s2 + s2'], is_list x
+ = error "sum_sum2_list: not real or [real]"
+ {
+ [n, s, s2] = sofar;
+ [n', s', s2'] = sum_sum2_list x;
+ }
+ }
+}
+
+deviationze x
+ = oo_unary_function deviationze_op x, is_class x
+ = deviationze_image x, is_image x
+ = deviationze_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "deviationze")
+{
+ deviationze_op = Operator "deviationze"
+ deviationze Operator_type.COMPOUND false;
+
+ deviationze_list l
+ = (abs (s2 - (s * s / n)) / (n - 1)) ** 0.5
+ {
+ [n, s, s2] = sum_sum2_list l;
+ }
+
+ // return number of non-zero elements, sum, sum of squares for a list of
+ // reals
+ sum_sum2_list x
+ = foldr accumulate [0, 0, 0] x
+ {
+ accumulate x sofar
+ = sofar, is_real x && x == 0
+ = [n + 1, x + s, x * x + s2], is_real x
+ = [n + n', s + s', s2 + s2'], is_list x
+ = error "sum_sum2_list: not real or [real]"
+ {
+ [n, s, s2] = sofar;
+ [n', s', s2'] = sum_sum2_list x;
+ }
+ }
+
+ deviationze_image i
+ = ((sum2 - sum * sum / N) / (N - 1)) ** 0.5
+ {
+ w = get_width i;
+ h = get_height i;
+ b = get_bands i;
+
+ st = stats i;
+ sum = st.value?0?2;
+ sum2 = st.value?0?3;
+
+ // find non-zero pixels (not zero in all bands)
+ zp = im_notequal_vec i (replicate b 0);
+
+ // number of non-zero pixels
+ N = b * (mean zp * w * h) / 255;
+ }
+}
+
+// find the centre of gravity of a histogram
+gravity x
+ = oo_unary_function gravity_op x, is_class x
+ = im_hist_gravity x, is_hist x
+ = gravity_list x, is_list x
+ = error (_ "bad arguments to " ++ "gravity")
+{
+ gravity_op = Operator "gravity" gravity Operator_type.COMPOUND false;
+
+ // centre of gravity of a histogram... use the histogram to weight an
+ // identity, then sum, then find the mean element
+ im_hist_gravity h
+ = m
+ {
+ // make horizontal
+ h'
+ = rot270 h, get_width h == 1
+ = h, get_height h == 1
+ = error "width or height not 1";
+
+ // number of elements
+ w = get_width h';
+
+ // matching identity
+ i
+ = im_identity_ushort 1 w, w <= 2 ** 16 - 1
+ = make_xy w 1 ? 0;
+
+ // weight identity and sum
+ s = mean (i * h') * w;
+
+ // sum of original histogram
+ s' = mean h * w;
+
+ // weighted mean
+ m = s / s';
+ }
+
+ gravity_list l
+ = m
+ {
+ w = len l;
+
+ // matching identity
+ i = [0, 1 .. w - 1];
+
+ // weight identity and sum
+ s = sum (map2 multiply i l);
+
+ // sum of original histogram
+ s' = sum l;
+
+ // weighted mean
+ m = s / s';
+ }
+}
+
+project x
+ = oo_unary_function project_op x, is_class x
+ = im_project x, is_image x
+ = error (_ "bad arguments to " ++ "project")
+{
+ project_op = Operator "project" project Operator_type.COMPOUND false;
+}
+
+abs x
+ = oo_unary_function abs_op x, is_class x
+ = im_abs x, is_image x
+ = abs_cmplx x, is_complex x
+ = abs_num x, is_real x
+ = abs_list x, is_real_list x
+ = abs_list (map abs_list x), is_matrix x
+ = error (_ "bad arguments to " ++ "abs")
+{
+ abs_op = Operator "abs" abs Operator_type.COMPOUND false;
+
+ abs_list l = (sum (map square l)) ** 0.5;
+
+ abs_num n
+ = n, n >= 0
+ = -n;
+
+ abs_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
+}
+
+copy x
+ = oo_unary_function copy_op x, is_class x
+ = im_copy x, is_image x
+ = x
+{
+ copy_op = Operator "copy" copy Operator_type.COMPOUND_REWRAP false;
+}
+
+// like abs, but treat pixels as vectors ... ie. always get a 1-band image
+// back ... also treat matricies as lists of vectors
+// handy for dE from object difference
+abs_vec x
+ = oo_unary_function abs_vec_op x, is_class x
+ = abs_vec_image x, is_image x
+ = abs_vec_cmplx x, is_complex x
+ = abs_vec_num x, is_real x
+ = abs_vec_list x, is_real_list x
+ = mean (map abs_vec_list x), is_matrix x
+ = error (_ "bad arguments to " ++ "abs_vec")
+{
+ abs_vec_op = Operator "abs_vec"
+ abs_vec Operator_type.COMPOUND false;
+
+ abs_vec_list l = (sum (map square l)) ** 0.5;
+
+ abs_vec_num n
+ = n, n >= 0
+ = -n;
+
+ abs_vec_cmplx c = ((re c)**2 + (im c)**2) ** 0.5;
+
+ abs_vec_image im
+ = (sum (map square (bandsplit im))) ** 0.5;
+}
+
+transpose x
+ = oo_unary_function transpose_op x, is_class x
+ = transpose_image x, is_image x
+ = transpose_list x, is_listof is_list x
+ = error (_ "bad arguments to " ++ "transpose")
+{
+ transpose_op = Operator "transpose"
+ transpose Operator_type.COMPOUND_REWRAP false;
+
+ transpose_list l
+ = [], l' == []
+ = (map hd l') : (transpose_list (map tl l'))
+ {
+ l' = takewhile (not_equal []) l;
+ }
+
+ transpose_image = im_flipver @ im_rot270;
+}
+
+rot45 x
+ = oo_unary_function rot45_op x, is_class x
+ = error "rot45 image: not implemented", is_image x
+ = error (_ "bad arguments to " ++ "rot45")
+{
+ rot45_op = Operator "rot45"
+ rot45_object Operator_type.COMPOUND_REWRAP false;
+
+ rot45_object x
+ = rot45_matrix x, is_odd_square_matrix x
+ = error "rot45 image: not implemented", is_image x
+ = error (_ "bad arguments to " ++ "rot45");
+
+ // slow, but what the heck
+ rot45_matrix l = (im_rotate_dmask45 (Matrix l)).value;
+}
+
+// apply an image function to a [[real]] ... matrix is converted to a 1 band
+// image for processing
+apply_matrix_as_image fn m
+ = (get_value @ im_vips2mask @ fn @ im_mask2vips @ Matrix) m;
+
+// a general image/matrix operation where the mat version is most easily done
+// by converting mat->image->mat
+apply_matim_operation name fn x
+ = oo_unary_function class_op x, is_class x
+ = fn x, is_image x
+ = apply_matrix_as_image fn x, is_matrix x
+ = error (_ "bad arguments to " ++ name)
+{
+ class_op = Operator name
+ (apply_matim_operation name fn) Operator_type.COMPOUND_REWRAP false;
+}
+
+rot90 = apply_matim_operation "rot90" im_rot90;
+rot180 = apply_matim_operation "rot180" im_rot180;
+rot270 = apply_matim_operation "rot270" im_rot270;
+rotquad = apply_matim_operation "rotquad" im_rotquad;
+fliplr = apply_matim_operation "fliplr" im_fliphor;
+fliptb = apply_matim_operation "flipud" im_flipver;
+
+image_set_type type x
+ = oo_unary_function image_set_type_op x, is_class x
+ = im_copy_set x (to_real type)
+ (get_header "Xres" x) (get_header "Yres" x)
+ (get_header "Xoffset" x) (get_header "Yoffset" x),
+ is_image x
+ = error (_ "bad arguments to " ++ "image_set_type:" ++
+ print type ++ " " ++ print x)
+{
+ image_set_type_op = Operator "image_set_type"
+ (image_set_type type) Operator_type.COMPOUND_REWRAP false;
+}
+
+image_set_origin xoff yoff x
+ = oo_unary_function image_set_origin_op x, is_class x
+ = im_copy_set x
+ (get_header "Type" x)
+ (get_header "Xres" x) (get_header "Yres" x)
+ (to_real xoff) (to_real yoff),
+ is_image x
+ = error (_ "bad arguments to " ++ "image_set_origin")
+{
+ image_set_origin_op = Operator "image_set_origin"
+ (image_set_origin xoff yoff)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+cache tile_width tile_height max_tiles x
+ = oo_unary_function cache_op x, is_class x
+ = im_tile_cache_random x (to_real tile_width) (to_real tile_height)
+ (to_real max_tiles), is_image x
+ = error (_ "bad arguments to " ++ "cache")
+{
+ cache_op = Operator "cache"
+ (cache tile_width tile_height max_tiles)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+tile across down x
+ = oo_unary_function tile_op x, is_class x
+ = im_replicate x (to_real across) (to_real down), is_image x
+ = error (_ "bad arguments to " ++ "tile")
+{
+ tile_op = Operator "tile"
+ (tile across down) Operator_type.COMPOUND_REWRAP false;
+}
+
+grid tile_height across down x
+ = oo_unary_function grid_op x, is_class x
+ = im_grid x (to_real tile_height) (to_real across) (to_real down),
+ is_image x
+ = error (_ "bad arguments to " ++ "grid")
+{
+ grid_op = Operator "grid"
+ (grid tile_height across down) Operator_type.COMPOUND_REWRAP false;
+}
+
+max_pair a b
+ = a, a > b
+ = b;
+
+min_pair a b
+ = a, a < b
+ = b;
+
+range min value max = min_pair max (max_pair min value);
+
+max x
+ = oo_unary_function max_op x, is_class x
+ = im_max x, is_image x
+ = max_list x, is_list x
+ = x, is_number x
+ = error (_ "bad arguments to " ++ "max")
+{
+ max_op = Operator "max" max Operator_type.COMPOUND false;
+
+ max_list x
+ = error "max []", x == []
+ = foldr1 max_pair x, is_real_list x
+ = foldr1 max_pair (map max_list x), is_list x
+ = max x;
+}
+
+min x
+ = oo_unary_function min_op x, is_class x
+ = im_min x, is_image x
+ = min_list x, is_list x
+ = x, is_number x
+ = error (_ "bad arguments to " ++ "min")
+{
+ min_op = Operator "min" min Operator_type.COMPOUND false;
+
+ min_list x
+ = error "min []", x == []
+ = foldr1 min_pair x, is_real_list x
+ = foldr1 min_pair (map min_list x), is_list x
+ = min x;
+}
+
+maxpos x
+ = oo_unary_function maxpos_op x, is_class x
+ = im_maxpos x, is_image x
+ = maxpos_matrix x, is_matrix x
+ = maxpos_list x, is_list x
+ = error (_ "bad arguments to " ++ "maxpos")
+{
+ maxpos_op = Operator "maxpos" maxpos Operator_type.COMPOUND false;
+
+ maxpos_matrix m
+ = (-1, -1), m == [[]]
+ = (indexes?row, row)
+ {
+ max_value = max (Matrix m);
+ indexes = map (index (equal max_value)) m;
+ row = index (not_equal (-1)) indexes;
+ }
+
+ maxpos_list l
+ = -1, l == []
+ = index (equal (max l)) l;
+}
+
+minpos x
+ = oo_unary_function minpos_op x, is_class x
+ = im_minpos x, is_image x
+ = minpos_matrix x, is_matrix x
+ = minpos_list x, is_list x
+ = error (_ "bad arguments to " ++ "minpos")
+{
+ minpos_op = Operator "minpos" minpos Operator_type.COMPOUND false;
+
+ minpos_matrix m
+ = (-1, -1), m == [[]]
+ = (indexes?row, row)
+ {
+ min_value = min (Matrix m);
+ indexes = map (index (equal min_value)) m;
+ row = index (not_equal (-1)) indexes;
+ }
+
+ minpos_list l
+ = -1, l == []
+ = index (equal (min l)) l;
+}
+
+stats x
+ = oo_unary_function stats_op x, is_class x
+ = im_stats x, is_image x
+ = im_stats (to_image x).value, is_matrix x
+ = error (_ "bad arguments to " ++ "stats")
+{
+ stats_op = Operator "stats"
+ stats Operator_type.COMPOUND false;
+}
+
+e = 2.7182818284590452354;
+
+pi = 3.14159265358979323846;
+
+rad d = 2 * pi * (d / 360);
+
+deg r = 360 * r / (2 * pi);
+
+sign x
+ = oo_unary_function sign_op x, is_class x
+ = im_sign x, is_image x
+ = sign_cmplx x, is_complex x
+ = sign_num x, is_real x
+ = error (_ "bad arguments to " ++ "sign")
+{
+ sign_op = Operator "sign" sign Operator_type.COMPOUND_REWRAP false;
+
+ sign_num n
+ = 0, n == 0
+ = 1, n > 0
+ = -1;
+
+ sign_cmplx c
+ = (0, 0), mod == 0
+ = (re c / mod, im c / mod)
+ {
+ mod = abs c;
+ }
+}
+
+rint x
+ = oo_unary_function rint_op x, is_class x
+ = im_rint x, is_image x
+ = rint_value x, is_number x
+ = error (_ "bad arguments to " ++ "rint")
+{
+ rint_op = Operator "rint" rint Operator_type.ARITHMETIC false;
+
+ rint_value x
+ = (int) (x + 0.5), x > 0
+ = (int) (x - 0.5);
+}
+
+scale x
+ = oo_unary_function scale_op x, is_class x
+ = (unsigned char) x, is_number x
+ = im_scale x, is_image x
+ = scale_list x, is_real_list x || is_matrix x
+ = error (_ "bad arguments to " ++ "scale")
+{
+ scale_op = Operator "scale" scale Operator_type.COMPOUND_REWRAP false;
+
+ scale_list l
+ = apply_scale s o l
+ {
+ mn = find_limit min_pair l;
+ mx = find_limit max_pair l;
+ s = 255.0 / (mx - mn);
+ o = -(mn * s);
+ }
+
+ find_limit fn l
+ = find_limit fn (map (find_limit fn) l), is_listof is_list l
+ = foldr1 fn l;
+
+ apply_scale s o x
+ = x * s + o, is_number x
+ = map (apply_scale s o) x;
+}
+
+scaleps x
+ = oo_unary_function scale_op x, is_class x
+ = im_scaleps x, is_image x
+ = error (_ "bad arguments to " ++ "scale")
+{
+ scale_op = Operator "scaleps"
+ scaleps Operator_type.COMPOUND_REWRAP false;
+}
+
+fwfft x
+ = oo_unary_function fwfft_op x, is_class x
+ = im_fwfft x, is_image x
+ = error (_ "bad arguments to " ++ "fwfft")
+{
+ fwfft_op = Operator "fwfft"
+ fwfft Operator_type.COMPOUND_REWRAP false;
+}
+
+invfft x
+ = oo_unary_function invfft_op x, is_class x
+ = im_invfftr x, is_image x
+ = error (_ "bad arguments to " ++ "invfft")
+{
+ invfft_op = Operator "invfft"
+ invfft Operator_type.COMPOUND_REWRAP false;
+}
+
+falsecolour x
+ = oo_unary_function falsecolour_op x, is_class x
+ = image_set_type Image_type.sRGB (im_falsecolour x), is_image x
+ = error (_ "bad arguments to " ++ "falsecolour")
+{
+ falsecolour_op = Operator "falsecolour"
+ falsecolour Operator_type.COMPOUND_REWRAP false;
+}
+
+polar x
+ = oo_unary_function polar_op x, is_class x
+ = im_c2amph x, is_image x
+ = polar_cmplx x, is_complex x
+ = error (_ "bad arguments to " ++ "polar")
+{
+ polar_op = Operator "polar" polar Operator_type.COMPOUND false;
+
+ polar_cmplx r
+ = (l, a)
+ {
+ a
+ = 270, x == 0 && y < 0
+ = 90, x == 0 && y >= 0
+ = 360 + atan (y / x), x > 0 && y < 0
+ = atan (y / x), x > 0 && y >= 0
+ = 180 + atan (y / x);
+
+ l = (x ** 2 + y ** 2) ** 0.5;
+
+ x = re r;
+ y = im r;
+ }
+}
+
+rectangular x
+ = oo_unary_function rectangular_op x, is_class x
+ = im_c2rect x, is_image x
+ = rectangular_cmplx x, is_complex x
+ = error (_ "bad arguments to " ++ "rectangular")
+{
+ rectangular_op = Operator "rectangular"
+ rectangular Operator_type.COMPOUND false;
+
+ rectangular_cmplx p
+ = (x, y)
+ {
+ l = re p;
+ a = im p;
+
+ x = l * cos a;
+ y = l * sin a;
+ }
+}
+
+// we can't use colour_unary: that likes 3 band only
+recomb matrix x
+ = oo_unary_function recomb_op x, is_class x
+ = im_recomb x matrix, is_image x
+ = recomb_real_list x, is_real_list x
+ = map recomb_real_list x, is_matrix x
+ = error (_ "bad arguments to " ++ "recomb")
+{
+ // COMPOUND_REWRAP ... signal to the colour class to go to image and
+ // back
+ recomb_op = Operator "recomb"
+ (recomb matrix) Operator_type.COMPOUND_REWRAP false;
+
+ // process [1,2,3 ..] as an image
+ recomb_real_list l
+ = (to_matrix im').value?0
+ {
+ im = (float) (to_image (Vector l)).value;
+ im' = recomb matrix im;
+ }
+}
+
+extract_area x y w h obj
+ = oo_unary_function extract_area_op obj, is_class obj
+ = im_extract_area obj x' y' w' h', is_image obj
+ = map (extract_range x' w') (extract_range y' h' obj), is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_area")
+{
+ x' = to_real x;
+ y' = to_real y;
+ w' = to_real w;
+ h' = to_real h;
+
+ extract_area_op = Operator "extract_area" (extract_area x y w h)
+ Operator_type.COMPOUND_REWRAP false;
+
+ extract_range from length list
+ = (take length @ drop from) list;
+}
+
+extract_band b obj = subscript obj b;
+
+extract_row y obj
+ = oo_unary_function extract_row_op obj, is_class obj
+ = extract_area 0 y' (get_width obj) 1 obj, is_image obj
+ = [obj?y'], is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_row")
+{
+ y' = to_real y;
+
+ extract_row_op = Operator "extract_row" (extract_row y)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+extract_column x obj
+ = oo_unary_function extract_column_op obj, is_class obj
+ = extract_area x' 0 1 height obj, is_image obj
+ = map (\row [row?x']) obj, is_matrix obj
+ = error (_ "bad arguments to " ++ "extract_column")
+{
+ x' = to_real x;
+ height = get_header "Ysize" obj;
+
+ extract_column_op = Operator "extract_column" (extract_column x)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+blend cond in1 in2
+ = oo_binary_function blend_op cond [in1,in2], is_class cond
+ = im_blend (get_image cond) (get_image in1) (get_image in2),
+ has_image cond && has_image in1 && has_image in2
+ = error (_ "bad arguments to " ++ "blend" ++ ": " ++
+ join_sep ", " (map print [cond, in1, in2]))
+{
+ blend_op = Operator "blend"
+ blend_obj Operator_type.COMPOUND_REWRAP false;
+
+ blend_obj cond x
+ = blend_result_image
+ {
+ [then_part, else_part] = x;
+
+ // get things about our output from inputs in this order
+ objects = [then_part, else_part, cond];
+
+ // properties of our output image
+ target_width = get_member_list has_width get_width objects;
+ target_height = get_member_list has_height get_height objects;
+ target_bands = get_member_list has_bands get_bands objects;
+ target_format = get_member_list has_format get_format objects;
+ target_type = get_member_list has_type get_type objects;
+
+ to_image x
+ = to_image_size target_width target_height
+ target_bands target_format x;
+
+ [then_image, else_image] = map to_image [then_part, else_part];
+
+ blend_result_image = image_set_type target_type
+ (im_blend cond then_image else_image);
+ }
+}
+
+// do big first: we want to keep big's class, if possible
+// eg. big is a Plot, small is a 1x1 Image
+insert x y small big
+ = oo_binary'_function insert_op small big, is_class big
+ = oo_binary_function insert_op small big, is_class small
+ = im_insert big small (to_real x) (to_real y),
+ is_image small && is_image big
+ = error (_ "bad arguments to " ++ "insert")
+{
+ insert_op = Operator "insert"
+ (insert x y) Operator_type.COMPOUND_REWRAP false;
+}
+
+insert_noexpand x y small big
+ = oo_binary_function insert_noexpand_op small big, is_class small
+ = oo_binary'_function insert_noexpand_op small big, is_class big
+ = im_insert_noexpand big small (to_real x) (to_real y),
+ is_image small && is_image big
+ = error (_ "bad arguments to " ++ "insert_noexpand")
+{
+ insert_noexpand_op = Operator "insert_noexpand"
+ (insert_noexpand x y) Operator_type.COMPOUND_REWRAP false;
+}
+
+decode im
+ = oo_unary_function decode_op im, is_class im
+ = decode_im im, is_image im
+ = error (_ "bad arguments to " ++ "decode")
+{
+ decode_op = Operator "decode"
+ decode Operator_type.COMPOUND_REWRAP false;
+
+ decode_im im
+ = im_LabQ2Lab im, get_coding im == Image_coding.LABPACK
+ = im_rad2float im, get_coding im == Image_coding.RAD
+ = im;
+}
+
+measure_draw across down measure image
+ = mark
+{
+ patch_width = image.width / across;
+ sample_width = patch_width * (measure / 100);
+ left_margin = (patch_width - sample_width) / 2;
+ patch_height = image.height / down;
+ sample_height = patch_height * (measure / 100);
+ top_margin = (patch_height - sample_height) / 2;
+
+ cods = [[x * patch_width + left_margin, y * patch_height + top_margin] ::
+ y <- [0 .. down - 1]; x <- [0 .. across - 1]];
+ x = map (extract 0) cods;
+ y = map (extract 1) cods;
+
+ outer = mkim [$pixel => 255] sample_width sample_height 1;
+ inner = mkim [] (sample_width - 4) (sample_height - 4) 1;
+ patch = insert 2 2 inner outer;
+
+ bg = mkim [] image.width image.height 1;
+
+ mask = Image (im_insertset bg.value patch.value x y);
+
+ image' = colour_transform_to Image_type.sRGB image;
+
+ mark = if mask then Vector [0, 255, 0] else image';
+}
+
+measure_sample across down measure image
+ = measures
+{
+ patch_width = image.width / across;
+ sample_width = patch_width * (measure / 100);
+ left_margin = (patch_width - sample_width) / 2;
+ patch_height = image.height / down;
+ sample_height = patch_height * (measure / 100);
+ top_margin = (patch_height - sample_height) / 2;
+
+ cods = [[x * patch_width + left_margin, y * patch_height + top_margin] ::
+ y <- [0 .. down - 1]; x <- [0 .. across - 1]];
+
+ image' = decode image;
+ patches = map (\p extract_area p?0 p?1 sample_width sample_height image')
+ cods;
+ measures = Matrix (map (map mean) (map bandsplit patches));
+}
+
+extract_bands b n obj
+ = oo_unary_function extract_bands_op obj, is_class obj
+ = im_extract_bands obj (to_real b) (to_real n), is_image obj
+ = error (_ "bad arguments to " ++ "extract_bands")
+{
+ extract_bands_op = Operator "extract_bands"
+ (extract_bands b n) Operator_type.COMPOUND_REWRAP false;
+}
+
+invert x
+ = oo_unary_function invert_op x, is_class x
+ = im_invert x, is_image x
+ = 255 - x, is_real x
+ = error (_ "bad arguments to " ++ "invert")
+{
+ invert_op = Operator "invert" invert Operator_type.COMPOUND false;
+}
+
+transform ipol wrap params image
+ = oo_unary_function transform_op image, is_class image
+ = im_transform image
+ (to_matrix params) (to_real ipol) (to_real wrap), is_image image
+ = error (_ "bad arguments to " ++ "transform")
+{
+ transform_op = Operator "transform"
+ (transform ipol wrap params)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+transform_search max_error max_iterations order ipol wrap sample reference
+ = oo_binary_function transform_search_op sample reference, is_class sample
+ = oo_binary'_function transform_search_op sample reference,
+ is_class reference
+ = im_transform_search sample reference
+ (to_real max_error) (to_real max_iterations) (to_real order)
+ (to_real ipol) (to_real wrap),
+ is_image sample && is_image reference
+ = error (_ "bad arguments to " ++ "transform_search")
+{
+ transform_search_op = Operator "transform_search"
+ (transform_search max_error max_iterations order ipol wrap)
+ Operator_type.COMPOUND false;
+}
+
+rotate interp angle image
+ = oo_binary_function rotate_op angle image, is_class angle
+ = oo_binary'_function rotate_op angle image, is_class image
+ = im_affinei_all image interp.value a (-b) b a 0 0,
+ is_real angle && is_image image
+ = error (_ "bad arguments to " ++ "rotate")
+{
+ rotate_op = Operator "rotate"
+ (rotate interp) Operator_type.COMPOUND_REWRAP false;
+ a = cos angle;
+ b = sin angle;
+}
+
+matrix_binary fn a b
+ = itom (fn (mtoi a) (mtoi b))
+{
+ mtoi x = im_mask2vips (Matrix x);
+ itom x = (im_vips2mask x).value;
+}
+
+join_lr a b
+ = oo_binary_function join_lr_op a b, is_class a
+ = oo_binary'_function join_lr_op a b, is_class b
+ = join_im a b, is_image a && is_image b
+ = matrix_binary join_im a b, is_matrix a && is_matrix b
+ = error (_ "bad arguments to " ++ "join_lr")
+{
+ join_lr_op = Operator "join_lr"
+ join_lr Operator_type.COMPOUND_REWRAP false;
+
+ join_im a b = insert (get_width a) 0 b a;
+}
+
+join_tb a b
+ = oo_binary_function join_tb_op a b, is_class a
+ = oo_binary'_function join_tb_op a b, is_class b
+ = join_im a b, is_image a && is_image b
+ = matrix_binary join_im a b, is_matrix a && is_matrix b
+ = error (_ "bad arguments to " ++ "join_tb")
+{
+ join_tb_op = Operator "join_tb"
+ join_tb Operator_type.COMPOUND_REWRAP false;
+
+ join_im a b = insert 0 (get_height a) b a;
+}
+
+conj x
+ = oo_unary_function conj_op x, is_class x
+ = (re x, -im x),
+ is_complex x ||
+ (is_image x && format == Image_format.COMPLEX) ||
+ (is_image x && format == Image_format.DPCOMPLEX)
+ // assume it's some sort of real
+ = x
+{
+ format = get_header "BandFmt" x;
+ conj_op = Operator "conj" conj Operator_type.COMPOUND false;
+}
+
+clip2fmt format image
+ = oo_unary_function clip2fmt_op image, is_class image
+ = im_clip2fmt image (to_real format), is_image image
+ = error (_ "bad arguments to " ++ "clip2fmt")
+{
+ clip2fmt_op = Operator "clip2fmt"
+ (clip2fmt format) Operator_type.COMPOUND_REWRAP false;
+}
+
+embed type x y w h im
+ = oo_unary_function embed_op im, is_class im
+ = im_embed im (to_real type)
+ (to_real x) (to_real y) (to_real w) (to_real h), is_image im
+ = error (_ "bad arguments to " ++ "embed")
+{
+ embed_op = Operator "embed"
+ (embed type x y w h) Operator_type.COMPOUND_REWRAP false;
+}
+
+/* Morph a mask with a [[real]] matrix ... turn m2 into an image, morph it
+ * with m1, turn it back to a matrix again.
+ */
+_morph_2_masks fn m1 m2
+ = m''
+{
+ // The [[real]] can contain 128 values ... squeeze them out!
+ image = im_mask2vips (Matrix m2) == 255;
+ m2_width = get_width image;
+ m2_height = get_height image;
+
+ // need to embed m2 in an image large enough for us to be able to
+ // position m1 all around the edges, with a 1 pixel overlap
+ image' = embed 0
+ (m1.width / 2) (m1.height / 2)
+ (m2_width + (m1.width - 1)) (m2_height + (m1.height - 1))
+ image;
+
+ // morph!
+ image'' = fn m1 image';
+
+ // back to mask
+ m' = im_vips2mask ((double) image'');
+
+ // Turn 0 in output to 128 (don't care).
+ m''
+ = map (map fn) m'.value
+ {
+ fn a
+ = 128, a == 0;
+ = a;
+ }
+}
+
+dilate mask image
+ = oo_unary_function dilate_op image, is_class image
+ = im_dilate image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "dilate")
+{
+ dilate_op = Operator "dilate"
+ dilate_object Operator_type.COMPOUND_REWRAP false;
+
+ dilate_object x
+ = _morph_2_masks dilate mask x, is_matrix x
+ = dilate mask x;
+}
+
+erode mask image
+ = oo_unary_function erode_op image, is_class image
+ = im_erode image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "erode")
+{
+ erode_op = Operator "erode"
+ erode_object Operator_type.COMPOUND_REWRAP false;
+
+ erode_object x
+ = _morph_2_masks erode mask x, is_matrix x
+ = erode mask x;
+}
+
+conv mask image
+ = oo_unary_function conv_op image, is_class image
+ = im_conv image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "conv" ++ ": " ++
+ "conv (" ++ print mask ++ ") (" ++ print image ++ ")")
+{
+ conv_op = Operator "conv"
+ (conv mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convf mask image
+ = oo_unary_function convf_op image, is_class image
+ = im_conv_f image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convf" ++ ": " ++
+ "convf (" ++ print mask ++ ") (" ++ print image ++ ")")
+{
+ convf_op = Operator "convf"
+ (convf mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convsep mask image
+ = oo_unary_function convsep_op image, is_class image
+ = im_convsep image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convsep")
+{
+ convsep_op = Operator "convsep"
+ (convsep mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+aconvsep layers mask image
+ = oo_unary_function aconvsep_op image, is_class image
+ = im_aconvsep image (to_matrix mask) (to_real layers), is_image image
+ = error (_ "bad arguments to " ++ "aconvsep")
+{
+ aconvsep_op = Operator "aconvsep"
+ (aconvsep layers mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+convsepf mask image
+ = oo_unary_function convsepf_op image, is_class image
+ = im_convsep_f image (to_matrix mask), is_image image
+ = error (_ "bad arguments to " ++ "convsepf")
+{
+ convsepf_op = Operator "convsepf"
+ (convsepf mask) Operator_type.COMPOUND_REWRAP false;
+}
+
+rank w h n image
+ = oo_unary_function rank_op image, is_class image
+ = im_rank image (to_real w) (to_real h) (to_real n), is_image image
+ = error (_ "bad arguments to " ++ "rank")
+{
+ rank_op = Operator "rank"
+ (rank w h n) Operator_type.COMPOUND_REWRAP false;
+}
+
+rank_image n x
+ = rlist x.value, is_Group x
+ = rlist x, is_list x
+ = error (_ "bad arguments to " ++ "rank_image")
+{
+ rlist l
+ = wrapper ranked, has_wrapper
+ = ranked
+ {
+ has_wrapper = has_member_list (has_member "Image") l;
+ wrapper = get_member_list (has_member "Image") (get_member "Image") l;
+ ranked = im_rank_image (map get_image l) (to_real n);
+ }
+}
+
+// find the correlation surface for a small image within a big one
+correlate small big
+ = oo_binary_function correlate_op small big, is_class small
+ = oo_binary'_function correlate_op small big, is_class big
+ = im_spcor big small, is_image small && is_image big
+ = error (_ "bad arguments to " ++ "correlate")
+{
+ correlate_op = Operator "correlate"
+ correlate Operator_type.COMPOUND_REWRAP false;
+}
+
+// just sum-of-squares-of-differences
+correlate_fast small big
+ = oo_binary_function correlate_fast_op small big, is_class small
+ = oo_binary'_function correlate_fast_op small big, is_class big
+ = im_fastcor big small, is_image small && is_image big
+ = error (_ "bad arguments to " ++ "correlate_fast")
+{
+ correlate_fast_op = Operator "correlate_fast"
+ correlate_fast Operator_type.COMPOUND_REWRAP false;
+}
+
+// set Type, wrap as Plot_hist if it's a class
+hist_tag x
+ = oo_unary_function hist_tag_op x, is_class x
+ = image_set_type Image_type.HISTOGRAM x, is_image x
+ = error (_ "bad arguments to " ++ "hist_tag")
+{
+ hist_tag_op = Operator "hist_tag"
+ (Plot_histogram @ hist_tag) Operator_type.COMPOUND false;
+}
+
+hist_find x
+ = oo_unary_function hist_find_op x, is_class x
+ = im_histgr x (-1), is_image x
+ = error (_ "bad arguments to " ++ "hist_find")
+{
+ hist_find_op = Operator "hist_find"
+ (Plot_histogram @ hist_find) Operator_type.COMPOUND false;
+}
+
+hist_find_nD bins image
+ = oo_unary_function hist_find_nD_op image, is_class image
+ = im_histnD image (to_real bins), is_image image
+ = error (_ "bad arguments to " ++ "hist_find_nD")
+{
+ hist_find_nD_op = Operator "hist_find_nD"
+ (hist_find_nD bins) Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_find_indexed mode index value
+ = oo_binary_function hist_find_indexed_op index value, is_class index
+ = oo_binary'_function hist_find_indexed_op index value, is_class value
+ = indexed index value, is_image index && is_image value
+ = error (_ "bad arguments to " ++ "hist_find_indexed")
+{
+ hist_find_indexed_op = Operator "hist_find_indexed"
+ (compose (compose Plot_histogram) (hist_find_indexed mode))
+ Operator_type.COMPOUND false;
+
+ indexed index value
+ = out
+ {
+ [out] = vips_call "hist_find_indexed" [value, index] [
+ "combine" => mode
+ ];
+ }
+}
+
+hist_entropy x
+ = oo_unary_function hist_entropy_op x, is_class x
+ = entropy x, is_image x
+ = error (_ "bad arguments to " ++ "hist_entropy")
+{
+ hist_entropy_op = Operator "hist_entropy"
+ hist_entropy Operator_type.COMPOUND_REWRAP false;
+
+ entropy x
+ = out
+ {
+ [out] = vips_call "hist_entropy" [x] [
+ ];
+ }
+}
+
+hist_map hist image
+ = oo_binary_function hist_map_op hist image, is_class hist
+ = oo_binary'_function hist_map_op hist image, is_class image
+ = im_maplut image hist, is_image hist && is_image image
+ = error (_ "bad arguments to " ++ "hist_map")
+{
+ // can't use rewrap, as we want to always wrap as image
+ hist_map_op = Operator "hist_map"
+ (compose (compose Image) hist_map) Operator_type.COMPOUND false;
+}
+
+hist_cum hist
+ = oo_unary_function hist_cum_op hist, is_class hist
+ = im_histcum hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_cum")
+{
+ hist_cum_op = Operator "hist_cum"
+ hist_cum Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_diff hist
+ = oo_unary_function hist_diff_op hist, is_class hist
+ = im_histdiff hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_diff")
+{
+ hist_diff_op = Operator "hist_diff"
+ hist_diff Operator_type.COMPOUND_REWRAP false;
+
+ im_histdiff h
+ = (conv (Matrix [[-1, 1]]) @ clip2fmt (fmt (get_format h))) h
+ {
+ // up the format so it can represent the whole range of
+ // possible values from this mask
+ fmt x
+ = Image_format.SHORT,
+ x == Image_format.UCHAR || x == Image_format.CHAR
+ = Image_format.INT,
+ x == Image_format.USHORT || x == Image_format.SHORT ||
+ x == Image_format.UINT
+ = x;
+ }
+}
+
+hist_norm hist
+ = oo_unary_function hist_norm_op hist, is_class hist
+ = im_histnorm hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_norm")
+{
+ hist_norm_op = Operator "hist_norm"
+ hist_norm Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_inv hist
+ = oo_unary_function hist_inv_op hist, is_class hist
+ = inv hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_inv")
+{
+ hist_inv_op = Operator "hist_inv"
+ hist_inv Operator_type.COMPOUND_REWRAP false;
+
+ inv im
+ = im_invertlut (to_matrix im''') len
+ {
+ // need a vertical doublemask
+ im'
+ = rot90 im, get_width im > 1 && get_height im == 1
+ = im, get_width im == 1 && get_height im > 1
+ = error (_ "not a hist");
+ len = get_height im';
+
+ // values must be scaled to 0 - 1
+ im'' = im' / (max im');
+
+ // add an index column on the left
+ // again, must be in 0-1
+ y = ((make_xy 1 len)?1) / len;
+ im''' = y ++ im'';
+ }
+}
+
+hist_match in ref
+ = oo_binary_function hist_match_op in ref, is_class in
+ = oo_binary'_function hist_match_op in ref, is_class ref
+ = im_histspec in ref, is_image in && is_image ref
+ = error (_ "bad arguments to " ++ "hist_match")
+{
+ hist_match_op = Operator "hist_match"
+ hist_match Operator_type.COMPOUND_REWRAP false;
+}
+
+hist_equalize x = hist_map ((hist_norm @ hist_cum @ hist_find) x) x;
+
+hist_equalize_local w h l image
+ = oo_unary_function hist_equalize_local_op image, is_class image
+ = out, is_image image
+ = error (_ "bad arguments to " ++ "hist_equalize_local")
+{
+ hist_equalize_local_op = Operator "hist_equalize_local"
+ (hist_equalize_local w h l) Operator_type.COMPOUND_REWRAP false;
+
+ [out] = vips_call "hist_local"
+ [image, to_real w, to_real h] [$max_slope => to_real l];
+}
+
+// find the threshold below which are percent of the image (percent in [0,1])
+// eg. hist_thresh 0.1 x == 12, then x < 12 will light up 10% of the pixels
+hist_thresh percent image
+ = x
+{
+ // our own normaliser ... we don't want to norm channels separately
+ // norm to [0,1]
+ my_hist_norm h = h / max h;
+
+ // normalised cumulative hist
+ // we sum the channels before we normalise, because we want to treat them
+ // all the same
+ h = (my_hist_norm @ sum @ bandsplit @ hist_cum @ hist_find)
+ image.value;
+
+ // threshold that, then use im_profile to search for the x position in the
+ // histogram
+ x = mean (im_profile (h > percent) 1);
+}
+
+/* Sometimes useful, despite plotting now being built in, for making
+ * diagnostic images.
+ */
+hist_plot hist
+ = oo_unary_function hist_plot_op hist, is_class hist
+ = im_histplot hist, is_image hist
+ = error (_ "bad arguments to " ++ "hist_plot")
+{
+ hist_plot_op = Operator "hist_plot"
+ (Image @ hist_plot) Operator_type.COMPOUND false;
+}
+
+zerox d x
+ = oo_unary_function zerox_op x, is_class x
+ = im_zerox x (to_real d), is_image x
+ = error (_ "bad arguments to " ++ "zerox")
+{
+ zerox_op = Operator "zerox"
+ (zerox d) Operator_type.COMPOUND_REWRAP false;
+}
+
+reduce kernel xshr yshr image
+ = oo_unary_function reduce_op image, is_class image
+ = reduce_im image, is_image image
+ = error (_ "bad arguments to " ++ "reduce")
+{
+ reduce_op = Operator "reduce"
+ reduce_im Operator_type.COMPOUND_REWRAP false;
+
+ reduce_im im
+ = out
+ {
+ [out] = vips_call "reduce" [im, xshr, yshr] [$kernel => kernel.value];
+ }
+}
+
+similarity interpolate scale angle image
+ = oo_unary_function similarity_op image, is_class image
+ = similarity_im image, is_image image
+ = error (_ "bad arguments to " ++ "similarity")
+{
+ similarity_op = Operator "similarity"
+ similarity_im Operator_type.COMPOUND_REWRAP false;
+
+ similarity_im im
+ = out
+ {
+ [out] = vips_call "similarity" [im] [
+ $interpolate => interpolate.value,
+ $scale => scale,
+ $angle => angle
+ ];
+ }
+}
+
+resize kernel xfac yfac image
+ = oo_unary_function resize_op image, is_class image
+ = resize_im image, is_image image
+ = error (_ "bad arguments to " ++ "resize")
+{
+ resize_op = Operator "resize"
+ resize_im Operator_type.COMPOUND_REWRAP false;
+
+ xfac' = to_real xfac;
+ yfac' = to_real yfac;
+
+ rxfac' = 1 / xfac';
+ ryfac' = 1 / yfac';
+
+ is_nn = kernel.type == Kernel_type.NEAREST_NEIGHBOUR;
+
+ resize_im im
+ // upscale by integer factor, nearest neighbour
+ = im_zoom im xfac' yfac',
+ is_int xfac' && is_int yfac' &&
+ xfac' >= 1 && yfac' >= 1 &&
+ is_nn
+
+ // downscale by integer factor, nearest neighbour
+ = im_subsample im rxfac' ryfac',
+ is_int rxfac' && is_int ryfac' &&
+ rxfac' >= 1 && ryfac' >= 1 &&
+ is_nn
+
+ // everything else ... we just pass on to vips_resize()
+ = vips_resize kernel xfac' yfac' im
+ {
+ vips_resize kernel hscale vscale im
+ = out
+ {
+ [out] = vips_call "resize" [im, hscale]
+ [$vscale => vscale, $kernel => kernel.value];
+ }
+ }
+}
+
+sharpen radius x1 y2 y3 m1 m2 in
+ = oo_unary_function sharpen_op in, is_class in
+ = im_sharpen in (to_real radius)
+ (to_real x1) (to_real y2) (to_real y3)
+ (to_real m1) (to_real m2), is_image in
+ = error (_ "bad arguments to " ++ "sharpen")
+{
+ sharpen_op = Operator "sharpen"
+ (sharpen radius x1 y2 y3 m1 m2)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+tone_analyse s m h sa ma ha in
+ = oo_unary_function tone_analyse_op in, is_class in
+ = im_tone_analyse in
+ (to_real s) (to_real m) (to_real h)
+ (to_real sa) (to_real ma) (to_real ha), is_image in
+ = error (_ "bad arguments to " ++ "tone_analyse")
+{
+ tone_analyse_op = Operator "tone_analyse"
+ (Plot_histogram @ tone_analyse s m h sa ma ha)
+ Operator_type.COMPOUND false;
+}
+
+tone_map hist image
+ = oo_binary_function tone_map_op hist image, is_class hist
+ = oo_binary'_function tone_map_op hist image, is_class image
+ = im_tone_map image hist, is_image hist && is_image image
+ = error (_ "bad arguments to " ++ "tone_map")
+{
+ tone_map_op = Operator "tone_map"
+ tone_map Operator_type.COMPOUND_REWRAP false;
+}
+
+tone_build fmt b w s m h sa ma ha
+ = (Plot_histogram @ clip2fmt fmt)
+ (im_tone_build_range mx mx
+ (to_real b) (to_real w)
+ (to_real s) (to_real m) (to_real h)
+ (to_real sa) (to_real ma) (to_real ha))
+{
+ mx = Image_format.maxval fmt;
+}
+
+icc_export depth profile intent in
+ = oo_unary_function icc_export_op in, is_class in
+ = im_icc_export_depth in
+ (to_real depth) (expand profile) (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_export")
+{
+ icc_export_op = Operator "icc_export"
+ (icc_export depth profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_import profile intent in
+ = oo_unary_function icc_import_op in, is_class in
+ = im_icc_import in
+ (expand profile) (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_import")
+{
+ icc_import_op = Operator "icc_import"
+ (icc_import profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_import_embedded intent in
+ = oo_unary_function icc_import_embedded_op in, is_class in
+ = im_icc_import_embedded in (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_import_embedded")
+{
+ icc_import_embedded_op = Operator "icc_import_embedded"
+ (icc_import_embedded intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_transform in_profile out_profile intent in
+ = oo_unary_function icc_transform_op in, is_class in
+ = im_icc_transform in
+ (expand in_profile) (expand out_profile)
+ (to_real intent), is_image in
+ = error (_ "bad arguments to " ++ "icc_transform")
+{
+ icc_transform_op = Operator "icc_transform"
+ (icc_transform in_profile out_profile intent)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+icc_ac2rc profile in
+ = oo_unary_function icc_ac2rc_op in, is_class in
+ = im_icc_ac2rc in (expand profile), is_image in
+ = error (_ "bad arguments to " ++ "icc_ac2rc")
+{
+ icc_ac2rc_op = Operator "icc_ac2rc"
+ (icc_ac2rc profile)
+ Operator_type.COMPOUND_REWRAP false;
+}
+
+
+// Given a xywh rect, flip it around so wh are always positive
+rect_normalise x y w h
+ = [x', y', w', h']
+{
+ x'
+ = x + w, w < 0
+ = x;
+ y'
+ = y + h, h < 0
+ = y;
+ w' = abs w;
+ h' = abs h;
+}
+
+draw_flood_blob x y ink image
+ = oo_unary_function draw_flood_blob_op image, is_class image
+ = im_draw_flood_blob image (to_real x) (to_real y) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_flood_blob")
+{
+ draw_flood_blob_op = Operator "draw_flood_blob"
+ (draw_flood_blob x y ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_flood x y ink image
+ = oo_unary_function draw_flood_op image, is_class image
+ = im_draw_flood image (to_real x) (to_real y) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_flood")
+{
+ draw_flood_op = Operator "draw_flood"
+ (draw_flood x y ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+/* This version of draw_rect uses insert_noexpand and will be fast, even for
+ * huge images.
+ */
+draw_rect_width x y w h f t ink image
+ = oo_unary_function draw_rect_width_op image, is_class image
+ = my_draw_rect_width image (to_int x) (to_int y)
+ (to_int w) (to_int h) (to_int f) (to_int t) ink,
+ is_image image
+ = error (_ "bad arguments to " ++ "draw_rect_width")
+{
+ draw_rect_width_op = Operator "draw_rect_width"
+ (draw_rect_width x y w h f t ink)
+ Operator_type.COMPOUND_REWRAP false;
+
+ my_draw_rect_width image x y w h f t ink
+ = insert x' y' (block w' h') image, f == 1
+ = (insert x' y' (block w' t) @
+ insert (x' + w' - t) y' (block t h') @
+ insert x' (y' + h' - t) (block w' t) @
+ insert x' y' (block t h')) image
+ {
+ insert = insert_noexpand;
+ block w h = image_new w h (get_bands image) (get_format image)
+ (get_coding image) (get_type image) ink' 0 0;
+ ink'
+ = Vector ink, is_list ink
+ = ink;
+ [x', y', w', h'] = rect_normalise x y w h;
+ }
+}
+
+/* Default to 1 pixel wide edges.
+ */
+draw_rect x y w h f ink image
+ = draw_rect_width x y w h f 1 ink image;
+
+/* This version of draw_rect uses the paintbox rect draw operation. It is an
+ * inplace operation and will use bucketloads of memory.
+ */
+draw_rect_paintbox x y w h f ink image
+ = oo_unary_function draw_rect_op image, is_class image
+ = im_draw_rect image (to_real x) (to_real y)
+ (to_real w) (to_real h) (to_real f) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_rect_paintbox")
+{
+ draw_rect_op = Operator "draw_rect"
+ (draw_rect x y w h f ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_circle x y r f ink image
+ = oo_unary_function draw_circle_op image, is_class image
+ = im_draw_circle image (to_real x) (to_real y)
+ (to_real r) (to_real f) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_circle")
+{
+ draw_circle_op = Operator "draw_circle"
+ (draw_circle x y r f ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+draw_line x1 y1 x2 y2 ink image
+ = oo_unary_function draw_line_op image, is_class image
+ = im_draw_line image (to_real x1) (to_real y1)
+ (to_real x2) (to_real y2) ink, is_image image
+ = error (_ "bad arguments to " ++ "draw_line")
+{
+ draw_line_op = Operator "draw_line"
+ (draw_line x1 y1 x2 y2 ink) Operator_type.COMPOUND_REWRAP false;
+}
+
+print_base base in
+ = oo_unary_function print_base_op in, is_class in
+ = map (print_base base) in, is_list in
+ = print_base_real, is_real in
+ = error (_ "bad arguments to " ++ "print_base")
+{
+ print_base_op
+ = Operator "print_base" (print_base base) Operator_type.COMPOUND false;
+
+ print_base_real
+ = error "print_base: bad base", base < 2 || base > 16
+ = "0", in < 0 || chars == []
+ = reverse chars
+ {
+ digits = map (\x x % base)
+ (takewhile (not_equal 0)
+ (iterate (\x idivide x base) in));
+ chars = map tohd digits;
+
+ tohd x
+ = (char) ((int) '0' + x), x < 10
+ = (char) ((int) 'A' + (x - 10));
+ }
+}
+
+/* id x: the identity function
+ *
+ * id :: * -> *
+ */
+id x = x;
+
+/* const x y: junk y, return x
+ *
+ * (const 3) is the function that always returns 3.
+ * const :: * -> ** -> *
+ */
+const x y = x;
+
+/* converse fn a b: swap order of args to fn
+ *
+ * converse fn a b == fn b a
+ * converse :: (* -> ** -> ***) -> ** -> * -> ***
+ */
+converse fn a b = fn b a;
+
+/* fix fn x: find the fixed point of a function
+ */
+fix fn x = limit (iterate fn x);
+
+/* until pred fn n: apply fn to n until pred succeeds; return that value
+ *
+ * until (more 1000) (multiply 2) 1 = 1024
+ * until :: (* -> bool) -> (* -> *) -> * -> *
+ */
+until pred fn n
+ = n, pred n
+ = until pred fn (fn n);
+
+/* Infinite list of primes.
+ */
+primes
+ = 1 : (sieve [2 ..])
+{
+ sieve l = hd l : sieve (filter (nmultiple (hd l)) (tl l));
+ nmultiple n x = x / n != (int) (x / n);
+}
+
+/* Map an n-ary function (pass the args as a list) over groups of objects.
+ * The objects can be single objects or groups. If more than one
+ * object is a group, we iterate for the length of the smallest group.
+ * Don't loop over plain lists, since we want (eg.) "mean [1, 2, 3]" to work.
+ * Treat [] as no-value --- ie. if any of the inputs are [] we put [] into the
+ * output and don't apply the function.
+
+ copy-pasted into _types, keep in sync
+
+ */
+map_nary fn args
+ = fn args, groups == []
+ = Group (map process [0, 1 .. shortest - 1])
+{
+ // find all the group arguments
+ groups = filter is_Group args;
+
+ // what's the length of the shortest group arg?
+ shortest = foldr1 min_pair (map (len @ get_value) groups);
+
+ // process index n ... pull that member from each argument
+ // recurse to handle application, so we work for nested groups too
+ process n
+ = NULL, any (map (is_noval n) args)
+ = map_nary fn (map (extract n) args)
+ {
+ extract n arg
+ = arg.value?n, is_Group arg
+ = arg;
+
+ is_noval n arg = is_Group arg && arg.value?n == NULL;
+ }
+}
+
+/* Map a 1-ary function over an object.
+ */
+map_unary fn a = map_nary (list_1ary fn) [a];
+
+/* Map a 2-ary function over a pair of objects.
+ */
+map_binary fn a b = map_nary (list_2ary fn) [a, b];
+
+/* Map a 3-ary function over three objects.
+ */
+map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
+
+/* Map a 4-ary function over three objects.
+ */
+map_quaternary fn a b c d = map_nary (list_4ary fn) [a, b, c, d];
+
+/* Same as map_nary, but for lists. Handy for (eg.) implementing arith ops on
+ * vectors and matricies.
+ */
+map_nary_list fn args
+ = fn args, lists == []
+ = map process [0, 1 .. shortest - 1]
+{
+ // find all the list arguments
+ lists = filter is_list args;
+
+ // what's the length of the shortest list arg?
+ shortest = foldr1 min_pair (map len lists);
+
+ // process index n ... pull that member from each argument
+ // recurse to handle application, so we work for nested lists too
+ process n
+ = map_nary_list fn (map (extract n) args)
+ {
+ extract n arg
+ = arg?n, is_list arg
+ = arg;
+ }
+}
+
+map_unaryl fn a = map_nary_list (list_1ary fn) [a];
+
+map_binaryl fn a b = map_nary_list (list_2ary fn) [a, b];
+
+/* Remove features smaller than x pixels across from an image. This used to be
+ * rather complex ... convsep is now good enough to use.
+ */
+smooth x image = convsep (matrix_gaussian_blur (to_real x * 2)) image;
+
+/* Chop up an image into a list of lists of smaller images. Pad edges with
+ * black.
+ */
+imagearray_chop tile_width tile_height hoverlap voverlap i
+ = map chop' [0, vstep .. last_y]
+{
+ width = get_width i;
+ height = get_height i;
+ bands = get_bands i;
+ format = get_format i;
+ type = get_type i;
+
+ tile_width' = to_real tile_width;
+ tile_height' = to_real tile_height;
+ hoverlap' = to_real hoverlap;
+ voverlap' = to_real voverlap;
+
+ /* Unique pixels per tile.
+ */
+ hstep = tile_width' - hoverlap';
+ vstep = tile_height' - voverlap';
+
+ /* Number of tiles across and down. Remember the case where width ==
+ * hstep.
+ */
+ across = (int) ((width - 1) / hstep) + 1;
+ down = (int) ((height - 1) / vstep) + 1;
+
+ /* top/left of final tile.
+ */
+ last_x = hstep * (across - 1);
+ last_y = vstep * (down - 1);
+
+ /* How much do we need to pad by?
+ */
+ sx = last_x + tile_width';
+ sy = last_y + tile_height';
+
+ /* Expand image with black to pad size.
+ */
+ pad = embed 0 0 0 sx sy i;
+
+ /* Chop up a row.
+ */
+ chop' y
+ = map chop'' [0, hstep .. last_x]
+ {
+ chop'' x = extract_area x y tile_width' tile_height' pad;
+ }
+}
+
+/* Reassemble image.
+ */
+imagearray_assemble hoverlap voverlap il
+ = (image_set_origin 0 0 @ foldl1 tbj @ map (foldl1 lrj)) il
+{
+ lrj l r = insert (get_width l + hoverlap) 0 r l;
+ tbj t b = insert 0 (get_height t + voverlap) b t;
+}
+
+/* Generate an nxn identity matrix.
+ */
+identity_matrix n
+ = error "identity_matrix: n > 0", n < 1
+ = map line [0 .. n - 1]
+{
+ line p = take p [0, 0 ..] ++ [1] ++ take (n - p - 1) [0, 0 ..];
+}
+
+/* Incomplete gamma function Q(a, x) == 1 - P(a, x)
+
+ FIXME ... this is now a builtin, until we can get a nice List class
+ (requires overloadable ':')
+
+gammq a x
+ = error "bad args", x < 0 || a <= 0
+ = 1 - gamser, x < a + 1
+ = gammcf
+{
+ gamser = (gser a x)?0;
+ gammcf = (gcf a x)?0;
+}
+ */
+
+/* Incomplete gamma function P(a, x) evaluated as series representation. Also
+ * return ln(gamma(a)) ... nr in c, pp 218
+ */
+gser a x
+ = [gamser, gln]
+{
+ gln = gammln a;
+ gamser
+ = error "bad args", x < 0
+ = 0, x == 0
+ = 1 // fix this
+ {
+ // maximum iterations
+ maxit = 100;
+
+ ap = List [a + 1, a + 2 ...];
+ xoap = x / ap;
+
+ del = map product (prefixes xoap.value);
+
+
+
+
+
+
+
+/*
+ del = map (multiply (1 / a)) (map product (prefixes xoap))
+
+ del =
+
+ xap = iterate (multiply
+ */
+
+ /* Generate all prefixes of a list ... [1,2,3] -> [[1], [1, 2], [1, 2,
+ * 3], [1, 2, 3, 4] ...]
+ */
+ prefixes l = map (converse take l) [1..];
+
+ }
+}
+
+/* ln(gamma(xx)) ... nr in c, pp 214
+ */
+gammln xx
+ = gln
+{
+ cof = [76.18009172947146, -86.50532032941677, 24.01409824083091,
+ -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
+ y = take 6 (iterate (add 1) (xx + 1));
+ ser = 1.000000000190015 + sum (map2 divide cof y);
+ tmp = xx + 0.5;
+ tmp' = tmp - ((xx + 0.5) * log tmp);
+ gln = -tmp + log (2.5066282746310005 * ser / xx);
+}
+
+/* make a LUT from a scatter
+ */
+buildlut x
+ = Plot_histogram (im_buildlut x), is_Matrix x && x.width > 1
+ = im_buildlut (Matrix x), is_matrix x && is_list_len_more 1 x?0
+ = error (_ "bad arguments to " ++ "buildlut");
+
+/* Linear regression. Return a class with the stuff we need in.
+ * from s15.2, p 665 NR in C
+ *
+ * Also calculate R2, see eg.:
+ * https://en.wikipedia.org/wiki/Coefficient_of_determination
+ */
+linreg xes yes
+ = obj
+{
+ obj = class {
+ // in case we ever get shown in the workspace
+ _vislevel = 2;
+
+ slope = sum [t * y :: [t, y] <- zip2 tes yes] / st2;
+ intercept = (sy - sx * slope) / ss;
+
+ chi2 = sum [(y - intercept - slope * x) ** 2 :: [x, y] <- zip2 xes yes];
+
+ siga = (chi2 / (ss - 2)) ** 0.5 *
+ ((1 + sx ** 2 / (ss * st2)) / ss) ** 0.5;
+ sigb = (chi2 / (ss - 2)) ** 0.5 * (1 / st2) ** 0.5;
+
+ // for compat with linregw, see below
+ q = 1.0;
+
+ R2 = 1 - chi2 / sum [(y - my) ** 2 :: y <- yes];
+ }
+
+ ss = len xes;
+ sx = sum xes;
+ sy = sum yes;
+ my = sy / ss;
+ sxoss = sx / ss;
+
+ tes = [x - sxoss :: x <- xes];
+ st2 = sum [t ** 2 :: t <- tes];
+}
+
+/* Weighted linear regression. Xes, yes and a list of deviations.
+ */
+linregw xes yes devs
+ = obj
+{
+ obj = class {
+ // in case we ever get shown in the workspace
+ _vislevel = 2;
+
+ slope = sum [(t * y) / sd :: [t, y, sd] <- zip3 tes yes devs] / st2;
+ intercept = (sy - sx * slope) / ss;
+
+ chi2 = sum [((y - intercept - slope * x) / sd) ** 2 ::
+ [x, y, sd] <- zip3 xes yes devs];
+
+ siga = ((1 + sx * sx / (ss * st2)) / ss) ** 0.5;
+ sigb = (1 / st2) ** 0.5;
+
+ q = gammq (0.5 * (len xes - 2)) (0.5 * chi2);
+
+ R2 = 1 - chi2 / sum [(y - my) ** 2 :: y <- yes];
+ }
+
+ wt = [sd ** -0.5 :: sd <- devs];
+
+ ss = sum wt;
+ sx = sum [x * w :: [x, w] <- zip2 xes wt];
+ sy = sum [y * w :: [y, w] <- zip2 yes wt];
+ my = sy / len xes;
+ sxoss = sx / ss;
+
+ tes = [(x - sxoss) / sd :: [x, sd] <- zip2 xes devs];
+ st2 = sum [t ** 2 :: t <- tes];
+}
+
+/* Clustering: pass in a list of points, repeatedly merge the
+ * closest two points until no two points are closer than the threshold.
+ * Return [merged-points, corresponding-weights]. A weight is a list of the
+ * indexes we merged to make that point, ie. len weight == how significant
+ * this point is.
+ *
+ * eg.
+ * cluster 12 [152,154,155,42,159] ==
+ * [[155,42],[[1,2,0,4],[3]]]
+ */
+cluster thresh points
+ = oo_unary_function cluster_op points, is_class points
+ // can't use [0..len points - 1], in case len points == 0
+ = merge [points, map (converse cons []) (take (len points) [0 ..])],
+ is_list points
+ = error (_ "bad arguments to " ++ "cluster")
+{
+ cluster_op = Operator "cluster"
+ (cluster thresh) Operator_type.COMPOUND false;
+
+ merge x
+ = x, m < 2 || d > thresh
+ = merge [points', weights']
+ {
+ [points, weights] = x;
+ m = len points;
+
+ // generate indexes of all possible pairs, avoiding comparing a thing
+ // to itself, and assuming that dist is reflexive
+ // first index is always less than 2nd index
+ // the +1,+2 makes sure we have an increasing generator, otherwise we
+ // can get [3 .. 4] (for example), which will make a decreasing
+ // sequence
+ pairs = [[x, y] :: x <- [0 .. m - 1]; y <- [x + 1, x + 2 .. m - 1]];
+
+ // distance function
+ // arg is eg. [3,1], meaning get distance from point 3 to point 1
+ dist x
+ = abs (points?i - points?j)
+ {
+ [i, j] = x;
+ }
+
+ // smallest distance, then the two points we merge
+ p = minpos (map dist pairs);
+ d = dist pairs?p;
+ [i, j] = pairs?p;
+
+ // new point and new weight
+ nw = weights?i ++ weights?j;
+ np = (points?i * len weights?i + points?j * len weights?j) / len nw;
+
+ // remove element i from a list
+ remove i l = take i l ++ drop (i + 1) l;
+
+ // remove two old points, add the new merged one
+ // i < j (see "pairs", above)
+ points' = np : remove i (remove j points);
+ weights' = nw : remove i (remove j weights);
+ }
+}
+
+/* Extract the area of an image around an arrow.
+ * Transform the image to make the arrow horizontal, then displace by hd and
+ * vd pxels, then cut out a bit h pixels high centered on the arrow.
+ */
+extract_arrow hd vd h arrow
+ = extract_area (re p' + hd) (im p' - h / 2 + vd) (re pv) h im'
+{
+ // the line as a polar vector
+ pv = polar (arrow.width, arrow.height);
+ a = im pv;
+
+ // smallest rotation that will make the line horizontal
+ a'
+ = 360 - a, a > 270
+ = 180 - a, a > 90
+ = -a;
+
+ im' = rotate Interpolate_bilinear a' arrow.image;
+
+ // look at the start and end of the arrow, pick the leftmost
+ p
+ = (arrow.left, arrow.top), arrow.left <= arrow.right
+ = (arrow.right, arrow.bottom);
+
+ // transform that point to im' space
+ p' = rectangular (polar p + (0, a')) + (im'.xoffset, im'.yoffset);
+}
+
+/* You'd think these would go in _convert, but they are not really colour ops,
+ * so put them here.
+ */
+rad2float image
+ = oo_unary_function rad2float_op image, is_class image
+ = im_rad2float image, is_image image
+ = error (_ "bad arguments to " ++ "rad2float")
+{
+ rad2float_op = Operator "rad2float"
+ rad2float Operator_type.COMPOUND_REWRAP false;
+}
+
+float2rad image
+ = oo_unary_function float2rad_op image, is_class image
+ = im_float2rad image, is_image image
+ = error (_ "bad arguments to " ++ "float2rad")
+{
+ float2rad_op = Operator "float2rad"
+ float2rad Operator_type.COMPOUND_REWRAP false;
+}
+
+segment x
+ = oo_unary_function segment_op x, is_class x
+ = image', is_image x
+ = error (_ "bad arguments to " ++ "segment")
+{
+ segment_op = Operator "segment"
+ segment Operator_type.COMPOUND_REWRAP false;
+
+ [image, nsegs] = im_segment x;
+ image' = im_copy_set_meta image "n-segments" nsegs;
+}
+
+point a b
+ = oo_binary_function point_op a b, is_class a
+ = oo_binary'_function point_op a b, is_class b
+ = im_read_point b x y, is_image b
+ = [b?x?y], is_matrix b
+ = [b?x], is_real_list b && y == 0
+ = [b?y], is_real_list b && x == 0
+ = error (_ "bad arguments to " ++ "point")
+{
+ point_op = Operator "point"
+ (\a\b Vector (point a b)) Operator_type.COMPOUND false;
+
+ (x, y)
+ = a, is_complex a;
+ = (a?0, a?1), is_real_list a && is_list_len 2 a
+ = error "bad position format";
+}
+
+/* One image in, one out.
+ */
+system_image command x
+ = oo_unary_function system_image_op x, is_class x
+ = system x, is_image x
+ = error (_ "bad arguments to " ++ "system_image")
+{
+ system_image_op = Operator "system_image"
+ (system_image command) Operator_type.COMPOUND_REWRAP false;
+
+ system im
+ = image_out
+ {
+ [image_out, log] =
+ im_system_image (get_image im) "%s.tif" "%s.tif" command;
+ }
+}
+
+/* Two images in, one out.
+ */
+system_image2 command x1 x2
+ = oo_binary_function system_image2_op x1 x2, is_class x1
+ = oo_binary'_function system_image2_op x1 x2, is_class x2
+ = system x1 x2, is_image x1 && is_image x2
+ = error (_ "bad arguments to " ++ "system_image2")
+{
+ system_image2_op = Operator "system_image2"
+ (system_image2 command) Operator_type.COMPOUND_REWRAP false;
+
+ system x1 x2
+ = image_out
+ {
+ [image_out] = vips_call "system" [command] [
+ $in => [x1, x2],
+ $out => true,
+ $out_format => "%s.tif",
+ $in_format => "%s.tif"
+ ];
+ }
+}
+
+/* Three images in, one out.
+ */
+system_image3 command x1 x2 x3
+ = oo_binary_function system_image2_op x2 x3, is_class x2
+ = oo_binary'_function system_image2_op x2 x3, is_class x3
+ = system x1 x2 x3, is_image x1 && is_image x2 && is_image x3
+ = error (_ "bad arguments to " ++ "system_image3")
+{
+ system_image2_op = Operator "system_image2"
+ (system_image3 command x1) Operator_type.COMPOUND_REWRAP false;
+
+ system x1 x2 x3
+ = image_out
+ {
+ [image_out] = vips_call "system" [command] [
+ $in => [x1, x2, x3],
+ $out => true,
+ $out_format => "%s.tif",
+ $in_format => "%s.tif"
+ ];
+ }
+}
+
+/* Zero images in, one out.
+ */
+system_image0 command
+ = Image image_out
+{
+ [image_out] = vips_call "system" [command] [
+ $out => true,
+ $out_format => "%s.tif"
+ ];
+}
+
+hough_line w h x
+ = oo_unary_function hough_line_op x, is_class x
+ = hline (to_real w) (to_real h) x, is_image x
+ = error (_ "bad arguments to " ++ "hough_line")
+{
+ hough_line_op = Operator "hough_line"
+ (hough_line w h) Operator_type.COMPOUND_REWRAP false;
+
+ hline w h x
+ = pspace
+ {
+ [pspace] = vips_call "hough_line" [x] [
+ $width => w,
+ $height => h
+ ];
+ }
+}
+
+hough_circle s mn mx x
+ = oo_unary_function hough_circle_op x, is_class x
+ = hcircle (to_real s) (to_real mn) (to_real mx) x, is_image x
+ = error (_ "bad arguments to " ++ "hough_circle")
+{
+ hough_circle_op = Operator "hough_circle"
+ (hough_circle s mn mx) Operator_type.COMPOUND_REWRAP false;
+
+ hcircle s mn mx x
+ = pspace
+ {
+ [pspace] = vips_call "hough_circle" [x] [
+ $scale => s,
+ $min_radius => mn,
+ $max_radius => mx
+ ];
+ }
+}
+
+mapim interp ind in
+ = oo_binary_function mapim_op ind in, is_class ind
+ = oo_binary'_function mapim_op ind in, is_class in
+ = mapim_fn ind in, is_image ind && is_image in
+ = error (_ "bad arguments to " ++ "mapim")
+{
+ mapim_op = Operator "mapim"
+ (mapim interp) Operator_type.COMPOUND_REWRAP false;
+
+ mapim_fn ind im
+ = out
+ {
+ [out] = vips_call "mapim" [im, ind] [$interpolate => interp];
+ }
+}
+
+perlin cell width height
+ = Image im
+{
+ [im] = vips_call "perlin" [to_real width, to_real height] [
+ $cell_size => to_real cell
+ ];
+}
+
+worley cell width height
+ = Image im
+{
+ [im] = vips_call "worley" [to_real width, to_real height] [
+ $cell_size => to_real cell
+ ];
+}
+
+gaussnoise width height mean sigma
+ = im
+{
+ [im] = vips_call "gaussnoise" [to_real width, to_real height] [
+ $mean => to_real mean,
+ $sigma => to_real sigma
+ ];
+}
+
+flattenimage bg x
+ = oo_unary_function flatten_op x, is_class x
+ = flt (to_vector bg) x, is_image x
+ = error (_ "bad arguments to " ++ "flattenimage")
+{
+ flatten_op = Operator "flatten"
+ (flattenimage bg) Operator_type.COMPOUND_REWRAP false;
+
+ flt bg x
+ = out
+ {
+ [out] = vips_call "flatten" [x] [
+ $background => bg.value
+ ];
+ }
+}
+
+premultiply x
+ = oo_unary_function premultiply_op x, is_class x
+ = prem x, is_image x
+ = error (_ "bad arguments to " ++ "premultiply")
+{
+ premultiply_op = Operator "premultiply"
+ premultiply Operator_type.COMPOUND_REWRAP false;
+
+ prem x
+ = out
+ {
+ [out] = vips_call "premultiply" [x] [
+ ];
+ }
+}
+
+unpremultiply x
+ = oo_unary_function unpremultiply_op x, is_class x
+ = unprem x, is_image x
+ = error (_ "bad arguments to " ++ "unpremultiply")
+{
+ unpremultiply_op = Operator "unpremultiply"
+ unpremultiply Operator_type.COMPOUND_REWRAP false;
+
+ unprem x
+ = out
+ {
+ [out] = vips_call "unpremultiply" [x] [
+ ];
+ }
+}
+
+hist_entropy x
+ = oo_unary_function hist_entropy_op x, is_class x
+ = entropy x, is_image x
+ = error (_ "bad arguments to " ++ "hist_entropy")
+{
+ hist_entropy_op = Operator "hist_entropy"
+ hist_entropy Operator_type.COMPOUND_REWRAP false;
+
+ entropy x
+ = out
+ {
+ [out] = vips_call "hist_entropy" [x] [
+ ];
+ }
+}
diff --git a/share/nip2/compat/8.6/_types.def b/share/nip2/compat/8.6/_types.def
new file mode 100644
index 00000000..d07712f6
--- /dev/null
+++ b/share/nip2/compat/8.6/_types.def
@@ -0,0 +1,1410 @@
+/* A list of things. Do automatic iteration of unary and binary operators on
+ * us.
+ * List [1, 2] + [2, 3] -> List [3, 5]
+ * hd (List [2, 3]) -> 2
+ * List [] == [] -> true
+ */
+List value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_list]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [apply2 op value x',
+ op.op_name == "subscript" || op.op_name == "subscript'" ||
+ op.op_name == "equal" || op.op_name == "equal'"],
+ [this.List (apply2 op value x'),
+ op.op_name == "join" || op.op_name == "join'"],
+ [this.List (map2 (apply2 op) value x'),
+ is_list x'],
+ [this.List (map (apply2 op' x) value),
+ true]
+ ] ++ super.oo_binary_table op x
+ {
+ op' = oo_converse op;
+
+ // strip the List wrapper, if any
+ x'
+ = x.value, is_List x
+ = x;
+
+ apply2 op x1 x2
+ = oo_binary_function op x1 x2, is_class x1
+ = oo_binary'_function op x1 x2, is_class x2
+ = op.fn x1 x2;
+ };
+
+ oo_unary_table op = [
+ [apply value,
+ op.op_name == "hd" || op.op_name == "tl"],
+ [this.List (map apply value),
+ true]
+ ] ++ super.oo_unary_table op
+ {
+ apply x
+ = oo_unary_function op x, is_class x
+ = op.fn x;
+ }
+}
+
+/* A group of things. Loop the operation over the group.
+ */
+Group value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_list]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ // if_then_else is really a trinary operator
+ [map_trinary ite this x?0 x?1,
+ op.op_name == "if_then_else"],
+ [map_binary op.fn this x,
+ is_Group x],
+ [map_unary (\a op.fn a x) this,
+ true]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [map_unary op.fn this,
+ true]
+ ] ++ super.oo_unary_table op;
+
+ // we can't call map_trinary directly, since it uses Group and we
+ // don't support mutually recursive top-level functions :-(
+ // copy-paste it here, keep in sync with the version in _stdenv
+ map_nary fn args
+ = fn args, groups == []
+ = Group (map process [0, 1 .. shortest - 1])
+ {
+ groups = filter is_Group args;
+
+ shortest = foldr1 min_pair (map (len @ get_value) groups);
+
+ process n
+ = NULL, any (map (is_noval n) args)
+ = map_nary fn (map (extract n) args)
+ {
+ extract n arg
+ = arg.value?n, is_Group arg
+ = arg;
+
+ is_noval n arg = is_Group arg && arg.value?n == NULL;
+ }
+ }
+
+ // need ite as a true trinary
+ ite a b c = if a then b else c;
+
+ map_unary fn a = map_nary (list_1ary fn) [a];
+ map_binary fn a b = map_nary (list_2ary fn) [a, b];
+ map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
+}
+
+/* Single real number ... eg slider.
+ */
+Real value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_real]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [this.Real (op.fn this.value x.value),
+ is_Real x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Real (op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC],
+ [op.fn this.value x.value,
+ is_Real x &&
+ op.type == Operator_type.RELATIONAL],
+ [op.fn this.value x,
+ !is_class x]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [this.Real (op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* Single bool ... eg Toggle.
+ */
+Bool value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_bool]
+ ];
+
+ // methods
+ oo_binary_table op x = [
+ [op.fn this.value x,
+ op.op_name == "if_then_else"],
+ [this.Bool (op.fn this.value x.value),
+ is_Bool x],
+ [this.Bool (op.fn this.value x),
+ is_bool x]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [this.Bool (op.fn this.value),
+ op.type == Operator_type.ARITHMETIC ||
+ op.type == Operator_type.RELATIONAL],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* An editable string.
+ */
+String caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* An editable real number.
+ */
+Number caption value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string]
+ ];
+
+ Real x = this.Number caption x;
+}
+
+/* An editable expression.
+ */
+Expression caption expr = class
+ (if is_class expr then expr else _Object) {
+ _check_args = [
+ [caption, "caption", check_string],
+ [expr, "expr", check_any]
+ ];
+}
+
+/* A ticking clock.
+ */
+Clock interval value = class
+ scope.Real value {
+ _check_args = [
+ [interval, "interval", check_real]
+ ];
+
+ Real x = this.Clock interval x;
+}
+
+/* An editable filename.
+ */
+Pathname caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* An editable fontname.
+ */
+Fontname caption value = class
+ _Object {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_string]
+ ];
+}
+
+/* Vector type ... just a finite list of real. Handy for wrapping an
+ * argument to eg. im_lintra_vec. Make it behave like a single pixel image.
+ */
+Vector value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_real_list]
+ ];
+
+ bands = len value;
+
+ // methods
+ oo_binary_table op x = [
+ // Vector ++ Vector means bandwise join
+ [this.Vector (op.fn this.value x.value),
+ is_Vector x &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ [this.Vector (op.fn this.value [get_number x]),
+ has_number x &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ // Vector ? number means extract element
+ [op.fn this.value (get_real x),
+ has_real x &&
+ (op.op_name == "subscript" ||
+ op.op_name == "subscript'")],
+ // extra check for lengths equal
+ [this.Vector (map_binaryl op.fn this.value x.value),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Vector (map_binaryl op.fn this.value (get_real x)),
+ has_real x &&
+ op.type == Operator_type.ARITHMETIC],
+
+ // need extra length check
+ [this.Vector (map bool_to_real
+ (map_binaryl op.fn this.value x.value)),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (map bool_to_real
+ (map_binaryl op.fn this.value (get_real x))),
+ has_real x &&
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (op.fn this.value x.value),
+ is_Vector x &&
+ len value == len x.value &&
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [x.Image (vec op'.op_name x.value value),
+ is_Image x],
+ [vec op'.op_name x value,
+ is_image x],
+ [op.fn this.value x,
+ is_real x]
+ ] ++ super.oo_binary_table op x
+ {
+ op' = oo_converse op;
+ };
+
+ oo_unary_table op = [
+ [this.Vector (map_unaryl op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [this.Vector (map bool_to_real
+ (map_unaryl op.fn this.value)),
+ op.type == Operator_type.RELATIONAL],
+ [this.Vector (op.fn this.value),
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+
+ // turn an ip bool (or a number, for Vector) into VIPSs 255/0
+ bool_to_real x
+ = 255, is_bool x && x
+ = 255, is_number x && x != 0
+ = 0;
+}
+
+/* A rectangular array of real.
+ */
+Matrix_base value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_matrix]
+ ];
+
+ // calculate these from value
+ width = len value?0;
+ height = len value;
+
+ // extract a rectanguar area
+ extract left top width height
+ = this.Matrix_base
+ ((map (take width) @ map (drop left) @
+ take height @ drop top) value);
+
+ // methods
+ oo_binary_table op x = [
+ // mat multiply is special
+ [this.Matrix_base mul.value,
+ is_Matrix x &&
+ op.op_name == "multiply"],
+ [this.Matrix_base mul'.value,
+ is_Matrix x &&
+ op.op_name == "multiply'"],
+
+ // mat divide is also special
+ [this.Matrix_base div.value,
+ is_Matrix x &&
+ op.op_name == "divide"],
+ [this.Matrix_base div'.value,
+ is_Matrix x &&
+ op.op_name == "divide'"],
+
+ // power -1 means invert
+ [this.Matrix_base inv.value,
+ is_real x && x == -1 &&
+ op.op_name == "power"],
+ [this.Matrix_base sq.value,
+ is_real x && x == 2 &&
+ op.op_name == "power"],
+ [error "matrix **-1 and **2 only",
+ op.op_name == "power" ||
+ op.op_name == "power'"],
+
+ // matrix op vector ... treat a vector as a 1 row matrix
+ [this.Matrix_base (map (map_binaryl op'.fn x.value) this.value),
+ is_Vector x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Matrix_base (map_binaryl op.fn this.value x.value),
+ (is_Matrix x || is_Real x) &&
+ op.type == Operator_type.ARITHMETIC],
+
+ [this.Matrix_base (map_binaryl op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC],
+
+ // compound ... don't do iteration
+ [this.Matrix_base (op.fn this.value x.value),
+ (is_Matrix x || is_Real x || is_Vector x) &&
+ op.type == Operator_type.COMPOUND_REWRAP],
+
+ [op.fn this.value x,
+ op.type == Operator_type.COMPOUND]
+
+ ] ++ super.oo_binary_table op x
+ {
+ mul = im_matmul this x;
+ mul' = im_matmul x this;
+ div = im_matmul this (im_matinv x);
+ div' = im_matmul x (im_matinv this);
+ inv = im_matinv this;
+ sq = im_matmul this this;
+ op' = oo_converse op;
+ }
+
+ oo_unary_table op = [
+ [this.Matrix_base (map_unaryl op.fn this.value),
+ op.type == Operator_type.ARITHMETIC],
+ [this.Matrix_base (op.fn this.value),
+ op.type == Operator_type.COMPOUND_REWRAP],
+ [op.fn this.value,
+ true]
+ ] ++ super.oo_unary_table op;
+}
+
+/* How to display a matrix: text, sliders, toggles, or text plus scale/offset.
+ */
+Matrix_display = class {
+ text = 0;
+ slider = 1;
+ toggle = 2;
+ text_scale_offset = 3;
+
+ is_display = member [text, slider, toggle, text_scale_offset];
+}
+
+/* A matrix as VIPS sees them ... add scale, offset and filename. For nip, add
+ * a display type as well to control how the widget renders.
+ */
+Matrix_vips value scale offset filename display = class
+ scope.Matrix_base value {
+ _check_args = [
+ [scale, "scale", check_real],
+ [offset, "offset", check_real],
+ [filename, "filename", check_string],
+ [display, "display", check_matrix_display]
+ ];
+
+ Matrix_base x = this.Matrix_vips x scale offset filename display;
+}
+
+/* A plain 'ol matrix which can be passed to VIPS.
+ */
+Matrix value = class
+ Matrix_vips value 1 0 "" Matrix_display.text {}
+
+/* Specialised constructors ... for convolutions, recombinations and
+ * morphologies.
+ */
+Matrix_con scale offset value = class
+ Matrix_vips value scale offset "" Matrix_display.text_scale_offset {};
+
+Matrix_rec value = class
+ Matrix_vips value 1 0 "" Matrix_display.slider {};
+
+Matrix_mor value = class
+ Matrix_vips value 1 0 "" Matrix_display.toggle {};
+
+Matrix_file filename = (im_read_dmask @ expand @ search) filename;
+
+/* A CIE colour ... a triple, plus a format (eg XYZ, Lab etc)
+ */
+Colour colour_space value = class
+ scope.Vector value {
+ _check_args = [
+ [colour_space, "colour_space", check_colour_space]
+ ];
+ _check_all = [
+ [is_list_len 3 value, "len value == 3"]
+ ];
+
+ Vector x = this.Colour colour_space x;
+
+ // make a colour-ish thing from an image
+ // back to Colour if we have another 3 band image
+ // to a vector if bands > 1
+ // to a number otherwise
+ itoc im
+ = this.Colour nip_type (to_matrix im).value?0,
+ bands == 3
+ = scope.Vector (map mean (bandsplit im)),
+ bands > 1
+ = mean im
+ {
+ type = get_header "Type" im;
+ bands = get_header "Bands" im;
+ nip_type = Image_type.colour_spaces.lookup 1 0 type;
+ }
+
+ // methods
+ oo_binary_table op x = [
+ [itoc (op.fn
+ ((float) (to_image this).value)
+ ((float) (to_image x).value)),
+ // here REWRAP means go via image
+ op.type == Operator_type.COMPOUND_REWRAP]
+ ] ++ super.oo_binary_table op x;
+
+ oo_unary_table op = [
+ [itoc (op.fn ((float) (to_image this).value)),
+ op.type == Operator_type.COMPOUND_REWRAP]
+ ] ++ super.oo_unary_table op;
+}
+
+// a subclass with widgets for picking a space and value
+Colour_picker default_colour default_value = class
+ Colour space.item colour.expr {
+ _vislevel = 3;
+
+ space = Option_enum "Colour space" Image_type.colour_spaces default_colour;
+ colour = Expression "Colour value" default_value;
+
+ Colour_edit colour_space value =
+ Colour_picker colour_space value;
+}
+
+/* Base scale type.
+ */
+Scale caption from to value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [from, "from", check_real],
+ [to, "to", check_real]
+ ];
+ _check_all = [
+ [from < to, "from < to"]
+ ];
+
+ Real x = this.Scale caption from to x;
+
+ // methods
+ oo_binary_table op x = [
+ [this.Scale caption (op.fn this.from x.from) (op.fn this.to x.to)
+ (op.fn this.value x.value),
+ is_Scale x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Scale caption (op.fn this.from x) (op.fn this.to x)
+ (op.fn this.value x),
+ is_real x &&
+ op.type == Operator_type.ARITHMETIC]
+ ] ++ super.oo_binary_table op x;
+}
+
+/* Base toggle type.
+ */
+Toggle caption value = class
+ scope.Bool value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [value, "value", check_bool]
+ ];
+
+ Bool x = this.Toggle caption x;
+}
+
+/* Base option type.
+ */
+Option caption labels value = class
+ scope.Real value {
+ _check_args = [
+ [caption, "caption", check_string],
+ [labels, "labels", check_string_list],
+ [value, "value", check_uint]
+ ];
+}
+
+/* An option whose value is a string rather than a number.
+ */
+Option_string caption labels item = class
+ Option caption labels (index (equal item) labels) {
+ Option_edit caption labels value
+ = this.Option_string caption labels (labels?value);
+}
+
+/* Make an option from an enum.
+ */
+Option_enum caption enum item = class
+ Option_string caption enum.names item {
+ // corresponding thing
+ value_thing = enum.get_thing item;
+
+ Option_edit caption labels value
+ = this.Option_enum caption enum (enum.names?value);
+}
+
+/* A rectangle. width and height can be -ve.
+ */
+Rect left top width height = class
+ _Object {
+ _check_args = [
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_real],
+ [height, "height", check_real]
+ ];
+
+ // derived
+ right = left + width;
+ bottom = top + height;
+
+ oo_binary_table op x = [
+ [equal x,
+ is_Rect x &&
+ (op.op_name == "equal" || op.op_name == "equal'")],
+ [!equal x,
+ is_Rect x &&
+ (op.op_name == "not_equal" ||
+ op.op_name == "not_equal'")],
+
+ // binops with a complex are the same as (comp op comp)
+ [oo_binary_function op this (Rect (re x) (im x) 0 0),
+ is_complex x],
+
+ // all others are just pairwise
+ [this.Rect left' top' width' height',
+ is_Rect x &&
+ op.type == Operator_type.ARITHMETIC],
+ [this.Rect left'' top'' width'' height'',
+ has_number x &&
+ op.type == Operator_type.ARITHMETIC]
+ ] ++ super.oo_binary_table op x
+ {
+ left' = op.fn left x.left;
+ top' = op.fn top x.top;
+ width' = op.fn width x.width;
+ height' = op.fn height x.height;
+
+ left'' = op.fn left x';
+ top'' = op.fn top x';
+ width'' = op.fn width x';
+ height'' = op.fn height x';
+ x' = get_number x;
+ }
+
+ oo_unary_table op = [
+ // arithmetic uops just map
+ [this.Rect left' top' width' height',
+ op.type == Operator_type.ARITHMETIC],
+
+ // compound uops are just like ops on complex
+ // do (width, height) so thing like abs(Arrow) work as you'd expect
+ [op.fn (width, height),
+ op.type == Operator_type.COMPOUND]
+ ] ++ super.oo_unary_table op
+ {
+ left' = op.fn left;
+ top' = op.fn top;
+ width' = op.fn width;
+ height' = op.fn height;
+ }
+
+ // empty? ie. contains no pixels
+ is_empty = width == 0 || height == 0;
+
+ // normalised version, ie. make width/height +ve and flip the origin
+ nleft
+ = left + width, width < 0
+ = left;
+ ntop
+ = top + height, height < 0
+ = top;
+ nwidth = abs width;
+ nheight = abs height;
+ nright = nleft + nwidth;
+ nbottom = ntop + nheight;
+
+ equal x = left == x.left && top == x.top &&
+ width == x.width && height == x.height;
+
+ // contains a point?
+ includes_point x y
+ = nleft <= x && x <= nright && ntop <= y && y <= nbottom;
+
+ // contains a rect? just test top left and bottom right points
+ includes_rect r
+ = includes_point r.nleft r.ntop &&
+ includes_point r.nright r.nbottom;
+
+ // bounding box of two rects
+ // if either is empty, can just return the other
+ union r
+ = r, is_empty
+ = this, r.is_empty
+ = Rect left' top' width' height'
+ {
+ left' = min_pair nleft r.nleft;
+ top' = min_pair ntop r.ntop;
+ width' = max_pair nright r.nright - left';
+ height' = max_pair nbottom r.nbottom - top';
+ }
+
+ // intersection of two rects ... empty rect if no intersection
+ intersect r
+ = Rect left' top' width'' height''
+ {
+ left' = max_pair nleft r.nleft;
+ top' = max_pair ntop r.ntop;
+ width' = min_pair nright r.nright - left';
+ height' = min_pair nbottom r.nbottom - top';
+ width''
+ = width', width > 0
+ = 0;
+ height''
+ = height', height > 0
+ = 0;
+ }
+
+ // expand/collapse by n pixels
+ margin_adjust n
+ = Rect (left - n) (top - n) (width + 2 * n) (height + 2 * n);
+}
+
+/* Values for Compression field in image.
+ */
+Image_compression = class {
+ NONE = 0;
+ NO_COMPRESSION = 0;
+ TCSF_COMPRESSION = 1;
+ JPEG_COMPRESSION = 2;
+ LABPACK_COMPRESSED = 3;
+ RGB_COMPRESSED = 4;
+ LUM_COMPRESSED = 5;
+}
+
+/* Values for Coding field in image.
+ */
+Image_coding = class {
+ NONE = 0;
+ NOCODING = 0;
+ COLQUANT = 1;
+ LABPACK = 2;
+ RAD = 6;
+}
+
+/* Values for BandFmt field in image.
+ */
+Image_format = class {
+ DPCOMPLEX = 9;
+ DOUBLE = 8;
+ COMPLEX = 7;
+ FLOAT = 6;
+ INT = 5;
+ UINT = 4;
+ SHORT = 3;
+ USHORT = 2;
+ CHAR = 1;
+ UCHAR = 0;
+ NOTSET = -1;
+
+ maxval fmt
+ = [
+ 255, // UCHAR
+ 127, // CHAR
+ 65535, // USHORT
+ 32767, // SHORT
+ 4294967295, // UINT
+ 2147483647, // INT
+ 255, // FLOAT
+ 255, // COMPLEX
+ 255, // DOUBLE
+ 255 // DPCOMPLEX
+ ] ? fmt, fmt >= 0 && fmt <= DPCOMPLEX
+ = error (_ "bad value for BandFmt");
+}
+
+/* A lookup table.
+ */
+Table value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_rectangular]
+ ];
+
+ /* Extract a column.
+ */
+ column n = map (extract n) value;
+
+ /* present col x: is there an x in column col
+ */
+ present col x = member (column col) x;
+
+ /* Look on column from, return matching item in column to.
+ */
+ lookup from to x
+ = value?n?to, n >= 0
+ = error (_ "item" ++ " " ++ print x ++ " " ++ _ "not in table")
+ {
+ n = index (equal x) (column from);
+ }
+}
+
+/* A two column lookup table with the first column a string and the second a
+ * thing. Used for representing various enums. Option_enum makes a selector
+ * from one of these.
+ */
+Enum value = class
+ Table value {
+ _check_args = [
+ [value, "value", check_enum]
+ ]
+ {
+ check_enum = [is_enum, _ "is [[char, *]]"];
+ is_enum x =
+ is_rectangular x &&
+ is_listof is_string (map (extract 0) x);
+ }
+
+ // handy ... all the names and things as lists
+ names = this.column 0;
+ things = this.column 1;
+
+ // is a legal name or thing
+ has_name x = this.present 1 x;
+ has_thing x = this.present 0 x;
+
+ // map things to strings and back
+ get_name x = this.lookup 1 0 x;
+ get_thing x = this.lookup 0 1 x;
+}
+
+/* Type field.
+ */
+Image_type = class {
+ MULTIBAND = 0;
+ B_W = 1;
+ HISTOGRAM = 10;
+ XYZ = 12;
+ LAB = 13;
+ CMYK = 15;
+ LABQ = 16;
+ RGB = 17;
+ UCS = 18;
+ LCH = 19;
+ LABS = 21;
+ sRGB = 22;
+ YXY = 23;
+ FOURIER = 24;
+ RGB16 = 25;
+ GREY16 = 26;
+ ARRAY = 27;
+ scRGB = 28;
+
+ /* Table to get names <-> numbers.
+ */
+ type_names = Enum [
+ $MULTIBAND => MULTIBAND,
+ $B_W => B_W,
+ $HISTOGRAM => HISTOGRAM,
+ $XYZ => XYZ,
+ $LAB => LAB,
+ $CMYK => CMYK,
+ $LABQ => LABQ,
+ $RGB => RGB,
+ $UCS => UCS,
+ $LCH => LCH,
+ $LABS => LABS,
+ $sRGB => sRGB,
+ $YXY => YXY,
+ $FOURIER => FOURIER,
+ $RGB16 => RGB16,
+ $GREY16 => GREY16,
+ $ARRAY => ARRAY,
+ $scRGB => scRGB
+ ];
+
+ /* Table relating nip's colour space names and VIPS's Type numbers.
+ * Options are generated from this, so match the order to the order in
+ * the Colour menu.
+ */
+ colour_spaces = Enum [
+ $sRGB => sRGB,
+ $scRGB => scRGB,
+ $Lab => LAB,
+ $LCh => LCH,
+ $XYZ => XYZ,
+ $Yxy => YXY,
+ $UCS => UCS
+ ];
+
+ /* A slightly larger table ... the types of colorimetric image we can
+ * have. Add mono, and the S and Q forms of LAB.
+ */
+ image_colour_spaces = Enum [
+ $Mono => B_W,
+ $sRGB => sRGB,
+ $scRGB => scRGB,
+ $RGB16 => RGB16,
+ $GREY16 => GREY16,
+ $Lab => LAB,
+ $LabQ => LABQ,
+ $LabS => LABS,
+ $LCh => LCH,
+ $XYZ => XYZ,
+ $Yxy => YXY,
+ $UCS => UCS
+ ];
+}
+
+/* Base image type. Simple layer over vips_image.
+ */
+Image value = class
+ _Object {
+ _check_args = [
+ [value, "value", check_image]
+ ];
+
+ // fields from VIPS header
+ width = get_width value;
+ height = get_height value;
+ bands = get_bands value;
+ format = get_format value;
+ bits = get_bits value;
+ coding = get_coding value;
+ type = get_type value;
+ xres = get_header "Xres" value;
+ yres = get_header "Yres" value;
+ xoffset = get_header "Xoffset" value;
+ yoffset = get_header "Yoffset" value;
+ filename = get_header "filename" value;
+
+ // convenience ... the area our pixels occupy, as a rect
+ rect = Rect 0 0 width height;
+
+ // operator overloading
+ // (op Image Vector) done in Vector class
+ oo_binary_table op x = [
+ // handle image ++ constant here
+ [wrap join_result_image,
+ (has_real x || is_Vector x) &&
+ (op.op_name == "join" || op.op_name == "join'")],
+ [wrap ite_result_image,
+ op.op_name == "if_then_else"],
+ [wrap (op.fn this.value (get_image x)),
+ has_image x],
+ [wrap (op.fn this.value (get_number x)),
+ has_number x],
+ // if it's not a class on the RHS, handle here ... just apply and
+ // rewrap
+ [wrap (op.fn this.value x),
+ !is_class x]
+ // all other cases handled by other classes
+ ] ++ super.oo_binary_table op x
+ {
+ // wrap the result with this
+ // x can be a non-image, eg. compare "Image v == []" vs.
+ // "Image v == 12"
+ wrap x
+ = x, op.type == Operator_type.COMPOUND ||
+ !is_image x
+ = this.Image x;
+
+ join_result_image
+ = value ++ new_stuff, op.op_name == "join"
+ = new_stuff ++ value
+ {
+ new_stuff = image_new width height new_bands
+ format
+ coding
+ Image_type.B_W x xoffset yoffset;
+ new_bands
+ = get_bands x, has_bands x
+ = 1;
+ }
+
+ [then_part, else_part] = x;
+
+ // get things about our output from inputs in this order
+ objects = [then_part, else_part, this];
+
+ // properties of our output image
+ target_bands = get_member_list has_bands get_bands objects;
+ target_type = get_member_list has_type get_type objects;
+
+ // if one of then/else is an image, get the target format from that
+ // otherwise, let the non-image objects set the target
+ target_format
+ = get_member_list has_format get_format x,
+ has_member_list has_format x
+ = NULL;
+
+ to_image x = to_image_size width height target_bands target_format x;
+
+ [then', else'] = map to_image x;
+
+ ite_result_image = image_set_type target_type
+ (if value then then' else else');
+ }
+
+ // FIXME ... yuk ... don't use operator hints, just always rewrap if
+ // we have an image result
+ // forced on us by things like abs:
+ // abs Vector -> real
+ // abs Image -> Image
+ // does not fit well with COMPOUND/whatever scheme
+ oo_unary_table op = [
+ [this.Image result,
+ is_image result],
+ [result,
+ true]
+ ] ++ super.oo_unary_table op
+ {
+ result = op.fn this.value;
+ }
+}
+
+/* Construct an image from a file.
+ */
+Image_file filename = class
+ Image value {
+ _check_args = [
+ [filename, "filename", check_string]
+ ];
+
+ value = vips_image filename;
+}
+
+Region image left top width height = class
+ Image value {
+ _check_args = [
+ [image, "Image", check_Image],
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_preal],
+ [height, "height", check_preal]
+ ];
+
+ // a rect for our coordinates
+ // region.rect gets the rect for the extracted image
+ region_rect = Rect left top width height;
+
+ // we need to always succeed ... value is our enclosing image if we're
+ // out of bounds
+ value
+ = extract_area left top width height image.value,
+ image.rect.includes_rect region_rect
+ = image.value;
+}
+
+Area image left top width height = class
+ scope.Region image left top width height {
+ Region image left top width height
+ = this.Area image left top width height;
+}
+
+Arrow image left top width height = class
+ scope.Rect left top width height {
+ _check_args = [
+ [image, "Image", check_Image],
+ [left, "left", check_real],
+ [top, "top", check_real],
+ [width, "width", check_real],
+ [height, "height", check_real]
+ ];
+
+ Rect l t w h = this.Arrow image l t w h;
+}
+
+HGuide image top = class
+ scope.Arrow image image.rect.left top image.width 0 {
+ Arrow image left top width height = this.HGuide image top;
+}
+
+VGuide image left = class
+ scope.Arrow image left image.rect.top 0 image.height {
+ Arrow image left top width height = this.VGuide image left;
+}
+
+Mark image left top = class
+ scope.Arrow image left top 0 0 {
+ Arrow image left top width height = this.Mark image left top;
+}
+
+// convenience functions: ... specify position as [0 .. 1)
+
+Region_relative image u v w h
+ = Region image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+Area_relative image u v w h
+ = Area image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+Arrow_relative image u v w h
+ = Arrow image
+ (image.width * u)
+ (image.height * v)
+ (image.width * w)
+ (image.height * h);
+
+VGuide_relative image v
+ = VGuide image (image.height * v);
+
+HGuide_relative image u
+ = HGuide image (image.width * u);
+
+Mark_relative image u v
+ = Mark image
+ (image.width * u)
+ (image.height * v);
+
+Kernel_type = class {
+ NEAREST_NEIGHBOUR = 0;
+ LINEAR = 1;
+ CUBIC = 2;
+ LANCZOS2 = 3;
+ LANCZOS3 = 4;
+
+ // Should introspect to get the list of interpolators :-(
+ // We can "dir" on VipsInterpolate to get a list of them, but we
+ // can't get i18n'd descriptions until we have more
+ // introspection stuff in nip2.
+
+ /* Table to map kernel numbers to descriptive strings
+ */
+ descriptions = [
+ _ "Nearest neighbour",
+ _ "Linear",
+ _ "Cubic",
+ _ "Lanczos, two lobes",
+ _ "Lanczos, three lobes"
+ ];
+
+ /* And to vips enum nicknames.
+ */
+ types = [
+ "nearest",
+ "linear",
+ "cubic",
+ "lanczos2",
+ "lanczos3"
+ ];
+}
+
+Kernel type = class {
+ value = Kernel_type.types?type;
+}
+
+Kernel_linear = Kernel Kernel_type.LINEAR;
+
+Kernel_picker default = class
+ Kernel kernel.value {
+ _vislevel = 2;
+
+ kernel = Option "Kernel" Kernel_type.descriptions default;
+}
+
+Interpolate_type = class {
+ NEAREST_NEIGHBOUR = 0;
+ BILINEAR = 1;
+ BICUBIC = 2;
+ LBB = 3;
+ NOHALO = 4;
+ VSQBS = 5;
+
+ // Should introspect to get the list of interpolators :-(
+ // We can "dir" on VipsInterpolate to get a list of them, but we
+ // can't get i18n'd descriptions until we have more
+ // introspection stuff in nip2.
+
+ /* Table to map interpol numbers to descriptive strings
+ */
+ descriptions = [
+ _ "Nearest neighbour",
+ _ "Bilinear",
+ _ "Bicubic",
+ _ "Upsize: reduced halo bicubic (LBB)",
+ _ "Upsharp: reduced halo bicubic with edge sharpening (Nohalo)",
+ _ "Upsmooth: quadratic B-splines with jaggy reduction (VSQBS)"
+ ];
+
+ /* And to vips type names.
+ */
+ types = [
+ "VipsInterpolateNearest",
+ "VipsInterpolateBilinear",
+ "VipsInterpolateBicubic",
+ "VipsInterpolateLbb",
+ "VipsInterpolateNohalo",
+ "VipsInterpolateVsqbs"
+ ];
+}
+
+Interpolate type options = class {
+ value = vips_object_new Interpolate_type.types?type [] options;
+}
+
+Interpolate_bilinear = Interpolate Interpolate_type.BILINEAR [];
+
+Interpolate_picker default = class
+ Interpolate interp.value [] {
+ _vislevel = 2;
+
+ interp = Option "Interpolation" Interpolate_type.descriptions default;
+}
+
+Render_intent = class {
+ PERCEPTUAL = 0;
+ RELATIVE = 1;
+ SATURATION = 2;
+ ABSOLUTE = 3;
+
+ /* Table to get names <-> numbers.
+ */
+ names = Enum [
+ _ "Perceptual" => PERCEPTUAL,
+ _ "Relative" => RELATIVE,
+ _ "Saturation" => SATURATION,
+ _ "Absolute" => ABSOLUTE
+ ];
+}
+
+// abstract base class for toolkit menus
+Menu = class {}
+
+// a "----" line in a menu
+Menuseparator = class Menu {}
+
+// abstract base class for items in menus
+Menuitem label tooltip = class Menu {}
+
+Menupullright label tooltip = class Menuitem label tooltip {}
+
+Menuaction label tooltip = class Menuitem label tooltip {}
+
+/* Plots.
+ */
+
+Plot_style = class {
+ POINT = 0;
+ LINE = 1;
+ SPLINE = 2;
+ BAR = 3;
+
+ names = Enum [
+ _ "Point" => POINT,
+ _ "Line" => LINE,
+ _ "Spline" => SPLINE,
+ _ "Bar" => BAR
+ ];
+}
+
+Plot_format = class {
+ YYYY = 0;
+ XYYY = 1;
+ XYXY = 2;
+
+ names = Enum [
+ _ "YYYY" => YYYY,
+ _ "XYYY" => XYXY,
+ _ "XYXY" => XYXY
+ ];
+}
+
+Plot_type = class {
+ /* Lots of Ys (ie. multiple line plots).
+ */
+ YYYY = 0;
+
+ /* First column of matrix is X position, others are Ys (ie. multiple XY
+ * line plots, all with the same Xes).
+ */
+ XYYY = 1;
+
+ /* Many independent XY plots.
+ */
+ XYXY = 2;
+}
+
+/* "options" is a list of ["key", value] pairs.
+ */
+Plot options value = class
+ scope.Image value {
+ Image value = this.Plot options value;
+ to_image dpi = extract_bands 0 3
+ (graph_export_image (to_real dpi) this);
+}
+
+Plot_matrix options value = class
+ Plot options (to_image value).value {
+}
+
+Plot_histogram value = class
+ scope.Plot [] value {
+}
+
+Plot_xy value = class
+ scope.Plot [$format => Plot_format.XYYY] value {
+}
+
+/* A no-value type. Call it NULL for C-alike fun. Used by Group to indicate
+ * empty slots, for example.
+ */
+NULL = class
+ _Object {
+ oo_binary_table op x = [
+ // the only operation we allow is equality .. use pointer equality,
+ // this lets us test a == NULL and a != NULL
+ [this === x,
+ op.type == Operator_type.RELATIONAL &&
+ op.op_name == "equal"],
+ [this !== x,
+ op.type == Operator_type.RELATIONAL &&
+ op.op_name == "not_equal"]
+ ] ++ super.oo_binary_table op x;
+}
+
+Blend_type = class {
+ CLEAR = 0;
+ SOURCE = 1;
+ OVER = 2;
+ IN = 3;
+ OUT = 4;
+ ATOP = 5;
+ DEST = 6;
+ DEST_OVER = 7;
+ DEST_IN = 8;
+ DEST_OUT = 9;
+ DEST_ATOP = 10;
+ XOR = 11;
+ ADD = 12;
+ SATURATE = 13;
+ MULTIPLY = 14;
+ SCREEN = 15;
+ OVERLAY = 16;
+ DARKEN = 17;
+ LIGHTEN = 18;
+ COLOUR_DODGE = 19;
+ COLOUR_BURN = 20;
+ HARD_LIGHT = 21;
+ SOFT_LIGHT = 22;
+ DIFFERENCE = 23;
+ EXCLUSION = 24;
+
+ /* Table to map blend numbers to descriptive strings
+ */
+ descriptions = [
+ _ "Clear",
+ _ "Source",
+ _ "Over",
+ _ "In",
+ _ "Out",
+ _ "Atop",
+ _ "Dest",
+ _ "Dest over",
+ _ "Dest in",
+ _ "Dest out",
+ _ "Dest atop",
+ _ "Xor",
+ _ "Add",
+ _ "Saturate",
+ _ "Multiply",
+ _ "Screen",
+ _ "Overlay",
+ _ "Darken",
+ _ "Lighten",
+ _ "Colour dodge",
+ _ "Colour burn",
+ _ "Hard light",
+ _ "Soft light",
+ _ "Difference",
+ _ "Exclusion"
+ ];
+
+ /* And to vips enum nicknames.
+ */
+ types = Enum [
+ $clear => "clear",
+ $source => "source",
+ $over => "over",
+ $in => "in",
+ $out => "out",
+ $atop => "atop",
+ $dest => "dest",
+ $dest_over => "dest_over",
+ $dest_in => "dest_in",
+ $dest_out => "dest_out",
+ $dest_atop => "dest_atop",
+ $xor => "xor",
+ $add => "add",
+ $saturate => "saturate",
+ $multiply => "multiply",
+ $screen => "screen",
+ $overlay => "overlay",
+ $darken => "darken",
+ $lighten => "lighten",
+ $colour_dodge => "colour_dodge",
+ $colour_burn => "colour_burn",
+ $hard_light => "hard_light",
+ $soft_light => "soft_light",
+ $difference => "difference",
+ $exclusion => "exclusion"
+ ];
+}
+
+Blend type = class {
+ value = Blend_type.types?type;
+}
+
+Blend_over = Blend Blend_type.OVER;
+
+Blend_picker default = class
+ Blend blend.value {
+ _vislevel = 2;
+
+ blend = Option "Blend" Blend_type.descriptions default;
+}
+
+Combine_type = class {
+ MAX = 0;
+ SUM = 1;
+ MIN = 2;
+
+ enum = Enum [
+ _ "Maximum" => MAX,
+ _ "Sum" => SUM,
+ _ "Minimum" => MIN
+ ];
+}
+
+Combine type = class {
+ value = Combine_type.enum.names?type;
+}
+
+Combine_sum = Combine Combine_type.SUM;
+
+Combine_picker default = Option "Combine" Combine_type.enum.names default;
diff --git a/share/nip2/compat/Makefile.am b/share/nip2/compat/Makefile.am
index 54c649db..3705b1c9 100644
--- a/share/nip2/compat/Makefile.am
+++ b/share/nip2/compat/Makefile.am
@@ -1 +1,2 @@
-SUBDIRS = 7.8 7.9 7.10 7.12 7.14 7.16 7.24 7.26 7.28 7.38 7.40 8.2 8.3 8.4
+SUBDIRS = 7.8 7.9 7.10 7.12 7.14 7.16 7.24 7.26 7.28 7.38 7.40 8.2 8.3 \
+ 8.4 8.5 8.6
diff --git a/share/nip2/data/examples/manual_balance/simp_base.png b/share/nip2/data/examples/manual_balance/simp_base.png
index 6430bc4b..9c7080f9 100644
Binary files a/share/nip2/data/examples/manual_balance/simp_base.png and b/share/nip2/data/examples/manual_balance/simp_base.png differ
diff --git a/share/nip2/start/Filter.def b/share/nip2/start/Filter.def
index a8ac20c0..15beddd7 100644
--- a/share/nip2/start/Filter.def
+++ b/share/nip2/start/Filter.def
@@ -20,6 +20,41 @@ Filter_conv_item = class
action x = map_unary (conv filter_sharp) x;
}
+ Usharp_item = class
+ Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ size = Option "Radius" [
+ "3 pixels",
+ "5 pixels",
+ "7 pixels",
+ "9 pixels",
+ "11 pixels",
+ "51 pixels"
+ ] 0;
+
+ st = Scale "Smoothness threshold" 0 5 2;
+ bm = Scale "Brighten by at most" 1 50 10;
+ dm = Scale "Darken by at most" 1 50 20;
+ fs = Scale "Sharpen flat areas by" 0 5 0.5;
+ js = Scale "Sharpen jaggy areas by" 0 5 1;
+
+ _result
+ = map_unary process x
+ {
+ process in
+ = Image in'''
+ {
+ in' = colour_transform_to Image_type.LABS in.value;
+ in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
+ in''' = colour_transform_to (get_type in) in'';
+ }
+ }
+ }
+ }
+
Emboss_item = class
Menuaction "_Emboss" "1 pixel displace emboss" {
action x = map_unary (conv filter_emboss) x;
@@ -32,16 +67,7 @@ Filter_conv_item = class
Sobel_item = class
Menuaction "So_bel" "3x3 Sobel edge detect" {
- action x
- = map_unary sobel x
- {
- sobel im
- = abs (a - 128) + abs (b - 128)
- {
- a = conv filter_sobel im;
- b = conv (rot270 filter_sobel) im;
- }
- }
+ action x = map_unary sobel x;
}
/* 3x3 line detect of image
@@ -62,37 +88,20 @@ Kirk
}
}
- Usharp_item = class
- Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
- action x = class
+ Canny_item = class
+ Menuaction "Canny" "Canny edge detector" {
+ action x = class
_result {
_vislevel = 3;
-
- size = Option "Radius" [
- "3 pixels",
- "5 pixels",
- "7 pixels",
- "9 pixels",
- "11 pixels",
- "51 pixels"
- ] 0;
-
- st = Scale "Smoothness threshold" 0 5 2;
- bm = Scale "Brighten by at most" 1 50 10;
- dm = Scale "Darken by at most" 1 50 20;
- fs = Scale "Sharpen flat areas by" 0 5 0.5;
- js = Scale "Sharpen jaggy areas by" 0 5 1;
-
- _result
+
+ sigma = Scale "Sigma" 0.1 10 2;
+ prec = Option "Precision" ["Int", "Float", "Approximate"] 1;
+
+ _result
= map_unary process x
{
process in
- = Image in'''
- {
- in' = colour_transform_to Image_type.LABS in.value;
- in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
- in''' = colour_transform_to (get_type in) in'';
- }
+ = canny sigma prec.value in;
}
}
}
diff --git a/share/nip2/start/Histogram.def b/share/nip2/start/Histogram.def
index 6280f4f9..27c61b00 100644
--- a/share/nip2/start/Histogram.def
+++ b/share/nip2/start/Histogram.def
@@ -80,21 +80,28 @@ Hist_find_item = class
Indexed_item = class
Menuaction "_Indexed"
"use a 1-band index image to pick bins for an n-band image" {
- action x y
- = map_binary map x y
- {
- map a b
- = hist_find_indexed index im
- {
- [im, index] = sortc (const is_index) [a, b];
+ action x y = class
+ _result {
+ _vislevel = 3;
+
+ combine = Combine_picker Combine_type.SUM;
- is_index x
- = has_image x && b == 1 &&
- (f == Image_format.UCHAR || f == Image_format.USHORT)
+ _result
+ = map_binary map x y
+ {
+ map a b
+ = hist_find_indexed combine.value index im
{
- im = get_image x;
- b = get_bands x;
- f = get_format x;
+ [im, index] = sortc (const is_index) [a, b];
+
+ is_index x
+ = has_image x && b == 1 &&
+ (f == Image_format.UCHAR || f == Image_format.USHORT)
+ {
+ im = get_image x;
+ b = get_bands x;
+ f = get_format x;
+ }
}
}
}
diff --git a/share/nip2/start/Image.def b/share/nip2/start/Image.def
index a010d989..4c0deb53 100644
--- a/share/nip2/start/Image.def
+++ b/share/nip2/start/Image.def
@@ -1286,6 +1286,29 @@ Image_crop_item = class
}
}
+Trim_item = class Menuaction "_Trim" "crop away edges" {
+ action x = class
+ _result {
+ _vislevel = 3;
+
+ thresh = Scale "threshold" 0 100 10;
+ background = Expression "Background" default_background
+ {
+ default_background
+ = map mean (bandsplit (extract_area 0 0 1 1 x));
+ }
+
+ _result
+ = Region x l t w h
+ {
+ [l, t, w, h] = vips_call "find_trim" [x.value] [
+ $threshold => thresh.value,
+ $background => background.expr
+ ];
+ }
+ }
+}
+
Image_insert_item = class
Menuaction "_Insert" "insert a small image into a large image" {
action a b
@@ -1902,16 +1925,40 @@ Pattern_images_item = class
text = String "Text to paint" "Hello world!";
font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
- wrap = Expression "Wrap text at" 500;
+ fontfile = Pathname "Font file" "";
+ width = Expression "Text width" 0;
+ height = Expression "Text height" 0;
align = Option "Alignment" [
"Left",
"Centre",
"Right"
] 0;
dpi = Expression "DPI" 300;
+ fit = Toggle "Fit text to box" false;
+ spacing = Expression "Line spacing" 0;
- _result = Image (im_text text.value font.value
- (to_real wrap) align.value (to_real dpi));
+ _result
+ = Image out
+ {
+ base_options = [
+ $font => font.value,
+ $align => align.value
+ ];
+
+ set_option name default value
+ = [name => value], value != default
+ = [];
+
+ options = base_options ++ concat [
+ set_option $width 0 (to_real width),
+ set_option $height 0 (to_real height),
+ set_option $fontfile "" fontfile.value,
+ if !fit then set_option $dpi 72 (to_real dpi) else [],
+ set_option $spacing 0 (to_real spacing)
+ ];
+
+ [out] = vips_call "text" [text.value] options;
+ }
}
}
@@ -1955,7 +2002,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f sense.value fc.value 0 0 0 0;
+ param f = f sense.value fc.value 0 0 0 0;
}
}
}
@@ -1976,7 +2023,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 6) fc.value rw.value 0 0 0;
+ param f = f (sense.value + 6) fc.value rw.value 0 0 0;
}
}
}
@@ -1998,7 +2045,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 12) fcx.value fcy.value
+ param f = f (sense.value + 12) fcx.value fcy.value
r.value 0 0;
}
}
@@ -2024,7 +2071,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 4) fc.value ac.value 0 0 0;
+ param f = f (sense.value + 4) fc.value ac.value 0 0 0;
}
}
}
@@ -2046,7 +2093,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 10) fc.value rw.value
+ param f = f (sense.value + 10) fc.value rw.value
ac.value 0 0;
}
}
@@ -2070,7 +2117,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 16) fcx.value fcy.value
+ param f = f (sense.value + 16) fcx.value fcy.value
r.value ac.value 0;
}
}
@@ -2097,7 +2144,7 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 2) order.value fc.value
+ param f = f (sense.value + 2) order.value fc.value
ac.value 0 0;
}
}
@@ -2121,8 +2168,8 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 8) order.value fc.value
- rw.value ac.value 0;
+ param f = f (sense.value + 8) order.value fc.value
+ rw.value ac.value 0;
}
}
}
@@ -2146,8 +2193,8 @@ Pattern_images_item = class
_result
= build param (to_real nsize)
{
- param f = f (sense.value + 14) order.value fcx.value
- fcy.value r.value ac.value;
+ param f = f (sense.value + 14) order.value fcx.value
+ fcy.value r.value ac.value;
}
}
}
diff --git a/share/nip2/start/Preferences.ws b/share/nip2/start/Preferences.ws
index 37a24978..8577a27c 100644
--- a/share/nip2/start/Preferences.ws
+++ b/share/nip2/start/Preferences.ws
@@ -1,5 +1,5 @@
-
+
diff --git a/share/nip2/start/_joe_extra.def b/share/nip2/start/_joe_extra.def
index 0277d261..1e7ac4aa 100644
--- a/share/nip2/start/_joe_extra.def
+++ b/share/nip2/start/_joe_extra.def
@@ -362,6 +362,41 @@ Select_item = class
}
}
+ Fill_item = class
+ Menuaction "_Fill" "fill zero pixels with the nearest non-zero" {
+ action x = class
+ Image _result {
+ _vislevel = 3;
+
+ distance = Image _distance;
+
+ [_result, _distance] = vips_call "fill_nearest" [x.value] [
+ "distance" => true
+ ];
+ }
+ }
+
+fill_nearest x
+ = oo_unary_function nearest_op x, is_class x
+ = near x, is_image x
+ = error (_ "bad arguments to " ++ "fill_nearest")
+{
+ nearest_op = Operator "fill_nearest"
+ fill_nearest Operator_type.COMPOUND_REWRAP false;
+
+ near x
+ = [out, distance]
+ {
+ [out, distance] = vips_call "fill_nearest" [x] [
+ "distance" => true
+ ];
+ }
+}
+
+
+
+
+
};
////////////////////////////////////////////////////////////////////////////////////
diff --git a/share/nip2/start/_stdenv.def b/share/nip2/start/_stdenv.def
index a7cf8f3a..350cd709 100644
--- a/share/nip2/start/_stdenv.def
+++ b/share/nip2/start/_stdenv.def
@@ -1434,15 +1434,39 @@ hist_find_nD bins image
(hist_find_nD bins) Operator_type.COMPOUND_REWRAP false;
}
-hist_find_indexed index value
+hist_find_indexed mode index value
= oo_binary_function hist_find_indexed_op index value, is_class index
= oo_binary'_function hist_find_indexed_op index value, is_class value
- = im_hist_indexed index value, is_image index && is_image value
+ = indexed index value, is_image index && is_image value
= error (_ "bad arguments to " ++ "hist_find_indexed")
{
hist_find_indexed_op = Operator "hist_find_indexed"
- (compose (compose Plot_histogram) hist_find_indexed)
+ (compose (compose Plot_histogram) (hist_find_indexed mode))
Operator_type.COMPOUND false;
+
+ indexed index value
+ = out
+ {
+ [out] = vips_call "hist_find_indexed" [value, index] [
+ "combine" => mode
+ ];
+ }
+}
+
+hist_entropy x
+ = oo_unary_function hist_entropy_op x, is_class x
+ = entropy x, is_image x
+ = error (_ "bad arguments to " ++ "hist_entropy")
+{
+ hist_entropy_op = Operator "hist_entropy"
+ hist_entropy Operator_type.COMPOUND_REWRAP false;
+
+ entropy x
+ = out
+ {
+ [out] = vips_call "hist_entropy" [x] [
+ ];
+ }
}
hist_map hist image
@@ -2594,4 +2618,37 @@ hist_entropy x
}
}
+canny sigma precision x
+ = oo_unary_function canny_op x, is_class x
+ = canny_im (to_real sigma) (to_int precision) x, is_image x
+ = error (_ "bad arguments to " ++ "canny")
+{
+ canny_op = Operator "canny"
+ (canny sigma precision) Operator_type.COMPOUND_REWRAP false;
+
+ canny_im sigma precision x
+ = out
+ {
+ [out] = vips_call "canny" [x] [
+ $sigma => sigma,
+ $precision => precision
+ ];
+ }
+
+}
+
+sobel x
+ = oo_unary_function sobel_op x, is_class x
+ = sobel_im x, is_image x
+ = error (_ "bad arguments to " ++ "sobel")
+{
+ sobel_op = Operator "sobel"
+ sobel Operator_type.COMPOUND_REWRAP false;
+ sobel_im x
+ = out
+ {
+ [out] = vips_call "sobel" [x] [
+ ];
+ }
+}
diff --git a/share/nip2/start/_types.def b/share/nip2/start/_types.def
index 7f589551..c657895f 100644
--- a/share/nip2/start/_types.def
+++ b/share/nip2/start/_types.def
@@ -1086,8 +1086,9 @@ Kernel_type = class {
NEAREST_NEIGHBOUR = 0;
LINEAR = 1;
CUBIC = 2;
- LANCZOS2 = 3;
- LANCZOS3 = 4;
+ MITCHELL = 3;
+ LANCZOS2 = 4;
+ LANCZOS3 = 5;
// Should introspect to get the list of interpolators :-(
// We can "dir" on VipsInterpolate to get a list of them, but we
@@ -1100,6 +1101,7 @@ Kernel_type = class {
_ "Nearest neighbour",
_ "Linear",
_ "Cubic",
+ _ "Mitchell",
_ "Lanczos, two lobes",
_ "Lanczos, three lobes"
];
@@ -1110,6 +1112,7 @@ Kernel_type = class {
"nearest",
"linear",
"cubic",
+ "mitchell",
"lanczos2",
"lanczos3"
];
@@ -1389,3 +1392,22 @@ Blend_picker default = class
blend = Option "Blend" Blend_type.descriptions default;
}
+Combine_type = class {
+ MAX = 0;
+ SUM = 1;
+ MIN = 2;
+
+ enum = Enum [
+ _ "Maximum" => MAX,
+ _ "Sum" => SUM,
+ _ "Minimum" => MIN
+ ];
+}
+
+Combine type = class {
+ value = Combine_type.enum.names?type;
+}
+
+Combine_sum = Combine Combine_type.SUM;
+
+Combine_picker default = Option "Combine" Combine_type.enum.names default;
diff --git a/src/Makefile.am b/src/Makefile.am
index a24aca77..bf00be38 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -320,7 +320,7 @@ nipmarshal.c:
nip2-model.o model.o: model.c parse.c
AM_CPPFLAGS = @IP_CFLAGS@
-LDADD = @IP_CFLAGS@ @IP_LIBS@ -lfl
+LDADD = @IP_CFLAGS@ @IP_LIBS@
AM_LDFLAGS = @LDFLAGS@
dist-hook:
diff --git a/src/compile.c b/src/compile.c
index bd9c6ae3..ecf40b85 100644
--- a/src/compile.c
+++ b/src/compile.c
@@ -815,7 +815,7 @@ compile_graph( Compile *compile, ParseNode *pn, PElement *out )
break;
case PARSE_CONST_BOOL:
- PEPUTP( out, ELEMENT_BOOL, pn->con.val.bool );
+ PEPUTP( out, ELEMENT_BOOL, pn->con.val.bol );
break;
case PARSE_CONST_ELIST:
@@ -2523,7 +2523,7 @@ compile_pattern_condition( Compile *compile,
int i;
n.type = PARSE_CONST_BOOL;
- n.val.bool = TRUE;
+ n.val.bol = TRUE;
node = tree_const_new( compile, n );
for( i = depth - 1; i >= 0; i-- ) {
diff --git a/src/doubleclick.c b/src/doubleclick.c
index 13df069d..75196876 100644
--- a/src/doubleclick.c
+++ b/src/doubleclick.c
@@ -40,6 +40,7 @@
#include
#include
+#include
#include
#include "doubleclick.h"
diff --git a/src/dummy.c b/src/dummy.c
new file mode 100644
index 00000000..679b26f3
--- /dev/null
+++ b/src/dummy.c
@@ -0,0 +1 @@
+int poop () {}
diff --git a/src/heap.c b/src/heap.c
index 919f90a6..5e9e9787 100644
--- a/src/heap.c
+++ b/src/heap.c
@@ -1895,12 +1895,13 @@ heap_ip_to_gvalue( PElement *in, GValue *out )
*/
else if( heap_is_imagevec( in, &result ) &&
result ) {
- Imageinfo *iivec[100];
+ Imageinfo *iivec[MAX_VEC];
VipsImage **ivec;
int n;
int i;
- if( (n = heap_get_imagevec( in, iivec, 100 )) < 0 )
+ if( (n = heap_get_imagevec( in,
+ iivec, MAX_VEC )) < 0 )
return( FALSE );
g_value_init( out, VIPS_TYPE_ARRAY_IMAGE );
vips_value_set_array_image( out, n );
@@ -1916,10 +1917,11 @@ heap_ip_to_gvalue( PElement *in, GValue *out )
}
else if( heap_is_realvec( in, &result ) &&
result ) {
- double realvec[100];
+ double realvec[MAX_VEC];
int n;
- if( (n = heap_get_realvec( in, realvec, 100 )) < 0 )
+ if( (n = heap_get_realvec( in,
+ realvec, MAX_VEC )) < 0 )
return( FALSE );
g_value_init( out, VIPS_TYPE_ARRAY_DOUBLE );
vips_value_set_array_double( out, realvec, n );
diff --git a/src/icontainer.c b/src/icontainer.c
index 1180b96a..5e68cef9 100644
--- a/src/icontainer.c
+++ b/src/icontainer.c
@@ -795,7 +795,7 @@ icontainer_class_init( iContainerClass *class )
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET( iContainerClass, child_remove ),
NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
TYPE_ICONTAINER );
@@ -804,7 +804,7 @@ icontainer_class_init( iContainerClass *class )
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET( iContainerClass, current ),
NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
TYPE_ICONTAINER );
@@ -813,7 +813,7 @@ icontainer_class_init( iContainerClass *class )
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET( iContainerClass, child_detach ),
NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
TYPE_ICONTAINER );
diff --git a/src/imageheader.c b/src/imageheader.c
index f0f01113..69e536d3 100644
--- a/src/imageheader.c
+++ b/src/imageheader.c
@@ -68,26 +68,20 @@ imageheader_add_item( IMAGE *im,
char txt[256];
VipsBuf buf = VIPS_BUF_STATIC( txt );
GtkTreeIter iter;
- char *value_str;
- const char *extra;
- value_str = g_strdup_value_contents( value );
- vips_buf_appendf( &buf, "%s", value_str );
-
- /* Look for enums and decode them.
+ /* Show the nicks for enums.
*/
- extra = NULL;
- if( strcmp( field, "coding" ) == 0 )
- extra = vips_enum_nick( VIPS_TYPE_CODING,
- g_value_get_int( value ) );
- else if( strcmp( field, "format" ) == 0 )
- extra = vips_enum_nick( VIPS_TYPE_BAND_FORMAT,
- g_value_get_int( value ) );
- else if( strcmp( field, "interpretation" ) == 0 )
- extra = vips_enum_nick( VIPS_TYPE_INTERPRETATION,
- g_value_get_int( value ) );
- if( extra )
- vips_buf_appendf( &buf, " - %s", extra );
+ if( G_VALUE_HOLDS_ENUM( value ) )
+ vips_buf_appendf( &buf, "%s",
+ vips_enum_nick( G_VALUE_TYPE( value ),
+ g_value_get_enum( value ) ) );
+ else {
+ char *value_str;
+
+ value_str = g_strdup_value_contents( value );
+ vips_buf_appendf( &buf, "%s", value_str );
+ g_free( value_str );
+ }
gtk_list_store_append( imageheader->store, &iter );
gtk_list_store_set( imageheader->store, &iter,
@@ -95,8 +89,6 @@ imageheader_add_item( IMAGE *im,
VALUE_COLUMN, vips_buf_all( &buf ),
-1 );
- g_free( value_str );
-
return( NULL );
}
diff --git a/src/imageinfo.c b/src/imageinfo.c
index 2abf10f8..14f70f92 100644
--- a/src/imageinfo.c
+++ b/src/imageinfo.c
@@ -720,7 +720,6 @@ imageinfo_proxy_add( Imageinfo *imageinfo )
*/
g_assert( !imageinfo->proxy );
if( !(imageinfo->proxy = IM_NEW( imageinfo->im, Imageinfoproxy )) )
- if( !(imageinfo->proxy = IM_NEW( NULL, Imageinfoproxy )) )
return;
imageinfo->proxy->im = imageinfo->im;
imageinfo->proxy->imageinfo = imageinfo;
diff --git a/src/ip.h b/src/ip.h
index 81852a23..6cfbbd46 100644
--- a/src/ip.h
+++ b/src/ip.h
@@ -108,7 +108,6 @@
#endif /*HAVE_SYS_STATVFS_H*/
#ifdef HAVE_SYS_VFS_H
#include
-extern int statfs();
#endif /*HAVE_SYS_VFS_H*/
#ifdef HAVE_SYS_MOUNT_H
#include
@@ -157,6 +156,7 @@ extern int statfs();
#endif /*HAVE_LIBGVC*/
#include
+#include
#include
#include
@@ -179,6 +179,10 @@ extern int statfs();
*/
#include "nipmarshal.h"
+/* XML namespace ... note, not nip2! We can't change this.
+ */
+#define NAMESPACE "http://www.vips.ecs.soton.ac.uk/nip"
+
#define MAXFILES (4000) /* Max. no of files in path */
#define STACK_SIZE (1000) /* Depth of eval stack */
#define LEN_LABEL (512) /* Label on windows */
@@ -188,18 +192,16 @@ extern int statfs();
#define MAX_STRSIZE (100000) /* Size of text for user defs */
#define MAX_TRACE (1024) /* Biggest thing we print in trace */
#define MAX_SSTACK (40) /* Scope stack for parser */
-#define VIPS_HOMEPAGE "http://www.vips.ecs.soton.ac.uk"
+#define VIPS_HOMEPAGE "https://github.com/jcupitt/nip2"
#define IP_NAME PACKAGE "-" VERSION
#define NIP_DOCPATH "$VIPSHOME" G_DIR_SEPARATOR_S "share" G_DIR_SEPARATOR_S \
"doc" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "html"
#define VIPS_DOCPATH "$VIPSHOME" G_DIR_SEPARATOR_S "share" G_DIR_SEPARATOR_S \
"doc" G_DIR_SEPARATOR_S "vips" G_DIR_SEPARATOR_S "html"
#define IP_NAME PACKAGE "-" VERSION
-#define NAMESPACE VIPS_HOMEPAGE "/" "nip"
- /* XML namespace ... note, not nip2! */
#define MAX_LINELENGTH (120) /* Max chars we display of value */
#define MAX_RECENT (10) /* Number of recent items in file menu */
-#define NIP_COPYRIGHT "%s: ©2017 Imperial College, London"
+#define NIP_COPYRIGHT "%s: ©2023 libvips.org"
/* Our stock_ids.
*/
diff --git a/src/lex.l b/src/lex.l
index db5d9935..b960b9b7 100644
--- a/src/lex.l
+++ b/src/lex.l
@@ -180,7 +180,7 @@ read_char( void )
/* Read string up to \n, EOF.
*/
- while( (ch = input()) != EOF && ch != '\n' )
+ while( (ch = input()) != EOF && ch != '\n' && ch != '\0')
;
}
@@ -207,7 +207,7 @@ TRUE {
BEGIN BINARY;
yylval.yy_const.type = PARSE_CONST_BOOL;
- yylval.yy_const.val.bool = TRUE;
+ yylval.yy_const.val.bol = TRUE;
return( TK_CONST );
}
@@ -216,7 +216,7 @@ FALSE {
BEGIN BINARY;
yylval.yy_const.type = PARSE_CONST_BOOL;
- yylval.yy_const.val.bool = FALSE;
+ yylval.yy_const.val.bol = FALSE;
return( TK_CONST );
}
diff --git a/src/main.c b/src/main.c
index 411148fd..aee51221 100644
--- a/src/main.c
+++ b/src/main.c
@@ -94,9 +94,9 @@ static gboolean main_option_benchmark = FALSE;
gboolean main_option_time_save = FALSE;
gboolean main_option_profile = FALSE;
gboolean main_option_i18n = FALSE;
+gboolean main_option_verbose = FALSE;
static gboolean main_option_print_main = FALSE;
static gboolean main_option_version = FALSE;
-static gboolean main_option_verbose = FALSE;
static gboolean main_option_test = FALSE;
static char *main_option_prefix = NULL;
@@ -908,10 +908,6 @@ main_set( const char *str )
{
Symbol *sym;
-#ifdef DEBUG
- printf( "main_set: %s\n", str );
-#endif /*DEBUG*/
-
attach_input_string( str );
if( !(sym = parse_set_symbol()) )
return( FALSE );
@@ -1485,9 +1481,14 @@ main( int argc, char *argv[] )
if( main_option_set ) {
int i;
- for( i = 0; main_option_set[i]; i++ )
+ for( i = 0; main_option_set[i]; i++ ) {
+ if( main_option_verbose )
+ printf( "main_set: %s\n", main_option_set[i] );
+
if( !main_set( main_option_set[i] ) )
- main_log_add( "%s\n", error_get_sub() );
+ main_log_add( "%s\n%s",
+ error_get_top(), error_get_sub() );
+ }
}
/* Make sure our start ws doesn't have modified set. We may have
diff --git a/src/main.h b/src/main.h
index 683265d3..4a5b6cb1 100644
--- a/src/main.h
+++ b/src/main.h
@@ -41,6 +41,7 @@ extern gboolean main_option_time_save; /* Time save image ops */
extern gboolean main_option_profile; /* Profile calcualtion */
extern gboolean main_option_i18n; /* Output i18n strings */
extern gboolean main_option_batch; /* Running in batch mode */
+extern gboolean main_option_verbose; /* Verbose output */
/* Styles for buttons etc.
*/
diff --git a/src/plot.c b/src/plot.c
index 52b7b9cd..fb310939 100644
--- a/src/plot.c
+++ b/src/plot.c
@@ -375,7 +375,7 @@ plot_class_get( Classmodel *classmodel, PElement *root )
Imageinfo *ii2;
IMAGE *t;
DOUBLEMASK *mask;
- int (*fn)();
+ int (*fn)(VipsImage *, VipsImage *);
/* nx1 or 1xm images only ... use Bands for columns.
*/
diff --git a/src/statusview.c b/src/statusview.c
index 5458d0b7..28e01554 100644
--- a/src/statusview.c
+++ b/src/statusview.c
@@ -402,7 +402,7 @@ statusview_mouse_band( StatusviewBand *svb, void *e )
break;
case IM_BANDFMT_UINT:
- set_glabel( svb->val, "%d",
+ set_glabel( svb->val, "%u",
((unsigned int *)e)[svb->bandno] );
break;
diff --git a/src/tree.h b/src/tree.h
index 2b18ef2a..8a764bbb 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -126,7 +126,7 @@ struct _ParseConst {
union {
double num;
char *str;
- gboolean bool;
+ gboolean bol;
int ch;
} val;
};
diff --git a/src/watch.c b/src/watch.c
index e16c0b0d..f3f372df 100644
--- a/src/watch.c
+++ b/src/watch.c
@@ -61,9 +61,9 @@ watchgroup_class_init( WatchgroupClass *class )
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET( WatchgroupClass, watch_changed ),
NULL, NULL,
- g_cclosure_marshal_VOID__POINTER,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
- G_TYPE_POINTER );
+ TYPE_WATCH );
}
static void
diff --git a/src/workspace.c b/src/workspace.c
index ba67bd1a..1847f44f 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -28,6 +28,7 @@
*/
/*
+#define DEBUG_VERBOSE
#define DEBUG
*/
@@ -42,10 +43,10 @@ static GSList *workspace_needs_layout = NULL;
void
workspace_set_needs_layout( Workspace *ws, gboolean needs_layout )
{
-#ifdef DEBUG
+#ifdef DEBUG_VERBOSE
printf( "workspace_set_needs_layout: %p %s %d\n",
ws, NN( IOBJECT( ws )->name ), needs_layout );
-#endif /*DEBUG*/
+#endif /*DEBUG_VERBOSE*/
if( !ws->needs_layout &&
needs_layout &&
@@ -652,9 +653,9 @@ workspace_changed( iObject *iobject )
Workspace *ws;
Workspacegroup *wsg;
-#ifdef DEBUG
+#ifdef DEBUG_VERBOSE
printf( "workspace_changed: %s\n", NN( iobject->name ) );
-#endif /*DEBUG*/
+#endif /*DEBUG_VERBOSE*/
g_return_if_fail( iobject != NULL );
g_return_if_fail( IS_WORKSPACE( iobject ) );
@@ -819,8 +820,8 @@ workspace_load( Model *model,
IM_FREEF( xmlFree, txt );
}
- (void) get_iprop( xnode, "major", &ws->compat_major );
- (void) get_iprop( xnode, "minor", &ws->compat_minor );
+ (void) get_iprop( xnode, "major", &ws->major );
+ (void) get_iprop( xnode, "minor", &ws->minor );
if( !MODEL_CLASS( parent_class )->load( model, state, parent, xnode ) )
return( FALSE );
@@ -859,11 +860,9 @@ workspace_save( Model *model, xmlNode *xnode )
if( !set_sprop( xthis, "filename", FILEMODEL( wsg )->filename ) )
return( NULL );
- if( ws->compat_major ) {
- if( !set_iprop( xthis, "major", ws->compat_major ) ||
- !set_iprop( xthis, "minor", ws->compat_minor ) )
- return( NULL );
- }
+ if( !set_iprop( xthis, "major", ws->major ) ||
+ !set_iprop( xthis, "minor", ws->minor ) )
+ return( NULL );
return( xthis );
}
@@ -986,14 +985,8 @@ workspace_have_compat( int major, int minor, int *best_major, int *best_minor )
void
workspace_get_version( Workspace *ws, int *major, int *minor )
{
- if( ws->compat_major ) {
- *major = ws->compat_major;
- *minor = ws->compat_minor;
- }
- else {
- *major = MAJOR_VERSION;
- *minor = MINOR_VERSION;
- }
+ *major = ws->major;
+ *minor = ws->minor;
}
gboolean
@@ -1024,10 +1017,19 @@ workspace_load_compat( Workspace *ws, int major, int minor )
}
path_free2( path );
+#ifdef DEBUG
+ printf( "workspace_load_compat: loaded %d.%d\n",
+ best_major, best_minor );
+#endif /*DEBUG*/
+
ws->compat_major = best_major;
ws->compat_minor = best_minor;
}
else {
+#ifdef DEBUG
+ printf( "workspace_load_compat: no compat necessary\n" );
+#endif /*DEBUG*/
+
/* No compat defs necessary for this ws.
*/
ws->compat_major = 0;
@@ -1088,6 +1090,9 @@ workspace_init( Workspace *ws )
ws->errors = NULL;
ws->mode = WORKSPACE_MODE_REGULAR;
+ ws->major = MAJOR_VERSION;
+ ws->minor = MINOR_VERSION;
+
ws->compat_major = 0;
ws->compat_minor = 0;
diff --git a/src/workspace.h b/src/workspace.h
index 1a11421a..c70c1866 100644
--- a/src/workspace.h
+++ b/src/workspace.h
@@ -68,8 +68,16 @@ struct _Workspace {
WorkspaceMode mode; /* Display mode */
gboolean locked; /* WS has been locked */
- /* Some versions (7.10 etc.) need special compat toolkits. 0 here for
- * no compat toolkits loaded.
+ /* The nip2 version that made this workspace.
+ */
+ int major;
+ int minor;
+
+ /* We may load some compat definitions to support this workspace, if it
+ * was written by an older version.
+ *
+ * The version number of the compat stuff we loaded. Zero for no compat
+ * stuff loaded.
*/
int compat_major;
int compat_minor;
diff --git a/test/workspaces/test_conv.ws b/test/workspaces/test_conv.ws
index c01bbe7f..29e6f5cc 100644
--- a/test/workspaces/test_conv.ws
+++ b/test/workspaces/test_conv.ws
@@ -1,7 +1,7 @@
-
-
-
+
+
+
@@ -96,7 +96,7 @@
-
+
@@ -147,7 +147,7 @@
-
+
@@ -230,7 +230,7 @@
-
+
@@ -289,7 +289,7 @@
-
+
@@ -305,7 +305,7 @@
-
+
@@ -356,7 +356,7 @@
-
+
@@ -407,7 +407,7 @@
-
+
@@ -485,7 +485,7 @@
-
+
@@ -556,9 +556,14 @@
+
+
+
+
+
-
+
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy