1# -*- coding: utf-8 -*- 2# 3# These tests cover the basic I/O & pythonic interfaces of the Image class. 4# 5import codecs 6import io 7import os 8import os.path 9import shutil 10import struct 11import sys 12import tempfile 13 14from pytest import mark, raises 15 16from wand.image import ClosedImageError, Image 17from wand.color import Color 18from wand.compat import PY3, text, text_type 19 20try: 21 filesystem_encoding = sys.getfilesystemencoding() 22except RuntimeError: 23 unicode_filesystem_encoding = False 24else: 25 try: 26 codec_info = codecs.lookup(filesystem_encoding) 27 except LookupError: 28 unicode_filesystem_encoding = False 29 else: 30 unicode_filesystem_encoding = codec_info.name in ( 31 'utf-8', 'utf-16', 'utf-16-be', 'utf-16-le', 32 'utf-32', 'utf-32-be', 'utf-32-le', 33 'mbcs' # for Windows 34 ) 35 36try: 37 import numpy as np 38except ImportError: 39 np = None 40 41 42def test_empty_image(): 43 with Image() as img: 44 assert img.size == (0, 0) 45 assert repr(img) == '<wand.image.Image: (empty)>' 46 47 48def test_image_invalid_params(): 49 with raises(TypeError): 50 Image(image=Image(), width=100, height=100) 51 with raises(TypeError): 52 Image(image=Image(), blob=b"blob") 53 with raises(TypeError): 54 Image(image=b"blob") 55 56 57def test_blank_image(): 58 gray = Color('#ccc') 59 transparent = Color('transparent') 60 with raises(ValueError): 61 Image(width=0, height=0) 62 with Image(width=20, height=10) as img: 63 assert img[10, 5] == transparent 64 with Image(width=20, height=10, background=gray) as img: 65 assert img.size == (20, 10) 66 assert img[10, 5] == gray 67 with Image(width=20, height=10, background='#ccc') as img: 68 assert img.size == (20, 10) 69 assert img[10, 5] == gray 70 71 72def test_raw_image(fx_asset): 73 b = b"".join([struct.pack("BBB", i, j, 0) 74 for i in range(256) for j in range(256)]) 75 with raises(ValueError): 76 Image(blob=b, depth=8, width=0, height=0, format="RGB") 77 with raises(TypeError): 78 Image(blob=b, depth=8, width=256, height=256, format=1) 79 with Image(blob=b, depth=8, width=256, height=256, format="RGB") as img: 80 assert img.size == (256, 256) 81 assert img[0, 0] == Color('#000000') 82 assert img[255, 255] == Color('#ffff00') 83 assert img[64, 128] == Color('#804000') 84 with Image(filename=str(fx_asset.join('blob.rgb')), 85 depth=8, width=256, height=256, format="RGB") as img: 86 assert img.size == (256, 256) 87 assert img[0, 0] == Color('#000000') 88 assert img[255, 255] == Color('#ffff00') 89 assert img[64, 128] == Color('#804000') 90 91 92def test_clear_image(fx_asset): 93 with Image() as img: 94 img.read(filename=str(fx_asset.join('mona-lisa.jpg'))) 95 assert img.size == (402, 599) 96 img.clear() 97 assert img.size == (0, 0) 98 img.read(filename=str(fx_asset.join('beach.jpg'))) 99 assert img.size == (800, 600) 100 101 102def test_read_from_filename(fx_asset): 103 with Image() as img: 104 img.read(filename=str(fx_asset.join('mona-lisa.jpg'))) 105 assert img.width == 402 106 img.clear() 107 with fx_asset.join('mona-lisa.jpg').open('rb') as f: 108 img.read(file=f) 109 assert img.width == 402 110 img.clear() 111 blob = fx_asset.join('mona-lisa.jpg').read('rb') 112 img.read(blob=blob) 113 assert img.width == 402 114 115 116@mark.skipif(not unicode_filesystem_encoding, 117 reason='Unicode filesystem encoding needed') 118def test_read_from_unicode_filename(fx_asset, tmpdir): 119 """https://github.com/emcconville/wand/issues/122""" 120 filename = '모나리자.jpg' 121 if not PY3: 122 filename = filename.decode('utf-8') 123 path = os.path.join(text_type(tmpdir), filename) # workaround py.path bug 124 shutil.copyfile(str(fx_asset.join('mona-lisa.jpg')), path) 125 with Image() as img: 126 img.read(filename=text(path)) 127 assert img.width == 402 128 129 130def test_read_with_colorspace(fx_asset): 131 fpath = str(fx_asset.join('cmyk.jpg')) 132 with Image(filename=fpath, 133 colorspace='srgb', 134 units='pixelspercentimeter') as img: 135 assert img.units == 'pixelspercentimeter' 136 137 138def test_read_with_extract(): 139 with Image(filename='rose:', extract="10x10+10+10") as img: 140 assert (10, 10) == img.size 141 with Image() as img: 142 img.read(filename='rose:', extract="10x10+10+10") 143 assert (10, 10) == img.size 144 145 146def test_new_from_file(fx_asset): 147 """Opens an image from the file object.""" 148 with fx_asset.join('mona-lisa.jpg').open('rb') as f: 149 with Image(file=f) as img: 150 assert img.width == 402 151 with raises(ClosedImageError): 152 img.wand 153 strio = io.BytesIO(fx_asset.join('mona-lisa.jpg').read('rb')) 154 with Image(file=strio) as img: 155 assert img.width == 402 156 strio.close() 157 with raises(ClosedImageError): 158 img.wand 159 with raises(TypeError): 160 Image(file='not file object') 161 162 163def test_new_from_filename(fx_asset): 164 """Opens an image through its filename.""" 165 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 166 assert img.width == 402 167 with raises(ClosedImageError): 168 img.wand 169 with raises(IOError): 170 Image(filename=str(fx_asset.join('not-exists.jpg'))) 171 172 173@mark.skipif(not unicode_filesystem_encoding, 174 reason='Unicode filesystem encoding needed') 175def test_new_from_unicode_filename(fx_asset, tmpdir): 176 """https://github.com/emcconville/wand/issues/122""" 177 filename = '모나리자.jpg' 178 if not PY3: 179 filename = filename.decode('utf-8') 180 path = os.path.join(text_type(tmpdir), filename) # workaround py.path bug 181 shutil.copyfile(str(fx_asset.join('mona-lisa.jpg')), path) 182 with Image(filename=text(path)) as img: 183 assert img.width == 402 184 185 186def test_new_from_blob(fx_asset): 187 """Opens an image from blob.""" 188 blob = fx_asset.join('mona-lisa.jpg').read('rb') 189 with Image(blob=blob) as img: 190 assert img.width == 402 191 with raises(ClosedImageError): 192 img.wand 193 194 195def test_new_with_format(fx_asset): 196 blob = fx_asset.join('google.ico').read('rb') 197 with raises(Exception): 198 Image(blob=blob) 199 with Image(blob=blob, format='ico') as img: 200 assert img.size == (16, 16) 201 202 203def test_new_from_pseudo(fx_asset): 204 with Image() as img: 205 img.pseudo(10, 10, 'xc:none') 206 assert img.size == (10, 10) 207 208 209def test_clone(fx_asset): 210 """Clones the existing image.""" 211 funcs = (lambda img: Image(image=img), 212 lambda img: img.clone()) 213 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 214 for func in funcs: 215 with func(img) as cloned: 216 assert img.wand is not cloned.wand 217 assert img.size == cloned.size 218 with raises(ClosedImageError): 219 cloned.wand 220 with raises(ClosedImageError): 221 img.wand 222 223 224def test_image_add(): 225 with Image(filename='rose:') as a: 226 with Image(filename='rose:') as b: 227 a.image_add(b) 228 assert a.iterator_length() == 2 229 with raises(TypeError): 230 with Image(filename='rose:') as img: 231 img.image_add(0xdeadbeef) 232 233 234def test_image_get(): 235 with Image(filename='rose:') as img: 236 with img.image_get() as i: 237 assert isinstance(i, Image) 238 239 240def test_image_remove(): 241 with Image(filename='null:') as empty: 242 empty.image_remove() 243 assert empty.iterator_length() == 0 244 245 246def test_image_set(): 247 with Image(filename='null:') as a: 248 with Image(filename='rose:') as b: 249 a.image_set(b) 250 assert a.iterator_length() == 1 251 252 253def test_image_swap(): 254 with Image(width=1, height=1, background='red') as a: 255 a.read(filename='xc:green') 256 a.read(filename='xc:blue') 257 was = a.iterator_get() 258 a.image_swap(0, 2) 259 assert a.iterator_get() == was 260 with raises(TypeError): 261 a.image_swap('a', 'b') 262 263def test_ping_from_filename(fx_asset): 264 file_path = str(fx_asset.join('mona-lisa.jpg')) 265 with Image.ping(filename=file_path) as img: 266 assert img.size == (402, 599) 267 268 269def test_ping_from_blob(fx_asset): 270 blob = fx_asset.join('mona-lisa.jpg').read('rb') 271 with Image.ping(blob=blob) as img: 272 assert img.size == (402, 599) 273 274 275def test_ping_from_file(fx_asset): 276 with fx_asset.join('mona-lisa.jpg').open('rb') as fd: 277 with Image.ping(file=fd) as img: 278 assert img.size == (402, 599) 279 280 281def test_save_to_filename(fx_asset): 282 """Saves an image to the filename.""" 283 savefile = os.path.join(tempfile.mkdtemp(), 'savetest.jpg') 284 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as orig: 285 orig.save(filename=savefile) 286 with raises(IOError): 287 orig.save(filename=os.path.join(savefile, 'invalid.jpg')) 288 with raises(TypeError): 289 orig.save(filename=1234) 290 assert os.path.isfile(savefile) 291 with Image(filename=savefile) as saved: 292 assert saved.size == (402, 599) 293 os.remove(savefile) 294 295 296@mark.skipif(not unicode_filesystem_encoding, 297 reason='Unicode filesystem encoding needed') 298def test_save_to_unicode_filename(fx_asset, tmpdir): 299 filename = '모나리자.jpg' 300 if not PY3: 301 filename = filename.decode('utf-8') 302 path = os.path.join(text_type(tmpdir), filename) # workaround py.path bug 303 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as orig: 304 orig.save(filename=path) 305 with Image(filename=path) as img: 306 assert img.width == 402 307 308 309def test_save_to_file(fx_asset): 310 """Saves an image to the Python file object.""" 311 buffer = io.BytesIO() 312 with tempfile.TemporaryFile() as savefile: 313 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as orig: 314 orig.save(file=savefile) 315 orig.save(file=buffer) 316 with raises(TypeError): 317 orig.save(file='filename') 318 with raises(TypeError): 319 orig.save(file=1234) 320 savefile.seek(0) 321 with Image(file=savefile) as saved: 322 assert saved.size == (402, 599) 323 buffer.seek(0) 324 with Image(file=buffer) as saved: 325 assert saved.size == (402, 599) 326 buffer.close() 327 328 329def test_save_full_animated_gif_to_file(fx_asset): 330 """Save all frames of an animated to a Python file object.""" 331 temp_filename = os.path.join(tempfile.mkdtemp(), 'savetest.gif') 332 orig_filename = str(fx_asset.join('nocomments.gif')) 333 with open(temp_filename, 'w+b') as fp: 334 with Image(filename=orig_filename) as orig: 335 orig.save(file=fp) 336 assert os.path.isfile(temp_filename) 337 with Image(filename=orig_filename) as orig: 338 with Image(filename=temp_filename) as temp: 339 assert len(orig.sequence) == len(temp.sequence) 340 os.remove(temp_filename) 341 342 343def test_save_error(fx_asset): 344 filename = os.path.join(tempfile.mkdtemp(), 'savetest.jpg') 345 fileobj = io.BytesIO() 346 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as orig: 347 with raises(TypeError): 348 orig.save() 349 with raises(TypeError): 350 orig.save(filename=filename, file=fileobj) 351 352 353def test_make_blob(fx_asset): 354 """Makes a blob string.""" 355 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 356 with Image(blob=img.make_blob('png')) as img2: 357 assert img2.size == (402, 599) 358 assert img2.format == 'PNG' 359 assert img.format == 'JPEG' 360 with raises(TypeError): 361 img.make_blob(123) 362 svg = b''' 363 <svg width="100px" height="100px"> 364 <circle cx="100" cy="50" r="40" stroke="black" 365 stroke-width="2" fill="red" /> 366 </svg> 367 ''' 368 with Image(blob=svg, format='svg') as img: 369 assert img.size == (100, 100) 370 assert img.format in ('SVG', 'MVG') 371 img.format = 'PNG' 372 assert img.size == (100, 100) 373 assert img.format == 'PNG' 374 png = img.make_blob() 375 with Image(blob=png, format='png') as img: 376 assert img.size == (100, 100) 377 assert img.format == 'PNG' 378 379 380def test_convert(fx_asset): 381 """Converts the image format.""" 382 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 383 with img.convert('png') as converted: 384 assert converted.format == 'PNG' 385 strio = io.BytesIO() 386 converted.save(file=strio) 387 strio.seek(0) 388 with Image(file=strio) as png: 389 assert png.format == 'PNG' 390 with raises(ValueError): 391 img.convert('HONG') 392 with raises(TypeError): 393 img.convert(123) 394 395 396@mark.slow 397def test_iterate(fx_asset): 398 """Uses iterator.""" 399 with Color('#000') as black: 400 with Color('transparent') as transparent: 401 with Image(filename=str(fx_asset.join('croptest.png'))) as img: 402 for i, row in enumerate(img): 403 assert len(row) == 300 404 if i % 30: 405 continue # avoid slowness 406 if 100 <= i < 200: 407 for x, color in enumerate(row): 408 if x % 30: 409 continue # avoid slowness 410 if 100 <= x < 200: 411 assert color == black 412 else: 413 assert color == transparent 414 else: 415 for color in row: 416 assert color == transparent 417 assert i == 299 418 419 420def test_slice_clone(fx_asset): 421 """Clones using slicing.""" 422 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 423 with img[:, :] as cloned: 424 assert img.size == cloned.size 425 426 427def test_slice_invalid_types(fx_asset): 428 """Slicing with invalid types should throw exceptions.""" 429 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 430 with raises(TypeError): 431 img['12'] 432 with raises(TypeError): 433 img[1.23] 434 with raises(ValueError): 435 img[()] 436 with raises(ValueError): 437 img[:, :, :] 438 with raises(ValueError): 439 img[::2, :] 440 with raises(IndexError): 441 img[1:1, :] 442 with raises(IndexError): 443 img[:, 2:2] 444 with raises(TypeError): 445 img[100.0:, 100.0] 446 with raises(TypeError): 447 img['100':, '100'] 448 with raises(IndexError): 449 img[500:, 900] 450 with raises(TypeError): 451 img['1', 0] 452 with raises(TypeError): 453 img[1, '0'] 454 with Image(filename=str(fx_asset.join('croptest.png'))) as img: 455 with raises(IndexError): 456 img[300, 300] 457 with raises(IndexError): 458 img[-301, -301] 459 460 461def test_index_pixel(fx_asset): 462 """Gets a pixel.""" 463 with Image(filename=str(fx_asset.join('croptest.png'))) as img: 464 assert img[0, 0] == Color('transparent') 465 assert img[99, 99] == Color('transparent') 466 assert img[100, 100] == Color('black') 467 assert img[150, 150] == Color('black') 468 assert img[-200, -200] == Color('black') 469 assert img[-201, -201] == Color('transparent') 470 471 472def test_index_pixel_set(fx_asset): 473 with Image(filename='rose:') as img: 474 with Color('black') as dot: 475 img[0, 0] = dot 476 assert img[0, 0] == dot 477 img[0, 0] = 'black' 478 assert img[0, 0] == dot 479 img.colorspace = 'gray' 480 with Color('gray50') as dot: 481 img[0, 0] = dot 482 assert img[0, 0] == dot 483 img.colorspace = 'cmyk' 484 with Color('cmyk(255, 0, 0, 0') as dot: 485 img[0, 0] = dot 486 assert img[0, 0] == dot 487 with raises(TypeError): 488 img[0, 0] = 1 489 with raises(TypeError): 490 img[0xDEADBEEF] = Color('black') 491 with raises(ValueError): 492 img[1, 2, 3] = Color('black') 493 with raises(TypeError): 494 img[0.5, "d"] = Color('black') 495 496 497def test_index_row(fx_asset): 498 """Gets a row.""" 499 with Color('transparent') as transparent: 500 with Color('black') as black: 501 with Image(filename=str(fx_asset.join('croptest.png'))) as img: 502 for c in img[0]: 503 assert c == transparent 504 for c in img[99]: 505 assert c == transparent 506 for i, c in enumerate(img[100]): 507 if 100 <= i < 200: 508 assert c == black 509 else: 510 assert c == transparent 511 for i, c in enumerate(img[150]): 512 if 100 <= i < 200: 513 assert c == black 514 else: 515 assert c == transparent 516 for i, c in enumerate(img[-200]): 517 if 100 <= i < 200: 518 assert c == black 519 else: 520 assert c == transparent 521 for c in img[-201]: 522 assert c == transparent 523 524 525def test_slice_crop(fx_asset): 526 """Crops using slicing.""" 527 with Image(filename=str(fx_asset.join('croptest.png'))) as img: 528 with img[100:200, 100:200] as cropped: 529 assert cropped.size == (100, 100) 530 with Color('#000') as black: 531 for row in cropped: 532 for col in row: 533 assert col == black 534 with img[150:, :150] as cropped: 535 assert cropped.size == (150, 150) 536 with img[-200:-100, -200:-100] as cropped: 537 assert cropped.size == (100, 100) 538 with img[100:200] as cropped: 539 assert cropped.size == (300, 100) 540 assert img.size == (300, 300) 541 with raises(IndexError): 542 img[:500, :500] 543 with raises(IndexError): 544 img[290:310, 290:310] 545 546 547def test_equal(fx_asset): 548 """Equals (``==``) and not equals (``!=``) operators.""" 549 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as a: 550 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as a2: 551 assert a == a2 552 assert not (a != a2) 553 with Image(filename=str(fx_asset.join('sasha.jpg'))) as b: 554 assert a != b 555 assert not (a == b) 556 with a.convert('png') as a3: 557 assert a == a3 558 assert not (a != a3) 559 560 561def test_object_hash(fx_asset): 562 """Gets :func:`hash()` of the image.""" 563 with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img: 564 a = hash(img) 565 img.format = 'png' 566 b = hash(img) 567 assert a == b 568 569 570def test_issue_150(fx_asset, tmpdir): 571 """Should not be terminated with segmentation fault. 572 573 https://github.com/emcconville/wand/issues/150 574 575 """ 576 with Image(filename=str(fx_asset.join('tiger_hd-1920x1080.jpg'))) as img: 577 img.format = 'pjpeg' 578 with open(str(tmpdir.join('out.jpg')), 'wb') as f: 579 img.save(file=f) 580 581 582@mark.skipif(np is None, reason='Numpy not available.') 583def test_from_array(): 584 # From float values. 585 rand = np.random.rand(10, 10, 4) 586 # We should have a 10x10 image with RGBA data created. 587 with Image.from_array(rand) as img: 588 assert img.size == (10, 10) 589 assert img.alpha_channel 590 # From char values 591 red8 = np.zeros([10, 10, 3], dtype=np.uint8) 592 red8[:, :] = [0xFF, 0x00, 0x00] 593 # We should have a RED image. 594 with Image.from_array(red8) as img: 595 assert img[0, 0] == Color('#F00') 596 # From short values. 597 green16 = np.zeros([10, 10, 3], dtype=np.uint16) 598 green16[:, :] = [0x0000, 0xFFFF, 0x0000] 599 # We should have a GREEN image. 600 with Image.from_array(green16) as img: 601 assert img[0, 0] == Color('#00FF00') 602 603 604@mark.skipif(np is None, reason='Numpy not available.') 605def test_array_interface(): 606 with Image(filename='rose:') as img: 607 img.alpha_channel = 'off' 608 array = np.array(img) 609 assert array.shape == (46, 70, 3) 610 with Image(filename='rose:') as img: 611 img.alpha_channel = 'off' 612 img.transform_colorspace('gray') 613 array = np.array(img) 614 assert array.shape == (46, 70, 1) 615 with Image(filename='rose:') as img: 616 img.alpha_channel = 'off' 617 img.transform_colorspace('cmyk') 618 array = np.array(img) 619 assert array.shape == (46, 70, 4) 620 621 622@mark.skipif(np is None, reason='Numpy not available.') 623def test_numpy_array_hairpinning(): 624 with Image(filename='rose:') as left: 625 with Image.from_array(left) as right: 626 assert left.size == right.size 627 628 629def test_data_url(): 630 with Image(filename='rose:') as img: 631 img.format = 'PNG' 632 data = img.data_url() 633 assert data.startswith('data:image/png;base64,') 634