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 rgb or rgba for svg files ... different versions of # IM handle this differently. GM even gives 1 band. im = pyvips.Image.magickload(SVG_FILE) assert im.bands == 3 or im.bands == 4 or im.bands == 1 # 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 im = pyvips.Image.magickload(GIF_ANIM_FILE) width = im.width height = im.height im = pyvips.Image.magickload(GIF_ANIM_FILE, n=-1) 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 # libvips has its own sniffer for ICO, test that with open(ICO_FILE, 'rb') as f: buf = f.read() im = pyvips.Image.new_from_buffer(buf, "") assert im.width == 16 assert im.height == 16
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_magickload(self): if pyvips.type_find("VipsForeign", "magickload") == 0 or \ not os.path.isfile(GIF_FILE): print("no magick support, skipping test") return def gif_valid(self, im): # some libMagick produce an RGB for this image, some a mono, some # rgba, some have a valid alpha, some don't :-( # therefore ... just test channel 0 a = im(10, 10)[0] self.assertAlmostEqual(a, 33) self.assertEqual(im.width, 159) self.assertEqual(im.height, 203) self.file_loader("magickload", GIF_FILE, gif_valid) self.buffer_loader("magickload_buffer", GIF_FILE, gif_valid) # we should have rgba for svg files im = pyvips.Image.magickload(SVG_FILE) self.assertEqual(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 # self.assertEqual(im.width, width * 2) # self.assertEqual(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) self.assertEqual(im.width, width) self.assertEqual(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) self.assertEqual(im.width, width) self.assertEqual(im.height, height * 2) page_height = im.get_value("page-height") self.assertEqual(page_height, height) # should work for dicom im = pyvips.Image.magickload(DICOM_FILE) self.assertEqual(im.width, 128) self.assertEqual(im.height, 128)
def resize(cls, im, *args, **kwargs): kernel = kwargs.pop('kernel', 'cubic') if at_least_libvips(8, 3): # resize with kernel added in 8.3 kwargs['kernel'] = kernel else: kwargs['interpolate'] = Interpolate.new('bi' + kernel) return im.resize(*args, **kwargs)
def get_args(self): args = [] if at_least_libvips(8, 7): p_names = ffi.new('char**[1]') p_flags = ffi.new('int*[1]') p_n_args = ffi.new('int[1]') result = vips_lib.vips_object_get_args(self.object, p_names, p_flags, p_n_args) if result != 0: raise Error('unable to get arguments from operation') p_names = p_names[0] p_flags = p_flags[0] n_args = p_n_args[0] for i in range(0, n_args): flags = p_flags[i] if (flags & _CONSTRUCT) != 0: name = _to_string(p_names[i]) # libvips uses '-' to separate parts of arg names, but we # need '_' for Python name = name.replace('-', '_') args.append([name, flags]) else: def add_construct(self, pspec, argument_class, argument_instance, a, b): flags = argument_class.flags if (flags & _CONSTRUCT) != 0: name = _to_string(pspec.name) # libvips uses '-' to separate parts of arg names, but we # need '_' for Python name = name.replace('-', '_') args.append([name, flags]) return ffi.NULL cb = ffi.callback('VipsArgumentMapFn', add_construct) vips_lib.vips_argument_map(self.object, cb, ffi.NULL, ffi.NULL) return args
def test_hist_local(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.hist_local(10, 10) assert im.width == im2.width assert im.height == im2.height assert im.avg() < im2.avg() assert im.deviate() < im2.deviate() if pyvips.at_least_libvips(8, 5): im3 = im.hist_local(10, 10, max_slope=3) assert im.width == im3.width assert im.height == im3.height assert im3.deviate() < im2.deviate()
def test_hist_local(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.hist_local(10, 10) self.assertEqual(im.width, im2.width) self.assertEqual(im.height, im2.height) self.assertTrue(im.avg() < im2.avg()) self.assertTrue(im.deviate() < im2.deviate()) if pyvips.at_least_libvips(8, 5): im3 = im.hist_local(10, 10, max_slope=3) self.assertEqual(im.width, im2.width) self.assertEqual(im.height, im2.height) self.assertTrue(im3.deviate() < im2.deviate())
def get_fields(self): """Get a list of all the metadata fields on an image. Returns: [string] """ names = [] if at_least_libvips(8, 5): array = vips_lib.vips_image_get_fields(self.pointer) i = 0 while array[i] != ffi.NULL: name = _to_string(array[i]) names.append(name) glib_lib.g_free(array[i]) i += 1 glib_lib.g_free(array) return names
def test_thumbnail(self): if not pyvips.at_least_libvips(8, 5): return im = pyvips.Image.thumbnail(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 = pyvips.Image.new_from_file(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 = pyvips.Image.thumbnail(JPEG_FILE, width) self.assertEqual(im.width, width) # should fit one of width or height im = pyvips.Image.thumbnail(JPEG_FILE, 100, height=300) self.assertEqual(im.width, 100) self.assertNotEqual(im.height, 300) im = pyvips.Image.thumbnail(JPEG_FILE, 300, height=100) self.assertNotEqual(im.width, 300) self.assertEqual(im.height, 100) # with @crop, should fit both width and height im = pyvips.Image.thumbnail(JPEG_FILE, 100, height=300, crop=True) self.assertEqual(im.width, 100) self.assertEqual(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) self.assertLess(abs(im1.avg() - im2.avg()), 1)
def fetch(self, x, y, w, h): """Fill a region with pixel data. Pixels are filled with data! Returns: Pixel data. Raises: :class:`.Error` """ if not at_least_libvips(8, 8): raise Error('libvips too old') psize = ffi.new('size_t *') pointer = vips_lib.vips_region_fetch(self.pointer, x, y, w, h, psize) if pointer == ffi.NULL: raise Error('unable to fetch from region') pointer = ffi.gc(pointer, glib_lib.g_free) return ffi.buffer(pointer, psize[0])
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
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 # our test gif has delay 0 for the first frame set in error assert x2.get("delay") == [0, 50, 50, 50, 50] x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1) assert x2.height == 4 * x1.height animation = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) filename = temp_filename(self.tempdir, '.png') animation.write_to_file(filename) # Uncomment to see output file # animation.write_to_file('cogs.png') assert filecmp.cmp(GIF_ANIM_EXPECTED_PNG_FILE, filename, shallow=False)
def __init__(self, operation_name): op = Operation.new_from_name(operation_name) self.description = op.get_description() self.flags = vips_lib.vips_operation_get_flags(op.pointer) # build a list of constructor arg [name, flags] pairs in arg order arguments = [] def add_args(name, flags): if (flags & _CONSTRUCT) != 0: # libvips uses '-' to separate parts of arg names, but we # need '_' for Python name = name.replace('-', '_') arguments.append([name, flags]) if at_least_libvips(8, 7): p_names = ffi.new('char**[1]') p_flags = ffi.new('int*[1]') p_n_args = ffi.new('int[1]') result = vips_lib.vips_object_get_args(op.vobject, p_names, p_flags, p_n_args) if result != 0: raise Error('unable to get arguments from operation') p_names = p_names[0] p_flags = p_flags[0] n_args = p_n_args[0] for i in range(0, n_args): add_args(_to_string(p_names[i]), p_flags[i]) else: def add_construct(self, pspec, argument_class, argument_instance, a, b): add_args(_to_string(pspec.name), argument_class.flags) return ffi.NULL cb = ffi.callback('VipsArgumentMapFn', add_construct) vips_lib.vips_argument_map(op.vobject, cb, ffi.NULL, ffi.NULL) # logger.debug('arguments = %s', self.arguments) # build a hash from arg name to detailed arg information self.details = {} for name, flags in arguments: self.details[name] = { "name": name, "flags": flags, "blurb": op.get_blurb(name), "type": op.get_typeof(name) } # lists of arg names by category self.required_input = [] self.optional_input = [] self.required_output = [] self.optional_output = [] for name, flags in arguments: if ((flags & _INPUT) != 0 and (flags & _REQUIRED) != 0 and (flags & _DEPRECATED) == 0): self.required_input.append(name) # required inputs which we MODIFY are also required outputs if (flags & _MODIFY) != 0: self.required_output.append(name) if ((flags & _OUTPUT) != 0 and (flags & _REQUIRED) != 0 and (flags & _DEPRECATED) == 0): self.required_output.append(name) # we let deprecated optional args through, but warn about them # if they get used, see below if ((flags & _INPUT) != 0 and (flags & _REQUIRED) == 0): self.optional_input.append(name) if ((flags & _OUTPUT) != 0 and (flags & _REQUIRED) == 0): self.optional_output.append(name) # find the first required input image arg, if any ... that will be self self.member_x = None for name in self.required_input: details = self.details[name] if details['type'] == GValue.image_type: self.member_x = name break # method args are required args, but without the image they are a # method on if self.member_x is not None: self.method_args = list(self.required_input) self.method_args.remove(self.member_x) else: self.method_args = self.required_input
def test_tiff(self): if pyvips.type_find("VipsForeign", "tiffload") == 0 or \ not os.path.isfile(TIF_FILE): print("no tiff support, skipping test") return def tiff_valid(self, im): a = im(10, 10) self.assertAlmostEqualObjects(a, [38671.0, 33914.0, 26762.0]) self.assertEqual(im.width, 290) self.assertEqual(im.height, 442) self.assertEqual(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_value("orientation", 2) x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get_value("orientation") self.assertEqual(y, 2) filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() x.set_value("orientation", 2) x.write_to_file(filename) x = pyvips.Image.new_from_file(filename) y = x.get_value("orientation") self.assertEqual(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_value("orientation") self.assertEqual(y, 1) filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() x.set_value("orientation", 6) x.write_to_file(filename) x1 = pyvips.Image.new_from_file(filename) x2 = pyvips.Image.new_from_file(filename, autorotate=True) self.assertEqual(x1.width, x2.height) self.assertEqual(x1.height, x2.width) # OME support in 8.5 if pyvips.at_least_libvips(8, 5): x = pyvips.Image.new_from_file(OME_FILE) self.assertEqual(x.width, 439) self.assertEqual(x.height, 167) page_height = x.height x = pyvips.Image.new_from_file(OME_FILE, n=-1) self.assertEqual(x.width, 439) self.assertEqual(x.height, page_height * 15) x = pyvips.Image.new_from_file(OME_FILE, page=1, n=-1) self.assertEqual(x.width, 439) self.assertEqual(x.height, page_height * 14) x = pyvips.Image.new_from_file(OME_FILE, page=1, n=2) self.assertEqual(x.width, 439) self.assertEqual(x.height, page_height * 2) x = pyvips.Image.new_from_file(OME_FILE, n=-1) self.assertEqual(x(0, 166)[0], 96) self.assertEqual(x(0, 167)[0], 0) self.assertEqual(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) self.assertEqual(x.width, 439) self.assertEqual(x.height, page_height * 15) self.assertEqual(x(0, 166)[0], 96) self.assertEqual(x(0, 167)[0], 0) self.assertEqual(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() self.assertEqual(len(buf), len(buf2)) a = pyvips.Image.new_from_buffer(buf, "", page=2) b = pyvips.Image.new_from_buffer(buf2, "", page=2) self.assertEqual(a.width, b.width) self.assertEqual(a.height, b.height) self.assertEqual(a.avg(), b.avg())
def height(self): """Height of pixels held by region.""" if not at_least_libvips(8, 8): raise Error('libvips too old') return vips_lib.vips_region_height(self.pointer)
def width(self): """Width of pixels held by region.""" if not at_least_libvips(8, 8): raise Error('libvips too old') return vips_lib.vips_region_width(self.pointer)
class TestConnections: @classmethod def setup_class(cls): cls.tempdir = tempfile.mkdtemp() @skip_if_no('jpegload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_connection(self): source = pyvips.Source.new_from_file(JPEG_FILE) image = pyvips.Image.new_from_source(source, '', access='sequential') filename = temp_filename(self.tempdir, '.png') target = pyvips.Target.new_to_file(filename) image.write_to_target(target, '.png') image = pyvips.Image.new_from_file(JPEG_FILE, access='sequential') image2 = pyvips.Image.new_from_file(filename, access='sequential') assert (image - image2).abs().max() < 10 @skip_if_no('jpegload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_source_custom_no_seek(self): input_file = open(JPEG_FILE, "rb") def read_handler(size): return input_file.read(size) source = pyvips.SourceCustom() source.on_read(read_handler) image = pyvips.Image.new_from_source(source, '', access='sequential') image2 = pyvips.Image.new_from_file(JPEG_FILE, access='sequential') assert (image - image2).abs().max() == 0 @skip_if_no('jpegload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_source_custom(self): input_file = open(JPEG_FILE, "rb") def read_handler(size): return input_file.read(size) def seek_handler(offset, whence): input_file.seek(offset, whence) return input_file.tell() source = pyvips.SourceCustom() source.on_read(read_handler) source.on_seek(seek_handler) image = pyvips.Image.new_from_source(source, '', access='sequential') image2 = pyvips.Image.new_from_file(JPEG_FILE, access='sequential') assert (image - image2).abs().max() == 0 @skip_if_no('jpegload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_target_custom(self): filename = temp_filename(self.tempdir, '.png') output_file = open(filename, "wb") def write_handler(chunk): return output_file.write(chunk) def finish_handler(): output_file.close() target = pyvips.TargetCustom() target.on_write(write_handler) target.on_finish(finish_handler) image = pyvips.Image.new_from_file(JPEG_FILE, access='sequential') image.write_to_target(target, '.png') image = pyvips.Image.new_from_file(JPEG_FILE, access='sequential') image2 = pyvips.Image.new_from_file(filename, access='sequential') assert (image - image2).abs().max() == 0 # test webp as well, since that maps the stream rather than using read @skip_if_no('webpload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_source_custom_webp_no_seek(self): input_file = open(WEBP_FILE, "rb") def read_handler(size): return input_file.read(size) source = pyvips.SourceCustom() source.on_read(read_handler) image = pyvips.Image.new_from_source(source, '', access='sequential') image2 = pyvips.Image.new_from_file(WEBP_FILE, access='sequential') assert (image - image2).abs().max() == 0 @skip_if_no('webpload') @pytest.mark.skipif(not pyvips.at_least_libvips(8, 9), reason="requires libvips >= 8.9") def test_source_custom_webp(self): input_file = open(WEBP_FILE, "rb") def read_handler(size): return input_file.read(size) def seek_handler(offset, whence): input_file.seek(offset, whence) return input_file.tell() source = pyvips.SourceCustom() source.on_read(read_handler) source.on_seek(seek_handler) image = pyvips.Image.new_from_source(source, '', access='sequential') image2 = pyvips.Image.new_from_file(WEBP_FILE, access='sequential') assert (image - image2).abs().max() == 0
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 rgb or rgba for svg files ... different versions of # IM handle this differently im = pyvips.Image.magickload(SVG_FILE) assert im.bands == 3 or 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 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) # libvips has its own sniffer for ICO, test that with open(ICO_FILE, 'rb') as f: buf = f.read() im = pyvips.Image.new_from_buffer(buf, "") assert im.width == 16 assert im.height == 16
class GValue(object): """Wrap GValue in a Python class. This class wraps :class:`.GValue` in a convenient interface. You can use instances of this class to get and set :class:`.GObject` properties. On construction, :class:`.GValue` is all zero (empty). You can pass it to a get function to have it filled by :class:`.GObject`, or use init to set a type, set to set a value, then use it to set an object property. GValue lifetime is managed automatically. """ # look up some common gtypes at init for speed gbool_type = type_from_name('gboolean') gint_type = type_from_name('gint') gdouble_type = type_from_name('gdouble') gstr_type = type_from_name('gchararray') genum_type = type_from_name('GEnum') gflags_type = type_from_name('GFlags') gobject_type = type_from_name('GObject') image_type = type_from_name('VipsImage') array_int_type = type_from_name('VipsArrayInt') array_double_type = type_from_name('VipsArrayDouble') array_image_type = type_from_name('VipsArrayImage') refstr_type = type_from_name('VipsRefString') blob_type = type_from_name('VipsBlob') pyvips.vips_lib.vips_band_format_get_type() format_type = type_from_name('VipsBandFormat') if at_least_libvips(8, 6): pyvips.vips_lib.vips_blend_mode_get_type() blend_mode_type = type_from_name('VipsBlendMode') # map a gtype to the name of the corresponding Python type _gtype_to_python = { gbool_type: 'bool', gint_type: 'int', gdouble_type: 'float', gstr_type: 'str', refstr_type: 'str', genum_type: 'str', gflags_type: 'int', gobject_type: 'GObject', image_type: 'Image', array_int_type: 'list[int]', array_double_type: 'list[float]', array_image_type: 'list[Image]', blob_type: 'str' } @staticmethod def gtype_to_python(gtype): """Map a gtype to the name of the Python type we use to represent it. """ fundamental = gobject_lib.g_type_fundamental(gtype) if gtype in GValue._gtype_to_python: return GValue._gtype_to_python[gtype] if fundamental in GValue._gtype_to_python: return GValue._gtype_to_python[fundamental] return '<unknown type>' @staticmethod def to_enum(gtype, value): """Turn a string into an enum value ready to be passed into libvips. """ if isinstance(value, basestring if _is_PY2 else str): enum_value = vips_lib.vips_enum_from_nick(b'pyvips', gtype, _to_bytes(value)) if enum_value < 0: raise Error('no value {0} in gtype {1} ({2})'.format( value, type_name(gtype), gtype)) else: enum_value = value return enum_value @staticmethod def from_enum(gtype, enum_value): """Turn an int back into an enum string. """ pointer = vips_lib.vips_enum_nick(gtype, enum_value) if pointer == ffi.NULL: raise Error('value not in enum') return _to_string(pointer) def __init__(self): # allocate memory for the gvalue which will be freed on GC self.pointer = ffi.new('GValue *') # logger.debug('GValue.__init__: pointer = %s', self.pointer) # and tag it to be unset on GC as well self.gvalue = ffi.gc(self.pointer, gobject_lib.g_value_unset) # logger.debug('GValue.__init__: gvalue = %s', self.gvalue) def set_type(self, gtype): """Set the type of a GValue. GValues have a set type, fixed at creation time. Use set_type to set the type of a GValue before assigning to it. GTypes are 32 or 64-bit integers (depending on the platform). See type_find. """ gobject_lib.g_value_init(self.gvalue, gtype) def set(self, value): """Set a GValue. The value is converted to the type of the GValue, if possible, and assigned. """ # logger.debug('GValue.set: value = %s', value) gtype = self.gvalue.g_type fundamental = gobject_lib.g_type_fundamental(gtype) if gtype == GValue.gbool_type: gobject_lib.g_value_set_boolean(self.gvalue, value) elif gtype == GValue.gint_type: gobject_lib.g_value_set_int(self.gvalue, int(value)) elif gtype == GValue.gdouble_type: gobject_lib.g_value_set_double(self.gvalue, value) elif fundamental == GValue.genum_type: gobject_lib.g_value_set_enum(self.gvalue, GValue.to_enum(gtype, value)) elif fundamental == GValue.gflags_type: gobject_lib.g_value_set_flags(self.gvalue, value) elif gtype == GValue.gstr_type: gobject_lib.g_value_set_string(self.gvalue, _to_bytes(value)) elif gtype == GValue.refstr_type: vips_lib.vips_value_set_ref_string(self.gvalue, _to_bytes(value)) elif fundamental == GValue.gobject_type: gobject_lib.g_value_set_object(self.gvalue, value.pointer) elif gtype == GValue.array_int_type: if isinstance(value, numbers.Number): value = [value] array = ffi.new('int[]', value) vips_lib.vips_value_set_array_int(self.gvalue, array, len(value)) elif gtype == GValue.array_double_type: if isinstance(value, numbers.Number): value = [value] array = ffi.new('double[]', value) vips_lib.vips_value_set_array_double(self.gvalue, array, len(value)) elif gtype == GValue.array_image_type: if isinstance(value, pyvips.Image): value = [value] vips_lib.vips_value_set_array_image(self.gvalue, len(value)) array = vips_lib.vips_value_get_array_image(self.gvalue, ffi.NULL) for i, image in enumerate(value): gobject_lib.g_object_ref(image.pointer) array[i] = image.pointer elif gtype == GValue.blob_type: # we need to set the blob to a copy of the string that vips_lib # can own memory = glib_lib.g_malloc(len(value)) ffi.memmove(memory, value, len(value)) # this is horrible! # # * in API mode, we must have 8.6+ and use set_blob_free to # attach the metadata to avoid leaks # * pre-8.6, we just pass a NULL free pointer and live with the # leak # # this is because in API mode you can't pass a builtin (what # vips_lib.g_free() becomes) as a parameter to ffi.callback(), and # vips_value_set_blob() needs a callback for arg 2 # # additionally, you can't make a py def which calls g_free() and # then use the py def in the callback, since libvips will trigger # these functions during cleanup, and py will have shut down by # then and you'll get a segv if at_least_libvips(8, 6): vips_lib.vips_value_set_blob_free(self.gvalue, memory, len(value)) else: if pyvips.API_mode: vips_lib.vips_value_set_blob(self.gvalue, ffi.NULL, memory, len(value)) else: vips_lib.vips_value_set_blob(self.gvalue, glib_lib.g_free, memory, len(value)) else: raise Error( 'unsupported gtype for set {0}, fundamental {1}'.format( type_name(gtype), type_name(fundamental))) def get(self): """Get the contents of a GValue. The contents of the GValue are read out as a Python type. """ # logger.debug('GValue.get: self = %s', self) gtype = self.gvalue.g_type fundamental = gobject_lib.g_type_fundamental(gtype) result = None if gtype == GValue.gbool_type: result = bool(gobject_lib.g_value_get_boolean(self.gvalue)) elif gtype == GValue.gint_type: result = gobject_lib.g_value_get_int(self.gvalue) elif gtype == GValue.gdouble_type: result = gobject_lib.g_value_get_double(self.gvalue) elif fundamental == GValue.genum_type: return GValue.from_enum(gtype, gobject_lib.g_value_get_enum(self.gvalue)) elif fundamental == GValue.gflags_type: result = gobject_lib.g_value_get_flags(self.gvalue) elif gtype == GValue.gstr_type: pointer = gobject_lib.g_value_get_string(self.gvalue) if pointer != ffi.NULL: result = _to_string(pointer) elif gtype == GValue.refstr_type: psize = ffi.new('size_t *') pointer = vips_lib.vips_value_get_ref_string(self.gvalue, psize) # psize[0] will be number of bytes in string, but just assume it's # NULL-terminated result = _to_string(pointer) elif gtype == GValue.image_type: # g_value_get_object() will not add a ref ... that is # held by the gvalue go = gobject_lib.g_value_get_object(self.gvalue) vi = ffi.cast('VipsImage *', go) # we want a ref that will last with the life of the vimage: # this ref is matched by the unref that's attached to finalize # by Image() gobject_lib.g_object_ref(go) result = pyvips.Image(vi) elif gtype == GValue.array_int_type: pint = ffi.new('int *') array = vips_lib.vips_value_get_array_int(self.gvalue, pint) result = [] for i in range(0, pint[0]): result.append(array[i]) elif gtype == GValue.array_double_type: pint = ffi.new('int *') array = vips_lib.vips_value_get_array_double(self.gvalue, pint) result = [] for i in range(0, pint[0]): result.append(array[i]) elif gtype == GValue.array_image_type: pint = ffi.new('int *') array = vips_lib.vips_value_get_array_image(self.gvalue, pint) result = [] for i in range(0, pint[0]): vi = array[i] gobject_lib.g_object_ref(vi) image = pyvips.Image(vi) result.append(image) elif gtype == GValue.blob_type: psize = ffi.new('size_t *') array = vips_lib.vips_value_get_blob(self.gvalue, psize) buf = ffi.cast('char*', array) result = ffi.unpack(buf, psize[0]) else: raise Error('unsupported gtype for get {0}'.format( type_name(gtype))) return result
def _marshal_image_progress(vi, pointer, handle): gobject_lib.g_object_ref(vi) image = pyvips.Image(vi) callback = ffi.from_handle(handle) progress = ffi.cast('VipsProgress*', pointer) callback(image, progress) _marshal_image_progress_cb = \ ffi.cast('GCallback', _marshal_image_progress) _marshalers = { 'preeval': _marshal_image_progress_cb, 'eval': _marshal_image_progress_cb, 'posteval': _marshal_image_progress_cb, } if at_least_libvips(8, 9): if pyvips.API_mode: @ffi.def_extern() def _marshal_read(source_custom, pointer, length, handle): buf = ffi.buffer(pointer, length) callback = ffi.from_handle(handle) return callback(buf) _marshal_read_cb = ffi.cast('GCallback', gobject_lib._marshal_read) else: @ffi.callback('gint64(VipsSourceCustom*, void*, gint64, void*)') def _marshal_read(source_custom, pointer, length, handle): buf = ffi.buffer(pointer, length) callback = ffi.from_handle(handle)
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 def test_resize_logo3__lanczos3(self): # 2382x711 -> 328x711 # 13.77%x100% # self.resize_and_sharpen(IMAGES + '/logo3.png', 328.0, 711.0) self.resize_and_sharpen(IMAGES + '/logo3.png', 328.0) def test_resize_and_sharpen_zetta(self): self.resize_and_sharpen(IMAGES + '/zetta.png', 436.0) def test_sharpen_resized_by_magick_olhos(self): # 828 × 322 -> 402 x 156 self.sharpen(IMAGES + '/olhos-resized-by-magick.png') def test_resize_and_sharpen_world_leaders(self): # w_640,h_416 self.resize_and_sharpen(IMAGES + '/world-leaders.jpg', 640.0, 416.0) def test_resize_and_sharpen_olhos(self): # 828 × 322 -> 402 x 156 self.resize_and_sharpen(IMAGES + '/olhos.png', 402.0, 156.0) def test_resize_and_sharpen_olhos__only_horizontal(self): # 828 × 322 -> 402 x 322 self.resize_and_sharpen(IMAGES + '/olhos-h.png', 402.0, 322.0) def test_resize_and_sharpen_olhos__only_vertical(self): # 828 × 322 -> 828 × 156 self.resize_and_sharpen(IMAGES + '/olhos-v.png', 828.0, 156.0) def test_resize_and_sharpen_40x40(self): # 828 × 322 -> 402 x 156 self.resize_and_sharpen(IMAGES + '/40x40.png', 39.0) def test_resize_and_sharpen_40x40__only_horizontal(self): # 828 × 322 -> 402 x 322 self.resize_and_sharpen(IMAGES + '/40x40-h.png', 39.0, 40.0) def test_resize_and_sharpen_40x40__only_vertical(self): # 828 × 322 -> 828 × 156 self.resize_and_sharpen(IMAGES + '/40x40-v.png', 40.0, 39.0) def test_resize_and_sharpen_tiny(self): self.resize_and_sharpen(IMAGES + '/4x4.png', 3.0) def test_resize_and_sharpen_two_strip(self): self.resize_and_sharpen(IMAGES + '/two-strip.png', 16.0) def test_resize_and_sharpen_1024_to_16(self): for i in range(1): print('----- Iteration %s' % i) self.resize_and_sharpen(IMAGES + '/1024x1024.png', 16.0) @staticmethod def resize_and_sharpen(filename, new_width, new_height=None): _, ext = os.path.splitext(filename) im = pyvips.Image.new_from_file(filename) new_height = new_height or im.height * new_width / im.width print('new height %s' % new_height) # im = im.colourspace('rgb16') print('new_width / im.width=', new_width / im.width) im = im.resize(new_width / im.width, vscale=new_height / im.height) print('Writing resized') im.write_to_file('%s.resized-lanczos%s' % (filename, ext)) im = im.sharpen(mode='rgb', sigma=0.66, m2=1.0, x1=1.0) im.write_to_file('%s.resized-lanczos-sharpened%s' % (filename, ext)) @staticmethod def sharpen(filename): im = pyvips.Image.new_from_file(filename) im = im.colourspace('rgb16') im = im.sharpen(mode='rgb', sigma=0.66, m2=1.0, x1=1.0) im.write_to_file('%s-sharpened.png' % filename) def test_thumbnail_logo3(self): filename = IMAGES + '/logo3.png' im = pyvips.Image.new_from_file(filename) im = im.thumbnail_image(328, linear=True) im.write_to_file('%s.thumbnail-linear.png' % filename) im = im.sharpen(mode='rgb', sigma=0.66, m2=1.0, x1=1.0) im.write_to_file('%s.thumbnail-linear-sharpened.png' % filename) 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 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) # thumb should be portrait assert thumb.width < thumb.height assert thumb.height == 100 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() < 40 # 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()
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) assert im2.width == round(im.width / 4.0) assert im2.height == round(im.height / 4.0) # test geometry rounding corner case im = pyvips.Image.black(100, 1) x = im.resize(0.5) assert x.width == 50 assert x.height == 1 def test_shrink(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.shrink(4, 4) assert im2.width == round(im.width / 4.0) assert im2.height == round(im.height / 4.0) assert abs(im.avg() - im2.avg()) < 1 im2 = im.shrink(2.5, 2.5) assert im2.width == round(im.width / 2.5) assert im2.height == round(im.height / 2.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.width == 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 width in range(1000, 1, -13): im = pyvips.Image.thumbnail(JPEG_FILE, width) assert im.width == width # 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 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() < 20
class TestIofuncs: # test the vips7 filename splitter ... this is very fragile and annoying # code with lots of cases def test_split7(self): def split(path): filename7 = pyvips.path_filename7(path) mode7 = pyvips.path_mode7(path) return [filename7, mode7] cases = [[ "c:\\silly:dir:name\\fr:ed.tif:jpeg:95,,,,c:\\icc\\srgb.icc", ["c:\\silly:dir:name\\fr:ed.tif", "jpeg:95,,,,c:\\icc\\srgb.icc"] ], ["I180:", ["I180", ""]], ["c:\\silly:", ["c:\\silly", ""]], [ "c:\\program files\\x:hello", ["c:\\program files\\x", "hello"] ], [ "C:\\fixtures\\2569067123_aca715a2ee_o.jpg", ["C:\\fixtures\\2569067123_aca715a2ee_o.jpg", ""] ]] for case in cases: assert_equal_objects(split(case[0]), case[1]) def test_new_from_image(self): im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True) im2 = im.new_from_image(12) assert im2.width == im.width assert im2.height == im.height assert im2.interpretation == im.interpretation assert im2.format == im.format assert im2.xres == im.xres assert im2.yres == im.yres assert im2.xoffset == im.xoffset assert im2.yoffset == im.yoffset assert im2.bands == 1 assert im2.avg() == 12 im2 = im.new_from_image([1, 2, 3]) assert im2.bands == 3 assert im2.avg() == 2 def test_new_from_memory(self): s = bytearray(200) im = pyvips.Image.new_from_memory(s, 20, 10, 1, 'uchar') assert im.width == 20 assert im.height == 10 assert im.format == 'uchar' assert im.bands == 1 assert im.avg() == 0 im += 10 assert im.avg() == 10 @pytest.mark.skipif(not pyvips.at_least_libvips(8, 5), reason="requires libvips >= 8.5") def test_get_fields(self): im = pyvips.Image.black(10, 10) fields = im.get_fields() # we might add more fields later assert len(fields) > 10 assert fields[0] == 'width' def test_write_to_memory(self): s = bytearray(200) im = pyvips.Image.new_from_memory(s, 20, 10, 1, 'uchar') t = im.write_to_memory() assert s == t
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 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 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() # region-shrink added in 8.7 x = pyvips.Image.new_from_file(TIF_FILE) buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mean") buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mode") buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="median")
def set(self, value): """Set a GValue. The value is converted to the type of the GValue, if possible, and assigned. """ # logger.debug('GValue.set: value = %s', value) gtype = self.gvalue.g_type fundamental = gobject_lib.g_type_fundamental(gtype) if gtype == GValue.gbool_type: gobject_lib.g_value_set_boolean(self.gvalue, value) elif gtype == GValue.gint_type: gobject_lib.g_value_set_int(self.gvalue, int(value)) elif gtype == GValue.gdouble_type: gobject_lib.g_value_set_double(self.gvalue, value) elif fundamental == GValue.genum_type: gobject_lib.g_value_set_enum(self.gvalue, GValue.to_enum(gtype, value)) elif fundamental == GValue.gflags_type: gobject_lib.g_value_set_flags(self.gvalue, value) elif gtype == GValue.gstr_type: gobject_lib.g_value_set_string(self.gvalue, _to_bytes(value)) elif gtype == GValue.refstr_type: vips_lib.vips_value_set_ref_string(self.gvalue, _to_bytes(value)) elif fundamental == GValue.gobject_type: gobject_lib.g_value_set_object(self.gvalue, value.pointer) elif gtype == GValue.array_int_type: if isinstance(value, numbers.Number): value = [value] array = ffi.new('int[]', value) vips_lib.vips_value_set_array_int(self.gvalue, array, len(value)) elif gtype == GValue.array_double_type: if isinstance(value, numbers.Number): value = [value] array = ffi.new('double[]', value) vips_lib.vips_value_set_array_double(self.gvalue, array, len(value)) elif gtype == GValue.array_image_type: if isinstance(value, pyvips.Image): value = [value] vips_lib.vips_value_set_array_image(self.gvalue, len(value)) array = vips_lib.vips_value_get_array_image(self.gvalue, ffi.NULL) for i, image in enumerate(value): gobject_lib.g_object_ref(image.pointer) array[i] = image.pointer elif gtype == GValue.blob_type: # we need to set the blob to a copy of the string that vips_lib # can own memory = glib_lib.g_malloc(len(value)) ffi.memmove(memory, value, len(value)) # this is horrible! # # * in API mode, we must have 8.6+ and use set_blob_free to # attach the metadata to avoid leaks # * pre-8.6, we just pass a NULL free pointer and live with the # leak # # this is because in API mode you can't pass a builtin (what # vips_lib.g_free() becomes) as a parameter to ffi.callback(), and # vips_value_set_blob() needs a callback for arg 2 # # additionally, you can't make a py def which calls g_free() and # then use the py def in the callback, since libvips will trigger # these functions during cleanup, and py will have shut down by # then and you'll get a segv if at_least_libvips(8, 6): vips_lib.vips_value_set_blob_free(self.gvalue, memory, len(value)) else: if pyvips.API_mode: vips_lib.vips_value_set_blob(self.gvalue, ffi.NULL, memory, len(value)) else: vips_lib.vips_value_set_blob(self.gvalue, glib_lib.g_free, memory, len(value)) else: raise Error( 'unsupported gtype for set {0}, fundamental {1}'.format( type_name(gtype), type_name(fundamental)))
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 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()
const char* vips_value_get_ref_string (const GValue* value, size_t* length); void* g_value_get_object (GValue* value); double* vips_value_get_array_double (const GValue* value, int* n); int* vips_value_get_array_int (const GValue* value, int* n); VipsImage** vips_value_get_array_image (const GValue* value, int* n); void* vips_value_get_blob (const GValue* value, size_t* length); // need to make some of these by hand GType vips_interpretation_get_type (void); GType vips_operation_flags_get_type (void); GType vips_band_format_get_type (void); ''') if at_least_libvips(8, 6): ffi.cdef(''' GType vips_blend_mode_get_type (void); ''') class GValue(object): """Wrap GValue in a Python class. This class wraps :class:`.GValue` in a convenient interface. You can use instances of this class to get and set :class:`.GObject` properties. On construction, :class:`.GValue` is all zero (empty). You can pass it to a get function to have it filled by :class:`.GObject`, or use init to set a type, set to set a value, then use it to set an object property.