def test_magicksave(self): # save to a file and load again ... we can't use save_load_file since # we want to make sure we use magickload/save # don't use BMP - GraphicsMagick always adds an alpha # don't use TIF - IM7 will save as 16-bit filename = temp_filename(self.tempdir, ".jpg") self.colour.magicksave(filename) x = pyvips.Image.magickload(filename) assert self.colour.width == x.width assert self.colour.height == x.height assert self.colour.bands == x.bands max_diff = (self.colour - x).abs().max() assert max_diff < 60 self.save_load_buffer("magicksave_buffer", "magickload_buffer", self.colour, 60, format="JPG") # try an animation if have("gifload"): x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) w1 = x1.magicksave_buffer(format="GIF") x2 = pyvips.Image.new_from_buffer(w1, "", n=-1) assert x1.get("delay") == x2.get("delay") assert x1.get("page-height") == x2.get("page-height") # magicks vary in how they handle this ... just pray we are close assert abs(x1.get("gif-loop") - x2.get("gif-loop")) < 5
def test_webp(self): def webp_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [71, 166, 236]) assert im.width == 550 assert im.height == 368 assert im.bands == 3 self.file_loader("webpload", WEBP_FILE, webp_valid) self.buffer_loader("webpload_buffer", WEBP_FILE, webp_valid) self.save_load_buffer("webpsave_buffer", "webpload_buffer", self.colour, 60) self.save_load("%s.webp", self.colour) # test lossless mode im = pyvips.Image.new_from_file(WEBP_FILE) buf = im.webpsave_buffer(lossless=True) im2 = pyvips.Image.new_from_buffer(buf, "") assert im.avg() == im2.avg() # higher Q should mean a bigger buffer b1 = im.webpsave_buffer(Q=10) b2 = im.webpsave_buffer(Q=90) assert len(b2) > len(b1) # try saving an image with an ICC profile and reading it back ... if we # can do it, our webp supports metadata load/save buf = self.colour.webpsave_buffer() im = pyvips.Image.new_from_buffer(buf, "") if im.get_typeof("icc-profile-data") != 0: # verify that the profile comes back unharmed p1 = self.colour.get("icc-profile-data") p2 = im.get("icc-profile-data") assert p1 == p2 # add tests for exif, xmp, ipct # the exif test will need us to be able to walk the header, # we can't just check exif-data # we can test that exif changes change the output of webpsave # first make sure we have exif support z = pyvips.Image.new_from_file(JPEG_FILE) if z.get_typeof("exif-ifd0-Orientation") != 0: x = self.colour.copy() x.set("orientation", 6) buf = x.webpsave_buffer() y = pyvips.Image.new_from_buffer(buf, "") assert y.get("orientation") == 6 # try converting an animated gif to webp ... can't do back to gif # again without IM support if have("gifload"): x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) w1 = x1.webpsave_buffer(Q=10) x2 = pyvips.Image.new_from_buffer(w1, "", n=-1) assert x1.width == x2.width assert x1.height == x2.height assert x1.get("gif-delay") == x2.get("gif-delay") assert x1.get("page-height") == x2.get("page-height") assert x1.get("gif-loop") == x2.get("gif-loop")
def test_magickload(self): def bmp_valid(im): a = im(100, 100) assert_almost_equal_objects(a, [227, 216, 201]) assert im.width == 1419 assert im.height == 1001 self.file_loader("magickload", BMP_FILE, bmp_valid) self.buffer_loader("magickload_buffer", BMP_FILE, bmp_valid) # we should have rgba for svg files im = pyvips.Image.magickload(SVG_FILE) assert im.bands == 4 # density should change size of generated svg im = pyvips.Image.magickload(SVG_FILE, density='100') width = im.width height = im.height im = pyvips.Image.magickload(SVG_FILE, density='200') # This seems to fail on travis, no idea why, some problem in their IM # perhaps # assert im.width == width * 2 # assert im.height == height * 2 # all-frames should load every frame of the animation # (though all-frames is deprecated) im = pyvips.Image.magickload(GIF_ANIM_FILE) width = im.width height = im.height im = pyvips.Image.magickload(GIF_ANIM_FILE, all_frames=True) assert im.width == width assert im.height == height * 5 # page/n let you pick a range of pages # 'n' param added in 8.5 if pyvips.at_least_libvips(8, 5): im = pyvips.Image.magickload(GIF_ANIM_FILE) width = im.width height = im.height im = pyvips.Image.magickload(GIF_ANIM_FILE, page=1, n=2) assert im.width == width assert im.height == height * 2 page_height = im.get("page-height") assert page_height == height # should work for dicom im = pyvips.Image.magickload(DICOM_FILE) assert im.width == 128 assert im.height == 128 # some IMs are 3 bands, some are 1, can't really test # assert im.bands == 1 # added in 8.7 if have("magicksave"): self.save_load_file(".bmp", "", self.colour, 0) self.save_load_buffer("magicksave_buffer", "magickload_buffer", self.colour, 0, format="BMP") self.save_load("%s.bmp", self.colour)
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_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 if have("heifload"): # this image is orientation 6 ... thumbnail should flip it im = pyvips.Image.new_from_file(HEIC_FILE) thumb = pyvips.Image.thumbnail(HEIC_FILE, 100) # original is landscape assert im.width > im.height # thumb should be portrait assert thumb.width < thumb.height assert thumb.height == 100
def test_gifsave(self): # Animated GIF round trip x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) b1 = x1.gifsave_buffer() x2 = pyvips.Image.new_from_buffer(b1, "", n=-1) assert x1.width == x2.width assert x1.height == x2.height assert x1.get("n-pages") == x2.get("n-pages") assert x1.get("delay") == x2.get("delay") assert x1.get("page-height") == x2.get("page-height") assert x1.get("loop") == x2.get("loop") # Reducing dither will typically reduce file size (and quality) little_dither = self.colour.gifsave_buffer(dither=0.1, effort=1) large_dither = self.colour.gifsave_buffer(dither=0.9, effort=1) assert len(little_dither) < len(large_dither) # Reducing effort will typically increase file size (and reduce quality) little_effort = self.colour.gifsave_buffer(effort=1) large_effort = self.colour.gifsave_buffer(effort=10) assert len(little_effort) > len(large_effort) # Reducing bitdepth will typically reduce file size (and reduce quality) bitdepth8 = self.colour.gifsave_buffer(bitdepth=8, effort=1) bitdepth7 = self.colour.gifsave_buffer(bitdepth=7, effort=1) assert len(bitdepth8) > len(bitdepth7) if have("webpload"): # Animated WebP to GIF x1 = pyvips.Image.new_from_file(WEBP_ANIMATED_FILE, n=-1) b1 = x1.gifsave_buffer() x2 = pyvips.Image.new_from_buffer(b1, "", n=-1) assert x1.width == x2.width assert x1.height == x2.height assert x1.get("n-pages") == x2.get("n-pages") assert x1.get("delay") == x2.get("delay") assert x1.get("page-height") == x2.get("page-height") assert x1.get("loop") == x2.get("loop")
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
def test_dzsave(self): # dzsave is hard to test, there are so many options # test each option separately and hope they all function together # correctly # default deepzoom layout ... we must use png here, since we want to # test the overlap for equality filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, suffix=".png") # test horizontal overlap ... expect 256 step, overlap 1 x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") assert x.width == 255 y = pyvips.Image.new_from_file(filename + "_files/10/1_0.png") assert y.width == 256 # the right two columns of x should equal the left two columns of y left = x.crop(x.width - 2, 0, 2, x.height) right = y.crop(0, 0, 2, y.height) assert (left - right).abs().max() == 0 # test vertical overlap assert x.height == 255 y = pyvips.Image.new_from_file(filename + "_files/10/0_1.png") assert y.height == 256 # the bottom two rows of x should equal the top two rows of y top = x.crop(0, x.height - 2, x.width, 2) bottom = y.crop(0, 0, y.width, 2) assert (top - bottom).abs().max() == 0 # there should be a bottom layer x = pyvips.Image.new_from_file(filename + "_files/0/0_0.png") assert x.width == 1 assert x.height == 1 # 10 should be the final layer assert not os.path.isdir(filename + "_files/11") # default google layout filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, layout="google") # test bottom-right tile ... default is 256x256 tiles, overlap 0 x = pyvips.Image.new_from_file(filename + "/2/2/3.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/2/2/4.jpg") assert not os.path.exists(filename + "/3") x = pyvips.Image.new_from_file(filename + "/blank.png") assert x.width == 256 assert x.height == 256 # google layout with overlap ... verify that we clip correctly # overlap 1, 510x510 pixels, 256 pixel tiles, should be exactly 2x2 # tiles, though in fact the bottom and right edges will be white filename = temp_filename(self.tempdir, '') self.colour.crop(0, 0, 510, 510).dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/1/1.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/0/2/2.jpg") # with 511x511, it'll fit exactly into 2x2 -- we we actually generate # 3x3, since we output the overlaps # 8.6 revised the rules on overlaps, so don't test earlier than that if pyvips.base.at_least_libvips(8, 6): filename = temp_filename(self.tempdir, '') self.colour.crop(0, 0, 511, 511).dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/2/2.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/0/3/3.jpg") # default zoomify layout filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, layout="zoomify") # 256x256 tiles, no overlap assert os.path.exists(filename + "/ImageProperties.xml") x = pyvips.Image.new_from_file(filename + "/TileGroup0/2-3-2.jpg") assert x.width == 256 assert x.height == 256 # test zip output filename = temp_filename(self.tempdir, '.zip') self.colour.dzsave(filename) # before 8.5.8, you needed a gc on pypy to flush small zip output to # disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() assert os.path.exists(filename) assert not os.path.exists(filename + "_files") assert not os.path.exists(filename + ".dzi") # test compressed zip output filename2 = temp_filename(self.tempdir, '.zip') self.colour.dzsave(filename2, compression=-1) # before 8.5.8, you needed a gc on pypy to flush small zip output to # disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() assert os.path.exists(filename2) assert os.path.getsize(filename2) < os.path.getsize(filename) # test suffix filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, suffix=".png") x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") assert x.width == 255 # test overlap filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, overlap=200) y = pyvips.Image.new_from_file(filename + "_files/10/1_1.jpeg") assert y.width == 654 # test tile-size filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, tile_size=512) y = pyvips.Image.new_from_file(filename + "_files/10/0_0.jpeg") assert y.width == 513 assert y.height == 513 # test save to memory buffer if have("dzsave_buffer"): filename = temp_filename(self.tempdir, '.zip') base = os.path.basename(filename) root, ext = os.path.splitext(base) self.colour.dzsave(filename) # before 8.5.8, you needed a gc on pypy to flush small zip # output to disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() with open(filename, 'rb') as f: buf1 = f.read() buf2 = self.colour.dzsave_buffer(basename=root) assert len(buf1) == len(buf2)
class TestForeign: tempdir = None @classmethod def setup_class(cls): cls.tempdir = tempfile.mkdtemp() cls.colour = pyvips.Image.jpegload(JPEG_FILE) cls.mono = cls.colour.extract_band(1) # we remove the ICC profile: the RGB one will no longer be appropriate cls.mono.remove("icc-profile-data") cls.rad = cls.colour.float2rad() cls.rad.remove("icc-profile-data") cls.cmyk = cls.colour.bandjoin(cls.mono) cls.cmyk = cls.cmyk.copy(interpretation=pyvips.Interpretation.CMYK) cls.cmyk.remove("icc-profile-data") im = pyvips.Image.new_from_file(GIF_FILE) cls.onebit = im > 128 @classmethod def teardown_class(cls): shutil.rmtree(cls.tempdir, ignore_errors=True) # we have test files for formats which have a clear standard def file_loader(self, loader, test_file, validate): im = pyvips.Operation.call(loader, test_file) validate(im) im = pyvips.Image.new_from_file(test_file) validate(im) def buffer_loader(self, loader, test_file, validate): with open(test_file, 'rb') as f: buf = f.read() im = pyvips.Operation.call(loader, buf) validate(im) im = pyvips.Image.new_from_buffer(buf, "") validate(im) def save_load(self, format, im): x = pyvips.Image.new_temp_file(format) im.write(x) assert im.width == x.width assert im.height == x.height assert im.bands == x.bands max_diff = (im - x).abs().max() assert max_diff == 0 def save_load_file(self, format, options, im, thresh): # yuk! # but we can't set format parameters for pyvips.Image.new_temp_file() filename = temp_filename(self.tempdir, format) im.write_to_file(filename + options) x = pyvips.Image.new_from_file(filename) assert im.width == x.width assert im.height == x.height assert im.bands == x.bands max_diff = (im - x).abs().max() assert max_diff <= thresh x = None def save_load_buffer(self, saver, loader, im, max_diff=0, **kwargs): buf = pyvips.Operation.call(saver, im, **kwargs) x = pyvips.Operation.call(loader, buf) assert im.width == x.width assert im.height == x.height assert im.bands == x.bands assert (im - x).abs().max() <= max_diff def save_buffer_tempfile(self, saver, suf, im, max_diff=0): filename = temp_filename(self.tempdir, suf) buf = pyvips.Operation.call(saver, im) f = open(filename, 'wb') f.write(buf) f.close() x = pyvips.Image.new_from_file(filename) assert im.width == x.width assert im.height == x.height assert im.bands == x.bands assert (im - x).abs().max() <= max_diff def test_vips(self): self.save_load_file(".v", "", self.colour, 0) # check we can save and restore metadata filename = temp_filename(self.tempdir, ".v") self.colour.write_to_file(filename) x = pyvips.Image.new_from_file(filename) before_exif = self.colour.get("exif-data") after_exif = x.get("exif-data") assert len(before_exif) == len(after_exif) for i in range(len(before_exif)): assert before_exif[i] == after_exif[i] x = None @pytest.mark.skipif(not have("jpegload"), reason="no jpeg support in this vips, skipping test") def test_jpeg(self): def jpeg_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [6, 5, 3]) profile = im.get("icc-profile-data") assert len(profile) == 1352 assert im.width == 1024 assert im.height == 768 assert im.bands == 3 self.file_loader("jpegload", JPEG_FILE, jpeg_valid) self.save_load("%s.jpg", self.mono) self.save_load("%s.jpg", self.colour) self.buffer_loader("jpegload_buffer", JPEG_FILE, jpeg_valid) self.save_load_buffer("jpegsave_buffer", "jpegload_buffer", self.colour, 80) # see if we have exif parsing: our test image has this field x = pyvips.Image.new_from_file(JPEG_FILE) if x.get_typeof("exif-ifd0-Orientation") != 0: # we need a copy of the image to set the new metadata on # otherwise we get caching problems # can set, save and load new orientation x = pyvips.Image.new_from_file(JPEG_FILE) x = x.copy() x.set("orientation", 2) filename = temp_filename(self.tempdir, '.jpg') x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 2 # can remove orientation, save, load again, orientation # has reset x.remove("orientation") filename = temp_filename(self.tempdir, '.jpg') x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 1 # autorotate load works filename = temp_filename(self.tempdir, '.jpg') x = pyvips.Image.new_from_file(JPEG_FILE) x = x.copy() x.set("orientation", 6) x.write_to_file(filename) x1 = pyvips.Image.new_from_file(filename) x2 = pyvips.Image.new_from_file(filename, autorotate=True) assert x1.width == x2.height assert x1.height == x2.width @pytest.mark.skipif(not have("pngload") or not os.path.isfile(PNG_FILE), reason="no png support, skipping test") def test_png(self): def png_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0]) assert im.width == 290 assert im.height == 442 assert im.bands == 3 self.file_loader("pngload", PNG_FILE, png_valid) self.buffer_loader("pngload_buffer", PNG_FILE, png_valid) self.save_load_buffer("pngsave_buffer", "pngload_buffer", self.colour) self.save_load("%s.png", self.mono) self.save_load("%s.png", self.colour) @pytest.mark.skipif(not have("tiffload") or not os.path.isfile(TIF_FILE), reason="no tiff support, skipping test") def test_tiff(self): def tiff_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0]) assert im.width == 290 assert im.height == 442 assert im.bands == 3 self.file_loader("tiffload", TIF_FILE, tiff_valid) self.buffer_loader("tiffload_buffer", TIF_FILE, tiff_valid) if pyvips.at_least_libvips(8, 5): self.save_load_buffer("tiffsave_buffer", "tiffload_buffer", self.colour) self.save_load("%s.tif", self.mono) self.save_load("%s.tif", self.colour) self.save_load("%s.tif", self.cmyk) self.save_load("%s.tif", self.onebit) self.save_load_file(".tif", "[squash]", self.onebit, 0) self.save_load_file(".tif", "[miniswhite]", self.onebit, 0) self.save_load_file(".tif", "[squash,miniswhite]", self.onebit, 0) self.save_load_file(".tif", "[profile={0}]".format(SRGB_FILE), self.colour, 0) self.save_load_file(".tif", "[tile]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid,compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[bigtiff]", self.colour, 0) self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[tile,tile-width=256]", self.colour, 10) filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() x.set("orientation", 2) x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 2 filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() x.set("orientation", 2) x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 2 x.remove("orientation") filename = temp_filename(self.tempdir, '.tif') x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 1 filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() x.set("orientation", 6) x.write_to_file(filename) x1 = pyvips.Image.new_from_file(filename) x2 = pyvips.Image.new_from_file(filename, autorotate=True) assert x1.width == x2.height assert x1.height == x2.width # OME support in 8.5 if pyvips.at_least_libvips(8, 5): x = pyvips.Image.new_from_file(OME_FILE) assert x.width == 439 assert x.height == 167 page_height = x.height x = pyvips.Image.new_from_file(OME_FILE, n=-1) assert x.width == 439 assert x.height == page_height * 15 x = pyvips.Image.new_from_file(OME_FILE, page=1, n=-1) assert x.width == 439 assert x.height == page_height * 14 x = pyvips.Image.new_from_file(OME_FILE, page=1, n=2) assert x.width == 439 assert x.height == page_height * 2 x = pyvips.Image.new_from_file(OME_FILE, n=-1) assert x(0, 166)[0] == 96 assert x(0, 167)[0] == 0 assert x(0, 168)[0] == 1 filename = temp_filename(self.tempdir, '.tif') x.write_to_file(filename) x = pyvips.Image.new_from_file(filename, n=-1) assert x.width == 439 assert x.height == page_height * 15 assert x(0, 166)[0] == 96 assert x(0, 167)[0] == 0 assert x(0, 168)[0] == 1 # pyr save to buffer added in 8.6 if pyvips.at_least_libvips(8, 6): x = pyvips.Image.new_from_file(TIF_FILE) buf = x.tiffsave_buffer(tile=True, pyramid=True) filename = temp_filename(self.tempdir, '.tif') x.tiffsave(filename, tile=True, pyramid=True) with open(filename, 'rb') as f: buf2 = f.read() assert len(buf) == len(buf2) a = pyvips.Image.new_from_buffer(buf, "", page=2) b = pyvips.Image.new_from_buffer(buf2, "", page=2) assert a.width == b.width assert a.height == b.height assert a.avg() == b.avg() @pytest.mark.skipif(not have("magickload") or not os.path.isfile(BMP_FILE), reason="no magick support, skipping test") def test_magickload(self): def bmp_valid(im): a = im(100, 100) assert_almost_equal_objects(a, [227, 216, 201]) assert im.width == 1419 assert im.height == 1001 self.file_loader("magickload", BMP_FILE, bmp_valid) self.buffer_loader("magickload_buffer", BMP_FILE, bmp_valid) # we should have rgba for svg files im = pyvips.Image.magickload(SVG_FILE) assert im.bands == 4 # density should change size of generated svg im = pyvips.Image.magickload(SVG_FILE, density='100') width = im.width height = im.height im = pyvips.Image.magickload(SVG_FILE, density='200') # This seems to fail on travis, no idea why, some problem in their IM # perhaps # assert im.width == width * 2 # assert im.height == height * 2 # all-frames should load every frame of the animation # (though all-frames is deprecated) im = pyvips.Image.magickload(GIF_ANIM_FILE) width = im.width height = im.height im = pyvips.Image.magickload(GIF_ANIM_FILE, all_frames=True) assert im.width == width assert im.height == height * 5 # page/n let you pick a range of pages # 'n' param added in 8.5 if pyvips.at_least_libvips(8, 5): im = pyvips.Image.magickload(GIF_ANIM_FILE) width = im.width height = im.height im = pyvips.Image.magickload(GIF_ANIM_FILE, page=1, n=2) assert im.width == width assert im.height == height * 2 page_height = im.get("page-height") assert page_height == height # should work for dicom im = pyvips.Image.magickload(DICOM_FILE) assert im.width == 128 assert im.height == 128 # some IMs are 3 bands, some are 1, can't really test # assert im.bands == 1 # added in 8.7 if have("magicksave"): self.save_load_file(".bmp", "", self.colour, 0) self.save_load_buffer("magicksave_buffer", "magickload_buffer", self.colour, 0, format="BMP") self.save_load("%s.bmp", self.colour) @pytest.mark.skipif(not have("webpload") or not os.path.isfile(WEBP_FILE), reason="no webp support, skipping test") def test_webp(self): def webp_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [71, 166, 236]) assert im.width == 550 assert im.height == 368 assert im.bands == 3 self.file_loader("webpload", WEBP_FILE, webp_valid) self.buffer_loader("webpload_buffer", WEBP_FILE, webp_valid) self.save_load_buffer("webpsave_buffer", "webpload_buffer", self.colour, 60) self.save_load("%s.webp", self.colour) # test lossless mode im = pyvips.Image.new_from_file(WEBP_FILE) buf = im.webpsave_buffer(lossless=True) im2 = pyvips.Image.new_from_buffer(buf, "") assert im.avg() == im2.avg() # higher Q should mean a bigger buffer b1 = im.webpsave_buffer(Q=10) b2 = im.webpsave_buffer(Q=90) assert len(b2) > len(b1) # try saving an image with an ICC profile and reading it back ... if we # can do it, our webp supports metadata load/save buf = self.colour.webpsave_buffer() im = pyvips.Image.new_from_buffer(buf, "") if im.get_typeof("icc-profile-data") != 0: # verify that the profile comes back unharmed p1 = self.colour.get("icc-profile-data") p2 = im.get("icc-profile-data") assert p1 == p2 # add tests for exif, xmp, ipct # the exif test will need us to be able to walk the header, # we can't just check exif-data # we can test that exif changes change the output of webpsave # first make sure we have exif support z = pyvips.Image.new_from_file(JPEG_FILE) if z.get_typeof("exif-ifd0-Orientation") != 0: x = self.colour.copy() x.set("orientation", 6) buf = x.webpsave_buffer() y = pyvips.Image.new_from_buffer(buf, "") assert y.get("orientation") == 6 @pytest.mark.skipif(not have("analyzeload") or not os.path.isfile(ANALYZE_FILE), reason="no analyze support, skipping test") def test_analyzeload(self): def analyze_valid(im): a = im(10, 10) assert pytest.approx(a[0]) == 3335 assert im.width == 128 assert im.height == 8064 assert im.bands == 1 self.file_loader("analyzeload", ANALYZE_FILE, analyze_valid) @pytest.mark.skipif(not have("matload") or not os.path.isfile(MATLAB_FILE), reason="no matlab support, skipping test") def test_matload(self): def matlab_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0]) assert im.width == 290 assert im.height == 442 assert im.bands == 3 self.file_loader("matload", MATLAB_FILE, matlab_valid) @pytest.mark.skipif(not have("openexrload") or not os.path.isfile(EXR_FILE), reason="no openexr support, skipping test") def test_openexrload(self): def exr_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [0.124512, 0.159668, 0.040375, 1.0], threshold=0.00001) assert im.width == 610 assert im.height == 406 assert im.bands == 4 self.file_loader("openexrload", EXR_FILE, exr_valid) @pytest.mark.skipif(not have("fitsload") or not os.path.isfile(FITS_FILE), reason="no fits support, skipping test") def test_fitsload(self): def fits_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [-0.165013, -0.148553, 1.09122, -0.942242], threshold=0.00001) assert im.width == 200 assert im.height == 200 assert im.bands == 4 self.file_loader("fitsload", FITS_FILE, fits_valid) self.save_load("%s.fits", self.mono) @pytest.mark.skipif(not have("openslideload") or # noqa: E501 not os.path.isfile(OPENSLIDE_FILE), reason="no openslide support, skipping test") def test_openslideload(self): def openslide_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [244, 250, 243, 255]) assert im.width == 2220 assert im.height == 2967 assert im.bands == 4 self.file_loader("openslideload", OPENSLIDE_FILE, openslide_valid) @pytest.mark.skipif(not have("pdfload") or not os.path.isfile(PDF_FILE), reason="no pdf support, skipping test") def test_pdfload(self): def pdf_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [35, 31, 32, 255]) assert im.width == 1133 assert im.height == 680 assert im.bands == 4 self.file_loader("pdfload", PDF_FILE, pdf_valid) self.buffer_loader("pdfload_buffer", PDF_FILE, pdf_valid) im = pyvips.Image.new_from_file(PDF_FILE) x = pyvips.Image.new_from_file(PDF_FILE, scale=2) assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 im = pyvips.Image.new_from_file(PDF_FILE) x = pyvips.Image.new_from_file(PDF_FILE, dpi=144) assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 @pytest.mark.skipif(not have("gifload") or not os.path.isfile(GIF_FILE), reason="no gif support, skipping test") def test_gifload(self): def gif_valid(im): a = im(10, 10) assert_almost_equal_objects(a, [33]) assert im.width == 159 assert im.height == 203 assert im.bands == 1 self.file_loader("gifload", GIF_FILE, gif_valid) self.buffer_loader("gifload_buffer", GIF_FILE, gif_valid) # 'n' param added in 8.5 if pyvips.at_least_libvips(8, 5): x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE) x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=2) assert x2.height == 2 * x1.height page_height = x2.get("page-height") assert page_height == x1.height x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) assert x2.height == 5 * x1.height x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1) assert x2.height == 4 * x1.height @pytest.mark.skipif(not have("svgload") or not os.path.isfile(SVG_FILE), reason="no svg support, skipping test") def test_svgload(self): def svg_valid(im): a = im(10, 10) # some old rsvg versions are way, way off assert abs(a[0] - 79) < 2 assert abs(a[1] - 79) < 2 assert abs(a[2] - 132) < 2 assert abs(a[3] - 255) < 2 assert im.width == 288 assert im.height == 470 assert im.bands == 4 self.file_loader("svgload", SVG_FILE, svg_valid) self.buffer_loader("svgload_buffer", SVG_FILE, svg_valid) self.file_loader("svgload", SVGZ_FILE, svg_valid) self.buffer_loader("svgload_buffer", SVGZ_FILE, svg_valid) self.file_loader("svgload", SVG_GZ_FILE, svg_valid) im = pyvips.Image.new_from_file(SVG_FILE) x = pyvips.Image.new_from_file(SVG_FILE, scale=2) assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 im = pyvips.Image.new_from_file(SVG_FILE) x = pyvips.Image.new_from_file(SVG_FILE, dpi=144) assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 def test_csv(self): self.save_load("%s.csv", self.mono) def test_matrix(self): self.save_load("%s.mat", self.mono) @pytest.mark.skipif(not have("ppmload"), reason="no PPM support, skipping test") def test_ppm(self): self.save_load("%s.ppm", self.mono) self.save_load("%s.ppm", self.colour) @pytest.mark.skipif(not have("radload"), reason="no Radiance support, skipping test") def test_rad(self): self.save_load("%s.hdr", self.colour) self.save_buffer_tempfile("radsave_buffer", ".hdr", self.rad, max_diff=0) @pytest.mark.skipif(not have("dzsave"), reason="no dzsave support, skipping test") def test_dzsave(self): # dzsave is hard to test, there are so many options # test each option separately and hope they all function together # correctly # default deepzoom layout ... we must use png here, since we want to # test the overlap for equality filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, suffix=".png") # test horizontal overlap ... expect 256 step, overlap 1 x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") assert x.width == 255 y = pyvips.Image.new_from_file(filename + "_files/10/1_0.png") assert y.width == 256 # the right two columns of x should equal the left two columns of y left = x.crop(x.width - 2, 0, 2, x.height) right = y.crop(0, 0, 2, y.height) assert (left - right).abs().max() == 0 # test vertical overlap assert x.height == 255 y = pyvips.Image.new_from_file(filename + "_files/10/0_1.png") assert y.height == 256 # the bottom two rows of x should equal the top two rows of y top = x.crop(0, x.height - 2, x.width, 2) bottom = y.crop(0, 0, y.width, 2) assert (top - bottom).abs().max() == 0 # there should be a bottom layer x = pyvips.Image.new_from_file(filename + "_files/0/0_0.png") assert x.width == 1 assert x.height == 1 # 10 should be the final layer assert not os.path.isdir(filename + "_files/11") # default google layout filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, layout="google") # test bottom-right tile ... default is 256x256 tiles, overlap 0 x = pyvips.Image.new_from_file(filename + "/2/2/3.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/2/2/4.jpg") assert not os.path.exists(filename + "/3") x = pyvips.Image.new_from_file(filename + "/blank.png") assert x.width == 256 assert x.height == 256 # google layout with overlap ... verify that we clip correctly # overlap 1, 510x510 pixels, 256 pixel tiles, should be exactly 2x2 # tiles, though in fact the bottom and right edges will be white filename = temp_filename(self.tempdir, '') self.colour.crop(0, 0, 510, 510).dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/1/1.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/0/2/2.jpg") # with 511x511, it'll fit exactly into 2x2 -- we we actually generate # 3x3, since we output the overlaps # 8.6 revised the rules on overlaps, so don't test earlier than that if pyvips.base.at_least_libvips(8, 6): filename = temp_filename(self.tempdir, '') self.colour.crop(0, 0, 511, 511).dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/2/2.jpg") assert x.width == 256 assert x.height == 256 assert not os.path.exists(filename + "/0/3/3.jpg") # default zoomify layout filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, layout="zoomify") # 256x256 tiles, no overlap assert os.path.exists(filename + "/ImageProperties.xml") x = pyvips.Image.new_from_file(filename + "/TileGroup0/2-3-2.jpg") assert x.width == 256 assert x.height == 256 # test zip output filename = temp_filename(self.tempdir, '.zip') self.colour.dzsave(filename) # before 8.5.8, you needed a gc on pypy to flush small zip output to # disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() assert os.path.exists(filename) assert not os.path.exists(filename + "_files") assert not os.path.exists(filename + ".dzi") # test compressed zip output filename2 = temp_filename(self.tempdir, '.zip') self.colour.dzsave(filename2, compression=-1) # before 8.5.8, you needed a gc on pypy to flush small zip output to # disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() assert os.path.exists(filename2) assert os.path.getsize(filename2) < os.path.getsize(filename) # test suffix filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, suffix=".png") x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") assert x.width == 255 # test overlap filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, overlap=200) y = pyvips.Image.new_from_file(filename + "_files/10/1_1.jpeg") assert y.width == 654 # test tile-size filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, tile_size=512) y = pyvips.Image.new_from_file(filename + "_files/10/0_0.jpeg") assert y.width == 513 assert y.height == 513 # test save to memory buffer if have("dzsave_buffer"): filename = temp_filename(self.tempdir, '.zip') base = os.path.basename(filename) root, ext = os.path.splitext(base) self.colour.dzsave(filename) # before 8.5.8, you needed a gc on pypy to flush small zip # output to disc if not pyvips.base.at_least_libvips(8, 6): gc.collect() with open(filename, 'rb') as f: buf1 = f.read() buf2 = self.colour.dzsave_buffer(basename=root) assert len(buf1) == len(buf2)
def test_webp(self): def webp_valid(im): a = im(10, 10) # different webp versions use different rounding systems leading # to small variations assert_almost_equal_objects(a, [71, 166, 236], threshold=2) assert im.width == 550 assert im.height == 368 assert im.bands == 3 self.file_loader("webpload", WEBP_FILE, webp_valid) self.buffer_loader("webpload_buffer", WEBP_FILE, webp_valid) self.save_load_buffer("webpsave_buffer", "webpload_buffer", self.colour, 60) self.save_load("%s.webp", self.colour) # test lossless mode im = pyvips.Image.new_from_file(WEBP_FILE) buf = im.webpsave_buffer(lossless=True) im2 = pyvips.Image.new_from_buffer(buf, "") assert abs(im.avg() - im2.avg()) < 1 # higher Q should mean a bigger buffer b1 = im.webpsave_buffer(Q=10) b2 = im.webpsave_buffer(Q=90) assert len(b2) > len(b1) # try saving an image with an ICC profile and reading it back ... if we # can do it, our webp supports metadata load/save buf = self.colour.webpsave_buffer() im = pyvips.Image.new_from_buffer(buf, "") if im.get_typeof("icc-profile-data") != 0: # verify that the profile comes back unharmed p1 = self.colour.get("icc-profile-data") p2 = im.get("icc-profile-data") assert p1 == p2 # add tests for exif, xmp, ipct # the exif test will need us to be able to walk the header, # we can't just check exif-data # we can test that exif changes change the output of webpsave # first make sure we have exif support z = pyvips.Image.new_from_file(JPEG_FILE) if z.get_typeof("exif-ifd0-Orientation") != 0: x = self.colour.copy() x.set("orientation", 6) buf = x.webpsave_buffer() y = pyvips.Image.new_from_buffer(buf, "") assert y.get("orientation") == 6 # try converting an animated gif to webp ... can't do back to gif # again without IM support if have("gifload"): x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) w1 = x1.webpsave_buffer(Q=10) # our test gif has delay 0 for the first frame set in error, # when converting to WebP this should result in a 100ms delay. expected_delay = [100 if d <= 10 else d for d in x1.get("delay")] x2 = pyvips.Image.new_from_buffer(w1, "", n=-1) assert x1.width == x2.width assert x1.height == x2.height assert expected_delay == x2.get("delay") assert x1.get("page-height") == x2.get("page-height") assert x1.get("gif-loop") == x2.get("gif-loop") # WebP image that happens to contain the string "<svg" if have("svgload"): x = pyvips.Image.new_from_file(WEBP_LOOKS_LIKE_SVG_FILE) assert x.get("vips-loader") == "webpload" # Animated WebP roundtrip x = pyvips.Image.new_from_file(WEBP_ANIMATED_FILE, n=-1) assert x.width == 13 assert x.height == 16393 buf = x.webpsave_buffer()