Skip to content
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

Open
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

yoursunny
Copy link

Fixes #1888 .

Changes proposed in this pull request:

  • Add support for high bit depth multichannel images

@aclark4life
Copy link
Member

Thanks!

Copy link
Member

@wiredfool wiredfool left a 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:

  1. Allowing RGB/CMYK images with more than 8 bytes/channel
  2. 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];
Copy link
Member

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?

Copy link
Author

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.

Copy link
Member

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"),
(
Copy link
Member

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.

Copy link
Author

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.

Copy link
Member

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.

Copy link
Member

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 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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});
Copy link
Member

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. */
Copy link
Member

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?

Copy link
Member

@aclark4life aclark4life Jul 19, 2024

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. 🤔

Copy link
Author

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.

Copy link
Member

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) {
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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") */
Copy link
Member

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:
Copy link
Member

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

@Yay295
Copy link
Contributor

Yay295 commented Jul 19, 2024

What's the metadata design for the pixel format and band layout

I proposed one back in #6547 if anyone wants to look at that again.

@aclark4life
Copy link
Member

aclark4life commented Jul 19, 2024

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.

@aclark4life aclark4life requested review from hugovk and radarhere July 19, 2024 14:57
Copy link
Author

@yoursunny yoursunny left a 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"),
(
Copy link
Author

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:
Copy link
Author

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];
Copy link
Author

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. */
Copy link
Author

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.

@wiredfool
Copy link
Member

What's the metadata design for the pixel format and band layout

There's a brief description in Imaging.h.

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.

@aclark4life
Copy link
Member

aclark4life commented Jul 19, 2024

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:

  • Implementation is solid and agreed upon and covers all known use cases
  • All known use cases are tested prior to release with test data from GIS, VFX and other industries.
  • A plan to support the implementation is made prior to release e.g.
    • "convert everything to MB over X number of releases … "

🚀 🚀 🚀

@aclark4life
Copy link
Member

Can you put together a markdown summary of what are the crux issues you see for this feature, and your plan/tradeoffs?

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

@fdintino
Copy link

fdintino commented Aug 2, 2024

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.

@ericvsmith
Copy link

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

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.

@aclark4life
Copy link
Member

@ericvsmith Also note we now have #8330 to consider, thanks @wiredfool !

@wiredfool
Copy link
Member

For clarity -- my PR is just about exposing the existing memory structure as an arrow array, not adding multibyte or replacing storage with arrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for high bit depth multichannel images
9 participants