introduce multi-band format (TIFF only)

This commit is contained in:
Junxiao Shi 2024-06-01 18:06:14 +00:00
parent 03df35777c
commit 936439b481
12 changed files with 132 additions and 37 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;