diff --git a/Tests/helper.py b/Tests/helper.py index c1399e89bf8..5d206f6449a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -29,6 +29,33 @@ uploader = "github_actions" +modes = ( + "1", + "L", + "LA", + "La", + "P", + "PA", + "F", + "I", + "I;16", + "I;16L", + "I;16B", + "I;16N", + "RGB", + "RGBA", + "RGBa", + "RGBX", + "BGR;15", + "BGR;16", + "BGR;24", + "CMYK", + "YCbCr", + "HSV", + "LAB", +) + + def upload(a: Image.Image, b: Image.Image) -> str | None: if uploader == "show": # local img.show for errors. diff --git a/Tests/test_image.py b/Tests/test_image.py index 941ec40d9bc..8e7e40c05da 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -28,43 +28,16 @@ assert_image_similar_tofile, assert_not_all_same, hopper, + is_big_endian, is_win32, mark_if_feature_version, + modes, skip_unless_feature, ) -# name, pixel size -image_modes = ( - ("1", 1), - ("L", 1), - ("LA", 4), - ("La", 4), - ("P", 1), - ("PA", 4), - ("F", 4), - ("I", 4), - ("I;16", 2), - ("I;16L", 2), - ("I;16B", 2), - ("I;16N", 2), - ("RGB", 4), - ("RGBA", 4), - ("RGBa", 4), - ("RGBX", 4), - ("BGR;15", 2), - ("BGR;16", 2), - ("BGR;24", 3), - ("CMYK", 4), - ("YCbCr", 4), - ("HSV", 4), - ("LAB", 4), -) - -image_mode_names = [name for name, _ in image_modes] - class TestImage: - @pytest.mark.parametrize("mode", image_mode_names) + @pytest.mark.parametrize("mode", modes) def test_image_modes_success(self, mode: str) -> None: Image.new(mode, (1, 1)) @@ -1045,7 +1018,7 @@ def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None: class TestImageBytes: - @pytest.mark.parametrize("mode", image_mode_names) + @pytest.mark.parametrize("mode", modes) def test_roundtrip_bytes_constructor(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() @@ -1053,7 +1026,7 @@ def test_roundtrip_bytes_constructor(self, mode: str) -> None: reloaded = Image.frombytes(mode, im.size, source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize("mode", image_mode_names) + @pytest.mark.parametrize("mode", modes) def test_roundtrip_bytes_method(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() @@ -1062,12 +1035,11 @@ def test_roundtrip_bytes_method(self, mode: str) -> None: reloaded.frombytes(source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize(("mode", "pixelsize"), image_modes) - def test_getdata_putdata(self, mode: str, pixelsize: int) -> None: - im = Image.new(mode, (2, 2)) - source_bytes = bytes(range(im.width * im.height * pixelsize)) - im.frombytes(source_bytes) - + @pytest.mark.parametrize("mode", modes) + def test_getdata_putdata(self, mode: str) -> None: + if is_big_endian and mode == "BGR;15": + pytest.xfail("Known failure of BGR;15 on big-endian") + im = hopper(mode) reloaded = Image.new(mode, im.size) reloaded.putdata(im.getdata()) assert_image_equal(im, reloaded) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 8c42da57a37..2cd2e0c34e5 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -10,7 +10,7 @@ from PIL import Image -from .helper import assert_image_equal, hopper, is_win32 +from .helper import assert_image_equal, hopper, is_win32, modes # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 @@ -33,7 +33,7 @@ class AccessTest: - # initial value + # Initial value _init_cffi_access = Image.USE_CFFI_ACCESS _need_cffi_access = False @@ -138,8 +138,8 @@ def color(mode: str) -> int | tuple[int, ...]: if bands == 1: return 1 if mode in ("BGR;15", "BGR;16"): - # These modes have less than 8 bits per band - # So (1, 2, 3) cannot be roundtripped + # These modes have less than 8 bits per band, + # so (1, 2, 3) cannot be roundtripped. return (16, 32, 49) return tuple(range(1, bands + 1)) @@ -151,7 +151,7 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None: self.color(mode) if expected_color_int is None else expected_color_int ) - # check putpixel + # Check putpixel im = Image.new(mode, (1, 1), None) im.putpixel((0, 0), expected_color) actual_color = im.getpixel((0, 0)) @@ -160,7 +160,7 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None: f"expected {expected_color} got {actual_color}" ) - # check putpixel negative index + # Check putpixel negative index im.putpixel((-1, -1), expected_color) actual_color = im.getpixel((-1, -1)) assert actual_color == expected_color, ( @@ -168,22 +168,21 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None: f"expected {expected_color} got {actual_color}" ) - # Check 0 + # Check 0x0 image with None initial color im = Image.new(mode, (0, 0), None) assert im.load() is not None - error = ValueError if self._need_cffi_access else IndexError with pytest.raises(error): im.putpixel((0, 0), expected_color) with pytest.raises(error): im.getpixel((0, 0)) - # Check 0 negative index + # Check negative index with pytest.raises(error): im.putpixel((-1, -1), expected_color) with pytest.raises(error): im.getpixel((-1, -1)) - # check initial color + # Check initial color im = Image.new(mode, (1, 1), expected_color) actual_color = im.getpixel((0, 0)) assert actual_color == expected_color, ( @@ -191,43 +190,22 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None: f"expected {expected_color} got {actual_color}" ) - # check initial color negative index + # Check initial color negative index actual_color = im.getpixel((-1, -1)) assert actual_color == expected_color, ( f"initial color failed with negative index for mode {mode}, " f"expected {expected_color} got {actual_color}" ) - # Check 0 + # Check 0x0 image with initial color im = Image.new(mode, (0, 0), expected_color) with pytest.raises(error): im.getpixel((0, 0)) - # Check 0 negative index + # Check negative index with pytest.raises(error): im.getpixel((-1, -1)) - @pytest.mark.parametrize( - "mode", - ( - "1", - "L", - "LA", - "I", - "I;16", - "I;16B", - "F", - "P", - "PA", - "BGR;15", - "BGR;16", - "BGR;24", - "RGB", - "RGBA", - "RGBX", - "CMYK", - "YCbCr", - ), - ) + @pytest.mark.parametrize("mode", modes) def test_basic(self, mode: str) -> None: self.check(mode) @@ -238,7 +216,7 @@ def test_list(self) -> None: @pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)) def test_signedness(self, mode: str, expected_color: int) -> None: - # see https://github.com/python-pillow/Pillow/issues/452 + # See https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* self.check(mode, expected_color) @@ -298,13 +276,6 @@ def test_get_vs_c(self) -> None: im = Image.new(mode, (10, 10), 40000) self._test_get_access(im) - # These don't actually appear to be modes that I can actually make, - # as unpack sets them directly into the I mode. - # im = Image.new('I;32L', (10, 10), -2**10) - # self._test_get_access(im) - # im = Image.new('I;32B', (10, 10), 2**10) - # self._test_get_access(im) - def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None: """Are we writing the correct bits into the image? @@ -336,23 +307,18 @@ def test_set_vs_c(self) -> None: self._test_set_access(hopper("LA"), (128, 128)) self._test_set_access(hopper("1"), 255) self._test_set_access(hopper("P"), 128) - # self._test_set_access(i, (128, 128)) #PA -- undone how to make + self._test_set_access(hopper("PA"), (128, 128)) self._test_set_access(hopper("F"), 1024.0) for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"): im = Image.new(mode, (10, 10), 40000) self._test_set_access(im, 45000) - # im = Image.new('I;32L', (10, 10), -(2**10)) - # self._test_set_access(im, -(2**13)+1) - # im = Image.new('I;32B', (10, 10), 2**10) - # self._test_set_access(im, 2**13-1) - @pytest.mark.filterwarnings("ignore::DeprecationWarning") def test_not_implemented(self) -> None: assert PyAccess.new(hopper("BGR;15")) is None - # ref https://github.com/python-pillow/Pillow/pull/2009 + # Ref https://github.com/python-pillow/Pillow/pull/2009 def test_reference_counting(self) -> None: size = 10 @@ -361,7 +327,7 @@ def test_reference_counting(self) -> None: with pytest.warns(DeprecationWarning): px = Image.new("L", (size, 1), 0).load() for i in range(size): - # pixels can contain garbage if image is released + # Pixels can contain garbage if image is released assert px[i, 0] == 0 @pytest.mark.parametrize("mode", ("P", "PA")) @@ -478,7 +444,7 @@ def test_embeddable(self) -> None: env = os.environ.copy() env["PATH"] = sys.prefix + ";" + env["PATH"] - # do not display the Windows Error Reporting dialog + # Do not display the Windows Error Reporting dialog getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) process = subprocess.Popen(["embed_pil.exe"], env=env) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 6a0e704b89a..f34ff7d0230 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -216,7 +216,10 @@ def test_I(self) -> None: ) def test_I16(self) -> None: - self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) + if sys.byteorder == "little": + self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) + else: + self.assert_pack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506) def test_F_float(self) -> None: self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) diff --git a/setup.py b/setup.py index 7d8e1c1ee21..4e096ec57c6 100644 --- a/setup.py +++ b/setup.py @@ -225,12 +225,18 @@ def _find_include_file(self, include): def _find_library_file(self, library): - ret = self.compiler.find_library_file(self.compiler.library_dirs, library) + ret = self.compiler.find_library_file( + self.compiler.library_dirs, library, debug=debug_build() + ) if ret: _dbg("Found library %s at %s", (library, ret)) - else: - _dbg("Couldn't find library %s in %s", (library, self.compiler.library_dirs)) - return ret + # we are only interested in the library name including a possible debug suffix + lib_name_base = os.path.basename(ret).split(".")[0] + # as the library prefix differs depending on library type and platform, + # we ignore it by looking for the actual library name + start_index = lib_name_base.find(library) + return lib_name_base[start_index:] + _dbg("Couldn't find library %s in %s", (library, self.compiler.library_dirs)) def _find_include_dir(self, dirname, include): @@ -657,18 +663,18 @@ def build_extensions(self): if feature.want("zlib"): _dbg("Looking for zlib") if _find_include_file(self, "zlib.h"): - if _find_library_file(self, "z"): - feature.zlib = "z" - elif sys.platform == "win32" and _find_library_file(self, "zlib"): - feature.zlib = "zlib" # alternative name + lib_zlib = _find_library_file(self, "z") + if lib_zlib is None and sys.platform == "win32": + lib_zlib = _find_library_file(self, "zlib") # alternative name + feature.zlib = lib_zlib if feature.want("jpeg"): _dbg("Looking for jpeg") if _find_include_file(self, "jpeglib.h"): - if _find_library_file(self, "jpeg"): - feature.jpeg = "jpeg" - elif sys.platform == "win32" and _find_library_file(self, "libjpeg"): - feature.jpeg = "libjpeg" # alternative name + lib_jpeg = _find_library_file(self, "jpeg") + if lib_jpeg is None and sys.platform == "win32": + lib_jpeg = _find_library_file(self, "libjpeg") # alternative name + feature.jpeg = lib_jpeg feature.openjpeg_version = None if feature.want("jpeg2000"): @@ -698,35 +704,35 @@ def build_extensions(self): (best_version, best_path), ) - if best_version and _find_library_file(self, "openjp2"): - # Add the directory to the include path so we can include - # rather than having to cope with the versioned - # include path - _add_directory(self.compiler.include_dirs, best_path, 0) - feature.jpeg2000 = "openjp2" - feature.openjpeg_version = ".".join(str(x) for x in best_version) + if best_version: + lib_jpeg2k = _find_library_file(self, "openjp2") + if lib_jpeg2k is not None: + # Add the directory to the include path so we can include + # rather than having to cope with the versioned + # include path + _add_directory(self.compiler.include_dirs, best_path, 0) + feature.jpeg2000 = lib_jpeg2k + feature.openjpeg_version = ".".join(str(x) for x in best_version) if feature.want("imagequant"): _dbg("Looking for imagequant") if _find_include_file(self, "libimagequant.h"): - if _find_library_file(self, "imagequant"): - feature.imagequant = "imagequant" - elif _find_library_file(self, "libimagequant"): - feature.imagequant = "libimagequant" + feature.imagequant = _find_library_file( + self, "imagequant" + ) or _find_library_file(self, "libimagequant") if feature.want("tiff"): _dbg("Looking for tiff") if _find_include_file(self, "tiff.h"): - if _find_library_file(self, "tiff"): - feature.tiff = "tiff" - if sys.platform in ["win32", "darwin"] and _find_library_file( - self, "libtiff" - ): - feature.tiff = "libtiff" + lib_tiff = _find_library_file(self, "tiff") + if sys.platform in ["win32", "darwin"]: + lib_tiff = _find_library_file(self, "libtiff") + feature.tiff = lib_tiff if feature.want("freetype"): _dbg("Looking for freetype") - if _find_library_file(self, "freetype"): + lib_freetype = _find_library_file(self, "freetype") + if lib_freetype is not None: # look for freetype2 include files freetype_version = 0 for subdir in self.compiler.include_dirs: @@ -743,7 +749,7 @@ def build_extensions(self): freetype_version = 21 break if freetype_version: - feature.freetype = "freetype" + feature.freetype = lib_freetype if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) @@ -751,10 +757,9 @@ def build_extensions(self): if not feature.want_vendor("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): - if _find_library_file(self, "raqm"): - feature.raqm = "raqm" - elif _find_library_file(self, "libraqm"): - feature.raqm = "libraqm" + feature.raqm = _find_library_file( + self, "raqm" + ) or _find_library_file(self, "libraqm") else: # want to build Raqm from src/thirdparty _dbg("Looking for HarfBuzz") feature.harfbuzz = None @@ -762,8 +767,7 @@ def build_extensions(self): if hb_dir: if isinstance(hb_dir, str): _add_directory(self.compiler.include_dirs, hb_dir, 0) - if _find_library_file(self, "harfbuzz"): - feature.harfbuzz = "harfbuzz" + feature.harfbuzz = _find_library_file(self, "harfbuzz") if feature.harfbuzz: if not feature.want_vendor("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") @@ -774,8 +778,8 @@ def build_extensions(self): _add_directory( self.compiler.include_dirs, fribidi_dir, 0 ) - if _find_library_file(self, "fribidi"): - feature.fribidi = "fribidi" + feature.fribidi = _find_library_file(self, "fribidi") + if feature.fribidi: feature.raqm = True else: # want to build FriBiDi shim from src/thirdparty feature.raqm = True @@ -783,11 +787,9 @@ def build_extensions(self): if feature.want("lcms"): _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): - if _find_library_file(self, "lcms2"): - feature.lcms = "lcms2" - elif _find_library_file(self, "lcms2_static"): - # alternate Windows name. - feature.lcms = "lcms2_static" + feature.lcms = _find_library_file(self, "lcms2") or _find_library_file( + self, "lcms2_static" + ) # alternate Windows name if feature.want("webp"): _dbg("Looking for webp") @@ -795,30 +797,26 @@ def build_extensions(self): self, "webp/decode.h" ): # In Google's precompiled zip it is call "libwebp": - if _find_library_file(self, "webp"): - feature.webp = "webp" - elif _find_library_file(self, "libwebp"): - feature.webp = "libwebp" + feature.webp = _find_library_file(self, "webp") or _find_library_file( + self, "libwebp" + ) if feature.want("webpmux"): _dbg("Looking for webpmux") if _find_include_file(self, "webp/mux.h") and _find_include_file( self, "webp/demux.h" ): - if _find_library_file(self, "webpmux") and _find_library_file( - self, "webpdemux" - ): - feature.webpmux = "webpmux" - if _find_library_file(self, "libwebpmux") and _find_library_file( - self, "libwebpdemux" - ): - feature.webpmux = "libwebpmux" + lib_webpmux = _find_library_file(self, "webpmux") + if lib_webpmux and _find_library_file(self, "webpdemux"): + feature.webpmux = lib_webpmux + lib_webpmux = _find_library_file(self, "libwebpmux") + if lib_webpmux and _find_library_file(self, "libwebpdemux"): + feature.webpmux = lib_webpmux if feature.want("xcb"): _dbg("Looking for xcb") if _find_include_file(self, "xcb/xcb.h"): - if _find_library_file(self, "xcb"): - feature.xcb = "xcb" + feature.xcb = _find_library_file(self, "xcb") for f in feature: if not getattr(feature, f) and feature.require(f): diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 091c84e18fa..04618df0965 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -81,12 +81,6 @@ get_pixel_16B(Imaging im, int x, int y, void *color) { #endif } -static void -get_pixel_16(Imaging im, int x, int y, void *color) { - UINT8 *in = (UINT8 *)&im->image[y][x + x]; - memcpy(color, in, sizeof(UINT16)); -} - static void get_pixel_BGR15(Imaging im, int x, int y, void *color) { UINT8 *in = (UINT8 *)&im->image8[y][x * 2]; @@ -207,7 +201,11 @@ ImagingAccessInit() { ADD("I;16", get_pixel_16L, put_pixel_16L); ADD("I;16L", get_pixel_16L, put_pixel_16L); ADD("I;16B", get_pixel_16B, put_pixel_16B); - ADD("I;16N", get_pixel_16, put_pixel_16L); +#ifdef WORDS_BIGENDIAN + ADD("I;16N", get_pixel_16B, put_pixel_16B); +#else + ADD("I;16N", get_pixel_16L, put_pixel_16L); +#endif ADD("I;32L", get_pixel_32L, put_pixel_32L); ADD("I;32B", get_pixel_32B, put_pixel_32B); ADD("F", get_pixel_32, put_pixel_32);