Skip to content

Commit

Permalink
introduce multi-band format (TIFF only)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Jun 1, 2024
1 parent 03df357 commit 936439b
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 37 deletions.
6 changes: 3 additions & 3 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,14 +877,14 @@ def test_oom(self, test_file: str) -> None:
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


Expand Down
4 changes: 3 additions & 1 deletion src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ def __init__(self, fp=None, filename=None):

self.readonly = 1 # until we know better

self.newconfig = ()

self.decoderconfig = ()
self.decodermaxblock = MAXBLOCK

Expand Down Expand Up @@ -317,7 +319,7 @@ def load(self):
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)
Expand Down
13 changes: 8 additions & 5 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
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
Expand Down Expand Up @@ -183,7 +183,7 @@
(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,
Expand All @@ -194,7 +194,7 @@
0,
0,
),
): ("I;16", "I;16"),
): ("MB", "MB"),
(
II,
1,
Expand All @@ -206,7 +206,7 @@
0,
0,
),
): ("I;16", "I;16"),
): ("MB", "MB"),
(
II,
1,
Expand All @@ -219,7 +219,7 @@
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"),
Expand Down Expand Up @@ -1474,6 +1474,9 @@ def _setup(self):

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

Expand Down
46 changes: 40 additions & 6 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 *
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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();
Expand Down
25 changes: 24 additions & 1 deletion src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions src/libImaging/Access.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_) \
Expand Down Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions src/libImaging/Imaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/libImaging/Point.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion src/libImaging/RankFilter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
5 changes: 3 additions & 2 deletions src/libImaging/RawDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 936439b

Please sign in to comment.