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 00000000..c8fd6038 Binary files /dev/null and b/doc/images/Combine.png differ diff --git a/doc/images/Memtrace.png b/doc/images/Memtrace.png new file mode 100644 index 00000000..d63e6018 Binary files /dev/null and b/doc/images/Memtrace.png differ diff --git a/doc/images/Sequence.png b/doc/images/Sequence.png new file mode 100644 index 00000000..8619c015 Binary files /dev/null and b/doc/images/Sequence.png differ diff --git a/doc/images/Sink.png b/doc/images/Sink.png new file mode 100644 index 00000000..2a830094 Binary files /dev/null and b/doc/images/Sink.png differ diff --git a/doc/images/Vips-smp.png b/doc/images/Vips-smp.png new file mode 100644 index 00000000..31e2d56a Binary files /dev/null and b/doc/images/Vips-smp.png differ diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml.in index d30d8097..f5a4a430 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml.in @@ -39,7 +39,7 @@ - + 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.