From 420b1ea72c7bf0c289624ad00dbf0df9a6651143 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 30 Mar 2017 09:40:33 +0100 Subject: [PATCH 01/15] try writing some docs in markdown we could move parts of the blog / wiki into core docs --- TODO | 5 + doc/Makefile.am | 6 + doc/gtkdoc-mkhtml | 109 ++++++++++++++++ doc/libvips-docs.xml.in | 1 + doc/pandoc-docbook-template.docbook | 32 +++++ doc/whatsnew-8.5.md | 184 ++++++++++++++++++++++++++++ 6 files changed, 337 insertions(+) create mode 100755 doc/gtkdoc-mkhtml create mode 100644 doc/pandoc-docbook-template.docbook create mode 100644 doc/whatsnew-8.5.md diff --git a/TODO b/TODO index 36f83c6e..c2068c1f 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,8 @@ +- think we need to output refsect1, not sect1 + + maybe a sed could do it? + + - not sure about utf8 error messages on win - strange: diff --git a/doc/Makefile.am b/doc/Makefile.am index 14e76f0b..53bb39ea 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -128,6 +128,10 @@ IGNORE_HFILES = $(IGNORE_VIPS_INCLUDE) $(IGNORE_VIPS_C) HTML_IMAGES = \ $(top_srcdir)/doc/images/interconvert.png +# we have some files in markdown ... convert to docbook for gtk-doc +.md.xml: + pandoc -s -S --template="$(realpath pandoc-docbook-template.docbook)" -V title=hello --wrap=none -f markdown -t docbook -o $@ $< + # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # e.g. content_files=running.sgml building.sgml changes-2.0.sgml content_files = \ @@ -139,6 +143,7 @@ content_files = \ extending.xml \ function-list.xml \ file-format.xml \ + whatsnew-8.5.xml \ binding.xml # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded @@ -153,6 +158,7 @@ expand_content_files = \ extending.xml \ function-list.xml \ file-format.xml \ + whatsnew-8.5.xml \ binding.xml # CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. diff --git a/doc/gtkdoc-mkhtml b/doc/gtkdoc-mkhtml new file mode 100755 index 00000000..0f3af842 --- /dev/null +++ b/doc/gtkdoc-mkhtml @@ -0,0 +1,109 @@ +#!/bin/sh +# + +usage() { +cat <&2 + exit 1 +fi + +module="$1" +shift +document="$1" +shift + +quiet="1" +if test $verbose = "1"; then + quiet="0" +fi + +if test $uninstalled = yes; then + # this does not work from buiddir!=srcdir + gtkdocdir=`dirname $0` + # traditional Bourne shells may not support -e here, use -f + if test ! -f $gtkdocdir/gtk-doc.xsl; then + # try to src dir (set from makefiles) too + if test -f $ABS_TOP_SRCDIR/gtk-doc.xsl; then + gtkdocdir=$ABS_TOP_SRCDIR + fi + fi + styledir=$gtkdocdir/style + #echo "uninstalled, gtkdocdir=$gtkdocdir, cwd=$PWD" +else + # the first two are needed to resolve datadir + prefix=/usr + datarootdir=${prefix}/share + gtkdocdir=${datarootdir}/gtk-doc/data + styledir=$gtkdocdir +fi + +# we could do "$path_option $PWD " +# to avoid needing rewriting entities that are copied from the header +# into docs under xml +if test "X$searchpath" = "X"; then + path_arg= +else + path_arg="--path $searchpath" +fi + +# profiling +profile_args="" +if test "$GTKDOC_PROFILE" != ""; then + profile_args="--profile" +fi + +#echo /usr/bin/xsltproc $path_arg --nonet --xinclude \ +# --stringparam gtkdoc.bookname $module \ +# --stringparam gtkdoc.version "1.25" \ +# "$@" $gtkdocdir/gtk-doc.xsl "$document" +/usr/bin/xsltproc 2>profile.txt $profile_args $path_arg --nonet --xinclude \ + --stringparam gtkdoc.bookname $module \ + --stringparam gtkdoc.version "1.25" \ + --stringparam chunk.quietly $quiet \ + --stringparam chunker.output.quiet $quiet \ + "$@" $gtkdocdir/gtk-doc.xsl "$document" || exit $? + +# profiling +if test "$GTKDOC_PROFILE" != ""; then + cat profile.txt | gprof2dot.py -e 0.01 -n 0.01 | dot -Tpng -o profile.png +else + rm profile.txt +fi + +# copy navigation images and stylesheets to html directory ... +cp -f $styledir/*.png $styledir/*.css ./ + + +echo "timestamp" > ../html.stamp + diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml.in index 5c25f04b..d30d8097 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml.in @@ -39,6 +39,7 @@ + diff --git a/doc/pandoc-docbook-template.docbook b/doc/pandoc-docbook-template.docbook new file mode 100644 index 00000000..e886f16d --- /dev/null +++ b/doc/pandoc-docbook-template.docbook @@ -0,0 +1,32 @@ + +$if(mathml)$ + +$else$ + +$endif$ + + + + $title$ + 3 + VIPS Library + + + + VIPS + $title$ + + +$for(include-before)$ +$include-before$ +$endfor$ + +$body$ + +$for(include-after)$ +$include-after$ +$endfor$ + + diff --git a/doc/whatsnew-8.5.md b/doc/whatsnew-8.5.md new file mode 100644 index 00000000..b6b1b2ee --- /dev/null +++ b/doc/whatsnew-8.5.md @@ -0,0 +1,184 @@ +libvips 8.5 should be out by the end of March 2017. This page introduces the +main features. + +## New operators + +Almost all of the logic from the `vipsthumbnail` program is now in a pair of +new operators, `vips_thumbnail()` and `vips_thumbnail_buffer()`. These are very +handy for the various scripting languages with vips bindings: you can now make +a high-quality, high-speed thumbnail in PHP (for example) with just: + +```php +$filename = ...; +$image = Vips\Image::thumbnail($filename, 200, ["height" => 200]); +$image.writeToFile("my-thumbnail.jpg"); +``` + +The new thumbnail operator has also picked up some useful features: + +* **Smart crop** A new cropping mode called `attention` searches the image for + edges, skin tones and areas of saturated colour, and attempts to position the + crop box over the most significant feature. There's a `vips_smartcrop()` + operator as well. + +* **Crop constraints** Thanks to tomasc, libvips has crop constraints. You + can set it to only thumbnail if the image is larger or smaller than the target + (the `<` and `>` modifiers in imagemagick), and to crop to a width or height. + +* **Buffer sources** `vips_thumbnail_buffer()` will thumbnail an image held as + a formatted block of data in memory. This is useful for cloud services, where + the filesystem is often rather slow. + +CLAHE, or Contrast-Limited Adaptive Histogram Equalisation, is a simple way to +make local histogram equalisation more useful. + +Plain local equalization removes +all global brightness variation and can make images hard to understand. +The `hist_local` operator now has a `max-slope` parameter you can use to limit +how much equalisation can alter your image. A value of 3 generally works well. + +## Toilet roll images + +libvips used to let you pick single pages out of multi-page images, such +as PDFs, but had little support for processing entire documents. + +libvips 8.5 now has good support for toilet roll images. You can load a +multipage image as a very tall, thin strip, process the whole thing, and write +back to another multi-page file. The extra feature is an `n` parameter which +gives the number of pages to load, or -1 to load all pages. + +For example, (OME- +TIFF)[https://www.openmicroscopy.org/site/support/ome-model/ome-tiff] +is a standard for microscopy data that stores volumetric images as multi-page +TIFFs. They have some (sample +data)[https://www.openmicroscopy.org/site/support/ome-model/ome-tiff/data.html] +including a 4D image of an embryo. + +Each TIFF contains 10 slices. Normally you just see page 0: + +``` +$ vipsheader tubhiswt_C0_TP13.ome.tif +tubhiswt_C0_TP13.ome.tif: 512x512 uchar, 1 band, b-w, tiffload +``` + +Use `n=-1` and you see all the pages as a very tall strip: + +``` +$ vipsheader tubhiswt_C0_TP13.ome.tif[n=-1] +tubhiswt_C0_TP13.ome.tif: 512x5120 uchar, 1 band, b-w, tiffload +``` + +You can work with PDF, TIFF, GIF and all imagemagick-supported formats in +this way. + +You can write this tall strip to another file, and it will be broken up into +pages: + +``` +$ vips copy tubhiswt_C0_TP13.ome.tif[n=-1] x.tif +$ vipsheader x.tif +x.tif: 512x512 uchar, 1 band, b-w, tiffload +$ vipsheader x.tif[n=-1] +x.tif: 512x5120 uchar, 1 band, b-w, tiffload +``` + +The extra magic is a `page-height` property that images carry around that says +how long each sheet of toilet paper is. + +There are clearly some restrictions with this style of multi-page document +handling: all pages must have identical width, height and colour depth; and image +processing operators have no idea they are dealing with a multi-page document, +so if you do something like `resize`, you'll need to update `page-height`. +You'll also need to be careful about edge effects if you're using spatial +filters. + +## Computation reordering + +Thanks to the developer of +(PhotoFlow)[https://github.com/aferrero2707/PhotoFlow], a non-destructive image +editor with a libvips backend, libvips can now reorder computations to reduce +recalculation. This can (sometimes) produce a dramatic speedup. + +This has been (discussed on the libvips +blog)[http://libvips.blogspot.co.uk/2017/01/automatic-computation-reordering.html], +but briefly, the order in which operator arguments are evaluated can have a +big effect on runtime due to the way libvips tries to cache and reuse results +behind the scenes. + +The blog post has some examples and some graphs. + +## New sequential mode + +libvips sequential mode has been around for a while. This is the thing libvips +uses to stream pixels through your computer, from input file to output file, +without having to have the whole image in memory all at the same time. When it +works, it give a nice performance boost and a large drop in memory use. + +There are some more complex cases where it didn't work. Consider this Python +program: + +```python +#!/usr/bin/python + +import sys +import random + +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips + +composite = Vips.Image.black(10000, 10000) + +for filename in sys.argv[2:]: + tile = Vips.Image.new_from_file(filename, access = Vips.Access.SEQUENTIAL) + x = random.randint(0, composite.width - tile.width) + y = random.randint(0, composite.height - tile.height) + composite = composite.insert(tile, x, y) + +composite.write_to_file(sys.argv[1]) +``` + +It makes a large 10,000 x 10,000 pixel image, then inserts all of the images +you list at random positions, then writes the result. + +You'd think this could work with sequential mode, but sadly with earlier +libvipses it will sometimes fail. The problem is that images can cover each +other, so while writing, libvips can discover that it only needs the bottom few +pixels of one of the input images. The image loaders used to track the current +read position, and if a request came in for some pixels way down the image, +they'd assume one of the evaluation threads had run ahead of the rest and +needed to be stalled. Once stalled, it was only restarted on a long timeout, +causing performance to drop through the floor. + +libvips 8.5 has a new implementation of sequential mode that changes the way +threads are kept together as images are processed. Rather than trying to add +constraints to load operations, instead it puts the constraints into operations +that can cause threads to become spread out, such as vertical shrink. + +As a result of this change, many more things can run in sequential mode, and +out of order reads should be impossible. + +## `libxml2` swapped out for `expat` + +libvips has used libxml2 as its XML parser since dinosaurs roamed the Earth. +Now libvips is based on gobject, the XML parser selected by glib, expat, makes +more sense, since it will already be linked. + +It's nice to be able to remove a required dependency for a change. + +## File format support + +As usual, there are a range of improvements to file format read and write. + +* Thanks to a push from Felix Bünemann, TIFF now supports load and save to and + from memory buffers. +* `dzsave` can write to memory (as a zip file) as well. +* Again, thanks to pushing from Felix, libvips now supports ICC, XMP and IPCT + metadata for WebP images. +* FITS images support `bzero` and `bscale`. +* `tiffload` memory use is now much lower for images with large strips. + +## Other + +Many small bug fixes, improvements to the C++ binding. As usual, the +ChangeLog has more detail, if you're interested. From 60e661614ff2e957159c584a6e229fab97d5c3be Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 08:11:46 +0100 Subject: [PATCH 02/15] woo working --- doc/Makefile.am | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Makefile.am b/doc/Makefile.am index 53bb39ea..c368179b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -130,7 +130,8 @@ HTML_IMAGES = \ # we have some files in markdown ... convert to docbook for gtk-doc .md.xml: - pandoc -s -S --template="$(realpath pandoc-docbook-template.docbook)" -V title=hello --wrap=none -f markdown -t docbook -o $@ $< + pandoc -s -S --template="$(realpath pandoc-docbook-template.docbook)" --wrap=none -V title="$<" -f markdown -t docbook -o $@ $< + sed -e s/sect1/refsect3/g < $@ > x && mv x $@ # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # e.g. content_files=running.sgml building.sgml changes-2.0.sgml From 30829ef0033f681388b9f801bbf89a4adfb18fb4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 10:57:04 +0100 Subject: [PATCH 03/15] add howitworks.md --- ChangeLog | 1 + doc/.gitignore | 1 + doc/How-it-works.md | 316 ++++++++++++++++++++++++++++ doc/Makefile.am | 22 +- doc/gtkdoc-mkhtml | 109 ---------- doc/images/Combine.png | Bin 0 -> 12931 bytes doc/images/Memtrace.png | Bin 0 -> 57638 bytes doc/images/Sequence.png | Bin 0 -> 37627 bytes doc/images/Sink.png | Bin 0 -> 14853 bytes doc/images/Vips-smp.png | Bin 0 -> 11363 bytes doc/libvips-docs.xml.in | 2 +- doc/pandoc-docbook-template.docbook | 4 +- doc/whatsnew-8.5.md | 184 ---------------- 13 files changed, 339 insertions(+), 300 deletions(-) create mode 100644 doc/.gitignore create mode 100644 doc/How-it-works.md delete mode 100755 doc/gtkdoc-mkhtml create mode 100644 doc/images/Combine.png create mode 100644 doc/images/Memtrace.png create mode 100644 doc/images/Sequence.png create mode 100644 doc/images/Sink.png create mode 100644 doc/images/Vips-smp.png delete mode 100644 doc/whatsnew-8.5.md diff --git a/ChangeLog b/ChangeLog index 633a60b3..6d8fb4e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 25/3/17 started 8.5.2 - better behaviour for truncated PNG files, thanks Yury +- move some docs from the wiki and blog into core libvips docs 25/3/17 started 8.5.1 - init more classes earlier, thanks David diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 00000000..258d81a5 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +How-it-works.xml diff --git a/doc/How-it-works.md b/doc/How-it-works.md new file mode 100644 index 00000000..bf0d67b2 --- /dev/null +++ b/doc/How-it-works.md @@ -0,0 +1,316 @@ +Compared to most image processing libraries, VIPS needs little RAM and runs +quickly, especially on machines with more than one CPU. VIPS achieves this +improvement by only keeping the pixels currently being processed in RAM +and by having an efficient, threaded image IO system. This page explains +how these features are implemented. + +### Images + +VIPS images have three dimensions: width, height and bands. Bands usually +(though not always) represent colour. These three dimensions can be any +size up to 2 ** 31 elements. Every band element in an image has to have the +same format. A format is an 8-, 16- or 32-bit int, signed or unsigned, 32- +or 64-bit float, and 64- or 128-bit complex. + +### Regions + +An image can be very large, much larger than the available memory, so you +can't just access pixels with a pointer \*. + +Instead, you read pixels from an image with a region. This is a rectangular +sub-area of an image. In C, the API looks like: + +```c +VipsImage *image = vips_image_new_from_file( filename, NULL ); +VipsRegion *region = vips_region_new( image ); + +// ask for a 100x100 pixel region at 0x0 (top left) +VipsRect r = { left: 0, top: 0, width: 100, height: 100 }; +if( vips_region_prepare( region, &r ) ) + vips_error( ... ); + +// get a pointer to the pixel at x, y, where x, y must +// be within the region + +// as long as you stay within the valid area for the region, +// you can address pixels with regular pointer arithmetic + +// compile with -DDEBUG and the macro will check bounds for you + +// add VIPS_REGION_LSKIP() to move down a line +VipsPel *pixel = VIPS_REGION_ADDR( region, x, y ); + +// you can call vips_region_prepare() many times + +// everything in libvips is a GObject ... when you're done, +// just free with +g_object_unref( region ); +``` + +The action that `vips_region_prepare()` takes varies with the type of +image. If the image is a file on disc, for example, then VIPS will arrange +for a section of the file to be read in. + +(\* there is an image access mode where you can just use a pointer, but +it's rarely used) + +### Partial images + +A partial image is one where, instead of storing a value for each pixel, VIPS +stores a function which can make any rectangular area of pixels on demand. + +If you use `vips_region_prepare()` on a region created on a partial image, +VIPS will allocate enough memory to hold the pixels you asked for and use +the stored function to calculate values for just those pixels \*. + +The stored function comes in three parts: a start function, a generate +function and a stop function. The start function creates a state, the +generate function uses the state plus a requested area to calculate pixel +values and the stop function frees the state again. Breaking the stored +function into three parts is good for SMP scaling: resource allocation and +synchronisation mostly happens in start functions, so generate functions +can run without having to talk to each other. + +VIPS makes a set of guarantees about parallelism that make this simple to +program. Start and stop functions are mutually exclusive and a state is +never used by more than one generate. In other words, a start / generate / +generate / stop sequence works like a thread. + +![](Sequence.png) + +(\* in fact VIPS keeps a cache of calculated pixel buffers and will return +a pointer to a previously-calculated buffer if it can) + +### Operations + +VIPS operations read input images and write output images, performing some +transformation on the pixels. When an operation writes to an image the +action it takes depends upon the image type. For example, if the image is a +file on disc then VIPS will start a data sink to stream pixels to the file, +or if the image is a partial one then it will just attach start / generate / +stop functions. + +Like most threaded image processing systems, all VIPS operations have to +be free of side-effects. In other words, operations cannot modify images, +they can only create new images. This could result in a lot of copying if +an operation is only making a small change to a large image so VIPS has a +set of mechanisms to copy image areas by just adjusting pointers. Most of +the time no actual copying is necessary and you can perform operations on +large images at low cost. + +### Run-time code generation + +VIPS uses [Orc](http://code.entropywave.com/orc/), a run-time compiler, to +generate code for some operations. For example, to compute a convolution +on an 8-bit image, VIPS will examine the convolution matrix and the source +image and generate a tiny program to calculate the convolution. This program +is then "compiled" to the vector instruction set for your CPU, for example +SSE3 on most x86 processors. + +Run-time vector code generation typically speeds operations up by a factor +of three or four. + +### Joining operations together + +The region create / prepare / prepare / free calls you use to get pixels +from an image are an exact parallel to the start / generate / generate / +stop calls that images use to create pixels. In fact, they are the same: +a region on a partial image holds the state created by that image for the +generate function that will fill the region with pixels. + +![](Combine.png) + +VIPS joins image processing operations together by linking the output of one +operation (the start / generate / stop sequence) to the input of the next +(the region it uses to get pixels for processing). This link is a single +function call, and very fast. Additionally, because of the the split between +allocation and processing, once a pipeline of operations has been set up, +VIPS is able to run without allocating and freeing memory. + +This graph (generated by `vipsprofile`, the vips profiler) shows memory use +over time for a vips pipeline running on a large image. The bottom trace +shows total memory, the upper traces show threads calculating useful results +(green), threads blocked on synchronisation (red) and memory allocations +(white ticks). + +![](Memtrace.png) + +Because the intermediate image is just a small region in memory, a pipeline +of operations running together needs very little RAM. In fact, intermediates +are small enough that they can fit in L2 cache on most machines, so an +entire pipeline can run without touching main memory. And finally, because +each thread runs a very cheap copy of just the writeable state of the +entire pipeline, threads can run with few locks. VIPS needs just four lock +operations per output tile, regardless of the pipeline length or complexity. + +### Data sources + +VIPS has data sources which can supply pixels for processing from a variety +of sources. VIPS can stream images from files in VIPS native format, from +tiled TIFF files, from binary PPM/PGM/PBM/PFM, from Radiance (HDR) files, +from FITS images and from tiled OpenEXR images. VIPS will automatically +unpack other formats to temporary disc files for you but this can +obviously generate a lot of disc traffic. It also has a special +sequential mode for streaming operations on non-random-access +formats. A post on the libvips blog [explains how libvips opens a +file](http://libvips.blogspot.co.uk/2012/06/how-libvips-opens-file.html). One +of the sources uses the [ImageMagick](http://www.imagemagick.org) (or +optionally [GraphicsMagick](http://www.graphicsmagick.org)) library, so +VIPS can read any image format that these libraries can read. + +VIPS images are held on disc as a 64-byte header containing basic image +information like width, height, bands and format, then the image data as +a single large block of pixels, left-to-right and top-to-bottom, then an +XML extension block holding all the image metadata, such as ICC profiles +and EXIF blocks. + +When reading from a large VIPS image (or any other format with the same +structure on disc, such as binary PPM), VIPS keeps a set of small rolling +windows into the file, some small number of scanlines in size. As pixels +are demanded by different threads VIPS will move these windows up and down +the file. As a result, VIPS can process images much larger than RAM, even +on 32-bit machines. + +### Data sinks + +In a demand-driven system, something has to do the demanding. VIPS has a +variety of data sinks that you can use to pull image data though a pipeline +in various situations. There are sinks that will build a complete image +in memory, sinks to draw to a display, sinks to loop over an image (useful +for statistical operations, for example) and sinks to stream an image to disc. + +The disc sink looks something like this: + +![](Sink.png) + +The sink keeps two buffers\*, each as wide as the image. It starts threads +as rapidly as it can up to the concurrency limit, filling each buffer with +tiles\*\* of calculated pixels, each thread calculating one tile at once. A +separate background thread watches each buffer and, as soon as the last tile +in a buffer finishes, writes that complete set of scanlines to disc using +whatever image write library is appropriate. VIPS can write with libjpeg, +libtiff, libpng and others. It then wipes the buffer and repositions it +further down the image, ready for the next set of tiles to stream in. + +These features in combination mean that, once a pipeline of image processing +operations has been built, VIPS can run almost lock-free. This is very +important for SMP scaling: you don't want the synchronization overhead to +scale with either the number of threads or the complexity of the pipeline +of operations being performed. As a result, VIPS scales almost linearly +with increasing numbers of threads: + +![](Vips-smp.png) + +Number of CPUs is on the horizontal axis, speedup is on the vertical +axis. Taken from the [[Benchmarks]] page. + +(\* there can actually be more than one, it allocate enough buffers to +ensure that there are at least two tiles for every thread) + +(\*\* tiles can be any shape and size, VIPS has a tile hint system that +operations use to tell sinks what tile geometry they prefer) + +### Operation cache + +Because VIPS operations are free of side-effects\*, you can cache them. Every +time you call an operation, VIPS searches the cache for a previous call to +the same operation with the same arguments. If it finds a match, you get +the previous result again. This can give a huge speedup. + +By default, VIPS caches the last 1,000 operation calls. You can also control +the cache size by memory use or by files opened. + +(\* Some vips operations DO have side effects, for example, +`vips_draw_circle()` will draw a circle on an image. These operations emit an +"invalidate" signal on the image they are called on and this signal makes +all downstream operations and caches drop their contents.) + +### Operation database and APIs + +VIPS has around 300 image processing operations written in this style. Each +operation is a GObject class. You can use the standard GObject calls to walk +the class hierarchy and discover operations, and libvips adds a small amount +of extra introspection metadata to handle things like optional arguments. + +The [C API](using-from-c.html) is a set of simple wrappers which create +class instances for you. The [C++ API](using-from-cpp.html) is a little +fancier and adds things like automatic object lifetime management. The +[command-line interface](using-cli.html) uses introspection to run any vips +operation in the class hierarchy. + +The [Python API](using-from-python.html) is built on top of +gobject-introspection. It is written in Python, so as long as you can get +gobject-introspection working, you should be able to use vips. It supports +python2 and python3 and works on Linux, OS X and Windows. + +### Snip + +The VIPS GUI, nip2, has its own scripting language called Snip. Snip is a +lazy, higher-order, purely functional, object oriented language. Almost all +of nip2's menus are implemented in it, and nip2 workspaces are Snip programs. + +VIPS operations listed in the operation database appear as Snip functions. For +example, `abs` can be used from Snip as: + +``` +// absolute value of image b +a = vips_call "abs" [b] []; +``` + +However, `abs` won't work on anything except the primitive vips image type. It +can't be used on any class, or list or number. Definitions in `_stdenv.dev` +wrap each VIPS operation as a higher level Snip operation. For example: + +``` +abs x + = oo_unary_function abs_op x, is_class x + = vips_call "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; +} +``` + +This defines the behaviour of `abs` for the base Snip types (number, list, +matrix, image and so on), then classes will use that to define operator +behaviour on higher-level objects. + +Now you can use: + +``` +// absolute value of anything +a = abs b; +``` + +and you ought to get sane behaviour for any object, including things like +the `Matrix` class. + +You can write Snip classes which present functions to the user as menu +items. For example, `Math.def` has this: + +``` +Math_arithmetic_item = class + Menupullright "_Arithmetic" "basic arithmetic for objects" { + + Absolute_value_item = class + Menuaction "A_bsolute Value" "absolute value of x" { + action x = map_unary abs x; + } +} +``` + +Now the user can select an object and click `Math / Abs` to find the absolute +value of that object. + diff --git a/doc/Makefile.am b/doc/Makefile.am index c368179b..8284af6a 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -126,13 +126,26 @@ IGNORE_HFILES = $(IGNORE_VIPS_INCLUDE) $(IGNORE_VIPS_C) # Images to copy into HTML directory. # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png HTML_IMAGES = \ - $(top_srcdir)/doc/images/interconvert.png + $(top_srcdir)/doc/images/interconvert.png \ + $(top_srcdir)/doc/images/Combine.png \ + $(top_srcdir)/doc/images/Memtrace.png \ + $(top_srcdir)/doc/images/Sequence.png \ + $(top_srcdir)/doc/images/Sink.png \ + $(top_srcdir)/doc/images/Vips-smp.png # we have some files in markdown ... convert to docbook for gtk-doc +# pandoc makes sect1 headers, we want refsect3 for gtk-doc .md.xml: pandoc -s -S --template="$(realpath pandoc-docbook-template.docbook)" --wrap=none -V title="$<" -f markdown -t docbook -o $@ $< sed -e s/sect1/refsect3/g < $@ > x && mv x $@ +# Our markdown source files +markdown_content_files = \ + How-it-works.md + +# converted to xml in this dir by pandoc +markdown_content_files_docbook = $(markdown_content_files:.md=.xml) + # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # e.g. content_files=running.sgml building.sgml changes-2.0.sgml content_files = \ @@ -144,7 +157,7 @@ content_files = \ extending.xml \ function-list.xml \ file-format.xml \ - whatsnew-8.5.xml \ + ${markdown_content_files_docbook} \ binding.xml # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded @@ -159,7 +172,7 @@ expand_content_files = \ extending.xml \ function-list.xml \ file-format.xml \ - whatsnew-8.5.xml \ + ${markdown_content_files_docbook} \ binding.xml # CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. @@ -176,13 +189,14 @@ include gtk-doc.make # Other files to distribute # e.g. EXTRA_DIST += version.xml.in EXTRA_DIST += \ + ${markdown_content_files} \ images \ gen-function-list.py # Files not to distribute # for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types # for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt -DISTCLEANFILES = libvips.types +DISTCLEANFILES = libvips.types # Comment this out if you want 'make check' to test you doc status # and run some sanity checks diff --git a/doc/gtkdoc-mkhtml b/doc/gtkdoc-mkhtml deleted file mode 100755 index 0f3af842..00000000 --- a/doc/gtkdoc-mkhtml +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/sh -# - -usage() { -cat <&2 - exit 1 -fi - -module="$1" -shift -document="$1" -shift - -quiet="1" -if test $verbose = "1"; then - quiet="0" -fi - -if test $uninstalled = yes; then - # this does not work from buiddir!=srcdir - gtkdocdir=`dirname $0` - # traditional Bourne shells may not support -e here, use -f - if test ! -f $gtkdocdir/gtk-doc.xsl; then - # try to src dir (set from makefiles) too - if test -f $ABS_TOP_SRCDIR/gtk-doc.xsl; then - gtkdocdir=$ABS_TOP_SRCDIR - fi - fi - styledir=$gtkdocdir/style - #echo "uninstalled, gtkdocdir=$gtkdocdir, cwd=$PWD" -else - # the first two are needed to resolve datadir - prefix=/usr - datarootdir=${prefix}/share - gtkdocdir=${datarootdir}/gtk-doc/data - styledir=$gtkdocdir -fi - -# we could do "$path_option $PWD " -# to avoid needing rewriting entities that are copied from the header -# into docs under xml -if test "X$searchpath" = "X"; then - path_arg= -else - path_arg="--path $searchpath" -fi - -# profiling -profile_args="" -if test "$GTKDOC_PROFILE" != ""; then - profile_args="--profile" -fi - -#echo /usr/bin/xsltproc $path_arg --nonet --xinclude \ -# --stringparam gtkdoc.bookname $module \ -# --stringparam gtkdoc.version "1.25" \ -# "$@" $gtkdocdir/gtk-doc.xsl "$document" -/usr/bin/xsltproc 2>profile.txt $profile_args $path_arg --nonet --xinclude \ - --stringparam gtkdoc.bookname $module \ - --stringparam gtkdoc.version "1.25" \ - --stringparam chunk.quietly $quiet \ - --stringparam chunker.output.quiet $quiet \ - "$@" $gtkdocdir/gtk-doc.xsl "$document" || exit $? - -# profiling -if test "$GTKDOC_PROFILE" != ""; then - cat profile.txt | gprof2dot.py -e 0.01 -n 0.01 | dot -Tpng -o profile.png -else - rm profile.txt -fi - -# copy navigation images and stylesheets to html directory ... -cp -f $styledir/*.png $styledir/*.css ./ - - -echo "timestamp" > ../html.stamp - diff --git a/doc/images/Combine.png b/doc/images/Combine.png new file mode 100644 index 0000000000000000000000000000000000000000..c8fd6038720c5f6c7eb80baeae17dc220acfc2e5 GIT binary patch literal 12931 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 Z4?M{~N+XY{e;5JiXz4#He`xpS{{Ug>8TtSK literal 0 HcmV?d00001 diff --git a/doc/images/Memtrace.png b/doc/images/Memtrace.png new file mode 100644 index 0000000000000000000000000000000000000000..d63e6018cf5acbf27f0e2c623b7c33ab6c5a6dc7 GIT binary patch literal 57638 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 bpf_Ry>4sl=BpchnB}86YMXFrFEckx_>{w%P literal 0 HcmV?d00001 diff --git a/doc/images/Sequence.png b/doc/images/Sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..8619c015c423c37c985f0f8bfdbcd9453c1e961a GIT binary patch literal 37627 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 literal 0 HcmV?d00001 diff --git a/doc/images/Sink.png b/doc/images/Sink.png new file mode 100644 index 0000000000000000000000000000000000000000..2a8300949dc9a379bea34e53a55841b3a923346f GIT binary patch literal 14853 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 - + diff --git a/doc/pandoc-docbook-template.docbook b/doc/pandoc-docbook-template.docbook index e886f16d..bef16217 100644 --- a/doc/pandoc-docbook-template.docbook +++ b/doc/pandoc-docbook-template.docbook @@ -11,11 +11,11 @@ $endif$ $title$ 3 - VIPS Library + libvips - VIPS + libvips $title$ diff --git a/doc/whatsnew-8.5.md b/doc/whatsnew-8.5.md deleted file mode 100644 index b6b1b2ee..00000000 --- a/doc/whatsnew-8.5.md +++ /dev/null @@ -1,184 +0,0 @@ -libvips 8.5 should be out by the end of March 2017. This page introduces the -main features. - -## New operators - -Almost all of the logic from the `vipsthumbnail` program is now in a pair of -new operators, `vips_thumbnail()` and `vips_thumbnail_buffer()`. These are very -handy for the various scripting languages with vips bindings: you can now make -a high-quality, high-speed thumbnail in PHP (for example) with just: - -```php -$filename = ...; -$image = Vips\Image::thumbnail($filename, 200, ["height" => 200]); -$image.writeToFile("my-thumbnail.jpg"); -``` - -The new thumbnail operator has also picked up some useful features: - -* **Smart crop** A new cropping mode called `attention` searches the image for - edges, skin tones and areas of saturated colour, and attempts to position the - crop box over the most significant feature. There's a `vips_smartcrop()` - operator as well. - -* **Crop constraints** Thanks to tomasc, libvips has crop constraints. You - can set it to only thumbnail if the image is larger or smaller than the target - (the `<` and `>` modifiers in imagemagick), and to crop to a width or height. - -* **Buffer sources** `vips_thumbnail_buffer()` will thumbnail an image held as - a formatted block of data in memory. This is useful for cloud services, where - the filesystem is often rather slow. - -CLAHE, or Contrast-Limited Adaptive Histogram Equalisation, is a simple way to -make local histogram equalisation more useful. - -Plain local equalization removes -all global brightness variation and can make images hard to understand. -The `hist_local` operator now has a `max-slope` parameter you can use to limit -how much equalisation can alter your image. A value of 3 generally works well. - -## Toilet roll images - -libvips used to let you pick single pages out of multi-page images, such -as PDFs, but had little support for processing entire documents. - -libvips 8.5 now has good support for toilet roll images. You can load a -multipage image as a very tall, thin strip, process the whole thing, and write -back to another multi-page file. The extra feature is an `n` parameter which -gives the number of pages to load, or -1 to load all pages. - -For example, (OME- -TIFF)[https://www.openmicroscopy.org/site/support/ome-model/ome-tiff] -is a standard for microscopy data that stores volumetric images as multi-page -TIFFs. They have some (sample -data)[https://www.openmicroscopy.org/site/support/ome-model/ome-tiff/data.html] -including a 4D image of an embryo. - -Each TIFF contains 10 slices. Normally you just see page 0: - -``` -$ vipsheader tubhiswt_C0_TP13.ome.tif -tubhiswt_C0_TP13.ome.tif: 512x512 uchar, 1 band, b-w, tiffload -``` - -Use `n=-1` and you see all the pages as a very tall strip: - -``` -$ vipsheader tubhiswt_C0_TP13.ome.tif[n=-1] -tubhiswt_C0_TP13.ome.tif: 512x5120 uchar, 1 band, b-w, tiffload -``` - -You can work with PDF, TIFF, GIF and all imagemagick-supported formats in -this way. - -You can write this tall strip to another file, and it will be broken up into -pages: - -``` -$ vips copy tubhiswt_C0_TP13.ome.tif[n=-1] x.tif -$ vipsheader x.tif -x.tif: 512x512 uchar, 1 band, b-w, tiffload -$ vipsheader x.tif[n=-1] -x.tif: 512x5120 uchar, 1 band, b-w, tiffload -``` - -The extra magic is a `page-height` property that images carry around that says -how long each sheet of toilet paper is. - -There are clearly some restrictions with this style of multi-page document -handling: all pages must have identical width, height and colour depth; and image -processing operators have no idea they are dealing with a multi-page document, -so if you do something like `resize`, you'll need to update `page-height`. -You'll also need to be careful about edge effects if you're using spatial -filters. - -## Computation reordering - -Thanks to the developer of -(PhotoFlow)[https://github.com/aferrero2707/PhotoFlow], a non-destructive image -editor with a libvips backend, libvips can now reorder computations to reduce -recalculation. This can (sometimes) produce a dramatic speedup. - -This has been (discussed on the libvips -blog)[http://libvips.blogspot.co.uk/2017/01/automatic-computation-reordering.html], -but briefly, the order in which operator arguments are evaluated can have a -big effect on runtime due to the way libvips tries to cache and reuse results -behind the scenes. - -The blog post has some examples and some graphs. - -## New sequential mode - -libvips sequential mode has been around for a while. This is the thing libvips -uses to stream pixels through your computer, from input file to output file, -without having to have the whole image in memory all at the same time. When it -works, it give a nice performance boost and a large drop in memory use. - -There are some more complex cases where it didn't work. Consider this Python -program: - -```python -#!/usr/bin/python - -import sys -import random - -import gi -gi.require_version('Vips', '8.0') -from gi.repository import Vips - -composite = Vips.Image.black(10000, 10000) - -for filename in sys.argv[2:]: - tile = Vips.Image.new_from_file(filename, access = Vips.Access.SEQUENTIAL) - x = random.randint(0, composite.width - tile.width) - y = random.randint(0, composite.height - tile.height) - composite = composite.insert(tile, x, y) - -composite.write_to_file(sys.argv[1]) -``` - -It makes a large 10,000 x 10,000 pixel image, then inserts all of the images -you list at random positions, then writes the result. - -You'd think this could work with sequential mode, but sadly with earlier -libvipses it will sometimes fail. The problem is that images can cover each -other, so while writing, libvips can discover that it only needs the bottom few -pixels of one of the input images. The image loaders used to track the current -read position, and if a request came in for some pixels way down the image, -they'd assume one of the evaluation threads had run ahead of the rest and -needed to be stalled. Once stalled, it was only restarted on a long timeout, -causing performance to drop through the floor. - -libvips 8.5 has a new implementation of sequential mode that changes the way -threads are kept together as images are processed. Rather than trying to add -constraints to load operations, instead it puts the constraints into operations -that can cause threads to become spread out, such as vertical shrink. - -As a result of this change, many more things can run in sequential mode, and -out of order reads should be impossible. - -## `libxml2` swapped out for `expat` - -libvips has used libxml2 as its XML parser since dinosaurs roamed the Earth. -Now libvips is based on gobject, the XML parser selected by glib, expat, makes -more sense, since it will already be linked. - -It's nice to be able to remove a required dependency for a change. - -## File format support - -As usual, there are a range of improvements to file format read and write. - -* Thanks to a push from Felix Bünemann, TIFF now supports load and save to and - from memory buffers. -* `dzsave` can write to memory (as a zip file) as well. -* Again, thanks to pushing from Felix, libvips now supports ICC, XMP and IPCT - metadata for WebP images. -* FITS images support `bzero` and `bscale`. -* `tiffload` memory use is now much lower for images with large strips. - -## Other - -Many small bug fixes, improvements to the C++ binding. As usual, the -ChangeLog has more detail, if you're interested. From 27e6c60967b874cccca3326333d7c2973aa1c431 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 13:26:25 +0100 Subject: [PATCH 04/15] add vipsthumbnail page --- autogen.sh | 2 + doc/.gitignore | 1 - doc/How-it-works.xml | 287 ++++++++++++++++++++++++++++ doc/Makefile.am | 4 +- doc/Making-image-pyramids.md | 191 +++++++++++++++++++ doc/Making-image-pyramids.xml | 188 +++++++++++++++++++ doc/Using-vipsthumbnail.md | 342 ++++++++++++++++++++++++++++++++++ doc/images/owl.jpg | Bin 0 -> 20996 bytes doc/images/tn_owl.jpg | Bin 0 -> 6928 bytes doc/images/x.jpg | Bin 0 -> 6861 bytes doc/libvips-docs.xml.in | 2 + tools/vipsthumbnail.c | 5 +- 12 files changed, 1018 insertions(+), 4 deletions(-) delete mode 100644 doc/.gitignore create mode 100644 doc/How-it-works.xml create mode 100644 doc/Making-image-pyramids.md create mode 100644 doc/Making-image-pyramids.xml create mode 100644 doc/Using-vipsthumbnail.md create mode 100644 doc/images/owl.jpg create mode 100644 doc/images/tn_owl.jpg create mode 100644 doc/images/x.jpg diff --git a/autogen.sh b/autogen.sh index 076f9e02..208a1bf2 100755 --- a/autogen.sh +++ b/autogen.sh @@ -27,6 +27,8 @@ find doc -depth \( \ -o -path 'doc/images/*' \ -o -name '*.xml' ! -name libvips-docs.xml ! -path 'doc/xml/*' \ -o -name '*.py' \ + -o -name '*.md' \ + -o -name '*.docbook' \ \) -prune -or \( \ -type f \ -o -type d -empty \ diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 258d81a5..00000000 --- a/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -How-it-works.xml diff --git a/doc/How-it-works.xml b/doc/How-it-works.xml new file mode 100644 index 00000000..5b4064dc --- /dev/null +++ b/doc/How-it-works.xml @@ -0,0 +1,287 @@ + + + + + + How-it-works.md + 3 + libvips + + + + libvips + How-it-works.md + + + + + Compared to most image processing libraries, VIPS needs little RAM and runs quickly, especially on machines with more than one CPU. VIPS achieves this improvement by only keeping the pixels currently being processed in RAM and by having an efficient, threaded image IO system. This page explains how these features are implemented. + + + Images + + VIPS images have three dimensions: width, height and bands. Bands usually (though not always) represent colour. These three dimensions can be any size up to 2 ** 31 elements. Every band element in an image has to have the same format. A format is an 8-, 16- or 32-bit int, signed or unsigned, 32- or 64-bit float, and 64- or 128-bit complex. + + + + Regions + + An image can be very large, much larger than the available memory, so you can’t just access pixels with a pointer *. + + + Instead, you read pixels from an image with a region. This is a rectangular sub-area of an image. In C, the API looks like: + + +VipsImage *image = vips_image_new_from_file( filename, NULL ); +VipsRegion *region = vips_region_new( image ); + +// ask for a 100x100 pixel region at 0x0 (top left) +VipsRect r = { left: 0, top: 0, width: 100, height: 100 }; +if( vips_region_prepare( region, &r ) ) + vips_error( ... ); + +// get a pointer to the pixel at x, y, where x, y must +// be within the region + +// as long as you stay within the valid area for the region, +// you can address pixels with regular pointer arithmetic + +// compile with -DDEBUG and the macro will check bounds for you + +// add VIPS_REGION_LSKIP() to move down a line +VipsPel *pixel = VIPS_REGION_ADDR( region, x, y ); + +// you can call vips_region_prepare() many times + +// everything in libvips is a GObject ... when you're done, +// just free with +g_object_unref( region ); + + + The action that vips_region_prepare() takes varies with the type of image. If the image is a file on disc, for example, then VIPS will arrange for a section of the file to be read in. + + + (* there is an image access mode where you can just use a pointer, but it’s rarely used) + + + + Partial images + + A partial image is one where, instead of storing a value for each pixel, VIPS stores a function which can make any rectangular area of pixels on demand. + + + If you use vips_region_prepare() on a region created on a partial image, VIPS will allocate enough memory to hold the pixels you asked for and use the stored function to calculate values for just those pixels *. + + + The stored function comes in three parts: a start function, a generate function and a stop function. The start function creates a state, the generate function uses the state plus a requested area to calculate pixel values and the stop function frees the state again. Breaking the stored function into three parts is good for SMP scaling: resource allocation and synchronisation mostly happens in start functions, so generate functions can run without having to talk to each other. + + + VIPS makes a set of guarantees about parallelism that make this simple to program. Start and stop functions are mutually exclusive and a state is never used by more than one generate. In other words, a start / generate / generate / stop sequence works like a thread. + +
+ + + + + + +
+ + (* in fact VIPS keeps a cache of calculated pixel buffers and will return a pointer to a previously-calculated buffer if it can) + +
+ + Operations + + VIPS operations read input images and write output images, performing some transformation on the pixels. When an operation writes to an image the action it takes depends upon the image type. For example, if the image is a file on disc then VIPS will start a data sink to stream pixels to the file, or if the image is a partial one then it will just attach start / generate / stop functions. + + + Like most threaded image processing systems, all VIPS operations have to be free of side-effects. In other words, operations cannot modify images, they can only create new images. This could result in a lot of copying if an operation is only making a small change to a large image so VIPS has a set of mechanisms to copy image areas by just adjusting pointers. Most of the time no actual copying is necessary and you can perform operations on large images at low cost. + + + + Run-time code generation + + VIPS uses Orc, a run-time compiler, to generate code for some operations. For example, to compute a convolution on an 8-bit image, VIPS will examine the convolution matrix and the source image and generate a tiny program to calculate the convolution. This program is then compiled to the vector instruction set for your CPU, for example SSE3 on most x86 processors. + + + Run-time vector code generation typically speeds operations up by a factor of three or four. + + + + Joining operations together + + The region create / prepare / prepare / free calls you use to get pixels from an image are an exact parallel to the start / generate / generate / stop calls that images use to create pixels. In fact, they are the same: a region on a partial image holds the state created by that image for the generate function that will fill the region with pixels. + +
+ + + + + + +
+ + VIPS joins image processing operations together by linking the output of one operation (the start / generate / stop sequence) to the input of the next (the region it uses to get pixels for processing). This link is a single function call, and very fast. Additionally, because of the the split between allocation and processing, once a pipeline of operations has been set up, VIPS is able to run without allocating and freeing memory. + + + This graph (generated by vipsprofile, the vips profiler) shows memory use over time for a vips pipeline running on a large image. The bottom trace shows total memory, the upper traces show threads calculating useful results (green), threads blocked on synchronisation (red) and memory allocations (white ticks). + +
+ + + + + + +
+ + Because the intermediate image is just a small region in memory, a pipeline of operations running together needs very little RAM. In fact, intermediates are small enough that they can fit in L2 cache on most machines, so an entire pipeline can run without touching main memory. And finally, because each thread runs a very cheap copy of just the writeable state of the entire pipeline, threads can run with few locks. VIPS needs just four lock operations per output tile, regardless of the pipeline length or complexity. + +
+ + Data sources + + VIPS has data sources which can supply pixels for processing from a variety of sources. VIPS can stream images from files in VIPS native format, from tiled TIFF files, from binary PPM/PGM/PBM/PFM, from Radiance (HDR) files, from FITS images and from tiled OpenEXR images. VIPS will automatically unpack other formats to temporary disc files for you but this can obviously generate a lot of disc traffic. It also has a special sequential mode for streaming operations on non-random-access formats. A post on the libvips blog explains how libvips opens a file. One of the sources uses the ImageMagick (or optionally GraphicsMagick) library, so VIPS can read any image format that these libraries can read. + + + VIPS images are held on disc as a 64-byte header containing basic image information like width, height, bands and format, then the image data as a single large block of pixels, left-to-right and top-to-bottom, then an XML extension block holding all the image metadata, such as ICC profiles and EXIF blocks. + + + When reading from a large VIPS image (or any other format with the same structure on disc, such as binary PPM), VIPS keeps a set of small rolling windows into the file, some small number of scanlines in size. As pixels are demanded by different threads VIPS will move these windows up and down the file. As a result, VIPS can process images much larger than RAM, even on 32-bit machines. + + + + Data sinks + + In a demand-driven system, something has to do the demanding. VIPS has a variety of data sinks that you can use to pull image data though a pipeline in various situations. There are sinks that will build a complete image in memory, sinks to draw to a display, sinks to loop over an image (useful for statistical operations, for example) and sinks to stream an image to disc. + + + The disc sink looks something like this: + +
+ + + + + + +
+ + The sink keeps two buffers*, each as wide as the image. It starts threads as rapidly as it can up to the concurrency limit, filling each buffer with tiles** of calculated pixels, each thread calculating one tile at once. A separate background thread watches each buffer and, as soon as the last tile in a buffer finishes, writes that complete set of scanlines to disc using whatever image write library is appropriate. VIPS can write with libjpeg, libtiff, libpng and others. It then wipes the buffer and repositions it further down the image, ready for the next set of tiles to stream in. + + + These features in combination mean that, once a pipeline of image processing operations has been built, VIPS can run almost lock-free. This is very important for SMP scaling: you don’t want the synchronization overhead to scale with either the number of threads or the complexity of the pipeline of operations being performed. As a result, VIPS scales almost linearly with increasing numbers of threads: + +
+ + + + + + +
+ + Number of CPUs is on the horizontal axis, speedup is on the vertical axis. Taken from the [[Benchmarks]] page. + + + (* there can actually be more than one, it allocate enough buffers to ensure that there are at least two tiles for every thread) + + + (** tiles can be any shape and size, VIPS has a tile hint system that operations use to tell sinks what tile geometry they prefer) + +
+ + Operation cache + + Because VIPS operations are free of side-effects*, you can cache them. Every time you call an operation, VIPS searches the cache for a previous call to the same operation with the same arguments. If it finds a match, you get the previous result again. This can give a huge speedup. + + + By default, VIPS caches the last 1,000 operation calls. You can also control the cache size by memory use or by files opened. + + + (* Some vips operations DO have side effects, for example, vips_draw_circle() will draw a circle on an image. These operations emit an invalidate signal on the image they are called on and this signal makes all downstream operations and caches drop their contents.) + + + + Operation database and APIs + + VIPS has around 300 image processing operations written in this style. Each operation is a GObject class. You can use the standard GObject calls to walk the class hierarchy and discover operations, and libvips adds a small amount of extra introspection metadata to handle things like optional arguments. + + + The C API is a set of simple wrappers which create class instances for you. The C++ API is a little fancier and adds things like automatic object lifetime management. The command-line interface uses introspection to run any vips operation in the class hierarchy. + + + The Python API is built on top of gobject-introspection. It is written in Python, so as long as you can get gobject-introspection working, you should be able to use vips. It supports python2 and python3 and works on Linux, OS X and Windows. + + + + Snip + + The VIPS GUI, nip2, has its own scripting language called Snip. Snip is a lazy, higher-order, purely functional, object oriented language. Almost all of nip2’s menus are implemented in it, and nip2 workspaces are Snip programs. + + + VIPS operations listed in the operation database appear as Snip functions. For example, abs can be used from Snip as: + + +// absolute value of image b +a = vips_call "abs" [b] []; + + + However, abs won’t work on anything except the primitive vips image type. It can’t be used on any class, or list or number. Definitions in _stdenv.dev wrap each VIPS operation as a higher level Snip operation. For example: + + +abs x + = oo_unary_function abs_op x, is_class x + = vips_call "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; +} + + + This defines the behaviour of abs for the base Snip types (number, list, matrix, image and so on), then classes will use that to define operator behaviour on higher-level objects. + + + Now you can use: + + +// absolute value of anything +a = abs b; + + + and you ought to get sane behaviour for any object, including things like the Matrix class. + + + You can write Snip classes which present functions to the user as menu items. For example, Math.def has this: + + +Math_arithmetic_item = class + Menupullright "_Arithmetic" "basic arithmetic for objects" { + + Absolute_value_item = class + Menuaction "A_bsolute Value" "absolute value of x" { + action x = map_unary abs x; + } +} + + + Now the user can select an object and click Math / Abs to find the absolute value of that object. + + + + +
diff --git a/doc/Makefile.am b/doc/Makefile.am index 8284af6a..77c58cc8 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -141,7 +141,9 @@ HTML_IMAGES = \ # Our markdown source files markdown_content_files = \ - How-it-works.md + How-it-works.md \ + Using-vipsthumbnail.md \ + Making-image-pyramids.md # converted to xml in this dir by pandoc markdown_content_files_docbook = $(markdown_content_files:.md=.xml) diff --git a/doc/Making-image-pyramids.md b/doc/Making-image-pyramids.md new file mode 100644 index 00000000..084ed2d5 --- /dev/null +++ b/doc/Making-image-pyramids.md @@ -0,0 +1,191 @@ +libvips includes `vips_dzsave()`, an operation that can build image pyramids +compatible with [DeepZoom](http://en.wikipedia.org/wiki/Deep_Zoom), Zoomify +and [Google Maps](https://developers.google.com/maps/) image viewers. It's +fast and can generate pyramids for large images using only a small amount +of memory. + +The TIFF writer, `vips_tiffsave()` can also build tiled pyramidal TIFF images, +but that's very simple to use. This page concentrates on the DeepZoom builder. + +Run dzsave with no arguments to see a summary: + +``` +$ vips dzsave +save image to deepzoom file +usage: + dzsave in filename +where: + in - Image to save, input VipsImage + filename - Filename to save to, input gchararray +optional arguments: + basename - Base name to save to, input gchararray + layout - Directory layout, input VipsForeignDzLayout + default: dz + allowed: dz, zoomify, google + suffix - Filename suffix for tiles, input gchararray + overlap - Tile overlap in pixels, input gint + default: 1 + min: 0, max: 8192 + tile-size - Tile size in pixels, input gint + default: 254 + min: 1, max: 8192 + centre - Center image in tile, input gboolean + default: false + depth - Pyramid depth, input VipsForeignDzDepth + default: onepixel + allowed: onepixel, onetile, one + angle - Rotate image during save, input VipsAngle + default: d0 + allowed: d0, d90, d180, d270 + container - Pyramid container type, input VipsForeignDzContainer + default: fs + allowed: fs, zip + properties - Write a properties file to the output directory, input +gboolean + default: false + compression - ZIP deflate compression level, input gint + default: 0 + min: -1, max: 9 + strip - Strip all metadata from image, input gboolean + default: false + background - Background value, input VipsArrayDouble +operation flags: sequential nocache +``` + +You can also call `vips_dzsave()` from any language with a libvips binding, or +by using `.dz` or `.szi` as an output file suffix. + +### Writing [DeepZoom](http://en.wikipedia.org/wiki/Deep_Zoom) pyramids + +The `--layout` option sets the basic mode of operation. With no +`--layout`, dzsave writes DeepZoom pyramids. For example: + +``` +$ vips dzsave huge.tif mydz +``` + +This will create a directory called `mydz_files` containing the image +tiles, and write a file called `mydz.dzi` containing the image +metadata.  + +You can use the `--suffix` option to control how tiles are written. For +example: + +``` +$ vips dzsave huge.tif mydz --suffix .jpg[Q=90] +``` + +will write JPEG tiles with the quality factor set to 90. You can set any +format write options you like, see the API docs for `vips_jpegsave()` +for details. + +### Writing Zoomify pyramids + +Use `--layout zoomify` to put dzsave into zoomify mode. For example: + +``` +$ vips dzsave huge.tif myzoom --layout zoomify +``` + +This will create a directory called `myzoom` containing a file called +`ImageProperties.xml` with the image metadata in, and a series of +directories called `TileGroupn`, each containing 256 image tiles. + +As with DeepZoom, you can use `--suffix` to set jpeg quality. + +### Writing [Google Maps](https://developers.google.com/maps/) pyramids + +Use `--layout google` to write Google maps-style pyramids. These are +compatible with the [NYU Pathology pyramid +builder](http://code.google.com/p/virtualmicroscope/wiki/SlideTiler). +For example: + +``` +$ vips dzsave wtc.tif gmapdir --layout google +``` + +Will create a directory called `gmapdir` containing `blank.png`, the +file to display for blank tiles, and a set of numbered directories, one +for each zoom level. The pyramid can be sparse (blank tiles are not +written). + +As with DeepZoom, you can use `--suffix` to set jpeg quality. + +Use `--background` to set the background colour. This is the colour +displayed for bits of the pyramid not in the image (image edges, for +example). By default, the image background is white. + +Use `--centre` to add a border to the image large enough to centre the +image within the lowest resolution tile. By default, images are not +centred. + +For example: + +``` +$ vips dzsave wtc.tif gmapdir --layout google --background 0 --centre +``` + +#### Other options + +You can use `--tile-size` and `--overlap` to control how large the tiles +are and how they overlap (obviously). They default to the correct values +for the selected layout. + +You can use `--depth` to control how deep the pyramid should be. Possible +values are `onepixel`, `onetile` and `one`. `onepixel` means the image +is shrunk until it fits within a single pixel. `onetile` means shrink +until it fits with a tile. `one` means only write one pyramid layer (the +highest resolution one). It defaults to the correct value for the selected +layout. `--depth one` is handy for slicing up a large image into tiles +(rather than a pyramid). + +You can use `--angle` to do a 90, 180 or 270 degree rotate of an image +during pyramid write. + +You can use `--container` to set the container type. Normally dzsave will +write a tree of directories, but with `--container zip` you'll get a zip file +instead. Use .zip as the directory suffix to turn on zip format automatically: + +``` +$ vips dzsave wtc.tif mypyr.zip +``` + +to write a zipfile containing the tiles. You can use `.szi` as a suffix to +enable zip output as well. + +Use `--properties` to output an XML file called `vips-properties.xml`. This +contains a dump of all the metadata vips has about the image as a set of +name-value pairs. It's handy with openslide image sources. + +#### Preprocessing images + +You can use `.dz` as a filename suffix, meaning send the image to +`vips_dzsave()`. This means you can write the output of any vips operation to a +pyramid. For example: + +``` +$ vips extract_area huge.svs mypy.dz[layout=google] 100 100 10000 10000 +``` + +The arguments to `extract_area` are image-in, image-out, left, top, +width, height. So this command will cut out a 10,000 by 10,000 pixel +area from near the top-left-hand corner of an Aperio slide image, then +build a pyramid in Google layout using just those pixels. + +If you are working from OpenSlide images, you can use the shrink-on-load +feature of many of those formats. For example: + +``` +$ vips dzsave CMU-1.mrxs[level=1] x +``` + +Will pull out level 1 (the half-resolution level of an MRXS slide) and +make a pyramid from that. + +#### Troubleshooting + +If you are building vips from source you do need to check the summary at +the end of configure carefully. You must have the `libgsf-1-dev` package +for `vips_dzsave()` to work. + + diff --git a/doc/Making-image-pyramids.xml b/doc/Making-image-pyramids.xml new file mode 100644 index 00000000..13f4952c --- /dev/null +++ b/doc/Making-image-pyramids.xml @@ -0,0 +1,188 @@ + + + + + + Making-image-pyramids.md + 3 + libvips + + + + libvips + Making-image-pyramids.md + + + + + libvips includes vips_dzsave(), an operation that can build image pyramids compatible with DeepZoom, Zoomify and Google Maps image viewers. It’s fast and can generate pyramids for large images using only a small amount of memory. + + + The TIFF writer, vips_tiffsave() can also build tiled pyramidal TIFF images, but that’s very simple to use. This page concentrates on the DeepZoom builder. + + + Run dzsave with no arguments to see a summary: + + +$ vips dzsave +save image to deepzoom file +usage: + dzsave in filename +where: + in - Image to save, input VipsImage + filename - Filename to save to, input gchararray +optional arguments: + basename - Base name to save to, input gchararray + layout - Directory layout, input VipsForeignDzLayout + default: dz + allowed: dz, zoomify, google + suffix - Filename suffix for tiles, input gchararray + overlap - Tile overlap in pixels, input gint + default: 1 + min: 0, max: 8192 + tile-size - Tile size in pixels, input gint + default: 254 + min: 1, max: 8192 + centre - Center image in tile, input gboolean + default: false + depth - Pyramid depth, input VipsForeignDzDepth + default: onepixel + allowed: onepixel, onetile, one + angle - Rotate image during save, input VipsAngle + default: d0 + allowed: d0, d90, d180, d270 + container - Pyramid container type, input VipsForeignDzContainer + default: fs + allowed: fs, zip + properties - Write a properties file to the output directory, input +gboolean + default: false + compression - ZIP deflate compression level, input gint + default: 0 + min: -1, max: 9 + strip - Strip all metadata from image, input gboolean + default: false + background - Background value, input VipsArrayDouble +operation flags: sequential nocache + + + You can also call vips_dzsave() from any language with a libvips binding, or by using .dz or .szi as an output file suffix. + + + Writing <ulink url="http://en.wikipedia.org/wiki/Deep_Zoom">DeepZoom</ulink> pyramids + + The --layout option sets the basic mode of operation. With no --layout, dzsave writes DeepZoom pyramids. For example: + + +$ vips dzsave huge.tif mydz + + + This will create a directory called mydz_files containing the image tiles, and write a file called mydz.dzi containing the image metadata.  + + + You can use the --suffix option to control how tiles are written. For example: + + +$ vips dzsave huge.tif mydz --suffix .jpg[Q=90] + + + will write JPEG tiles with the quality factor set to 90. You can set any format write options you like, see the API docs for vips_jpegsave() for details. + + + + Writing Zoomify pyramids + + Use --layout zoomify to put dzsave into zoomify mode. For example: + + +$ vips dzsave huge.tif myzoom --layout zoomify + + + This will create a directory called myzoom containing a file called ImageProperties.xml with the image metadata in, and a series of directories called TileGroupn, each containing 256 image tiles. + + + As with DeepZoom, you can use --suffix to set jpeg quality. + + + + Writing <ulink url="https://developers.google.com/maps/">Google Maps</ulink> pyramids + + Use --layout google to write Google maps-style pyramids. These are compatible with the NYU Pathology pyramid builder. For example: + + +$ vips dzsave wtc.tif gmapdir --layout google + + + Will create a directory called gmapdir containing blank.png, the file to display for blank tiles, and a set of numbered directories, one for each zoom level. The pyramid can be sparse (blank tiles are not written). + + + As with DeepZoom, you can use --suffix to set jpeg quality. + + + Use --background to set the background colour. This is the colour displayed for bits of the pyramid not in the image (image edges, for example). By default, the image background is white. + + + Use --centre to add a border to the image large enough to centre the image within the lowest resolution tile. By default, images are not centred. + + + For example: + + +$ vips dzsave wtc.tif gmapdir --layout google --background 0 --centre + + + Other options + + You can use --tile-size and --overlap to control how large the tiles are and how they overlap (obviously). They default to the correct values for the selected layout. + + + You can use --depth to control how deep the pyramid should be. Possible values are onepixel, onetile and one. onepixel means the image is shrunk until it fits within a single pixel. onetile means shrink until it fits with a tile. one means only write one pyramid layer (the highest resolution one). It defaults to the correct value for the selected layout. --depth one is handy for slicing up a large image into tiles (rather than a pyramid). + + + You can use --angle to do a 90, 180 or 270 degree rotate of an image during pyramid write. + + + You can use --container to set the container type. Normally dzsave will write a tree of directories, but with --container zip you’ll get a zip file instead. Use .zip as the directory suffix to turn on zip format automatically: + + +$ vips dzsave wtc.tif mypyr.zip + + + to write a zipfile containing the tiles. You can use .szi as a suffix to enable zip output as well. + + + Use --properties to output an XML file called vips-properties.xml. This contains a dump of all the metadata vips has about the image as a set of name-value pairs. It’s handy with openslide image sources. + + + + Preprocessing images + + You can use .dz as a filename suffix, meaning send the image to vips_dzsave(). This means you can write the output of any vips operation to a pyramid. For example: + + +$ vips extract_area huge.svs mypy.dz[layout=google] 100 100 10000 10000 + + + The arguments to extract_area are image-in, image-out, left, top, width, height. So this command will cut out a 10,000 by 10,000 pixel area from near the top-left-hand corner of an Aperio slide image, then build a pyramid in Google layout using just those pixels. + + + If you are working from OpenSlide images, you can use the shrink-on-load feature of many of those formats. For example: + + +$ vips dzsave CMU-1.mrxs[level=1] x + + + Will pull out level 1 (the half-resolution level of an MRXS slide) and make a pyramid from that. + + + + Troubleshooting + + If you are building vips from source you do need to check the summary at the end of configure carefully. You must have the libgsf-1-dev package for vips_dzsave() to work. + + + + + + diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md new file mode 100644 index 00000000..91391338 --- /dev/null +++ b/doc/Using-vipsthumbnail.md @@ -0,0 +1,342 @@ + +libvips has shipped with a handy thumbnail maker for a while now. I +thought a post of tips and tricks might be useful. Scroll all the way to +the bottom for a summary and recommended usage. + +### Why use vipsthumbnail?  + +It’s fast and uses little memory. For example, here’s ImageMagick with +`wtc.tif`, a 10,000 x 10,000 pixel RGB tiff image: + +``` +$ time convert wtc.tif -resize 128 tn_wtc.jpg +peak RSS: 705m +real 0m2.639s +user 0m4.036s +sys 0m0.516s +``` + +And here’s `vipsthumbnail`: + +``` +$ time vipsthumbnail wtc.tif +peak RSS: 52mb +real 0m0.239s +user 0m0.168s +sys 0m0.072s +``` + +So `vipsthumbnail` +is about 11 times faster and needs 1 / 13th of the memory. + +`vipsthumbnail` +and `convert` +are using the same downsizing algorithm: a fast box filter for +large-scale reduction, and a high-quality lanczos3 interpolator for the +final 200%. + +You see similar improvements with png images, but much less with jpeg. +This is because libjpeg includes support for shrink-during-load, so the +image processing system has much less effect. + +``` +$ time convert -define jpeg:size=256x256 wtc.jpg -resize 128 +tn_wtc.jpg +peak rss: 19mb +real 0m0.259s +user 0m0.284s +sys 0m0.004s +$ time vipsthumbnail wtc.jpg +peak rss: 30mb +real 0m0.268s +user 0m0.256s +sys 0m0.016s +``` + + +The `define` argument makes `convert` +load the image at twice the target size, then use a high-quality +downsampler to get to the exact output dimensions. If you don’t leave +this headroom you can get bad aliasing artifacts. `vipsthumbnail` +does exactly this automatically. + +At larger output sizes you start to see a difference, since there are +actually some pixels being processed: + +``` +$ time convert -define jpeg:size=4000x4000 wtc.jpg -resize 2000 +tn_wtc.jpg +peak rss: 285mb +real 0m1.126s +user 0m2.508s +sys 0m0.240s +$ time vipsthumbnail wtc.jpg -s 2000 +peak rss: 47mb +real 0m0.499s +user 0m0.928s +sys 0m0.028s +``` + +### libvips options + +`vipsthumbnail` supports the usual range of vips command-line options. A +few of them are useful: + +`--vips-cache-trace` shows each operation as libvips starts it. It can be +handy to see exactly what operations `vipsthumbnail` is running for you. + +`--vips-leak` turns on the libvips memory leak checker. As well as reporting +leaks (hopefully there are none) it also tracks and reports peak memory use. + +`--vips-progress` runs a progress indicator during computation. It can be +useful to see where libvips is looping and how often. + +`--vips-info` shows a higher level view of the operations that `vipsthumbnail` +is running.  + +### Looping + +vipsthumbnail can process many images in one operation. For example: + +``` +$ vipsthumbnail *.jpg +``` + +will make a thumbnail for every jpeg in the current directory.  See the +**Output directory** section below to see how to change where thumbnails +are written. + +`vipsthumbnail` will process images one after the other. You can get a good +speedup by running several `vipsthumbnail`s in parallel, depending on how +much load you want to put on your system. + +### Thumbnail size + +You can set the bounding box of the generated thumbnail with the `--size` +option. For example: + +``` +$ vipsthumbnail shark.jpg --size 200x100 +``` + +Use a single number to set a square bounding box. You can omit either number +but keep the x to mean resize just based on that axis, for example: + +``` +$ vipsthumbnail shark.jpg --size 200x +``` + +Will resize to 200 pixels across, no matter what the height of the input image +is. + +You can append `<` or `>` to mean only resize if the image is smaller or larger +than the target. + +### Cropping + +`vipsthumbnail` normally shrinks images to fit within the box set by `--size`. +You can use the `--smartcrop` option to crop to fill the box instead. Excess +pixels are trimmed away using the strategy you set. For example: + +``` +$ vipsthumbnail owl.jpg --smartcrop attention -s 128 +``` + +Where `owl.jpg` is an off-centre composition: + +![](owl.jpg) + +Gives this result: + +![](tn_owl.jpg) + +First it shrinks the image to get the vertical axis to 128 pixels, then crops +down to 128 pixels across using the `attention` strategy. This one searches +the image for features which might catch a human eye, see `vips_smartcrop()` +for details. + +### Linear light + +Shrinking images involves combining many pixels into one. Arithmetic +averaging really ought to be in terms of the number of photons, but (for +historical reasons) the values stored in image files are usually related +to the voltage that should be applied to a CRT electron gun. + +`vipsthumbnail` has an option to perform image shrinking in linear space, that +is, a colourspace where values are proportional to photon numbers. For example: + +``` +$ vipsthumbnail fred.jpg --linear +``` + +The downside is that in linear mode, none of the very fast shrink-on-load +tricks that `vipsthumbnail` normally uses are possible, since the shrinking +done by the image libraries is done at encode time, and done in +terms of CRT voltage, not light. This can make linear light thumbnailing of +large images extremely slow. + +### Output directory + +You set the thumbnail write parameters with the `-o` +option. This is a pattern which the input filename is pasted into to +produce the output filename. For example: + +``` +$ vipsthumbnail fred.jpg jim.tif -o tn_%s.jpg +``` + +For each of the files to be thumbnailed, `vipsthumbnail` +will drop the extension (`.jpg` and `.tif` +in this case) and then substitute the name into the `-o` +option, replacing the `%s` +So this example will write thumbnails to `tn_fred.jpg` and `tn_jim.jpg`. + +If the pattern given to `-o` +is an absolute path, any path components are dropped from the input +filenames. This lets you write all of your thumbnails to a specific +directory, if you want. For example: + +``` +$ vipsthumbnail fred.jpg ../jim.tif -o /mythumbs/tn_%s.jpg +``` + +Now both thumbnails will be written to `/mythumbs`, +even though the source images are in different directories. + +Conversely, if `-o` +is set to a relative path, any path component from the input file is +prepended. For example: + +``` +$ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg +``` + +Now both input files will have thumbnails written to a subdirectory of +their current directory. + +### Output format and options + +You can use `-o` +to specify the thumbnail image format too. For example:  + +``` +$ vipsthumbnail fred.jpg ../jim.tif -o tn_%s.png +``` + +Will write thumbnails in PNG format. + +You can give options to the image write operation as a list of +comma-separated arguments in square brackets. For example: + +``` +$ vipsthumbnail fred.jpg ../jim.tif -o > tn_%s.jpg[Q=90,optimize_coding] +``` + +will write jpeg images with quality 90, and will turn on the libjpeg +coding optimizer. + +Check the image write operations to see all the possible options. For +example: + +``` +$ vips jpegsave +save image to jpeg file +usage: + jpegsave in filename +where: + in - Image to save, input VipsImage + filename - Filename to save to, input gchararray +optional arguments: + Q - Q factor, input gint + default: 75 + min: 1, max: 100 + profile - ICC profile to embed, input gchararray + optimize-coding - Compute optimal Huffman coding tables, input gboolean + default: false + interlace - Generate an interlaced (progressive) jpeg, input gboolean + default: false + no-subsample - Disable chroma subsample, input gboolean + default: false + trellis-quant - Apply trellis quantisation to each 8x8 block, input gboolean + default: false + overshoot-deringing - Apply overshooting to samples with extreme values, input gboolean + default: false + optimize-scans - Split the spectrum of DCT coefficients into separate scans, input gboolean + default: false + quant-table - Use predefined quantization table with given index, input gint + default: 0 + min: 0, max: 8 + strip - Strip all metadata from image, input gboolean + default: false + background - Background value, input VipsArrayDouble +``` + +The `strip` option is especially useful. Many image have very large IPCT, ICC or +XMP metadata items embedded in them, and removing these can give a large +saving. + +For example: + +``` +$ vipsthumbnail 42-32157534.jpg +$ ls -l tn_42-32157534.jpg +-rw-r–r– 1 john john 6682 Nov 12 21:27 tn_42-32157534.jpg +``` + +`strip` almost halves the size of the thumbnail: + +``` +$ vipsthumbnail 42-32157534.jpg -o x.jpg[optimize_coding,strip] +$ ls -l x.jpg +-rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg +``` + +### Colour management + +`vipsthumbnail` will optionally put images through LittleCMS for you. You can +use this to move all thumbnails to the same colour space. All web browsers +assume that images without an ICC profile are in sRGB colourspace, so if +you move your thumbnails to sRGB, you can strip all the embedded profiles. +This can save several kb per thumbnail. + +For example: + +``` +$ vipsthumbnail shark.jpg +$ ls -l tn_shark.jpg +-rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg +``` + +Now encode with sRGB and delete any embedded profile: + +``` +$ vipsthumbnail shark.jpg --eprofile /usr/share/color/icc/sRGB.icc --delete +$ ls -l tn_shark.jpg +-rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg +``` + +It’ll look identical to a user, but be almost half the size. + +You can also specify a fallback input profile to use if the image has no +embedded one, but this is less useful. + +### Auto-rotate + +Many JPEG files have a hint set in the header giving the image orientation. If +you strip out the metadata, this hint will be lost, and the image will appear +to be rotated. + +If you use the `--rotate` option, `vipsthumbnail` examines the image header and +if there's an orientation tag, applies and removes it. + +### Final suggestion + +Putting all this together, I suggest this as a sensible set of options: + +``` +$ vipsthumbnail fred.jpg \ + --size 128 \ + -o tn_%s.jpg[optimize_coding,strip] \ + --eprofile /usr/share/color/icc/sRGB.icc \ + --rotate +``` diff --git a/doc/images/owl.jpg b/doc/images/owl.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e144147560587d22a8e84fda13f430d014b3ff9c GIT binary patch literal 20996 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�T<~d7&Gf^Mz>jS|e~gTCdxeM1~%%lBhmi9MSr6 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 ojK%J)7J4@}RgY=GoR4F1= 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 + +
diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 2a4fe18f..5c5f5f00 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -156,9 +156,10 @@ static GOptionEntry options[] = { { "linear", 'a', 0, G_OPTION_ARG_NONE, &linear_processing, N_( "process in linear space" ), NULL }, - { "smartcrop", 'c', 0, + { "smartcrop", 'm', 0, G_OPTION_ARG_STRING, &smartcrop_image, - N_( "crop exactly to SIZE" ), NULL }, + N_( "shrink and crop to fill SIZE using STRATEGY" ), + N_( "STRATEGY" ) }, { "rotate", 't', 0, G_OPTION_ARG_NONE, &rotate_image, N_( "auto-rotate" ), NULL }, From ab16d9560fdf95f759e7279db5e88452f9b211c6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 13:27:42 +0100 Subject: [PATCH 05/15] notes --- TODO | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index c2068c1f..b9d8b8c5 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ -- think we need to output refsect1, not sect1 - - maybe a sed could do it? +- add note about vips_thumbnail() and vips_thumbnail_buffer() + move "how libvips opens files" into docs? - not sure about utf8 error messages on win From b2b95ca04593998cd5ce860cc9dcc496ab422d5d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 18:11:39 +0100 Subject: [PATCH 06/15] finish up thumbnail docs --- doc/Makefile.am | 2 + doc/Using-vipsthumbnail.md | 91 ++---------- doc/Using-vipsthumbnail.xml | 287 ++++++++++++++++++++++++++++++++++++ 3 files changed, 301 insertions(+), 79 deletions(-) create mode 100644 doc/Using-vipsthumbnail.xml diff --git a/doc/Makefile.am b/doc/Makefile.am index 77c58cc8..1b3ae99e 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -126,6 +126,8 @@ IGNORE_HFILES = $(IGNORE_VIPS_INCLUDE) $(IGNORE_VIPS_C) # Images to copy into HTML directory. # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png HTML_IMAGES = \ + $(top_srcdir)/doc/images/owl.jpg \ + $(top_srcdir)/doc/images/tn_owl.jpg \ $(top_srcdir)/doc/images/interconvert.png \ $(top_srcdir)/doc/images/Combine.png \ $(top_srcdir)/doc/images/Memtrace.png \ diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md index 91391338..cdcb33ec 100644 --- a/doc/Using-vipsthumbnail.md +++ b/doc/Using-vipsthumbnail.md @@ -1,81 +1,10 @@ +libvips ships with a handy command-line image thumbnailer, `vipsthumbnail`. +This page introduces it with examples. -libvips has shipped with a handy thumbnail maker for a while now. I -thought a post of tips and tricks might be useful. Scroll all the way to -the bottom for a summary and recommended usage. - -### Why use vipsthumbnail?  - -It’s fast and uses little memory. For example, here’s ImageMagick with -`wtc.tif`, a 10,000 x 10,000 pixel RGB tiff image: - -``` -$ time convert wtc.tif -resize 128 tn_wtc.jpg -peak RSS: 705m -real 0m2.639s -user 0m4.036s -sys 0m0.516s -``` - -And here’s `vipsthumbnail`: - -``` -$ time vipsthumbnail wtc.tif -peak RSS: 52mb -real 0m0.239s -user 0m0.168s -sys 0m0.072s -``` - -So `vipsthumbnail` -is about 11 times faster and needs 1 / 13th of the memory. - -`vipsthumbnail` -and `convert` -are using the same downsizing algorithm: a fast box filter for -large-scale reduction, and a high-quality lanczos3 interpolator for the -final 200%. - -You see similar improvements with png images, but much less with jpeg. -This is because libjpeg includes support for shrink-during-load, so the -image processing system has much less effect. - -``` -$ time convert -define jpeg:size=256x256 wtc.jpg -resize 128 -tn_wtc.jpg -peak rss: 19mb -real 0m0.259s -user 0m0.284s -sys 0m0.004s -$ time vipsthumbnail wtc.jpg -peak rss: 30mb -real 0m0.268s -user 0m0.256s -sys 0m0.016s -``` - - -The `define` argument makes `convert` -load the image at twice the target size, then use a high-quality -downsampler to get to the exact output dimensions. If you don’t leave -this headroom you can get bad aliasing artifacts. `vipsthumbnail` -does exactly this automatically. - -At larger output sizes you start to see a difference, since there are -actually some pixels being processed: - -``` -$ time convert -define jpeg:size=4000x4000 wtc.jpg -resize 2000 -tn_wtc.jpg -peak rss: 285mb -real 0m1.126s -user 0m2.508s -sys 0m0.240s -$ time vipsthumbnail wtc.jpg -s 2000 -peak rss: 47mb -real 0m0.499s -user 0m0.928s -sys 0m0.028s -``` +The thumbnailing functionality is implemeted by +`vips_thumbnail()` and +`vips_thumbnail_buffer()`, see the docs for details. You can use these +functions from any language with a libvips binding. ### libvips options @@ -108,7 +37,11 @@ are written. `vipsthumbnail` will process images one after the other. You can get a good speedup by running several `vipsthumbnail`s in parallel, depending on how -much load you want to put on your system. +much load you want to put on your system. For example: + +``` +$ parallel vipsthumbnail ::: *.jpg +``` ### Thumbnail size @@ -130,7 +63,7 @@ Will resize to 200 pixels across, no matter what the height of the input image is. You can append `<` or `>` to mean only resize if the image is smaller or larger -than the target. +than the target. ### Cropping diff --git a/doc/Using-vipsthumbnail.xml b/doc/Using-vipsthumbnail.xml new file mode 100644 index 00000000..563112c0 --- /dev/null +++ b/doc/Using-vipsthumbnail.xml @@ -0,0 +1,287 @@ + + + + + + Using-vipsthumbnail.md + 3 + libvips + + + + libvips + Using-vipsthumbnail.md + + + + + libvips ships with a handy command-line image thumbnailer, vipsthumbnail. This page introduces it with examples. + + + The thumbnailing functionality is implemeted by vips_thumbnail() and vips_thumbnail_buffer(), see the docs for details. You can use these functions from any language with a libvips binding. + + + libvips options + + vipsthumbnail supports the usual range of vips command-line options. A few of them are useful: + + + --vips-cache-trace shows each operation as libvips starts it. It can be handy to see exactly what operations vipsthumbnail is running for you. + + + --vips-leak turns on the libvips memory leak checker. As well as reporting leaks (hopefully there are none) it also tracks and reports peak memory use. + + + --vips-progress runs a progress indicator during computation. It can be useful to see where libvips is looping and how often. + + + --vips-info shows a higher level view of the operations that vipsthumbnail is running.  + + + + Looping + + vipsthumbnail can process many images in one operation. For example: + + +$ vipsthumbnail *.jpg + + + will make a thumbnail for every jpeg in the current directory.  See the Output directory section below to see how to change where thumbnails are written. + + + vipsthumbnail will process images one after the other. You can get a good speedup by running several vipsthumbnails in parallel, depending on how much load you want to put on your system. For example: + + +$ parallel vipsthumbnail ::: *.jpg + + + + Thumbnail size + + You can set the bounding box of the generated thumbnail with the --size option. For example: + + +$ vipsthumbnail shark.jpg --size 200x100 + + + Use a single number to set a square bounding box. You can omit either number but keep the x to mean resize just based on that axis, for example: + + +$ vipsthumbnail shark.jpg --size 200x + + + Will resize to 200 pixels across, no matter what the height of the input image is. + + + You can append < or > to mean only resize if the image is smaller or larger than the target. + + + + Cropping + + vipsthumbnail normally shrinks images to fit within the box set by --size. You can use the --smartcrop option to crop to fill the box instead. Excess pixels are trimmed away using the strategy you set. For example: + + +$ vipsthumbnail owl.jpg --smartcrop attention -s 128 + + + Where owl.jpg is an off-centre composition: + +
+ + + + + + +
+ + Gives this result: + +
+ + + + + + +
+ + First it shrinks the image to get the vertical axis to 128 pixels, then crops down to 128 pixels across using the attention strategy. This one searches the image for features which might catch a human eye, see vips_smartcrop() for details. + +
+ + Linear light + + Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for historical reasons) the values stored in image files are usually related to the voltage that should be applied to a CRT electron gun. + + + vipsthumbnail has an option to perform image shrinking in linear space, that is, a colourspace where values are proportional to photon numbers. For example: + + +$ vipsthumbnail fred.jpg --linear + + + The downside is that in linear mode, none of the very fast shrink-on-load tricks that vipsthumbnail normally uses are possible, since the shrinking done by the image libraries is done at encode time, and done in terms of CRT voltage, not light. This can make linear light thumbnailing of large images extremely slow. + + + + Output directory + + You set the thumbnail write parameters with the -o option. This is a pattern which the input filename is pasted into to produce the output filename. For example: + + +$ vipsthumbnail fred.jpg jim.tif -o tn_%s.jpg + + + For each of the files to be thumbnailed, vipsthumbnail will drop the extension (.jpg and .tif in this case) and then substitute the name into the -o option, replacing the %s So this example will write thumbnails to tn_fred.jpg and tn_jim.jpg. + + + If the pattern given to -o is an absolute path, any path components are dropped from the input filenames. This lets you write all of your thumbnails to a specific directory, if you want. For example: + + +$ vipsthumbnail fred.jpg ../jim.tif -o /mythumbs/tn_%s.jpg + + + Now both thumbnails will be written to /mythumbs, even though the source images are in different directories. + + + Conversely, if -o is set to a relative path, any path component from the input file is prepended. For example: + + +$ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg + + + Now both input files will have thumbnails written to a subdirectory of their current directory. + + + + Output format and options + + You can use -o to specify the thumbnail image format too. For example:  + + +$ vipsthumbnail fred.jpg ../jim.tif -o tn_%s.png + + + Will write thumbnails in PNG format. + + + You can give options to the image write operation as a list of comma-separated arguments in square brackets. For example: + + +$ vipsthumbnail fred.jpg ../jim.tif -o > tn_%s.jpg[Q=90,optimize_coding] + + + will write jpeg images with quality 90, and will turn on the libjpeg coding optimizer. + + + Check the image write operations to see all the possible options. For example: + + +$ vips jpegsave +save image to jpeg file +usage: + jpegsave in filename +where: + in - Image to save, input VipsImage + filename - Filename to save to, input gchararray +optional arguments: + Q - Q factor, input gint + default: 75 + min: 1, max: 100 + profile - ICC profile to embed, input gchararray + optimize-coding - Compute optimal Huffman coding tables, input gboolean + default: false + interlace - Generate an interlaced (progressive) jpeg, input gboolean + default: false + no-subsample - Disable chroma subsample, input gboolean + default: false + trellis-quant - Apply trellis quantisation to each 8x8 block, input gboolean + default: false + overshoot-deringing - Apply overshooting to samples with extreme values, input gboolean + default: false + optimize-scans - Split the spectrum of DCT coefficients into separate scans, input gboolean + default: false + quant-table - Use predefined quantization table with given index, input gint + default: 0 + min: 0, max: 8 + strip - Strip all metadata from image, input gboolean + default: false + background - Background value, input VipsArrayDouble + + + The strip option is especially useful. Many image have very large IPCT, ICC or XMP metadata items embedded in them, and removing these can give a large saving. + + + For example: + + +$ vipsthumbnail 42-32157534.jpg +$ ls -l tn_42-32157534.jpg +-rw-r–r– 1 john john 6682 Nov 12 21:27 tn_42-32157534.jpg + + + strip almost halves the size of the thumbnail: + + +$ vipsthumbnail 42-32157534.jpg -o x.jpg[optimize_coding,strip] +$ ls -l x.jpg +-rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg + + + + Colour management + + vipsthumbnail will optionally put images through LittleCMS for you. You can use this to move all thumbnails to the same colour space. All web browsers assume that images without an ICC profile are in sRGB colourspace, so if you move your thumbnails to sRGB, you can strip all the embedded profiles. This can save several kb per thumbnail. + + + For example: + + +$ vipsthumbnail shark.jpg +$ ls -l tn_shark.jpg +-rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg + + + Now encode with sRGB and delete any embedded profile: + + +$ vipsthumbnail shark.jpg --eprofile /usr/share/color/icc/sRGB.icc --delete +$ ls -l tn_shark.jpg +-rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg + + + It’ll look identical to a user, but be almost half the size. + + + You can also specify a fallback input profile to use if the image has no embedded one, but this is less useful. + + + + Auto-rotate + + Many JPEG files have a hint set in the header giving the image orientation. If you strip out the metadata, this hint will be lost, and the image will appear to be rotated. + + + If you use the --rotate option, vipsthumbnail examines the image header and if there’s an orientation tag, applies and removes it. + + + + Final suggestion + + Putting all this together, I suggest this as a sensible set of options: + + +$ vipsthumbnail fred.jpg \ + --size 128 \ + -o tn_%s.jpg[optimize_coding,strip] \ + --eprofile /usr/share/color/icc/sRGB.icc \ + --rotate + + + + +
From bc247770f49c5d373c47d3e8d907490ffb64849f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 20:15:33 +0100 Subject: [PATCH 07/15] fix small things --- doc/How-it-works.md | 22 ++++----- doc/Making-image-pyramids.md | 12 ++--- doc/Making-image-pyramids.xml | 90 +++++++++++++++++------------------ doc/Using-vipsthumbnail.md | 20 ++++---- doc/Using-vipsthumbnail.xml | 40 ++++++++-------- 5 files changed, 92 insertions(+), 92 deletions(-) diff --git a/doc/How-it-works.md b/doc/How-it-works.md index bf0d67b2..683e678a 100644 --- a/doc/How-it-works.md +++ b/doc/How-it-works.md @@ -4,7 +4,7 @@ improvement by only keeping the pixels currently being processed in RAM and by having an efficient, threaded image IO system. This page explains how these features are implemented. -### Images +# Images VIPS images have three dimensions: width, height and bands. Bands usually (though not always) represent colour. These three dimensions can be any @@ -12,7 +12,7 @@ size up to 2 ** 31 elements. Every band element in an image has to have the same format. A format is an 8-, 16- or 32-bit int, signed or unsigned, 32- or 64-bit float, and 64- or 128-bit complex. -### Regions +# Regions An image can be very large, much larger than the available memory, so you can't just access pixels with a pointer \*. @@ -54,7 +54,7 @@ for a section of the file to be read in. (\* there is an image access mode where you can just use a pointer, but it's rarely used) -### Partial images +# Partial images A partial image is one where, instead of storing a value for each pixel, VIPS stores a function which can make any rectangular area of pixels on demand. @@ -81,7 +81,7 @@ generate / stop sequence works like a thread. (\* in fact VIPS keeps a cache of calculated pixel buffers and will return a pointer to a previously-calculated buffer if it can) -### Operations +# Operations VIPS operations read input images and write output images, performing some transformation on the pixels. When an operation writes to an image the @@ -98,7 +98,7 @@ set of mechanisms to copy image areas by just adjusting pointers. Most of the time no actual copying is necessary and you can perform operations on large images at low cost. -### Run-time code generation +# Run-time code generation VIPS uses [Orc](http://code.entropywave.com/orc/), a run-time compiler, to generate code for some operations. For example, to compute a convolution @@ -110,7 +110,7 @@ SSE3 on most x86 processors. Run-time vector code generation typically speeds operations up by a factor of three or four. -### Joining operations together +# Joining operations together The region create / prepare / prepare / free calls you use to get pixels from an image are an exact parallel to the start / generate / generate / @@ -143,7 +143,7 @@ each thread runs a very cheap copy of just the writeable state of the entire pipeline, threads can run with few locks. VIPS needs just four lock operations per output tile, regardless of the pipeline length or complexity. -### Data sources +# Data sources VIPS has data sources which can supply pixels for processing from a variety of sources. VIPS can stream images from files in VIPS native format, from @@ -171,7 +171,7 @@ are demanded by different threads VIPS will move these windows up and down the file. As a result, VIPS can process images much larger than RAM, even on 32-bit machines. -### Data sinks +# Data sinks In a demand-driven system, something has to do the demanding. VIPS has a variety of data sinks that you can use to pull image data though a pipeline @@ -210,7 +210,7 @@ ensure that there are at least two tiles for every thread) (\*\* tiles can be any shape and size, VIPS has a tile hint system that operations use to tell sinks what tile geometry they prefer) -### Operation cache +# Operation cache Because VIPS operations are free of side-effects\*, you can cache them. Every time you call an operation, VIPS searches the cache for a previous call to @@ -225,7 +225,7 @@ the cache size by memory use or by files opened. "invalidate" signal on the image they are called on and this signal makes all downstream operations and caches drop their contents.) -### Operation database and APIs +# Operation database and APIs VIPS has around 300 image processing operations written in this style. Each operation is a GObject class. You can use the standard GObject calls to walk @@ -243,7 +243,7 @@ gobject-introspection. It is written in Python, so as long as you can get gobject-introspection working, you should be able to use vips. It supports python2 and python3 and works on Linux, OS X and Windows. -### Snip +# Snip The VIPS GUI, nip2, has its own scripting language called Snip. Snip is a lazy, higher-order, purely functional, object oriented language. Almost all diff --git a/doc/Making-image-pyramids.md b/doc/Making-image-pyramids.md index 084ed2d5..399fb4a2 100644 --- a/doc/Making-image-pyramids.md +++ b/doc/Making-image-pyramids.md @@ -55,7 +55,7 @@ operation flags: sequential nocache You can also call `vips_dzsave()` from any language with a libvips binding, or by using `.dz` or `.szi` as an output file suffix. -### Writing [DeepZoom](http://en.wikipedia.org/wiki/Deep_Zoom) pyramids +# Writing [DeepZoom](http://en.wikipedia.org/wiki/Deep_Zoom) pyramids The `--layout` option sets the basic mode of operation. With no `--layout`, dzsave writes DeepZoom pyramids. For example: @@ -79,7 +79,7 @@ will write JPEG tiles with the quality factor set to 90. You can set any format write options you like, see the API docs for `vips_jpegsave()` for details. -### Writing Zoomify pyramids +# Writing Zoomify pyramids Use `--layout zoomify` to put dzsave into zoomify mode. For example: @@ -93,7 +93,7 @@ directories called `TileGroupn`, each containing 256 image tiles. As with DeepZoom, you can use `--suffix` to set jpeg quality. -### Writing [Google Maps](https://developers.google.com/maps/) pyramids +# Writing [Google Maps](https://developers.google.com/maps/) pyramids Use `--layout google` to write Google maps-style pyramids. These are compatible with the [NYU Pathology pyramid @@ -125,7 +125,7 @@ For example: $ vips dzsave wtc.tif gmapdir --layout google --background 0 --centre ``` -#### Other options +# Other options You can use `--tile-size` and `--overlap` to control how large the tiles are and how they overlap (obviously). They default to the correct values @@ -157,7 +157,7 @@ Use `--properties` to output an XML file called `vips-properties.xml`. This contains a dump of all the metadata vips has about the image as a set of name-value pairs. It's handy with openslide image sources. -#### Preprocessing images +# Preprocessing images You can use `.dz` as a filename suffix, meaning send the image to `vips_dzsave()`. This means you can write the output of any vips operation to a @@ -182,7 +182,7 @@ $ vips dzsave CMU-1.mrxs[level=1] x Will pull out level 1 (the half-resolution level of an MRXS slide) and make a pyramid from that. -#### Troubleshooting +# Troubleshooting If you are building vips from source you do need to check the summary at the end of configure carefully. You must have the `libgsf-1-dev` package diff --git a/doc/Making-image-pyramids.xml b/doc/Making-image-pyramids.xml index 13f4952c..a43754ce 100644 --- a/doc/Making-image-pyramids.xml +++ b/doc/Making-image-pyramids.xml @@ -131,57 +131,57 @@ $ vips dzsave wtc.tif gmapdir --layout google $ vips dzsave wtc.tif gmapdir --layout google --background 0 --centre - - Other options - - You can use --tile-size and --overlap to control how large the tiles are and how they overlap (obviously). They default to the correct values for the selected layout. - - - You can use --depth to control how deep the pyramid should be. Possible values are onepixel, onetile and one. onepixel means the image is shrunk until it fits within a single pixel. onetile means shrink until it fits with a tile. one means only write one pyramid layer (the highest resolution one). It defaults to the correct value for the selected layout. --depth one is handy for slicing up a large image into tiles (rather than a pyramid). - - - You can use --angle to do a 90, 180 or 270 degree rotate of an image during pyramid write. - - - You can use --container to set the container type. Normally dzsave will write a tree of directories, but with --container zip you’ll get a zip file instead. Use .zip as the directory suffix to turn on zip format automatically: - - + + + Other options + + You can use --tile-size and --overlap to control how large the tiles are and how they overlap (obviously). They default to the correct values for the selected layout. + + + You can use --depth to control how deep the pyramid should be. Possible values are onepixel, onetile and one. onepixel means the image is shrunk until it fits within a single pixel. onetile means shrink until it fits with a tile. one means only write one pyramid layer (the highest resolution one). It defaults to the correct value for the selected layout. --depth one is handy for slicing up a large image into tiles (rather than a pyramid). + + + You can use --angle to do a 90, 180 or 270 degree rotate of an image during pyramid write. + + + You can use --container to set the container type. Normally dzsave will write a tree of directories, but with --container zip you’ll get a zip file instead. Use .zip as the directory suffix to turn on zip format automatically: + + $ vips dzsave wtc.tif mypyr.zip - - to write a zipfile containing the tiles. You can use .szi as a suffix to enable zip output as well. - - - Use --properties to output an XML file called vips-properties.xml. This contains a dump of all the metadata vips has about the image as a set of name-value pairs. It’s handy with openslide image sources. - - - - Preprocessing images - - You can use .dz as a filename suffix, meaning send the image to vips_dzsave(). This means you can write the output of any vips operation to a pyramid. For example: - - + + to write a zipfile containing the tiles. You can use .szi as a suffix to enable zip output as well. + + + Use --properties to output an XML file called vips-properties.xml. This contains a dump of all the metadata vips has about the image as a set of name-value pairs. It’s handy with openslide image sources. + + + + Preprocessing images + + You can use .dz as a filename suffix, meaning send the image to vips_dzsave(). This means you can write the output of any vips operation to a pyramid. For example: + + $ vips extract_area huge.svs mypy.dz[layout=google] 100 100 10000 10000 - - The arguments to extract_area are image-in, image-out, left, top, width, height. So this command will cut out a 10,000 by 10,000 pixel area from near the top-left-hand corner of an Aperio slide image, then build a pyramid in Google layout using just those pixels. - - - If you are working from OpenSlide images, you can use the shrink-on-load feature of many of those formats. For example: - - + + The arguments to extract_area are image-in, image-out, left, top, width, height. So this command will cut out a 10,000 by 10,000 pixel area from near the top-left-hand corner of an Aperio slide image, then build a pyramid in Google layout using just those pixels. + + + If you are working from OpenSlide images, you can use the shrink-on-load feature of many of those formats. For example: + + $ vips dzsave CMU-1.mrxs[level=1] x - - Will pull out level 1 (the half-resolution level of an MRXS slide) and make a pyramid from that. - - - - Troubleshooting - - If you are building vips from source you do need to check the summary at the end of configure carefully. You must have the libgsf-1-dev package for vips_dzsave() to work. - - + + Will pull out level 1 (the half-resolution level of an MRXS slide) and make a pyramid from that. + + + + Troubleshooting + + If you are building vips from source you do need to check the summary at the end of configure carefully. You must have the libgsf-1-dev package for vips_dzsave() to work. + diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md index cdcb33ec..c4fd6ac4 100644 --- a/doc/Using-vipsthumbnail.md +++ b/doc/Using-vipsthumbnail.md @@ -6,7 +6,7 @@ The thumbnailing functionality is implemeted by `vips_thumbnail_buffer()`, see the docs for details. You can use these functions from any language with a libvips binding. -### libvips options +# libvips options `vipsthumbnail` supports the usual range of vips command-line options. A few of them are useful: @@ -23,7 +23,7 @@ useful to see where libvips is looping and how often. `--vips-info` shows a higher level view of the operations that `vipsthumbnail` is running.  -### Looping +# Looping vipsthumbnail can process many images in one operation. For example: @@ -43,7 +43,7 @@ much load you want to put on your system. For example: $ parallel vipsthumbnail ::: *.jpg ``` -### Thumbnail size +# Thumbnail size You can set the bounding box of the generated thumbnail with the `--size` option. For example: @@ -65,7 +65,7 @@ is. You can append `<` or `>` to mean only resize if the image is smaller or larger than the target. -### Cropping +# Cropping `vipsthumbnail` normally shrinks images to fit within the box set by `--size`. You can use the `--smartcrop` option to crop to fill the box instead. Excess @@ -88,7 +88,7 @@ down to 128 pixels across using the `attention` strategy. This one searches the image for features which might catch a human eye, see `vips_smartcrop()` for details. -### Linear light +# Linear light Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for @@ -108,7 +108,7 @@ done by the image libraries is done at encode time, and done in terms of CRT voltage, not light. This can make linear light thumbnailing of large images extremely slow. -### Output directory +# Output directory You set the thumbnail write parameters with the `-o` option. This is a pattern which the input filename is pasted into to @@ -147,7 +147,7 @@ $ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg Now both input files will have thumbnails written to a subdirectory of their current directory. -### Output format and options +# Output format and options You can use `-o` to specify the thumbnail image format too. For example:  @@ -224,7 +224,7 @@ $ ls -l x.jpg -rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg ``` -### Colour management +# Colour management `vipsthumbnail` will optionally put images through LittleCMS for you. You can use this to move all thumbnails to the same colour space. All web browsers @@ -253,7 +253,7 @@ It’ll look identical to a user, but be almost half the size. You can also specify a fallback input profile to use if the image has no embedded one, but this is less useful. -### Auto-rotate +# Auto-rotate Many JPEG files have a hint set in the header giving the image orientation. If you strip out the metadata, this hint will be lost, and the image will appear @@ -262,7 +262,7 @@ to be rotated. If you use the `--rotate` option, `vipsthumbnail` examines the image header and if there's an orientation tag, applies and removes it. -### Final suggestion +# Final suggestion Putting all this together, I suggest this as a sensible set of options: diff --git a/doc/Using-vipsthumbnail.xml b/doc/Using-vipsthumbnail.xml index 563112c0..525b125b 100644 --- a/doc/Using-vipsthumbnail.xml +++ b/doc/Using-vipsthumbnail.xml @@ -21,7 +21,7 @@ The thumbnailing functionality is implemeted by vips_thumbnail() and vips_thumbnail_buffer(), see the docs for details. You can use these functions from any language with a libvips binding. - + libvips options vipsthumbnail supports the usual range of vips command-line options. A few of them are useful: @@ -38,8 +38,8 @@ --vips-info shows a higher level view of the operations that vipsthumbnail is running.  - - + + Looping vipsthumbnail can process many images in one operation. For example: @@ -56,8 +56,8 @@ $ vipsthumbnail *.jpg $ parallel vipsthumbnail ::: *.jpg - - + + Thumbnail size You can set the bounding box of the generated thumbnail with the --size option. For example: @@ -77,8 +77,8 @@ $ vipsthumbnail shark.jpg --size 200x You can append < or > to mean only resize if the image is smaller or larger than the target. - - + + Cropping vipsthumbnail normally shrinks images to fit within the box set by --size. You can use the --smartcrop option to crop to fill the box instead. Excess pixels are trimmed away using the strategy you set. For example: @@ -111,8 +111,8 @@ $ vipsthumbnail owl.jpg --smartcrop attention -s 128 First it shrinks the image to get the vertical axis to 128 pixels, then crops down to 128 pixels across using the attention strategy. This one searches the image for features which might catch a human eye, see vips_smartcrop() for details. - - + + Linear light Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for historical reasons) the values stored in image files are usually related to the voltage that should be applied to a CRT electron gun. @@ -126,8 +126,8 @@ $ vipsthumbnail fred.jpg --linear The downside is that in linear mode, none of the very fast shrink-on-load tricks that vipsthumbnail normally uses are possible, since the shrinking done by the image libraries is done at encode time, and done in terms of CRT voltage, not light. This can make linear light thumbnailing of large images extremely slow. - - + + Output directory You set the thumbnail write parameters with the -o option. This is a pattern which the input filename is pasted into to produce the output filename. For example: @@ -156,8 +156,8 @@ $ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg Now both input files will have thumbnails written to a subdirectory of their current directory. - - + + Output format and options You can use -o to specify the thumbnail image format too. For example:  @@ -231,8 +231,8 @@ $ vipsthumbnail 42-32157534.jpg -o x.jpg[optimize_coding,strip] $ ls -l x.jpg -rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg - - + + Colour management vipsthumbnail will optionally put images through LittleCMS for you. You can use this to move all thumbnails to the same colour space. All web browsers assume that images without an ICC profile are in sRGB colourspace, so if you move your thumbnails to sRGB, you can strip all the embedded profiles. This can save several kb per thumbnail. @@ -259,8 +259,8 @@ $ ls -l tn_shark.jpg You can also specify a fallback input profile to use if the image has no embedded one, but this is less useful. - - + + Auto-rotate Many JPEG files have a hint set in the header giving the image orientation. If you strip out the metadata, this hint will be lost, and the image will appear to be rotated. @@ -268,8 +268,8 @@ $ ls -l tn_shark.jpg If you use the --rotate option, vipsthumbnail examines the image header and if there’s an orientation tag, applies and removes it. - - + + Final suggestion Putting all this together, I suggest this as a sensible set of options: @@ -281,7 +281,7 @@ $ vipsthumbnail fred.jpg \ --eprofile /usr/share/color/icc/sRGB.icc \ --rotate - + From a78ef520e2703b2b5b3403573228e0817a858e38 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Mar 2017 22:33:15 +0100 Subject: [PATCH 08/15] add how-it-opens-files page --- TODO | 4 +- doc/How-it-opens-files.md | 131 +++++++++++++++++++++++++++++++++++++ doc/How-it-opens-files.xml | 122 ++++++++++++++++++++++++++++++++++ doc/Makefile.am | 1 + doc/libvips-docs.xml.in | 1 + 5 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 doc/How-it-opens-files.md create mode 100644 doc/How-it-opens-files.xml diff --git a/TODO b/TODO index b9d8b8c5..48d6e63c 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,4 @@ -- add note about vips_thumbnail() and vips_thumbnail_buffer() - - move "how libvips opens files" into docs? +- move "how libvips opens files" into docs? - not sure about utf8 error messages on win diff --git a/doc/How-it-opens-files.md b/doc/How-it-opens-files.md new file mode 100644 index 00000000..4e960202 --- /dev/null +++ b/doc/How-it-opens-files.md @@ -0,0 +1,131 @@ +libvips now has at least four different ways of opening image files, each best +for different file types, file sizes and image use cases. libvips +tries hard to pick the best strategy in each case and mostly you don't need +to know what it is doing behind the scenes, except unfortunately when you do. + +This post tries to explain what the different strategies are and when each is +used. If you are running into unexpected memory, disc or CPU use, this might +be helpful. `vips_image_new_from_file()` has the official documentation. + +# Direct access + +This is the fastest and simplest one. The file is mapped directly into +the process's address space and can be read with ordinary pointer +access. Small files are completely mapped; large files are mapped in a +series of small windows that are shared and which scroll about as pixels +are read. Files which are accessed like this can be read by many threads +at once, making them especially quick. They also interact well with the +computer's operating system: your OS will use spare memory to cache +recently used chunks of the file, very handy. + +For this to be possible, the file format needs to be a simple dump of a memory +array. libvips supports direct access for vips, 8-bit binary ppm/pbm/pnm, +analyse and raw. + +libvips has a special direct write mode where pixels can be written directly +to the file image. This is used for the [draw operators](libvips-draw.html). + +# Random access via load library + +Some image file formats have libraries which allow true random access to +image pixels. For example, libtiff lets you read any tile out of a tiled +tiff image very quickly. Because the libraries allow true random access, +libvips can simply hook the image load library up to the input of the +operation pipeline. + +These libraries are generally single-threaded, so only one thread may +read at once, making them slower than simple direct access. +Additionally, tiles are often compressed, meaning that each time a tile +is fetched it must be decompressed. libvips keeps a cache of +recently-decompressed tiles to try to avoid repeatedly decompressing the +same tile. + +libvips can load tiled tiff, tiled OpenEXR, FITS and OpenSlide images in +this manner. + +# Full decompression + +Many image load libraries do not support random access. In order to use +images of this type as inputs to pipelines, libvips has to convert them +to a random access format first. + +For small images (less than 100mb when decompressed), libvips allocates +a large area of memory and decompresses the entire image to that. It +then uses that memory buffer of decompressed pixels to feed the +pipeline. For large images, libvips decompresses to a temporary file on +disc, then loads that temporary file in direct access mode (see above). +Note that on open libvips just reads the image header and is quick: the +image decompress happens on the first pixel access. + +You can control this process with environment variables, command-line +flags and API calls as you choose, see +`vips_image_new_from_file()`. +They let you set the threshold at which libvips switches between memory +and disc and where on disc the temporary files are held. + +This is the slowest and most memory-hungry way to read files, but it's +unavoidable for many file formats. Unless you can use the next one! + +# Sequential access + +This a fairly recent addition to libvips and is a hybrid of the previous +two. + +Imagine how this command might be executed: + +``` +$ vips flip fred.jpg jim.jpg vertical +``` + +meaning, read `fred.jpg`, flip it up-down, and write as `jim.jpg`. + +In order to write `jim.jpg` top-to-bottom, it'll have to read `fred.jpg` +bottom-to-top. Unfortunately libjpeg only supports top-to-bottom reading +and writing, so libvips must convert `fred.jpg` to a random access format +before it can run the flip operation. + +However many useful operations do not require true random access.  For +example: + +``` +$ vips shrink fred.png jim.png 10 10 +``` + +meaning shrink `fred.png` by a factor of 10 in both axies and write as +`jim.png`. + +You can imagine this operation running without needing `fred.png` to be +completely decompressed first. You just read 10 lines from `fred.png` for +every one line you write to `jim.png`. + +To help in this case, libvips has a hint you can give to loaders to say +"I will only need pixels from this image in top-to-bottom order". With +this hint set, libvips will hook up the pipeline of operations directly +to the read-a-line interface provided by the image library, and add a +small cache of the most recent 100 or so lines. + +There's an extra unbuffered sequential mode where vips does not keep a +cache of recent lines. This gives a useful memory saving for operations +like copy which do not need any non-local access.  + +# Debugging + +There are a few flags you can use to find out what libvips is doing. + +`--vips-leak` This makes libvips test for leaks on program exit. It checks +for images which haven't been closed and also (usefully) shows the memory +high-water mark. It counts all memory allocated in libvips for pixel buffers. + +`--vips-progress` This makes libvips show a crude progress bar for every major +image loop, with destination and elapsed time. You can see whether images +are going to disc or to memory and how long the decompression is taking. + +`--vips-cache-trace This shows a line for every vips operation that executes, +with arguments. It's part of vips8, so it doesn't display vips7 operations, +sadly. + +# Summary + +libvips tries hard to do the quickest thing in every case, but will +sometimes fail. You can prod it in the right direction with a mixture of +hints and flags to the load system. diff --git a/doc/How-it-opens-files.xml b/doc/How-it-opens-files.xml new file mode 100644 index 00000000..1941bd97 --- /dev/null +++ b/doc/How-it-opens-files.xml @@ -0,0 +1,122 @@ + + + + + + How-it-opens-files.md + 3 + libvips + + + + libvips + How-it-opens-files.md + + + + + libvips now has at least four different ways of opening image files, each best for different file types, file sizes and image use cases. libvips tries hard to pick the best strategy in each case and mostly you don’t need to know what it is doing behind the scenes, except unfortunately when you do. + + + This post tries to explain what the different strategies are and when each is used. If you are running into unexpected memory, disc or CPU use, this might be helpful. vips_image_new_from_file() has the official documentation. + + + Direct access + + This is the fastest and simplest one. The file is mapped directly into the process’s address space and can be read with ordinary pointer access. Small files are completely mapped; large files are mapped in a series of small windows that are shared and which scroll about as pixels are read. Files which are accessed like this can be read by many threads at once, making them especially quick. They also interact well with the computer’s operating system: your OS will use spare memory to cache recently used chunks of the file, very handy. + + + For this to be possible, the file format needs to be a simple dump of a memory array. libvips supports direct access for vips, 8-bit binary ppm/pbm/pnm, analyse and raw. + + + libvips has a special direct write mode where pixels can be written directly to the file image. This is used for the draw operators. + + + + Random access via load library + + Some image file formats have libraries which allow true random access to image pixels. For example, libtiff lets you read any tile out of a tiled tiff image very quickly. Because the libraries allow true random access, libvips can simply hook the image load library up to the input of the operation pipeline. + + + These libraries are generally single-threaded, so only one thread may read at once, making them slower than simple direct access. Additionally, tiles are often compressed, meaning that each time a tile is fetched it must be decompressed. libvips keeps a cache of recently-decompressed tiles to try to avoid repeatedly decompressing the same tile. + + + libvips can load tiled tiff, tiled OpenEXR, FITS and OpenSlide images in this manner. + + + + Full decompression + + Many image load libraries do not support random access. In order to use images of this type as inputs to pipelines, libvips has to convert them to a random access format first. + + + For small images (less than 100mb when decompressed), libvips allocates a large area of memory and decompresses the entire image to that. It then uses that memory buffer of decompressed pixels to feed the pipeline. For large images, libvips decompresses to a temporary file on disc, then loads that temporary file in direct access mode (see above). Note that on open libvips just reads the image header and is quick: the image decompress happens on the first pixel access. + + + You can control this process with environment variables, command-line flags and API calls as you choose, see vips_image_new_from_file(). They let you set the threshold at which libvips switches between memory and disc and where on disc the temporary files are held. + + + This is the slowest and most memory-hungry way to read files, but it’s unavoidable for many file formats. Unless you can use the next one! + + + + Sequential access + + This a fairly recent addition to libvips and is a hybrid of the previous two. + + + Imagine how this command might be executed: + + +$ vips flip fred.jpg jim.jpg vertical + + + meaning, read fred.jpg, flip it up-down, and write as jim.jpg. + + + In order to write jim.jpg top-to-bottom, it’ll have to read fred.jpg bottom-to-top. Unfortunately libjpeg only supports top-to-bottom reading and writing, so libvips must convert fred.jpg to a random access format before it can run the flip operation. + + + However many useful operations do not require true random access.  For example: + + +$ vips shrink fred.png jim.png 10 10 + + + meaning shrink fred.png by a factor of 10 in both axies and write as jim.png. + + + You can imagine this operation running without needing fred.png to be completely decompressed first. You just read 10 lines from fred.png for every one line you write to jim.png. + + + To help in this case, libvips has a hint you can give to loaders to say I will only need pixels from this image in top-to-bottom order. With this hint set, libvips will hook up the pipeline of operations directly to the read-a-line interface provided by the image library, and add a small cache of the most recent 100 or so lines. + + + There’s an extra unbuffered sequential mode where vips does not keep a cache of recent lines. This gives a useful memory saving for operations like copy which do not need any non-local access.  + + + + Debugging + + There are a few flags you can use to find out what libvips is doing. + + + --vips-leak This makes libvips test for leaks on program exit. It checks for images which haven’t been closed and also (usefully) shows the memory high-water mark. It counts all memory allocated in libvips for pixel buffers. + + + --vips-progress This makes libvips show a crude progress bar for every major image loop, with destination and elapsed time. You can see whether images are going to disc or to memory and how long the decompression is taking. + + + `–vips-cache-trace This shows a line for every vips operation that executes, with arguments. It’s part of vips8, so it doesn’t display vips7 operations, sadly. + + + + Summary + + libvips tries hard to do the quickest thing in every case, but will sometimes fail. You can prod it in the right direction with a mixture of hints and flags to the load system. + + + + + diff --git a/doc/Makefile.am b/doc/Makefile.am index 1b3ae99e..f36779a8 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -145,6 +145,7 @@ HTML_IMAGES = \ markdown_content_files = \ How-it-works.md \ Using-vipsthumbnail.md \ + How-it-opens-files.md \ Making-image-pyramids.md # converted to xml in this dir by pandoc diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml.in index 828859d9..b043a908 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml.in @@ -40,6 +40,7 @@ +
From ab7bd3000b4286f18cfe2d33f5c977ec28dad97d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 1 Apr 2017 17:05:05 +0100 Subject: [PATCH 09/15] polish docs --- TODO | 2 - doc/How-it-opens-files.md | 39 +++++++----- doc/How-it-opens-files.xml | 21 +++---- doc/How-it-works.md | 15 ++++- doc/How-it-works.xml | 17 ++--- doc/Making-image-pyramids.md | 11 ++++ doc/Making-image-pyramids.xml | 17 ++--- doc/Using-vipsthumbnail.md | 96 ++++++++++++++++++----------- doc/Using-vipsthumbnail.xml | 85 ++++++++++++++----------- doc/pandoc-docbook-template.docbook | 11 ---- 10 files changed, 178 insertions(+), 136 deletions(-) diff --git a/TODO b/TODO index 48d6e63c..36f83c6e 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -- move "how libvips opens files" into docs? - - not sure about utf8 error messages on win - strange: diff --git a/doc/How-it-opens-files.md b/doc/How-it-opens-files.md index 4e960202..3e4e4165 100644 --- a/doc/How-it-opens-files.md +++ b/doc/How-it-opens-files.md @@ -1,22 +1,33 @@ -libvips now has at least four different ways of opening image files, each best -for different file types, file sizes and image use cases. libvips -tries hard to pick the best strategy in each case and mostly you don't need -to know what it is doing behind the scenes, except unfortunately when you do. + + Opening files + 3 + libvips + -This post tries to explain what the different strategies are and when each is + + Opening + How libvips opens files + + +libvips now has at least four different ways of opening image files, each +best for different file types, file sizes and image use cases. libvips tries +hard to pick the best strategy in each case and mostly you don't need to +know what it is doing behind the scenes, except unfortunately when you do. + +This page tries to explain what the different strategies are and when each is used. If you are running into unexpected memory, disc or CPU use, this might -be helpful. `vips_image_new_from_file()` has the official documentation. +be helpful. `vips_image_new_from_file()` has the official documentation. # Direct access -This is the fastest and simplest one. The file is mapped directly into -the process's address space and can be read with ordinary pointer -access. Small files are completely mapped; large files are mapped in a -series of small windows that are shared and which scroll about as pixels -are read. Files which are accessed like this can be read by many threads -at once, making them especially quick. They also interact well with the -computer's operating system: your OS will use spare memory to cache -recently used chunks of the file, very handy. +This is the fastest and simplest one. The file is mapped directly into the +process's address space and can be read with ordinary pointer access. Small +files are completely mapped; large files are mapped in a series of small +windows that are shared and which scroll about as pixels are read. Files +which are accessed like this can be read by many threads at once, making +them especially quick. They also interact well with the computer's operating +system: your OS will use spare memory to cache recently used chunks of the +file. For this to be possible, the file format needs to be a simple dump of a memory array. libvips supports direct access for vips, 8-bit binary ppm/pbm/pnm, diff --git a/doc/How-it-opens-files.xml b/doc/How-it-opens-files.xml index 1941bd97..64e213d7 100644 --- a/doc/How-it-opens-files.xml +++ b/doc/How-it-opens-files.xml @@ -3,28 +3,23 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - - How-it-opens-files.md - 3 - libvips - - - - libvips - How-it-opens-files.md - - + + Opening files 3 libvips + + + Opening How libvips opens files + libvips now has at least four different ways of opening image files, each best for different file types, file sizes and image use cases. libvips tries hard to pick the best strategy in each case and mostly you don’t need to know what it is doing behind the scenes, except unfortunately when you do. - This post tries to explain what the different strategies are and when each is used. If you are running into unexpected memory, disc or CPU use, this might be helpful. vips_image_new_from_file() has the official documentation. + This page tries to explain what the different strategies are and when each is used. If you are running into unexpected memory, disc or CPU use, this might be helpful. vips_image_new_from_file() has the official documentation. Direct access - This is the fastest and simplest one. The file is mapped directly into the process’s address space and can be read with ordinary pointer access. Small files are completely mapped; large files are mapped in a series of small windows that are shared and which scroll about as pixels are read. Files which are accessed like this can be read by many threads at once, making them especially quick. They also interact well with the computer’s operating system: your OS will use spare memory to cache recently used chunks of the file, very handy. + This is the fastest and simplest one. The file is mapped directly into the process’s address space and can be read with ordinary pointer access. Small files are completely mapped; large files are mapped in a series of small windows that are shared and which scroll about as pixels are read. Files which are accessed like this can be read by many threads at once, making them especially quick. They also interact well with the computer’s operating system: your OS will use spare memory to cache recently used chunks of the file. For this to be possible, the file format needs to be a simple dump of a memory array. libvips supports direct access for vips, 8-bit binary ppm/pbm/pnm, analyse and raw. diff --git a/doc/How-it-works.md b/doc/How-it-works.md index 683e678a..fc74394a 100644 --- a/doc/How-it-works.md +++ b/doc/How-it-works.md @@ -1,3 +1,14 @@ + + How libvips works + 3 + libvips + + + + Internals + A high-level technical overview of libvips's evaluation system + + Compared to most image processing libraries, VIPS needs little RAM and runs quickly, especially on machines with more than one CPU. VIPS achieves this improvement by only keeping the pixels currently being processed in RAM @@ -152,8 +163,8 @@ from FITS images and from tiled OpenEXR images. VIPS will automatically unpack other formats to temporary disc files for you but this can obviously generate a lot of disc traffic. It also has a special sequential mode for streaming operations on non-random-access -formats. A post on the libvips blog [explains how libvips opens a -file](http://libvips.blogspot.co.uk/2012/06/how-libvips-opens-file.html). One +formats. Another section in these docs explains [how libvips opens a +file](How-it-opens-files.html). One of the sources uses the [ImageMagick](http://www.imagemagick.org) (or optionally [GraphicsMagick](http://www.graphicsmagick.org)) library, so VIPS can read any image format that these libraries can read. diff --git a/doc/How-it-works.xml b/doc/How-it-works.xml index 5b4064dc..8cdf0606 100644 --- a/doc/How-it-works.xml +++ b/doc/How-it-works.xml @@ -3,18 +3,13 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - - How-it-works.md - 3 - libvips - - - - libvips - How-it-works.md - - + + How libvips works 3 libvips + + + Internals A high-level technical overview of libvips’s evaluation system + Compared to most image processing libraries, VIPS needs little RAM and runs quickly, especially on machines with more than one CPU. VIPS achieves this improvement by only keeping the pixels currently being processed in RAM and by having an efficient, threaded image IO system. This page explains how these features are implemented. diff --git a/doc/Making-image-pyramids.md b/doc/Making-image-pyramids.md index 399fb4a2..4eab76ad 100644 --- a/doc/Making-image-pyramids.md +++ b/doc/Making-image-pyramids.md @@ -1,3 +1,14 @@ + + Image pyramids + 3 + libvips + + + + Pyramids + How to use libvips to make image pyramids + + libvips includes `vips_dzsave()`, an operation that can build image pyramids compatible with [DeepZoom](http://en.wikipedia.org/wiki/Deep_Zoom), Zoomify and [Google Maps](https://developers.google.com/maps/) image viewers. It's diff --git a/doc/Making-image-pyramids.xml b/doc/Making-image-pyramids.xml index a43754ce..6be7b4f7 100644 --- a/doc/Making-image-pyramids.xml +++ b/doc/Making-image-pyramids.xml @@ -3,18 +3,13 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - - Making-image-pyramids.md - 3 - libvips - - - - libvips - Making-image-pyramids.md - - + + Image pyramids 3 libvips + + + Pyramids How to use libvips to make image pyramids + libvips includes vips_dzsave(), an operation that can build image pyramids compatible with DeepZoom, Zoomify and Google Maps image viewers. It’s fast and can generate pyramids for large images using only a small amount of memory. diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md index c4fd6ac4..09428ea6 100644 --- a/doc/Using-vipsthumbnail.md +++ b/doc/Using-vipsthumbnail.md @@ -1,10 +1,27 @@ -libvips ships with a handy command-line image thumbnailer, `vipsthumbnail`. -This page introduces it with examples. + + Using `vipsthumbnail` + 3 + libvips + -The thumbnailing functionality is implemeted by -`vips_thumbnail()` and -`vips_thumbnail_buffer()`, see the docs for details. You can use these -functions from any language with a libvips binding. + + `vipsthumbnail` + Introduction to `vipsthumbnail`, with examples + + +libvips ships with a handy command-line image thumbnailer, `vipsthumbnail`. +This page introduces it, with some examples. + +The thumbnailing functionality is implemented by `vips_thumbnail()` and +`vips_thumbnail_buffer()` (which thumbnails an image held as a string), +see the docs for details. You can use these functions from any language +with a libvips binding. For example, from PHP you could write: + +```php +$filename = ...; +$image = Vips\Image::thumbnail($filename, 200, ["height" => 200]); +$image.writeToFile("my-thumbnail.jpg"); +``` # libvips options @@ -25,15 +42,15 @@ is running.  # Looping -vipsthumbnail can process many images in one operation. For example: +`vipsthumbnail` can process many images in one command. For example: ``` $ vipsthumbnail *.jpg ``` will make a thumbnail for every jpeg in the current directory.  See the -**Output directory** section below to see how to change where thumbnails -are written. +[Output directory](#output-directory) section below to see how to change +where thumbnails are written. `vipsthumbnail` will process images one after the other. You can get a good speedup by running several `vipsthumbnail`s in parallel, depending on how @@ -93,7 +110,7 @@ for details. Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for historical reasons) the values stored in image files are usually related -to the voltage that should be applied to a CRT electron gun. +to the voltage that should be applied to the electron gun in a CRT display. `vipsthumbnail` has an option to perform image shrinking in linear space, that is, a colourspace where values are proportional to photon numbers. For example: @@ -103,10 +120,22 @@ $ vipsthumbnail fred.jpg --linear ``` The downside is that in linear mode, none of the very fast shrink-on-load -tricks that `vipsthumbnail` normally uses are possible, since the shrinking -done by the image libraries is done at encode time, and done in -terms of CRT voltage, not light. This can make linear light thumbnailing of -large images extremely slow. +tricks that `vipsthumbnail` normally uses are possible, since the shrinking is +done at encode time, not decode time, and is done in terms of CRT voltage, not +photons. This can make linear light thumbnailing of large images extremely slow. + +For example, for a 10,000 x 10,000 pixel JPEG I see: + +``` +$ time vipsthumbnail wtc.jpg +real 0m0.317s +user 0m0.292s +sys 0m0.016s +$ time vipsthumbnail wtc.jpg --linear +real 0m4.660s +user 0m4.640s +sys 0m0.016s +``` # Output directory @@ -118,27 +147,24 @@ produce the output filename. For example: $ vipsthumbnail fred.jpg jim.tif -o tn_%s.jpg ``` -For each of the files to be thumbnailed, `vipsthumbnail` -will drop the extension (`.jpg` and `.tif` -in this case) and then substitute the name into the `-o` -option, replacing the `%s` -So this example will write thumbnails to `tn_fred.jpg` and `tn_jim.jpg`. +For each of the files to be thumbnailed, `vipsthumbnail` will drop the +extension (`.jpg` and `.tif` in this case) and then substitute the name into +the `-o` option, replacing the `%s` So this example will write thumbnails to +`tn_fred.jpg` and `tn_jim.jpg`. -If the pattern given to `-o` -is an absolute path, any path components are dropped from the input -filenames. This lets you write all of your thumbnails to a specific -directory, if you want. For example: +If the pattern given to `-o` is an absolute path, any path components are +dropped from the input filenames. This lets you write all of your thumbnails +to a specific directory, if you want. For example: ``` $ vipsthumbnail fred.jpg ../jim.tif -o /mythumbs/tn_%s.jpg ``` -Now both thumbnails will be written to `/mythumbs`, -even though the source images are in different directories. +Now both thumbnails will be written to `/mythumbs`, even though the source +images are in different directories. -Conversely, if `-o` -is set to a relative path, any path component from the input file is -prepended. For example: +Conversely, if `-o` is set to a relative path, any path component from the +input file is prepended. For example: ``` $ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg @@ -149,8 +175,7 @@ their current directory. # Output format and options -You can use `-o` -to specify the thumbnail image format too. For example:  +You can use `-o` to specify the thumbnail image format too. For example:  ``` $ vipsthumbnail fred.jpg ../jim.tif -o tn_%s.png @@ -158,18 +183,17 @@ $ vipsthumbnail fred.jpg ../jim.tif -o tn_%s.png Will write thumbnails in PNG format. -You can give options to the image write operation as a list of -comma-separated arguments in square brackets. For example: +You can give options to the image write operation as a list of comma-separated +arguments in square brackets. For example: ``` $ vipsthumbnail fred.jpg ../jim.tif -o > tn_%s.jpg[Q=90,optimize_coding] ``` -will write jpeg images with quality 90, and will turn on the libjpeg -coding optimizer. +will write jpeg images with quality 90, and will turn on the libjpeg coding +optimizer. -Check the image write operations to see all the possible options. For -example: +Check the image write operations to see all the possible options. For example: ``` $ vips jpegsave diff --git a/doc/Using-vipsthumbnail.xml b/doc/Using-vipsthumbnail.xml index 525b125b..71d62f62 100644 --- a/doc/Using-vipsthumbnail.xml +++ b/doc/Using-vipsthumbnail.xml @@ -3,25 +3,25 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - - Using-vipsthumbnail.md - 3 - libvips - - - - libvips - Using-vipsthumbnail.md - - - libvips ships with a handy command-line image thumbnailer, vipsthumbnail. This page introduces it with examples. + Using vipsthumbnail 3 libvips - The thumbnailing functionality is implemeted by vips_thumbnail() and vips_thumbnail_buffer(), see the docs for details. You can use these functions from any language with a libvips binding. + vipsthumbnail Introduction to vipsthumbnail, with examples - + + libvips ships with a handy command-line image thumbnailer, vipsthumbnail. This page introduces it, with some examples. + + + The thumbnailing functionality is implemented by vips_thumbnail() and vips_thumbnail_buffer() (which thumbnails an image held as a string), see the docs for details. You can use these functions from any language with a libvips binding. For example, from PHP you could write: + + +$filename = ...; +$image = Vips\Image::thumbnail($filename, 200, ["height" => 200]); +$image.writeToFile("my-thumbnail.jpg"); + + libvips options vipsthumbnail supports the usual range of vips command-line options. A few of them are useful: @@ -38,17 +38,17 @@ --vips-info shows a higher level view of the operations that vipsthumbnail is running.  - - + + Looping - vipsthumbnail can process many images in one operation. For example: + vipsthumbnail can process many images in one command. For example: $ vipsthumbnail *.jpg - will make a thumbnail for every jpeg in the current directory.  See the Output directory section below to see how to change where thumbnails are written. + will make a thumbnail for every jpeg in the current directory.  See the Output directory section below to see how to change where thumbnails are written. vipsthumbnail will process images one after the other. You can get a good speedup by running several vipsthumbnails in parallel, depending on how much load you want to put on your system. For example: @@ -56,8 +56,8 @@ $ vipsthumbnail *.jpg $ parallel vipsthumbnail ::: *.jpg - - + + Thumbnail size You can set the bounding box of the generated thumbnail with the --size option. For example: @@ -77,8 +77,8 @@ $ vipsthumbnail shark.jpg --size 200x You can append < or > to mean only resize if the image is smaller or larger than the target. - - + + Cropping vipsthumbnail normally shrinks images to fit within the box set by --size. You can use the --smartcrop option to crop to fill the box instead. Excess pixels are trimmed away using the strategy you set. For example: @@ -111,8 +111,8 @@ $ vipsthumbnail owl.jpg --smartcrop attention -s 128 First it shrinks the image to get the vertical axis to 128 pixels, then crops down to 128 pixels across using the attention strategy. This one searches the image for features which might catch a human eye, see vips_smartcrop() for details. - - + + Linear light Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for historical reasons) the values stored in image files are usually related to the voltage that should be applied to a CRT electron gun. @@ -124,10 +124,23 @@ $ vipsthumbnail owl.jpg --smartcrop attention -s 128 $ vipsthumbnail fred.jpg --linear - The downside is that in linear mode, none of the very fast shrink-on-load tricks that vipsthumbnail normally uses are possible, since the shrinking done by the image libraries is done at encode time, and done in terms of CRT voltage, not light. This can make linear light thumbnailing of large images extremely slow. + The downside is that in linear mode, none of the very fast shrink-on-load tricks that vipsthumbnail normally uses are possible, since the shrinking is done at encode time, not decode time, and is done in terms of CRT voltage, not photons. This can make linear light thumbnailing of large images extremely slow. - - + + For example, for a 10,000 x 10,000 pixel JPEG I see: + + +$ time vipsthumbnail wtc.jpg +real 0m0.317s +user 0m0.292s +sys 0m0.016s +$ time vipsthumbnail wtc.jpg --linear +real 0m4.660s +user 0m4.640s +sys 0m0.016s + + + Output directory You set the thumbnail write parameters with the -o option. This is a pattern which the input filename is pasted into to produce the output filename. For example: @@ -156,8 +169,8 @@ $ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg Now both input files will have thumbnails written to a subdirectory of their current directory. - - + + Output format and options You can use -o to specify the thumbnail image format too. For example:  @@ -231,8 +244,8 @@ $ vipsthumbnail 42-32157534.jpg -o x.jpg[optimize_coding,strip] $ ls -l x.jpg -rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg - - + + Colour management vipsthumbnail will optionally put images through LittleCMS for you. You can use this to move all thumbnails to the same colour space. All web browsers assume that images without an ICC profile are in sRGB colourspace, so if you move your thumbnails to sRGB, you can strip all the embedded profiles. This can save several kb per thumbnail. @@ -259,8 +272,8 @@ $ ls -l tn_shark.jpg You can also specify a fallback input profile to use if the image has no embedded one, but this is less useful. - - + + Auto-rotate Many JPEG files have a hint set in the header giving the image orientation. If you strip out the metadata, this hint will be lost, and the image will appear to be rotated. @@ -268,8 +281,8 @@ $ ls -l tn_shark.jpg If you use the --rotate option, vipsthumbnail examines the image header and if there’s an orientation tag, applies and removes it. - - + + Final suggestion Putting all this together, I suggest this as a sensible set of options: @@ -281,7 +294,7 @@ $ vipsthumbnail fred.jpg \ --eprofile /usr/share/color/icc/sRGB.icc \ --rotate - + diff --git a/doc/pandoc-docbook-template.docbook b/doc/pandoc-docbook-template.docbook index bef16217..ef420a8d 100644 --- a/doc/pandoc-docbook-template.docbook +++ b/doc/pandoc-docbook-template.docbook @@ -8,17 +8,6 @@ $else$ $endif$ - - $title$ - 3 - libvips - - - - libvips - $title$ - - $for(include-before)$ $include-before$ $endfor$ From f7b01ed93004c65680aa7e94da5c92565c93729d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 4 Apr 2017 10:13:10 +0100 Subject: [PATCH 10/15] started examples page --- TODO | 2 + doc/Examples.md | 297 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 doc/Examples.md diff --git a/TODO b/TODO index 36f83c6e..7aa1f7ac 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +- add analytics tags to docs output + - not sure about utf8 error messages on win - strange: diff --git a/doc/Examples.md b/doc/Examples.md new file mode 100644 index 00000000..9d4d0d71 --- /dev/null +++ b/doc/Examples.md @@ -0,0 +1,297 @@ + + Using `vipsthumbnail` + 3 + libvips + + + + `vipsthumbnail` + Introduction to `vipsthumbnail`, with examples + + +This page shows a few examples of using VIPS from Python. + +# Average a region of interest box on an image + +``` python +#!/usr/bin/env python + +import sys +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips + +roix = 10 +roiy = 10 +roiw = 64 +roih = 64 + +image = Vips.Image.new_from_file(sys.argv[1]) +roi = image.crop(roix, roiy, roiw, roih) +print 'average: ', roi.avg() +``` + +# VIPS and PIL + +This script moves an image between PIL and VIPS. + +``` python +#!/usr/bin/python + +import sys + +from vipsCC import * +import Image + +# try this 1,000 times and check for leaks +for i in range (0,1000): + vim = VImage.VImage (sys.argv[1]) + + # do some processing in vips ... cut out a small piece of image + vim = vim.extract_area (500, 500, 100, 100) + + # make a PIL image + # we use Image.frombuffer (), so PIL is using vim's memory + # you need to be very careful not to destroy vim until you're done with pim + # ideally you should make a proxy class that wraps this lifetime problem up + mode = VImage.PIL_mode_from_vips (vim) + size = (vim.Xsize (), vim.Ysize ()) + data = vim.tobuffer () + pim = Image.frombuffer (mode, size, data, 'raw', mode, 0, 1) + + # rotate 12 degrees with PIL + pim = pim.rotate (12, Image.BILINEAR, 1) + + # back to vips again + # PIL doesn't have a tobuffer method, so we have to use tostring to copy the + # data out of PIL and then fromstring to copy back into VIPS + str = pim.tostring () + bands, format, type = VImage.vips_from_PIL_mode (pim.mode) + width, height = pim.size + vim2 = VImage.VImage.fromstring (str, width, height, bands, format) + + # finally write from vips + vim2.write (sys.argv[2]) +``` + +Leak testing +------------ + +This loads an image, does some simple processing, and saves again. Handy for leak testing. + +``` python +#!/usr/bin/python + +import sys + +# just need this for leaktesting +import gc + +from vipsCC import * + +if len (sys.argv) != 3: + print 'usage:', sys.argv[0], 'inputimage outputimage' + sys.exit (1) + +try: + a = VImage.VImage (sys.argv[1]) + b = a.invert () + c = b.lin ([1,2,3],[4,5,6]) + m = VMask.VIMask (3, 3, 1, 0, + [-1, -1, -1, + -1, 8, -1, + -1, -1, -1]) + d = a.conv (m) + d.write (sys.argv[2]) +except VError.VError, e: + e.perror (sys.argv[0]) + +# we can get properties of VImage too +print 'inputimage is', a.Xsize (), 'pixels across' + +print 'starting shutdown ...' +del b +del a +del c +del d +del m +# sometimes have to do several GCs to get them all, not sure why +for i in range(10): + gc.collect () +print 'shutdown!' + +print 'leaked IMAGEs:' +VImage.im__print_all () +print 'done ... hopefully you saw no leaks' +``` + +Build image mosaic +------------------ + +This loads a lot of images (RGB or greyscale) and pastes them at random positions in a 10,000 by 10,000 pixel output image. 8-bit only, but it'd be easy to change that. + +``` python +#!/usr/bin/python + +import sys +import random +from vipsCC import * + +# the size of the image we build +size = 10000 + +try: + if len(sys.argv) < 3: + print 'usage:', sys.argv[0], 'outfile infile1 ...' + sys.exit (1) + + # make the background image + bg = VImage.VImage.black (size, size, 3) + + # paste each argument in + for file in sys.argv[2:]: + im = VImage.VImage (file) + + # is this a mono image? convert to RGB by joining three of them + # together + if im.Bands() == 1: + im = im.bandjoin (im).bandjoin (im) + + x = random.randint (0, size - im.Xsize () - 1) + y = random.randint (0, size - im.Ysize () - 1) + bg = bg.insert_noexpand (im, x, y) + + # write result + bg.write (sys.argv[1]) + +except VError.VError, e: + e.perror (sys.argv[0]) +``` + +Build image pyramid +------------------- + +This makes a tiled image pyramid, with each tile in a separate 512x512 pixel file. + +``` python +#!/usr/bin/python + +import sys +from vipsCC import * + +tilesize = 512 +maxlevel = 100 + +try: + im = VImage.VImage (sys.argv[1]) + + for level in range (maxlevel, -1, -1): + print "Creating tiles for level", level + + # loop to create the tiles + for y in range (0, im.Ysize(), tilesize): + for x in range (0, im.Xsize(), tilesize): + filename = '%dx%d_y%d.jpg' % (level, x / tilesize, y / tilesize) + # clip tilesize against image size + width = min (im.Xsize () - x, tilesize) + height = min (im.Ysize () - y, tilesize) + + # expand edge tiles up to the full tilesize ... Google maps likes this + # im.extract_area (x, y, width, height).embed(0, 0, 0, tilesize, tilesize).write(filename) + + # let edge tiles be smaller than the full tile size, tiff tiling prefers this + im.extract_area (x, y, width, height).write (filename) + + # was there only a single tile? we are done + if im.Xsize() <= tilesize and im.Ysize() <= tilesize: + break + + # create next pyramid level in RAM + shrink = im.rightshift_size (1, 1, im.BandFmt()) + im = shrink.write (VImage.VImage ("temp", "t")) + +except VError.VError, e: + e.perror (sys.argv[0]) +``` + +Rename DICOM images using header fields +--------------------------------------- + +DICOM images commonly come in an awful directory hierarchy named as something like images/a/b/e/z04. There can be thousands of files and it can be very hard to find the one you want. + +This utility copies files to a single flat directory, naming them using fields from the DICOM header. You can actually find stuff! Useful. + +``` python +#!/usr/bin/python + +import sys +import re +import os +import shutil + +from vipsCC import * + +if len (sys.argv) != 3: + print 'rename DICOM files using tags from the header' + print 'usage:' + print '\t%s srcdir destdir' % sys.argv[0] + print 'the directory tree below srcdir is searched, all files are' + print 'renamed and put into destdir in a flat list' + sys.exit (1) + +srcdir = sys.argv[1] +destdir = sys.argv[2] + +if not os.access (destdir, os.F_OK | os.R_OK | os.W_OK | os.X_OK): + os.mkdir (destdir) + +def get_field (vim, field): + result = vim.meta_get_string (field) + + # remove any \n etc. + result = re.sub ("\n", "", result) + + # remove any leading or trailing spaces + result = re.sub (" $", "", result) + result = re.sub ("^ ", "", result) + + return result + +id_name = "magick-dcm:Patient'sID" +modality_name = "magick-dcm:Modality" +series_name = "magick-dcm:SeriesNumber" +instance_name = "magick-dcm:Instance(formerlyImage)Number" +date_name = "magick-dcm:ImageDate" + +n_processed = 0 + +for (dirpath, dirnames, filenames) in os.walk (srcdir): + for file in filenames: + path = os.path.join (dirpath, file) + + try: + vim = VImage.VImage (path) + except VError.VError, e: + print 'unable to open', path + continue + + try: + id = get_field (vim, id_name) + modality = get_field (vim, modality_name) + series = get_field (vim, series_name) + instance = get_field (vim, instance_name) + date = get_field (vim, date_name) + except VError.VError, e: + print 'unable to get fields from header', path + continue + + match = re.match ("(\d\d\d\d)(\d\d)(\d\d)", date) + date = match.group (1) + "." + match.group (2) + "." + match.group (3) + newname = id + "." + modality + "." + series + "." + instance + "." + date + ".IMA" + + shutil.copyfile(path, os.path.join (destdir, newname)) + + n_processed += 1 + +print '\t(%d files processed)' % n_processed +``` From d5858efd741ea68b77481054ba265f2391c14044 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 4 Apr 2017 17:14:17 +0100 Subject: [PATCH 11/15] mostly done examples --- doc/Examples.md | 356 ++++++++++++++++++++++------------------ doc/Makefile.am | 1 + doc/libvips-docs.xml.in | 1 + 3 files changed, 198 insertions(+), 160 deletions(-) diff --git a/doc/Examples.md b/doc/Examples.md index 9d4d0d71..b5134702 100644 --- a/doc/Examples.md +++ b/doc/Examples.md @@ -9,7 +9,11 @@ Introduction to `vipsthumbnail`, with examples -This page shows a few examples of using VIPS from Python. +This page shows a few libvips examples using Python. They will work with small syntax +changes in any language with a libvips binding. + +The libvips test suite is written in Python and exercises every operation in the API. +It's also a useful source of examples. # Average a region of interest box on an image @@ -21,205 +25,237 @@ import gi gi.require_version('Vips', '8.0') from gi.repository import Vips -roix = 10 -roiy = 10 -roiw = 64 -roih = 64 +left = 10 +top = 10 +width = 64 +height = 64 image = Vips.Image.new_from_file(sys.argv[1]) -roi = image.crop(roix, roiy, roiw, roih) -print 'average: ', roi.avg() +roi = image.crop(left, top, width, height) +print 'average:', roi.avg() ``` -# VIPS and PIL +# libvips and numpy -This script moves an image between PIL and VIPS. +You can use `Vips.Image.new_from_memory_copy()` to make a vips image from an area of +memory. The memory array needs to be laid out band-interleaved, as a set of scanlines, +with no padding between lines. -``` python +This example moves an image from numpy to vips, but it's simple to move the other way +(use `Vips.Image.write_to_memory()`) to to move images into or out of PIL. + +```python #!/usr/bin/python +import numpy +import scipy.ndimage +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips + +def np_dtype_to_vips_format(np_dtype): + ''' + Map numpy data types to VIPS data formats. + + Parameters + ---------- + np_dtype: numpy.dtype + + Returns + ------- + gi.overrides.Vips.BandFormat + ''' + lookup = { + numpy.dtype('int8'): Vips.BandFormat.CHAR, + numpy.dtype('uint8'): Vips.BandFormat.UCHAR, + numpy.dtype('int16'): Vips.BandFormat.SHORT, + numpy.dtype('uint16'): Vips.BandFormat.USHORT, + numpy.dtype('int32'): Vips.BandFormat.INT, + numpy.dtype('float32'): Vips.BandFormat.FLOAT, + numpy.dtype('float64'): Vips.BandFormat.DOUBLE + } + return lookup[np_dtype] + +def np_array_to_vips_image(array): + ''' + Convert a `numpy` array to a `Vips` image object. + + Parameters + ---------- + nparray: numpy.ndarray + + Returns + ------- + gi.overrides.Vips.image + ''' + # Look up what VIPS format corresponds to the type of this np array + vips_format = np_dtype_to_vips_format(array.dtype) + dims = array.shape + height = dims[0] + width = 1 + bands = 1 + if len(dims) > 1: + width = dims[1] + if len(dims) > 2: + bands = dims[2] + img = Vips.Image.new_from_memory_copy(array.data, + width, height, bands, vips_format) + + return img + +array = numpy.random.random((10,10)) +vips_image = np_array_to_vips_image(array) +print 'avg =', vips_image.avg() + +array = scipy.ndimage.imread("test.jpg") +vips_image = np_array_to_vips_image(array) +print 'avg =', vips_image.avg() +vips_image.write_to_file("test2.jpg") +``` + +# Watermarking + +This example renders a simple watermark on an image. Use it like this: + + +``` +./watermark.py somefile.png output.jpg "hello world" +``` + +The text is rendered in transparent red pixels all over the image. It knows about +transparency, CMYK, and 16-bit images. + +```python +#!/usr/bin/python + import sys +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips + +im = Vips.Image.new_from_file(sys.argv[1], access = Vips.Access.SEQUENTIAL) + +text = Vips.Image.text(sys.argv[3], width = 500, dpi = 300) +text = (text * 0.3).cast("uchar") +text = text.embed(100, 100, text.width + 200, text.width + 200) +text = text.replicate(1 + im.width / text.width, 1 + im.height / text.height) +text = text.crop(0, 0, im.width, im.height) -from vipsCC import * -import Image +# we want to blend into the visible part of the image and leave any alpha +# channels untouched ... we need to split im into two parts -# try this 1,000 times and check for leaks -for i in range (0,1000): - vim = VImage.VImage (sys.argv[1]) +# 16-bit images have 65535 as white +if im.format == Vips.BandFormat.USHORT: + white = 65535 +else: + white = 255 - # do some processing in vips ... cut out a small piece of image - vim = vim.extract_area (500, 500, 100, 100) +# guess how many bands from the start of im contain visible colour information +if im.bands >= 4 and im.interpretation == Vips.Interpretation.CMYK: + # cmyk image ... put the white into the magenta channel + n_visible_bands = 4 + text_colour = [0, white, 0, 0] +elif im.bands >= 3: + # colour image ... put the white into the red channel + n_visible_bands = 3 + text_colour = [white, 0, 0] +else: + # mono image + n_visible_bands = 1 + text_colour = white - # make a PIL image - # we use Image.frombuffer (), so PIL is using vim's memory - # you need to be very careful not to destroy vim until you're done with pim - # ideally you should make a proxy class that wraps this lifetime problem up - mode = VImage.PIL_mode_from_vips (vim) - size = (vim.Xsize (), vim.Ysize ()) - data = vim.tobuffer () - pim = Image.frombuffer (mode, size, data, 'raw', mode, 0, 1) +# split into image and alpha +if im.bands - n_visible_bands > 0: + alpha = im.extract_band(n_visible_bands, n = im.bands - n_visible_bands) + im = im.extract_band(0, n = n_visible_bands) +else: + alpha = None - # rotate 12 degrees with PIL - pim = pim.rotate (12, Image.BILINEAR, 1) +# blend means do a smooth fade using the 0 - 255 values in the condition channel +# (test in this case) ... this will render the anit-aliasing +im = text.ifthenelse(text_colour, im, blend = True) - # back to vips again - # PIL doesn't have a tobuffer method, so we have to use tostring to copy the - # data out of PIL and then fromstring to copy back into VIPS - str = pim.tostring () - bands, format, type = VImage.vips_from_PIL_mode (pim.mode) - width, height = pim.size - vim2 = VImage.VImage.fromstring (str, width, height, bands, format) +# reattach alpha +if alpha: + im = im.bandjoin(alpha) + +im.write_to_file(sys.argv[2]) - # finally write from vips - vim2.write (sys.argv[2]) ``` -Leak testing ------------- +# Build huge image mosaic -This loads an image, does some simple processing, and saves again. Handy for leak testing. +This makes a 100,000 x 100,000 black image, then inserts all the images you pass on the +command-line into it at random positions. libvips is able to run this program in +sequential mode: it'll open all the input images at the same time, and stream pixels from +them as it needs them to generate the output. -``` python -#!/usr/bin/python +To test it, first make a large 1-bit image. This command will take the green channel and +write as a 1-bit fax image. `wtc.jpg` is a test 10,000 x 10,000 jpeg: -import sys - -# just need this for leaktesting -import gc - -from vipsCC import * - -if len (sys.argv) != 3: - print 'usage:', sys.argv[0], 'inputimage outputimage' - sys.exit (1) - -try: - a = VImage.VImage (sys.argv[1]) - b = a.invert () - c = b.lin ([1,2,3],[4,5,6]) - m = VMask.VIMask (3, 3, 1, 0, - [-1, -1, -1, - -1, 8, -1, - -1, -1, -1]) - d = a.conv (m) - d.write (sys.argv[2]) -except VError.VError, e: - e.perror (sys.argv[0]) - -# we can get properties of VImage too -print 'inputimage is', a.Xsize (), 'pixels across' - -print 'starting shutdown ...' -del b -del a -del c -del d -del m -# sometimes have to do several GCs to get them all, not sure why -for i in range(10): - gc.collect () -print 'shutdown!' - -print 'leaked IMAGEs:' -VImage.im__print_all () -print 'done ... hopefully you saw no leaks' +``` +$ vips extract_band wtc.jpg x.tif[squash,compression=ccittfax4,strip] 1 ``` -Build image mosaic ------------------- +Now make 1,000 copies of that image in a subdirectory: -This loads a lot of images (RGB or greyscale) and pastes them at random positions in a 10,000 by 10,000 pixel output image. 8-bit only, but it'd be easy to change that. +``` +$ mkdir test +$ for i in {1..1000}; do cp x.tif test/$i.tif; done +``` + +And run this Python program on them: + +``` +$ time ./try255.py x.tif[squash,compression=ccittfax4,strip,bigtif] test/* +real 1m59.924s +user 4m5.388s +sys 0m8.936s +``` + +It completes in just under two minutes on this laptop, and needs about +7gb of RAM to run. It would need about the same amount of memory for a +full-colour RGB image, I was just keen to keep disc usage down. + +If you wanted to handle transparency, or if you wanted mixed CMYK and RGB images, you'd +need to do some more work to convert them all into the same colourspace before +inserting them. ``` python -#!/usr/bin/python +#!/usr/bin/env python import sys import random -from vipsCC import * -# the size of the image we build -size = 10000 +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips -try: - if len(sys.argv) < 3: - print 'usage:', sys.argv[0], 'outfile infile1 ...' - sys.exit (1) +# turn on progress reporting +Vips.progress_set(True) - # make the background image - bg = VImage.VImage.black (size, size, 3) +# this makes a 8-bit, mono image of 100,000 x 100,000 pixels, each pixel zero +im = Vips.Image.black(100000, 100000) - # paste each argument in - for file in sys.argv[2:]: - im = VImage.VImage (file) +for filename in sys.argv[2:]: + tile = Vips.Image.new_from_file(filename, access = Vips.Access.SEQUENTIAL) - # is this a mono image? convert to RGB by joining three of them - # together - if im.Bands() == 1: - im = im.bandjoin (im).bandjoin (im) + im = im.insert(tile, + random.randint(0, im.width - tile.width), + random.randint(0, im.height - tile.height)) - x = random.randint (0, size - im.Xsize () - 1) - y = random.randint (0, size - im.Ysize () - 1) - bg = bg.insert_noexpand (im, x, y) +im.write_to_file(sys.argv[1]) - # write result - bg.write (sys.argv[1]) - -except VError.VError, e: - e.perror (sys.argv[0]) ``` -Build image pyramid -------------------- +# Rename DICOM images using header fields -This makes a tiled image pyramid, with each tile in a separate 512x512 pixel file. +DICOM images commonly come in an awful directory hierarchy named as something +like `images/a/b/e/z04`. There can be thousands of files and it can be very +hard to find the one you want. -``` python -#!/usr/bin/python - -import sys -from vipsCC import * - -tilesize = 512 -maxlevel = 100 - -try: - im = VImage.VImage (sys.argv[1]) - - for level in range (maxlevel, -1, -1): - print "Creating tiles for level", level - - # loop to create the tiles - for y in range (0, im.Ysize(), tilesize): - for x in range (0, im.Xsize(), tilesize): - filename = '%dx%d_y%d.jpg' % (level, x / tilesize, y / tilesize) - # clip tilesize against image size - width = min (im.Xsize () - x, tilesize) - height = min (im.Ysize () - y, tilesize) - - # expand edge tiles up to the full tilesize ... Google maps likes this - # im.extract_area (x, y, width, height).embed(0, 0, 0, tilesize, tilesize).write(filename) - - # let edge tiles be smaller than the full tile size, tiff tiling prefers this - im.extract_area (x, y, width, height).write (filename) - - # was there only a single tile? we are done - if im.Xsize() <= tilesize and im.Ysize() <= tilesize: - break - - # create next pyramid level in RAM - shrink = im.rightshift_size (1, 1, im.BandFmt()) - im = shrink.write (VImage.VImage ("temp", "t")) - -except VError.VError, e: - e.perror (sys.argv[0]) -``` - -Rename DICOM images using header fields ---------------------------------------- - -DICOM images commonly come in an awful directory hierarchy named as something like images/a/b/e/z04. There can be thousands of files and it can be very hard to find the one you want. - -This utility copies files to a single flat directory, naming them using fields from the DICOM header. You can actually find stuff! Useful. +This utility copies files to a single flat directory, naming them using +fields from the DICOM header. You can actually find stuff! Useful. ``` python #!/usr/bin/python diff --git a/doc/Makefile.am b/doc/Makefile.am index f36779a8..382e4321 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -146,6 +146,7 @@ markdown_content_files = \ How-it-works.md \ Using-vipsthumbnail.md \ How-it-opens-files.md \ + Examples.md \ Making-image-pyramids.md # converted to xml in this dir by pandoc diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml.in index b043a908..ef1dcf99 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml.in @@ -43,6 +43,7 @@ + From 28693c060b7b1bee8a1134c512b90c9b1859df5d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 5 Apr 2017 08:41:10 +0100 Subject: [PATCH 12/15] more examples work --- doc/Examples.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Examples.md b/doc/Examples.md index b5134702..ac6e6385 100644 --- a/doc/Examples.md +++ b/doc/Examples.md @@ -1,12 +1,12 @@ - Using `vipsthumbnail` + Examples 3 libvips - `vipsthumbnail` - Introduction to `vipsthumbnail`, with examples + libvips examples + A few example Python programs using libvips This page shows a few libvips examples using Python. They will work with small syntax From a0f420ea7e1049d9988fc5f4242875c93e418fbc Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 5 Apr 2017 14:37:17 +0100 Subject: [PATCH 13/15] fix dicom example --- doc/Examples.md | 87 +++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/doc/Examples.md b/doc/Examples.md index ac6e6385..b39ad012 100644 --- a/doc/Examples.md +++ b/doc/Examples.md @@ -257,77 +257,70 @@ hard to find the one you want. This utility copies files to a single flat directory, naming them using fields from the DICOM header. You can actually find stuff! Useful. -``` python -#!/usr/bin/python +```python +#!/usr/bin/env python import sys import re import os import shutil -from vipsCC import * +import gi +gi.require_version('Vips', '8.0') +from gi.repository import Vips -if len (sys.argv) != 3: - print 'rename DICOM files using tags from the header' - print 'usage:' - print '\t%s srcdir destdir' % sys.argv[0] - print 'the directory tree below srcdir is searched, all files are' - print 'renamed and put into destdir in a flat list' - sys.exit (1) +if len(sys.argv) != 3: + print 'rename DICOM files using tags from the header' + sys.exit(1) srcdir = sys.argv[1] destdir = sys.argv[2] -if not os.access (destdir, os.F_OK | os.R_OK | os.W_OK | os.X_OK): - os.mkdir (destdir) +if not os.access(destdir, os.F_OK | os.R_OK | os.W_OK | os.X_OK): + os.mkdir(destdir) -def get_field (vim, field): - result = vim.meta_get_string (field) +def get_field(vim, field): + result = vim.get_value(field) - # remove any \n etc. - result = re.sub ("\n", "", result) + # remove any \n etc. + result = re.sub("\n", "", result) - # remove any leading or trailing spaces - result = re.sub (" $", "", result) - result = re.sub ("^ ", "", result) + # remove any leading or trailing spaces + result = re.sub(" $", "", result) + result = re.sub("^ ", "", result) - return result + return result -id_name = "magick-dcm:Patient'sID" modality_name = "magick-dcm:Modality" series_name = "magick-dcm:SeriesNumber" instance_name = "magick-dcm:Instance(formerlyImage)Number" date_name = "magick-dcm:ImageDate" -n_processed = 0 +for(dirpath, dirnames, filenames) in os.walk(srcdir): + for file in filenames: + path = os.path.join(dirpath, file) -for (dirpath, dirnames, filenames) in os.walk (srcdir): - for file in filenames: - path = os.path.join (dirpath, file) + try: + vim = Vips.Image.new_from_file(path) + except Vips.Error, e: + print 'unable to open', path + print e + continue - try: - vim = VImage.VImage (path) - except VError.VError, e: - print 'unable to open', path - continue + try: + modality = get_field(vim, modality_name) + series = get_field(vim, series_name) + instance = get_field(vim, instance_name) + date = get_field(vim, date_name) + except Vips.Error, e: + print 'unable to get fields from header', path + print e + continue - try: - id = get_field (vim, id_name) - modality = get_field (vim, modality_name) - series = get_field (vim, series_name) - instance = get_field (vim, instance_name) - date = get_field (vim, date_name) - except VError.VError, e: - print 'unable to get fields from header', path - continue + match = re.match("(\d\d\d\d)(\d\d)(\d\d)", date) + date = match.group(1) + "." + match.group(2) + "." + match.group(3) - match = re.match ("(\d\d\d\d)(\d\d)(\d\d)", date) - date = match.group (1) + "." + match.group (2) + "." + match.group (3) - newname = id + "." + modality + "." + series + "." + instance + "." + date + ".IMA" + newname = "lan." + modality + "." + instance + "." + date + ".IMA" - shutil.copyfile(path, os.path.join (destdir, newname)) - - n_processed += 1 - -print '\t(%d files processed)' % n_processed + shutil.copyfile(path, os.path.join(destdir, newname)) ``` From aae2c2716bfca9ca451037a615848de0ede0d8f7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 5 Apr 2017 14:42:35 +0100 Subject: [PATCH 14/15] gint -> int32 in disc format header description --- doc/file-format.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/file-format.xml b/doc/file-format.xml index 1803565e..dc9afe0d 100644 --- a/doc/file-format.xml +++ b/doc/file-format.xml @@ -92,21 +92,21 @@ $ vips gamma t.v output.tif 4 -- 7 - %gint + int32 width Width of image, in pixels 8 -- 11 - %gint + int32 height Height of image, in pixels 12 -- 15 - %gint + int32 bands Number of image bands @@ -141,14 +141,14 @@ $ vips gamma t.v output.tif 32 -- 35 - %gfloat + float32 xres Horizontal resolution, in pixels per millimetre 36 -- 39 - %gfloat + float32 yres Vertical resolution, in pixels per millimetre @@ -162,14 +162,14 @@ $ vips gamma t.v output.tif 48 -- 51 - %gint + int32 xoffset Horizontal offset of origin, in pixels 52 -- 55 - %gint + int32 yoffset Vertical offset of origin, in pixels From 887eb249e46d59dd827dc4af7a2387caefec26ff Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 6 Apr 2017 10:19:35 +0100 Subject: [PATCH 15/15] revise region docs --- libvips/include/vips/private.h | 4 ++++ libvips/include/vips/region.h | 7 ------- libvips/iofuncs/region.c | 31 +++++++++++++++++-------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h index 6e3bd497..0d522b2a 100644 --- a/libvips/include/vips/private.h +++ b/libvips/include/vips/private.h @@ -178,10 +178,14 @@ void vips__demand_hint_array( struct _VipsImage *image, int vips__image_copy_fields_array( struct _VipsImage *out, struct _VipsImage *in[] ); +void vips__region_count_pixels( struct _VipsRegion *region, const char *nickname ); +void vips_region_dump_all( void ); + /* Deprecated. */ int vips__init( const char *argv0 ); size_t vips__get_sizeof_vipsobject( void ); +int vips_region_prepare_many( struct _VipsRegion **reg, VipsRect *r ); #ifdef __cplusplus } diff --git a/libvips/include/vips/region.h b/libvips/include/vips/region.h index ce171a39..a1474508 100644 --- a/libvips/include/vips/region.h +++ b/libvips/include/vips/region.h @@ -121,16 +121,9 @@ int vips_region_shrink( VipsRegion *from, VipsRegion *to, VipsRect *target ); int vips_region_prepare( VipsRegion *reg, VipsRect *r ); int vips_region_prepare_to( VipsRegion *reg, VipsRegion *dest, VipsRect *r, int x, int y ); -int vips_region_prepare_many( VipsRegion **reg, VipsRect *r ); void vips_region_invalidate( VipsRegion *reg ); -void vips_region_dump_all( void ); - -#ifdef DEBUG_LEAK -void vips__region_count_pixels( VipsRegion *region, const char *nickname ); -#endif /*DEBUG_LEAK*/ - /* Use this to count pixels passing through key points. Handy for spotting bad * overcomputation. */ diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index d0e24a01..c0d32247 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -105,7 +105,7 @@ * generate * @include: vips/vips.h * - * A #VipsRegion is a small part of an image and some pixels. You use regions to + * A #VipsRegion is a small part of an image. You use regions to * read pixels out of images without having to have the whole image in memory * at once. * @@ -558,7 +558,7 @@ vips_region_new( VipsImage *image ) * @r: #VipsRect of pixels you need to be able to address * * The region is transformed so that at least @r pixels are available as a - * memory buffer. + * memory buffer that can be written to. * * Returns: 0 on success, or -1 for error. */ @@ -626,8 +626,8 @@ vips_region_buffer( VipsRegion *reg, VipsRect *r ) * @reg: region to operate upon * @r: #VipsRect of pixels you need to be able to address * - * The region is transformed so that at least @r pixels are available directly - * from the image. The image needs to be a memory buffer or represent a file + * The region is transformed so that at least @r pixels are available to be read from + * image. The image needs to be a memory buffer or represent a file * on disc that has been mapped or can be mapped. * * Returns: 0 on success, or -1 for error. @@ -725,7 +725,7 @@ vips_region_image( VipsRegion *reg, VipsRect *r ) * Performs all clipping necessary to ensure that @reg->valid is indeed * valid. * - * If the region we attach to is modified, we can be left with dangling + * If the region we attach to is moved or destroyed, we can be left with dangling * pointers! If the region we attach to is on another image, the two images * must have * the same sizeof( pel ). @@ -933,7 +933,11 @@ vips_region_fill( VipsRegion *reg, VipsRect *r, VipsRegionFillFn fn, void *a ) * @r: area to paint * @value: value to paint * - * Paints @value into @reg covering rectangle @r. For int images, @value is + * Paints @value into @reg covering rectangle @r. + * @r is clipped against + * @reg->valid. + * + * For int images, @value is * passed to memset(), so it usually needs to be 0 or 255. For float images, * value is cast to a float and copied in to each band element. * @@ -955,7 +959,6 @@ vips_region_paint( VipsRegion *reg, VipsRect *r, int value ) int y; if( vips_band_format_isint( reg->im->BandFmt ) ) { - for( y = 0; y < clipped.height; y++ ) { memset( (char *) q, value, wd ); q += ls; @@ -1519,19 +1522,17 @@ vips_region_prepare_to_generate( VipsRegion *reg, * @x: postion of @r in @dest * @y: postion of @r in @dest * - * Like vips_region_prepare(): fill @reg with data, ready to be read from by - * our caller. Unlike vips_region_prepare(), rather than allocating memory - * local to @reg for the result, we guarantee that we will fill the pixels - * in @dest at offset @x, @y. In other words, we generate an extra copy - * operation if necessary. + * Like vips_region_prepare(): fill @reg with the pixels in area @r. + * Unlike vips_region_prepare(), rather than writing the result to @reg, the pixels are + * written into @dest + * at offset @x, @y. * * Also unlike vips_region_prepare(), @dest is not set up for writing for * you with * vips_region_buffer(). You can * point @dest at anything, and pixels really will be written there. * This makes vips_region_prepare_to() useful for making the ends of - * pipelines, since - * it (effectively) makes a break in the pipe. + * pipelines. * * See also: vips_region_prepare(), vips_sink_disc(). * @@ -1671,6 +1672,8 @@ vips_region_prepare_to( VipsRegion *reg, return( 0 ); } +/* Don't use this, use vips_reorder_prepare_many() instead. + */ int vips_region_prepare_many( VipsRegion **reg, VipsRect *r ) {