ForgeTotal.js 5
en

Total.js 5

Resize and convert images

Total.js 5 · total5@0.0.19 imagesthumbnailsgraphicsmagick Executed against Total.js 5

Total.js processes images by calling GraphicsMagick (gm) or ImageMagick, which must be installed on the server (brew install graphicsmagick, apt install graphicsmagick). The $imageconverter setting chooses the program: gm (default), im or magick.

Image.load(filename) (or a Buffer or a stream) returns a chain that records the operations and runs them when you call .save(filename, callback). The useful operations are resize(width, height) (keeps the proportions inside the box), thumbnail(width, height) (a fast, small preview), miniature(width, height) (fits the image in the box and fills the rest with the background), crop(width, height, x, y), rotate(degrees), grayscale(), blur(radius), quality(percentage) and output('jpg') to convert the format. Uploaded files do it directly: file.image() in a handler for @upload routes returns the same chain.

Reading the size does not need GraphicsMagick: Image.measure('png', buffer) returns { width, height } from the first bytes of PNG, JPG, GIF and WebP data.

$.image(options) is for a route that creates a picture on demand: ext is the format, load(next) hands over the source (a stream or a path) with next(...), image(img) describes the operations and cache is a name under which the result is kept in tmp/ for the next requests.

controllers/images.js
const Zlib = require('zlib');
const Fs = require('fs');

exports.install = function() {
	ROUTE('GET /resize/', resize);
	ROUTE('GET /thumb/', thumb);
};

// Builds a solid red PNG so that the example needs no file
function makepng(width, height) {

	function chunk(type, data) {
		var length = Buffer.alloc(4);
		length.writeUInt32BE(data.length);
		var body = Buffer.concat([Buffer.from(type), data]);
		var crc = Buffer.alloc(4);
		crc.writeUInt32BE(Zlib.crc32(body));
		return Buffer.concat([length, body, crc]);
	}

	var header = Buffer.alloc(13);
	header.writeUInt32BE(width, 0);
	header.writeUInt32BE(height, 4);
	header[8] = 8; // bit depth
	header[9] = 2; // RGB

	var row = Buffer.alloc(1 + width * 3);
	for (var x = 0; x < width; x++)
		row[1 + x * 3] = 255;

	var raw = Buffer.concat(Array(height).fill(row));

	return Buffer.concat([
		Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
		chunk('IHDR', header),
		chunk('IDAT', Zlib.deflateSync(raw)),
		chunk('IEND', Buffer.alloc(0))
	]);
}

function source() {
	var filename = PATH.tmp('source.png');
	Fs.writeFileSync(filename, makepng(200, 100));
	return filename;
}

function resize($) {

	var target = PATH.tmp('small.jpg');

	Image.load(source()).resize(50, 50).output('jpg').save(target, function(err) {

		if (err) {
			$.invalid(err);
			return;
		}

		var size = Image.measure('jpg', Fs.readFileSync(target));
		$.json({ width: size.width, height: size.height, original: Image.measure('png', Fs.readFileSync(source())) });
	});
}

function thumb($) {
	$.image({
		ext: 'png',
		cache: 'thumb-example',
		load: function(next) {
			next(Fs.createReadStream(source()));
		},
		image: function(img) {
			img.resize(40, 40);
		}
	});
}