libvips/test/test-suite/test_resample.py

292 lines
11 KiB
Python

# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import JPEG_FILE, JPEG_FILE_XYB, OME_FILE, HEIC_FILE, TIF_FILE, \
all_formats, have, RGBA_FILE, RGBA_CORRECT_FILE, AVIF_FILE
# Run a function expecting a complex image on a two-band image
def run_cmplx(fn, image):
if image.format == pyvips.BandFormat.FLOAT:
new_format = pyvips.BandFormat.COMPLEX
elif image.format == pyvips.BandFormat.DOUBLE:
new_format = pyvips.BandFormat.DPCOMPLEX
else:
raise pyvips.Error("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 = pyvips.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 = pyvips.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)
class TestResample:
def test_affine(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
# vsqbs is non-interpolatory, don't test this way
for name in ["nearest", "bicubic", "bilinear", "nohalo", "lbb"]:
x = im
interpolate = pyvips.Interpolate.new(name)
for i in range(4):
x = x.affine([0, 1, 1, 0], interpolate=interpolate)
assert (x - im).abs().max() == 0
def test_reduce(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
# cast down to 0-127, the smallest range, so we aren't messed up by
# clipping
im = im.cast(pyvips.BandFormat.CHAR)
for fac in [1, 1.1, 1.5, 1.999]:
for fmt in all_formats:
for kernel in ["nearest", "linear",
"cubic", "lanczos2", "lanczos3"]:
x = im.cast(fmt)
r = x.reduce(fac, fac, kernel=kernel)
d = abs(r.avg() - im.avg())
assert d < 2
# try constant images ... should not change the constant
for const in [0, 1, 2, 254, 255]:
im = (pyvips.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())
assert d == 0
def test_resize(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.resize(0.25)
# in py3, round() does not round to nearest in the obvious way, so we
# have to do it by hand
assert im2.width == int(im.width / 4.0 + 0.5)
assert im2.height == int(im.height / 4.0 + 0.5)
# test geometry rounding corner case
im = pyvips.Image.black(100, 1)
x = im.resize(0.5)
assert x.width == 50
assert x.height == 1
# test whether we use double-precision calculations in reduce{h,v}
im = pyvips.Image.black(1600, 1000)
x = im.resize(10.0 / im.width)
assert x.width == 10
assert x.height == 6
# test round-up option of shrink
im = pyvips.Image.black(2049 - 2, 2047 - 2, bands=3)
im = im.embed(1, 1, 2049, 2047,
extend=pyvips.Extend.BACKGROUND,
background=[255, 0, 0])
for scale in [8, 9.4, 16]:
x = im.resize(1 / scale, vscale=1 / scale)
for point in ([(round(x.width / 2), 0),
(x.width - 1, round(x.height / 2)),
(round(x.width / 2), x.height - 1),
(0, round(x.height / 2))]):
y = x(*point)[0]
assert y != 0
def test_shrink(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.shrink(4, 4)
# in py3, round() does not round to nearest in the obvious way, so we
# have to do it by hand
assert im2.width == int(im.width / 4.0 + 0.5)
assert im2.height == int(im.height / 4.0 + 0.5)
assert abs(im.avg() - im2.avg()) < 1
im2 = im.shrink(2.5, 2.5)
assert im2.width == int(im.width / 2.5 + 0.5)
assert im2.height == int(im.height / 2.5 + 0.5)
assert abs(im.avg() - im2.avg()) < 1
@pytest.mark.skipif(not pyvips.at_least_libvips(8, 5),
reason="requires libvips >= 8.5")
def test_thumbnail(self):
im = pyvips.Image.thumbnail(JPEG_FILE, 100)
assert im.height == 100
assert im.bands == 3
assert im.bands == 3
# the average shouldn't move too much
im_orig = pyvips.Image.new_from_file(JPEG_FILE)
assert abs(im_orig.avg() - im.avg()) < 1
# make sure we always get the right width
for height in range(440, 1, -13):
im = pyvips.Image.thumbnail(JPEG_FILE, height)
assert im.height == height
# should fit one of width or height
im = pyvips.Image.thumbnail(JPEG_FILE, 100, height=300)
assert im.width == 100
assert im.height != 300
im = pyvips.Image.thumbnail(JPEG_FILE, 300, height=100)
assert im.width != 300
assert im.height == 100
# with @crop, should fit both width and height
im = pyvips.Image.thumbnail(JPEG_FILE, 100,
height=300, crop=True)
assert im.width == 100
assert im.height == 300
im1 = pyvips.Image.thumbnail(JPEG_FILE, 100)
with open(JPEG_FILE, 'rb') as f:
buf = f.read()
im2 = pyvips.Image.thumbnail_buffer(buf, 100)
assert abs(im1.avg() - im2.avg()) < 1
# should be able to thumbnail many-page tiff
im = pyvips.Image.thumbnail(OME_FILE, 100)
assert im.width == 100
assert im.height == 38
# should be able to thumbnail individual pages from many-page tiff
im1 = pyvips.Image.thumbnail(OME_FILE + "[page=0]", 100)
assert im1.width == 100
assert im1.height == 38
im2 = pyvips.Image.thumbnail(OME_FILE + "[page=1]", 100)
assert im2.width == 100
assert im2.height == 38
assert (im1 - im2).abs().max() != 0
# should be able to thumbnail entire many-page tiff as a toilet-roll
# image
im = pyvips.Image.thumbnail(OME_FILE + "[n=-1]", 100)
assert im.width == 100
assert im.height == 570
# should be able to thumbnail a single-page tiff in a buffer
im1 = pyvips.Image.thumbnail(TIF_FILE, 100)
with open(TIF_FILE, 'rb') as f:
buf = f.read()
im2 = pyvips.Image.thumbnail_buffer(buf, 100)
assert abs(im1.avg() - im2.avg()) < 1
# linear shrink should work on rgba images
im1 = pyvips.Image.thumbnail(RGBA_FILE, 64, linear=True)
im2 = pyvips.Image.new_from_file(RGBA_CORRECT_FILE)
assert abs(im1.flatten(background=255).avg() - im2.avg()) < 1
if have("heifload"):
# this image is orientation 6 ... thumbnail should flip it
im = pyvips.Image.new_from_file(AVIF_FILE)
thumb = pyvips.Image.thumbnail(AVIF_FILE, 100)
# thumb should be portrait
assert thumb.width < thumb.height
assert thumb.height == 100
@pytest.mark.skipif(not pyvips.at_least_libvips(8, 5),
reason="requires libvips >= 8.5")
def test_thumbnail_icc(self):
im = pyvips.Image.thumbnail(JPEG_FILE_XYB, 442, export_profile="srgb", intent="perceptual")
assert im.width == 290
assert im.height == 442
assert im.bands == 3
assert im.bands == 3
# the colour distance should not deviate too much
# (i.e. the embedded profile should not be ignored)
im_orig = pyvips.Image.new_from_file(JPEG_FILE)
assert im_orig.de00(im).max() < 10
def test_similarity(self):
im = pyvips.Image.new_from_file(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
assert (im2 - im3).abs().max() < 50
def test_similarity_scale(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.similarity(scale=2)
im3 = im.affine([2, 0, 0, 2])
assert (im2 - im3).abs().max() == 0
# added in 8.7
def test_rotate(self):
if have("rotate"):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.rotate(90)
im3 = im.affine([0, -1, 1, 0])
# rounding in calculating the affine transform from the angle stops
# this being exactly true
assert (im2 - im3).abs().max() < 50
def test_mapim(self):
im = pyvips.Image.new_from_file(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)
assert (a - b).abs().max() < 50
# this was a bug at one point, strangely, if executed with debug
# enabled
mp = pyvips.Image.xyz(im.width, im.height)
interp = pyvips.Interpolate.new('bicubic')
assert im.mapim(mp, interpolate=interp).avg() == im.avg()
if __name__ == '__main__':
pytest.main()