diff --git a/Tests/images/hopper_naxis_zero.fits b/Tests/images/hopper_naxis_zero.fits deleted file mode 100644 index 580cf3a2c00..00000000000 Binary files a/Tests/images/hopper_naxis_zero.fits and /dev/null differ diff --git a/Tests/test_file_fits.py b/Tests/test_file_fits.py index 1c1df0d980d..081a9a9f54d 100644 --- a/Tests/test_file_fits.py +++ b/Tests/test_file_fits.py @@ -24,6 +24,8 @@ def test_open() -> None: def test_gzip1() -> None: with Image.open("Tests/images/m13_gzip.fits") as im: + assert im.getpixel((0, 0)) == 111 + assert_image_equal_tofile(im, "Tests/images/m13.fits") @@ -36,6 +38,22 @@ def test_invalid_file() -> None: FitsImagePlugin.FitsImageFile(invalid_file) +def test_unsupported_number_of_bits() -> None: + image_data = b"".join( + data.ljust(80, b" ") + for data in [ + b"SIMPLE = T", + b"BITPIX = 128", + b"NAXIS = 1", + b"NAXIS1 = 0", + b"END", + ] + ) + with pytest.raises(OSError, match="Unsupported number of bits"): + with Image.open(BytesIO(image_data)): + pass + + def test_truncated_fits() -> None: # No END to headers image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE" @@ -44,10 +62,11 @@ def test_truncated_fits() -> None: def test_naxis_zero() -> None: - # This test image has been manually hexedited - # to set the number of data axes to zero - with pytest.raises(ValueError): - with Image.open("Tests/images/hopper_naxis_zero.fits"): + image_data = b"".join( + data.ljust(80, b" ") for data in [b"SIMPLE = T", b"NAXIS = 0", b"END"] + ).ljust(2881) + with pytest.raises(ValueError, match="No image data"): + with Image.open(BytesIO(image_data)): pass diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index e918407784d..86fde16e8cb 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -107,17 +107,23 @@ def _parse_headers( number_of_bits = int(headers[prefix + b"BITPIX"]) if number_of_bits == 8: - self._mode = "L" + self._mode = rawmode = "L" elif number_of_bits == 16: self._mode = "I;16" + rawmode = "I;16B" elif number_of_bits == 32: self._mode = "I" - elif number_of_bits in (-32, -64): + rawmode = "I;32B" + elif number_of_bits == -32 or (decoder_name == "raw" and number_of_bits == -64): self._mode = "F" + rawmode = f"F;{number_of_bits * -1}BF" + else: + msg = "Unsupported number of bits" + raise OSError(msg) args: tuple[str | int, ...] if decoder_name == "raw": - args = (self.mode, 0, -1) + args = (rawmode, 0, -1) else: args = (number_of_bits,) return decoder_name, offset, args @@ -133,11 +139,13 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int rows = [] offset = 0 - number_of_bits = min(self.args[0] // 8, 4) + number_of_bytes = abs(self.args[0]) // 8 for y in range(self.state.ysize): row = bytearray() for x in range(self.state.xsize): - row += value[offset + (4 - number_of_bits) : offset + 4] + row += value[ + offset + 4 - 1 : offset + (4 - number_of_bytes) - 1 : -1 + ] offset += 4 rows.append(row) self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row]))