Skip to content

HOWTO Image shrinking

elisbyberi edited this page Feb 4, 2020 · 24 revisions

This page attempts to give best-practice guidelines for image resizing with libvips. They are implemented in the vips_thumbnail() and vips_thumbnail_buffer() operations, so read along in the source code if you're curious.

You can call these operations from any language with a libvips binding, so just use (for example):

$filename = ...;
$image = Vips\Image::thumbnail($filename, 200, ["height" => 200]);
$image->writeToFile("my-thumbnail.jpg");

And everything listed here will be done for you.

The vipsthumbnail command-line tool gives a handy interface to these operations, see the docs for an introduction.

When downsizing an image you need to follow approximately these steps:

Opening images

Many image formats support some sort of resize-on-load. This is generally much faster than shrinking in libvips, so it's a good idea to exploit this if you can. vipsthumbnail does shrink-on-load for the following formats:

  • libjpeg (and libjpeg-turbo) can quickly produce a 1/8, 1/4 or 1/2 size image during decode.

    This down-sampled image is produced by the equivalent of a block shrink (each pixel is the simple average of the corresponding 2x2, 4x4 or 8x8 block), and the hard edges of these square blocks can cause rather bad aliasing in the final image. It's best to use jpeg shrink-on-load to make an image about 2x larger than your final output size and then do a further shrink with something more sophisticated. See the Block shrink section below.

  • Vector formats like PDF and SVG can be rendered directly at the required size. Do this and don't do anything else.

  • libwebp supports a rather general shrink-on-load feature. Again, keep at least x2 headroom for the final resize.

  • Pyramidal formats like openslide and TIFF pyramids can have layers which contain subsampled versions of the image. As with libjpeg, to avoid aliasing it's best to extract a layer 2x larger than the target and do a further shrink with something else.

  • HEIC embeds a thumbnail, and the thumbnail is a good representation of the image (unlike the EXIF thumbnail in JPEG). Use that if you can.

libvips supports file and memory input. See vips_image_new_from_file() and vips_image_new_from_buffer(). You can also make images from simple memory arrays; useful if you've used some other library to decode the image. See vips_image_new_from_memory().

Streaming

libvips supports streaming -- instead of performing operations as a set of separate steps, images can be streamed through a pipeline of operations in a series of small regions. This lets all stages run in parallel, including load and save, and can give a good drop in runtime, as well as a significant improvement in processing speed.

Use the access: sequential option to vips_image_new_from_file().

Linear light

If you mix two image samples, it's like mixing two sources of light. Or it should be; in fact, most image formats do not deal in values which are related to the amount of light, mostly they deal in perception, that is, how bright a point would appear to a human observer.

If you want to resize an image, you really ought to translate the image to a linear light colourspace first. @nathanaeljones has a nice example of the difference this makes. If you resample in a perceptual space you see this:

If you resample in a linear space you see this:

Obviously the snowflakes are more visible in linear light.

Unfortunately this translation is not cheap and things like shrink-on-load, essential for good performance, do not support linear light. For now, linear light resampling is a luxury.

If you want to do linear-light resampling in libvips you must not do any of the shrink-on-load tricks and you need to go to a linear-light space immediately after loading the image. If there's an attached ICC profile, use vips_icc_import() to import the image to XYZ PCS. If there's no profile, use vips_colourspace() to transform the image to CIEXYZ. The --linear flag to vipsthumbnail enables this.

Processing colourspace

After loading the image, you need to move it to a processing colourspace. libvips supports a large number of colourspaces, with sRGB being the most common. If you are not doing linear light resampling, use vips_colourspace() to move to sRGB.

Premultiply

If the image has an alpha channel, you need to premultiply before shrinking. See vips_premultiply().

Block shrink

libvips has a very fast block shrink, vips_shrink(), where each output pixel is the simple average of the square nxn block of pixels in the source. Use this to get to 2x above your final target size.

The x2 headroom is necessary to prevent aliasing. Here's a 800x600 JPEG image shrunk to 400x300 with a 2x2 block shrink (in fact, with libjpeg's shrink-on-load feature):

And here's the same image, but shrunk from 800x600 to 400x300 with lanczos3:

If you look at the roof of the block-shrink version you'll see nasty aliasing artifacts. To fix these, you have to block shrink to a size above your target and then use something better to get to the final target size.

Resize to target

For the final resize, use vips_reduce(). This uses a pair of high-quality 1D lanczos3 kernels to get the image to the exact dimensions you need. You can pick other kernels if you wish.

vips_resize() combines the block-shrink and lanczos3 in a single operation. If you'll be doing exactly those calls, use vips_resize() instead.

Unpremultiply

If you have an alpha, now's the time to call vips_unpremultiply().

Colour management

Most image viewers, including web browsers, will show an image with no embedded colour profile as sRGB. You can therefore save the storage and transmission costs of the profile by writing the output image as sRGB and removing any profile data.

When libvips opens an image, it attaches any colour profile embedded in the image as metadata. You can use this with vips_icc_transform() to move the image to sRGB using an sRGB profile. If you are doing linear light resampling, use vips_colourspace() to transform directly to sRGB or vips_icc_export() with XYZ PCS if you used vips_icc_import() to convert to XYZ colourspace.

Autorotate

Some images will have orientation tags giving the camera angle when the photo was taken. You can save transmitting this metadata by baking the orientation into the image, see vips_autorot().

If you are streaming the source image, you will need to copy the thumbnail to a memory buffer before rotating it.

Delete metadata

You can save a lot of space by deleting metadata. See vips_image_remove(). Image savers also all support a strip option which will remove all metadata.

Final write

libvips supports some of mozjpeg's output options. You can get a smaller final image by adjusting these controls. See the documentation for vips_jpegsave().

Clone this wiki locally