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 @@
+