-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for high bit depth multichannel images #8224
base: main
Are you sure you want to change the base?
Conversation
This only gets us to opening the first band, it doesn't actually allow the other bands to be retrieved.
Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrote a review a few days back but apparently it got lost in GH somewhere. So from memory...
There are 2 potential issues that the multiband storage should be able to solve:
- Allowing RGB/CMYK images with more than 8 bytes/channel
- Allowing N channel images, where n>4.
For example, we should be able to read a 16 bit RGB image and do all operations on it, and we should be able to read a 5 band image and use 4 of the bands as RGBA.
This is currently complicated by modes encoding two separate issues -- The band numerical representation (8 bit int/float/etc) and logical meaning (L/rgb/cmyk).
What I'm generally missing in this PR is an indication of:
- What's the metadata design for the pixel format and band layout
- What's the approach to doing native operations on these images of arbitrary struct shape/size?
@@ -433,6 +433,36 @@ float16tofloat32(const FLOAT16 in) { | |||
return out[0]; | |||
} | |||
|
|||
static inline PyObject * | |||
getpixel_mb(Imaging im, ImagingAccess access, int x, int y) { | |||
UINT8 pixel[sizeof(INT32) * 6]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the 6 from here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be UINT8 pixel[im->pixelsize]
but a strict compiler is unhappy.
Currently the largest entry in OPEN_INFO
has 6 bands, so that it's defined as 6.
I may have to put a pre-allocated uint8[pixelsize]
buffer in Imaging
struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C layer has to be internally safe, we can't be relying on inderict python restrictions for memory safety like that. If we can't do this statically, it's going to have to be dynamic, or there needs to be an arbitrary band limit constant somewhere in the code. (Which ultimately, may be a good idea, if only to prevent someone from creating an image with 2^32-1 bands)
@@ -182,6 +182,43 @@ | |||
(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,)): ("MB", "MB"), | |||
( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This formatting is actively bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's forced by make lint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well then, make lint
is wrong. ;>
This may just be my long term fight against minless conformity, but sometimes black's formatting obscures the code. And this is one of the cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest just # noqa
around whatever you don't want formatted for now … also the kids are using ruff
these days 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use # fmt: off
+# fmt: on
or # fmt: skip
here:
https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#ignoring-sections
src/libImaging/Crop.c
Outdated
@@ -37,7 +37,8 @@ ImagingCrop(Imaging imIn, int sx0, int sy0, int sx1, int sy1) { | |||
ysize = 0; | |||
} | |||
|
|||
imOut = ImagingNewDirty(imIn->mode, xsize, ysize); | |||
imOut = ImagingNewDirty( | |||
imIn->mode, (ImagingNewParams){xsize, ysize, imIn->depth, imIn->bands}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to have im->params
as {xsize, ysize, depth, bands
} for passing into ImagingNewDirty?
int xsize; | ||
int ysize; | ||
int depth; /** MB mode only. */ | ||
int bands; /** MB mode only. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't we seting bands/depth for the non MB modes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The depth
variable appears to be aspirational and may never have been used, possibly the same is true for bands, but haven't confirmed either yet.
If so, and depth
was "ignored in this version" in Fredrik's original implementation and subsequent development in the 90s and 2000s, that makes this feature even more compelling to add now, as it appears to have been envisioned in some form from the beginning. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my hope that all other modes are eventually converted to MB mode only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#WIPFor25Years Can be a thing
Imaging im; | ||
|
||
/* linesize overflow check, roughly the current largest space req'd */ | ||
if (xsize > (INT_MAX / 4) - 1) { | ||
if (p.xsize > (INT_MAX / 4) - 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need to be updated for bounds checking for MB images.
Specifically, xsize > (INT_MAX / (bands*depth) -1)
needs to error.
"multi-band missing bands and depth"); | ||
} | ||
im->bands = p.bands; | ||
im->depth = p.depth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are we checking for valid Bands/Depth?
stride = xsize * 2; | ||
} else if (strcmp(mode, IMAGING_MODE_MB) == 0) { | ||
stride = xsize * depth * bands; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bounds checking here?
@@ -68,10 +78,21 @@ 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") */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- "MB"
UINT8 *pos = pixel; | ||
for (int i = 0; i < im->bands; ++i) { | ||
switch (im->depth) { | ||
case CHAR_BIT: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style note - CHAR_BIT isn't used elsewhere here, We're just using 8
I proposed one back in #6547 if anyone wants to look at that again. |
Thanks for the review @wiredfool ! I'm hopeful that between now and October or EOY at the latest we (@yoursunny @radarhere @Yay295 et al.) can develop this into something you'll approve for merging. Adding all four of us as reviewers to confirm acceptance when we get there, most importantly @hugovk and @radarhere will need to sign off on this as they are currently on the "front lines" of Pillow development and we don't want to release something that will make any of our lives harder. My current assumption is that an implementation that satisfies the requirements and does not introduce any or minimal technical debt is possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the metadata design for the pixel format and band layout
There's a brief description in Imaging.h
.
@@ -182,6 +182,43 @@ | |||
(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,)): ("MB", "MB"), | |||
( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's forced by make lint
.
dst[i + 1] = src[i]; | ||
} | ||
return; | ||
case CHAR_BIT: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved below line330.
(I ducked up)
@@ -433,6 +433,36 @@ float16tofloat32(const FLOAT16 in) { | |||
return out[0]; | |||
} | |||
|
|||
static inline PyObject * | |||
getpixel_mb(Imaging im, ImagingAccess access, int x, int y) { | |||
UINT8 pixel[sizeof(INT32) * 6]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be UINT8 pixel[im->pixelsize]
but a strict compiler is unhappy.
Currently the largest entry in OPEN_INFO
has 6 bands, so that it's defined as 6.
I may have to put a pre-allocated uint8[pixelsize]
buffer in Imaging
struct.
int xsize; | ||
int ysize; | ||
int depth; /** MB mode only. */ | ||
int bands; /** MB mode only. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my hope that all other modes are eventually converted to MB mode only.
Can you put together a markdown summary of what are the crux issues you see for this feature, and your plan/tradeoffs? You've got a good start, but I'd like to see your understanding of the complexity of the problem and your plan of attack. I'm a little worried that there's a required complexity out there that's not part of the early code that will be a showstopper. |
Well said and I'd also encourage other folks tracking this issue to provide such ideas too. The way this gets to production is:
🚀 🚀 🚀 |
@ericvsmith Can you help with this one? We have a description of the requirements and are in need of a better definition of the format requirements and detailed description of how we plan to support those formats. We know @yoursunny is using the existing data structures so maybe that makes the job of describing our implementation in slightly easier 🤔 Thank you for any advice or guidance. |
My hope is that whatever solution tackles #1888 would also support YCbCr(A), and perhaps chroma subsampling. I'm not sure that a single multi-band image mode is compatible with being able to store raw YUV images. |
Sorry, I just noticed this. I'm not going to be able to look at this for a few weeks, but will check when I return. |
@ericvsmith Also note we now have #8330 to consider, thanks @wiredfool ! |
For clarity -- my PR is just about exposing the existing memory structure as an arrow array, not adding multibyte or replacing storage with arrow. |
Fixes #1888 .
Changes proposed in this pull request: