diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 8a54f0756..fc98901a4 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -877,14 +877,14 @@ class TestFileTiff: def test_open_tiff_uint16_multiband(self): """Test opening multiband TIFFs and reading all channels.""" base_value = 4660 - for i in range(2, 6): + for i in range(1, 6): infile = f"Tests/images/uint16_{i}_{base_value}.tif" im = Image.open(infile) im.load() - pixel = [base_value + j for j in range(0, i)] + pixel = tuple([base_value + j for j in range(0, i)]) actual_pixel = im.getpixel((0, 0)) if isinstance(actual_pixel, int): - actual_pixel = [actual_pixel] + actual_pixel = (actual_pixel,) assert actual_pixel == pixel diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index c0224782a..6c92ea4f5 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -117,6 +117,8 @@ class ImageFile(Image.Image): self.readonly = 1 # until we know better + self.newconfig = () + self.decoderconfig = () self.decodermaxblock = MAXBLOCK @@ -317,7 +319,7 @@ class ImageFile(Image.Image): def load_prepare(self) -> None: # create image memory if necessary if not self.im or self.im.mode != self.mode or self.im.size != self.size: - self.im = Image.core.new(self.mode, self.size) + self.im = Image.core.new(self.mode, self.size, *self.newconfig) # create palette (optional) if self.mode == "P": Image.Image.load(self) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 8f7958702..a829e8db5 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -60,7 +60,7 @@ from ._deprecate import deprecate from .TiffTags import TYPES logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) # XXX hack202406 +logger.setLevel(logging.DEBUG) # XXX hack202406 # Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False @@ -183,7 +183,7 @@ OPEN_INFO = { (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), (II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"), (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), - (II, 1, (1, 1), 1, (16, 16), (0,)): ("I;16", "I;16"), + (II, 1, (1, 1), 1, (16, 16), (0,)): ("MB", "MB"), ( II, 1, @@ -194,7 +194,7 @@ OPEN_INFO = { 0, 0, ), - ): ("I;16", "I;16"), + ): ("MB", "MB"), ( II, 1, @@ -206,7 +206,7 @@ OPEN_INFO = { 0, 0, ), - ): ("I;16", "I;16"), + ): ("MB", "MB"), ( II, 1, @@ -219,7 +219,7 @@ OPEN_INFO = { 0, 0, ), - ): ("I;16", "I;16"), + ): ("MB", "MB"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"), (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"), @@ -1474,6 +1474,9 @@ class TiffImageFile(ImageFile.ImageFile): logger.debug("- raw mode: %s", rawmode) logger.debug("- pil mode: %s", self.mode) + if self.mode == "MB": + assert max(bps_tuple) == min(bps_tuple) + self.newconfig = (max(bps_tuple), samples_per_pixel) self.info["compression"] = self._compression diff --git a/src/_imaging.c b/src/_imaging.c index 756768b75..4eee912f6 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -310,7 +310,7 @@ getbands(const char *mode) { int bands; /* FIXME: add primitive to libImaging to avoid extra allocation */ - im = ImagingNew(mode, 0, 0); + im = ImagingNew(mode, 0, 0, -1, -1); if (!im) { return -1; } @@ -434,6 +434,35 @@ float16tofloat32(const FLOAT16 in) { return out[0]; } +static inline PyObject * +getpixel_mb(Imaging im, ImagingAccess access, int x, int y) { + UINT8 pixel[im->pixelsize]; + access->get_pixel(im, x, y, &pixel); + + PyObject *tuple = PyTuple_New(im->bands); + if (tuple == NULL) { + return NULL; + } + + UINT8 *pos = pixel; + for (int i = 0; i < im->bands; ++i) { + switch (im->depth) { + case CHAR_BIT: + PyTuple_SET_ITEM(tuple, i, PyLong_FromLong(*pos)); + break; + case 2 * CHAR_BIT: + PyTuple_SET_ITEM(tuple, i, PyLong_FromLong(*(UINT16 *)pos)); + break; + case 4 * CHAR_BIT: + PyTuple_SET_ITEM(tuple, i, PyLong_FromLong(*(INT32 *)pos)); + break; + } + pos += im->depth / CHAR_BIT; + } + + return tuple; +} + static inline PyObject * getpixel(Imaging im, ImagingAccess access, int x, int y) { union { @@ -455,6 +484,10 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) { return NULL; } + if (im->type == IMAGING_TYPE_MB) { + return getpixel_mb(im, access, x, y); + } + access->get_pixel(im, x, y, &pixel); switch (im->type) { @@ -685,13 +718,13 @@ _fill(PyObject *self, PyObject *args) { static PyObject * _new(PyObject *self, PyObject *args) { char *mode; - int xsize, ysize; + int xsize, ysize, depth = -1, bands = -1; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { + if (!PyArg_ParseTuple(args, "s(ii)|ii", &mode, &xsize, &ysize, &depth, &bands)) { return NULL; } - return PyImagingNew(ImagingNew(mode, xsize, ysize)); + return PyImagingNew(ImagingNew(mode, xsize, ysize, depth, bands)); } static PyObject * @@ -1714,7 +1747,8 @@ _quantize(ImagingObject *self, PyObject *args) { if (!self->image->xsize || !self->image->ysize) { /* no content; return an empty image */ - return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize)); + return PyImagingNew( + ImagingNew("P", self->image->xsize, self->image->ysize, -1, -1)); } return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans)); @@ -2782,7 +2816,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { return NULL; } - im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize); + im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize, -1, -1); if (!im) { free(text); return ImagingError_MemoryError(); diff --git a/src/decode.c b/src/decode.c index ea2f3af80..af0801018 100644 --- a/src/decode.c +++ b/src/decode.c @@ -291,11 +291,33 @@ static PyTypeObject ImagingDecoderType = { /* -------------------------------------------------------------------- */ -int +static void +mb_shuffle_passthru(UINT8 *dst, const UINT8 *src, Imaging im, ImagingCodecState state) { + state->shuffle(dst, src, state->xsize); +} + +static void +shuffle_mb_unavail(UINT8 *dst, const UINT8 *src, int pixels) { + abort(); +} + +static void +mb_shuffle(UINT8 *dst, const UINT8 *src, Imaging im, ImagingCodecState state) { + memcpy(dst, src, state->xsize * im->pixelsize); +} + +static int get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) { int bits; ImagingShuffler unpack; + if (strcmp(mode, IMAGING_MODE_MB) == 0) { + decoder->state.shuffle = shuffle_mb_unavail; + decoder->state.mb_shuffle = mb_shuffle; + decoder->state.bits = -1; + return 0; + } + unpack = ImagingFindUnpacker(mode, rawmode, &bits); if (!unpack) { Py_DECREF(decoder); @@ -304,6 +326,7 @@ get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmod } decoder->state.shuffle = unpack; + decoder->state.mb_shuffle = mb_shuffle_passthru; decoder->state.bits = bits; return 0; diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 3a5e918e8..f567bdbab 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -133,6 +133,11 @@ get_pixel_32B(Imaging im, int x, int y, void *color) { #endif } +static void +get_pixel_mb(Imaging im, int x, int y, void *color) { + memcpy(color, &im->image[y][x * im->pixelsize], im->pixelsize); +} + /* store individual pixel */ static void @@ -183,6 +188,11 @@ put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); } +static void +put_pixel_mb(Imaging im, int x, int y, void *color) { + memcpy(&im->image[y][x * im->pixelsize], color, im->pixelsize); +} + void ImagingAccessInit() { #define ADD(mode_, get_pixel_, put_pixel_) \ @@ -222,6 +232,7 @@ ImagingAccessInit() { ADD("YCbCr", get_pixel_32, put_pixel_32); ADD("LAB", get_pixel_32, put_pixel_32); ADD("HSV", get_pixel_32, put_pixel_32); + ADD("MB", get_pixel_mb, put_pixel_mb); } ImagingAccess diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 1f2c03e93..ee2f6ea9d 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -68,10 +68,13 @@ typedef struct ImagingPaletteInstance *ImagingPalette; #define IMAGING_TYPE_INT32 1 #define IMAGING_TYPE_FLOAT32 2 #define IMAGING_TYPE_SPECIAL 3 /* check mode for details */ +#define IMAGING_TYPE_MB 4 /* multi-band format */ #define IMAGING_MODE_LENGTH \ 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ +#define IMAGING_MODE_MB "MB" /* multi-band format */ + typedef struct { char *ptr; int size; @@ -80,9 +83,9 @@ typedef struct { struct ImagingMemoryInstance { /* Format */ char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", - "YCbCr", "BGR;xy") */ + "YCbCr", "BGR;xy", "MB") */ int type; /* Data type (IMAGING_TYPE_*) */ - int depth; /* Depth (ignored in this version) */ + int depth; /* Sample size (1, 2, or 4) in multi-band format */ int bands; /* Number of bands (1, 2, 3, or 4) */ int xsize; /* Image dimension. */ int ysize; @@ -173,7 +176,7 @@ extern void ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size); extern Imaging -ImagingNew(const char *mode, int xsize, int ysize); +ImagingNew(const char *mode, int xsize, int ysize, int depth, int bands); extern Imaging ImagingNewDirty(const char *mode, int xsize, int ysize); extern Imaging @@ -185,9 +188,10 @@ extern Imaging ImagingNewBlock(const char *mode, int xsize, int ysize); extern Imaging -ImagingNewPrologue(const char *mode, int xsize, int ysize); +ImagingNewPrologue(const char *mode, int xsize, int ysize, int depth, int bands); extern Imaging -ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size); +ImagingNewPrologueSubtype( + const char *mode, int xsize, int ysize, int depth, int bands, int structure_size); extern void ImagingCopyPalette(Imaging destination, Imaging source); @@ -663,6 +667,8 @@ struct ImagingCodecStateInstance { int ystep; int xsize, ysize, xoff, yoff; ImagingShuffler shuffle; + void (*mb_shuffle)( + UINT8 *dst, const UINT8 *src, Imaging im, ImagingCodecState state); int bits, bytes; UINT8 *buffer; void *context; diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index dd06f3940..3d54008cb 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -152,7 +152,7 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) { goto mode_mismatch; } - imOut = ImagingNew(mode, imIn->xsize, imIn->ysize); + imOut = ImagingNew(mode, imIn->xsize, imIn->ysize, -1, -1); if (!imOut) { return NULL; } @@ -214,7 +214,7 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) { return (Imaging)ImagingError_ModeError(); } - imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize); + imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize, -1, -1); if (!imOut) { return NULL; } diff --git a/src/libImaging/RankFilter.c b/src/libImaging/RankFilter.c index 73a6baecb..da40c892f 100644 --- a/src/libImaging/RankFilter.c +++ b/src/libImaging/RankFilter.c @@ -84,7 +84,8 @@ MakeRankFunction(UINT8) MakeRankFunction(INT32) MakeRankFunction(FLOAT32) return (Imaging)ImagingError_ValueError("bad rank value"); } - imOut = ImagingNew(im->mode, im->xsize - 2 * margin, im->ysize - 2 * margin); + imOut = + ImagingNew(im->mode, im->xsize - 2 * margin, im->ysize - 2 * margin, -1, -1); if (!imOut) { return NULL; } diff --git a/src/libImaging/RawDecode.c b/src/libImaging/RawDecode.c index 24abe4804..8d8d24051 100644 --- a/src/libImaging/RawDecode.c +++ b/src/libImaging/RawDecode.c @@ -71,10 +71,11 @@ ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt } /* Unpack data */ - state->shuffle( + state->mb_shuffle( (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, ptr, - state->xsize); + im, + state); ptr += state->bytes; bytes -= state->bytes; diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index b27195a35..cdf62ceb3 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -42,7 +42,8 @@ */ Imaging -ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { +ImagingNewPrologueSubtype( + const char *mode, int xsize, int ysize, int depth, int bands, int size) { Imaging im; /* linesize overflow check, roughly the current largest space req'd */ @@ -190,6 +191,17 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { im->pixelsize = 4; im->linesize = xsize * 4; + } else if (strcmp(mode, IMAGING_MODE_MB) == 0) { + if (bands <= 0 || depth <= 0) { + return (Imaging)ImagingError_ValueError( + "multi-band missing bands and depth"); + } + im->bands = bands; + im->depth = depth; + im->pixelsize = depth * bands; + im->linesize = xsize * im->pixelsize; + im->type = IMAGING_TYPE_MB; + } else { free(im); return (Imaging)ImagingError_ValueError("unrecognized image mode"); @@ -225,9 +237,9 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { } Imaging -ImagingNewPrologue(const char *mode, int xsize, int ysize) { +ImagingNewPrologue(const char *mode, int xsize, int ysize, int depth, int bands) { return ImagingNewPrologueSubtype( - mode, xsize, ysize, sizeof(struct ImagingMemoryInstance)); + mode, xsize, ysize, depth, bands, sizeof(struct ImagingMemoryInstance)); } void @@ -485,15 +497,16 @@ ImagingAllocateBlock(Imaging im) { * Create a new, internally allocated, image. */ -Imaging -ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) { +static Imaging +ImagingNewInternal( + const char *mode, int xsize, int ysize, int depth, int bands, int dirty) { Imaging im; if (xsize < 0 || ysize < 0) { return (Imaging)ImagingError_ValueError("bad image size"); } - im = ImagingNewPrologue(mode, xsize, ysize); + im = ImagingNewPrologue(mode, xsize, ysize, depth, bands); if (!im) { return NULL; } @@ -514,13 +527,13 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) { } Imaging -ImagingNew(const char *mode, int xsize, int ysize) { - return ImagingNewInternal(mode, xsize, ysize, 0); +ImagingNew(const char *mode, int xsize, int ysize, int depth, int bands) { + return ImagingNewInternal(mode, xsize, ysize, depth, bands, 0); } Imaging ImagingNewDirty(const char *mode, int xsize, int ysize) { - return ImagingNewInternal(mode, xsize, ysize, 1); + return ImagingNewInternal(mode, xsize, ysize, -1, -1, 1); } Imaging @@ -531,7 +544,7 @@ ImagingNewBlock(const char *mode, int xsize, int ysize) { return (Imaging)ImagingError_ValueError("bad image size"); } - im = ImagingNewPrologue(mode, xsize, ysize); + im = ImagingNewPrologue(mode, xsize, ysize, -1, -1); if (!im) { return NULL; } diff --git a/src/map.c b/src/map.c index c298bd148..7b4514751 100644 --- a/src/map.c +++ b/src/map.c @@ -119,7 +119,8 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { return NULL; } - im = ImagingNewPrologueSubtype(mode, xsize, ysize, sizeof(ImagingBufferInstance)); + im = ImagingNewPrologueSubtype( + mode, xsize, ysize, -1, -1, sizeof(ImagingBufferInstance)); if (!im) { PyBuffer_Release(&view); return NULL;