#!/usr/bin/python # vim: set fileencoding=utf-8 : import unittest import math #import logging #logging.basicConfig(level = logging.DEBUG) import gi gi.require_version('Vips', '8.0') from gi.repository import Vips Vips.leak_set(True) unsigned_formats = [Vips.BandFormat.UCHAR, Vips.BandFormat.USHORT, Vips.BandFormat.UINT] signed_formats = [Vips.BandFormat.CHAR, Vips.BandFormat.SHORT, Vips.BandFormat.INT] float_formats = [Vips.BandFormat.FLOAT, Vips.BandFormat.DOUBLE] complex_formats = [Vips.BandFormat.COMPLEX, Vips.BandFormat.DPCOMPLEX] int_formats = unsigned_formats + signed_formats noncomplex_formats = int_formats + float_formats all_formats = int_formats + float_formats + complex_formats # Run a function expecting a complex image on a two-band image def run_cmplx(fn, image): if image.format == Vips.BandFormat.FLOAT: new_format = Vips.BandFormat.COMPLEX elif image.format == Vips.BandFormat.DOUBLE: new_format = Vips.BandFormat.DPCOMPLEX else: raise "run_cmplx: not float or double" # tag as complex, run, revert tagging cmplx = image.copy(bands = 1, format = new_format) cmplx_result = fn(cmplx) return cmplx_result.copy(bands = 2, format = image.format) def to_polar(image): """Transform image coordinates to polar. The image is transformed so that it is wrapped around a point in the centre. Vertical straight lines become circles or segments of circles, horizontal straight lines become radial spokes. """ # xy image, zero in the centre, scaled to fit image to a circle xy = Vips.Image.xyz(image.width, image.height) xy -= [image.width / 2.0, image.height / 2.0] scale = min(image.width, image.height) / float(image.width) xy *= 2.0 / scale # to polar, scale vertical axis to 360 degrees index = run_cmplx(lambda x: x.polar(), xy) index *= [1, image.height / 360.0] return image.mapim(index) def to_rectangular(image): """Transform image coordinates to rectangular. The image is transformed so that it is unwrapped from a point in the centre. Circles or segments of circles become vertical straight lines, radial lines become horizontal lines. """ # xy image, vertical scaled to 360 degrees xy = Vips.Image.xyz(image.width, image.height) xy *= [1, 360.0 / image.height] # to rect, scale to image rect index = run_cmplx(lambda x: x.rect(), xy) scale = min(image.width, image.height) / float(image.width) index *= scale / 2.0 index += [image.width / 2.0, image.height / 2.0] return image.mapim(index) # an expanding zip ... if either of the args is a scalar or a one-element list, # duplicate it down the other side def zip_expand(x, y): # handle singleton list case if isinstance(x, list) and len(x) == 1: x = x[0] if isinstance(y, list) and len(y) == 1: y = y[0] if isinstance(x, list) and isinstance(y, list): return list(zip(x, y)) elif isinstance(x, list): return [[i, y] for i in x] elif isinstance(y, list): return [[x, j] for j in y] else: return [[x, y]] class TestResample(unittest.TestCase): # test a pair of things which can be lists for approx. equality def assertAlmostEqualObjects(self, a, b, places = 4, msg = ''): # print 'assertAlmostEqualObjects %s = %s' % (a, b) for x, y in zip_expand(a, b): self.assertAlmostEqual(x, y, places = places, msg = msg) def setUp(self): self.jpeg_file = "images/йцук.jpg" self.rotated_jpeg_file = "images/Landscape_6.jpg" def test_affine(self): im = Vips.Image.new_from_file(self.jpeg_file) # vsqbs is non-interpolatory, don't test this way for name in ["nearest", "bicubic", "bilinear", "nohalo", "lbb"]: x = im interpolate = Vips.Interpolate.new(name) for i in range(4): x = x.affine([0, 1, 1, 0], interpolate = interpolate) self.assertEqual((x - im).abs().max(), 0) def test_reduce(self): im = Vips.Image.new_from_file(self.jpeg_file) # cast down to 0-127, the smallest range, so we aren't messed up by # clipping im = im.cast(Vips.BandFormat.CHAR) bicubic = Vips.Interpolate.new("bicubic") bilinear = Vips.Interpolate.new("bilinear") nearest = Vips.Interpolate.new("nearest") for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: x = im.cast(fmt) r = x.reduce(fac, fac, kernel = "cubic") a = x.affine([1.0 / fac, 0, 0, 1.0 / fac], interpolate = bicubic, oarea = [0, 0, x.width / fac, x.height / fac]) d = (r - a).abs().max() self.assertLess(d, 10) for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: x = im.cast(fmt) r = x.reduce(fac, fac, kernel = "linear") a = x.affine([1.0 / fac, 0, 0, 1.0 / fac], interpolate = bilinear, oarea = [0, 0, x.width / fac, x.height / fac]) d = (r - a).abs().max() self.assertLess(d, 10) # for other kernels, just see if avg looks about right for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: for kernel in ["nearest", "lanczos2", "lanczos3"]: x = im.cast(fmt) r = x.reduce(fac, fac, kernel = kernel) d = abs(r.avg() - im.avg()) self.assertLess(d, 2) # try constant images ... should not change the constant for const in [0, 1, 2, 254, 255]: im = (Vips.Image.black(10, 10) + const).cast("uchar") for kernel in ["nearest", "linear", "cubic", "lanczos2", "lanczos3"]: # print "testing kernel =", kernel # print "testing const =", const shr = im.reduce(2, 2, kernel = kernel) d = abs(shr.avg() - im.avg()) self.assertEqual(d, 0) def test_resize(self): im = Vips.Image.new_from_file(self.jpeg_file) im2 = im.resize(0.25) self.assertEqual(im2.width, round(im.width / 4.0)) self.assertEqual(im2.height, round(im.height / 4.0)) # test geometry rounding corner case im = Vips.Image.black(100, 1); x = im.resize(0.5) self.assertEqual(x.width, 50) self.assertEqual(x.height, 1) def test_shrink(self): im = Vips.Image.new_from_file(self.jpeg_file) im2 = im.shrink(4, 4) self.assertEqual(im2.width, round(im.width / 4.0)) self.assertEqual(im2.height, round(im.height / 4.0)) self.assertTrue(abs(im.avg() - im2.avg()) < 1) im2 = im.shrink(2.5, 2.5) self.assertEqual(im2.width, round(im.width / 2.5)) self.assertEqual(im2.height, round(im.height / 2.5)) self.assertLess(abs(im.avg() - im2.avg()), 1) def test_thumbnail(self): im = Vips.Image.thumbnail(self.jpeg_file, 100) self.assertEqual(im.width, 100) self.assertEqual(im.bands, 3) self.assertEqual(im.bands, 3) # the average shouldn't move too much im_orig = Vips.Image.new_from_file(self.jpeg_file) self.assertLess(abs(im_orig.avg() - im.avg()), 1) # make sure we always get the right width for width in range(1000, 1, -13): im = Vips.Image.thumbnail(self.jpeg_file, width) self.assertEqual(im.width, width) # should fit one of width or height im = Vips.Image.thumbnail(self.jpeg_file, 100, height = 300) self.assertEqual(im.width, 100) self.assertNotEqual(im.height, 300) im = Vips.Image.thumbnail(self.jpeg_file, 300, height = 100) self.assertNotEqual(im.width, 300) self.assertEqual(im.height, 100) # force should fit width and height ... although this jpg has an # orientation tag, we ignore it unless autorot is on im = Vips.Image.thumbnail(self.rotated_jpeg_file, 100, height = 300, size = "force") self.assertEqual(im.width, 100) self.assertEqual(im.height, 300) # with force + autorot, we spin the image, but the output size should # not change im = Vips.Image.thumbnail(self.rotated_jpeg_file, 100, height = 300, size = "force", auto_rotate = True) self.assertEqual(im.width, 100) self.assertEqual(im.height, 300) # with @crop, should fit both width and height im = Vips.Image.thumbnail(self.jpeg_file, 100, height = 300, crop = "centre") self.assertEqual(im.width, 100) self.assertEqual(im.height, 300) # with size up, should not downsize im = Vips.Image.thumbnail(self.jpeg_file, 100, size = "up") self.assertEqual(im.width, im_orig.width) im = Vips.Image.thumbnail(self.jpeg_file, 10000, size = "up") self.assertEqual(im.width, 10000) # with size down, should not upsize im = Vips.Image.thumbnail(self.jpeg_file, 100, size = "down") self.assertEqual(im.width, 100) im = Vips.Image.thumbnail(self.jpeg_file, 10000, size = "down") self.assertEqual(im.width, im_orig.width) im1 = Vips.Image.thumbnail(self.jpeg_file, 100) with open(self.jpeg_file, 'rb') as f: buf = f.read() im2 = Vips.Image.thumbnail_buffer(buf, 100) self.assertLess(abs(im1.avg() - im2.avg()), 1) def test_similarity(self): im = Vips.Image.new_from_file(self.jpeg_file) im2 = im.similarity(angle = 90) im3 = im.affine([0, -1, 1, 0]) # rounding in calculating the affine transform from the angle stops # this being exactly true self.assertLess((im2 - im3).abs().max(), 50) def test_similarity_scale(self): im = Vips.Image.new_from_file(self.jpeg_file) im2 = im.similarity(scale = 2) im3 = im.affine([2, 0, 0, 2]) self.assertEqual((im2 - im3).abs().max(), 0) def test_mapim(self): im = Vips.Image.new_from_file(self.jpeg_file) p = to_polar(im) r = to_rectangular(p) # the left edge (which is squashed to the origin) will be badly # distorted, but the rest should not be too bad a = r.crop(50, 0, im.width - 50, im.height).gaussblur(2) b = im.crop(50, 0, im.width - 50, im.height).gaussblur(2) self.assertLess((a - b).abs().max(), 20) if __name__ == '__main__': unittest.main()