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

Delegate Image mode and size to ImagingCore #7271

Closed
wants to merge 5 commits into from

Conversation

Yay295
Copy link
Contributor

@Yay295 Yay295 commented Jul 9, 2023

This causes a lot of errors. The main issues are that Image.im isn't always set, so we still need to keep Python-only variables for these properties, and the Python values are writable, but the C values are not writable from Python.

@Yay295
Copy link
Contributor Author

Yay295 commented Jul 9, 2023

There is a setmode method on ImagingCore that could perhaps be used as the setter for ImagingCore.mode. I'm not sure why it wasn't, so I didn't make that change.

src/PIL/Image.py Outdated Show resolved Hide resolved
@radarhere
Copy link
Member

Could you provide an explanation for why this change is helpful?

@Yay295
Copy link
Contributor Author

Yay295 commented Jul 9, 2023

I think keeping the mode and size of Image and ImagingCore in sync is a good thing, and delegating to ImagingCore when possible keeps them in sync.

@radarhere
Copy link
Member

As this PR knows, because self.im starts out as None, and stays that way until an image is loaded, mode and size can't always be delegated to C, and must exist sometimes in Python.

I think keeping the mode and size of Image and ImagingCore in sync is a good thing

I agree, and I would think that they are already in sync.

and delegating to ImagingCore when possible keeps them in sync.

While this PR isn't yet finalised, it would seem that the idea here is to make Python's _mode and the ImagingCore mode go out of sync. The public API's mode might stay correct, but I'm uncomfortable with the idea of Python's _mode sometimes disagreeing with the C mode. It would seem to be clearer code if they were both kept the same.

@Yay295
Copy link
Contributor Author

Yay295 commented Aug 2, 2023

The original issue that I created this change for was discussed here: #7260 (comment)

The problem was that changing an image's mode in C does not change the mode seen in Python, because the C code doesn't have access to the Python value. So if an image's mode or size is ever changed directly in C, it will be out of sync with the value(s) in Python. Likewise, if one of these values is changed in Python, it will be out of sync with the value(s) in C (making Image.size and Image.mode read-only helps prevent this). By always using the C value if it exists we can be sure we are using the same value everywhere.

@radarhere
Copy link
Member

The problem was that changing an image's mode in C does not change the mode seen in Python, because the C code doesn't have access to the Python value. So if an image's mode or size is ever changed directly in C, it will be out of sync with the value(s) in Python.

Pillow has a public Python API. Our intention is not for users to access the C API directly. If ImagingCore.rewrite_mode() is something that is added in the future, we can talk about that then, but for the moment, I'm not aware of any logic in C determines what the ImagingCore mode is - the logic to do so lives in Python.

Likewise, if one of these values is changed in Python, it will be out of sync with the value(s) in C (making Image.size and Image.mode read-only helps prevent this).

I think ignoring the Python mode would obscure the fact that in this scenario, setting the mode in Python is not having the intended effect. That doesn't sound helpful to me.

By always using the C value if it exists we can be sure we are using the same value everywhere.

It ensures that im.mode reflects the C mode, but you're also deliberately removing setting _mode in this PR, making the Python _mode sometimes incorrect. This is not part of the public API, so you may think it doesn't matter, but _mode is something that plugins still use, and so something that users writing plugins still need to understand. It seems to me like this makes the code less clear.

@Yay295
Copy link
Contributor Author

Yay295 commented Aug 3, 2023

If ImagingCore.rewrite_mode() is something that is added in the future, we can talk about that then,

ImagingCore.rewrite_mode() has been proposed as part of #7260, which is why we are talking about this now.

I think ignoring the Python mode would obscure the fact that in this scenario, setting the mode in Python is not having the intended effect.

This is currently the case. Before #7307 (which is not yet part of a release), it was possible to set Image.mode. This would sometimes actually work if the C mode wasn't used, and sometimes it wouldn't if the C mode was used.

but you're also deliberately removing setting _mode in this PR

_mode did not exist before #7307. This change was made before that one, and I did add a setter for mode. Your changes are the ones that removed the setters.

@radarhere
Copy link
Member

If you'd like to wait until #7260 is resolved, okay.

It ensures that im.mode reflects the C mode, but you're also deliberately removing setting _mode in this PR, making the Python _mode sometimes incorrect.

When I said this, I wasn't referring to setters for mode, I was referring to
Screenshot 2023-08-03 at 3 32 24 pm

@Yay295
Copy link
Contributor Author

Yay295 commented Aug 4, 2023

Image.im is also set in all of those locations, so Image.mode would have gotten its data from Image.im.mode, making those lines redundant. I suppose Image.im could be set to None after that point though, so they wouldn't be redundant in that case.

…ilable

When setting the values on Image also try to update the values on Image.im. There isn't currently a way to update values in the other direction.
@Yay295 Yay295 force-pushed the image_mode_size_delegation branch from e311a46 to 919dbbe Compare August 5, 2023 20:02
@Yay295
Copy link
Contributor Author

Yay295 commented Aug 5, 2023

New change based on main.

mode and size are read-only properties.
_mode and _size are read-write properties.
__mode and __size are the actual values.

If _mode or _size are changed, it will also attempt to set those values on Image.im.

and use double quotes instead of single quotes
@Yay295 Yay295 force-pushed the image_mode_size_delegation branch from def6e62 to b879ada Compare August 5, 2023 20:11
@@ -139,6 +139,9 @@ def get_format_mimetype(self):
if self.format is not None:
return Image.MIME.get(self.format.upper())

def _use_im_values(self):
return self.tile is None and self.im is not None
Copy link
Member

Choose a reason for hiding this comment

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

What is purpose of self.tile check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@radarhere proposed that change for ImageFile in a pull request they made for the previous code. The issue is that when seeking to another frame, the Image values may not match the Image.im values. Using not self.tile like in their pull request wasn't working for me though, so I changed it to self.tile is None, which after looking into it more, probably isn't correct.

Comment on lines +3687 to +3688
self->image->xsize = xsize;
self->image->ysize = ysize;
Copy link
Member

Choose a reason for hiding this comment

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

Why we want to do this? It will just break image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that if Image.size is changed, Image.im.size will have the same value. Another option could be that if Image.size is changed, and Image.im.size doesn't match, it raises an exception.

Comment on lines +515 to +517
if self._use_im_values():
return self.im.size
return self.__size
Copy link
Member

Choose a reason for hiding this comment

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

Why this can't be just in size property?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The _size getter is actually used in quite a few places, so that would have to be changed to use the size getter first. It would save three lines if the _size getter was removed though:

    @property
    def size(self):
        if self._use_im_values():
            return self.im.size
        return self.__size

    def _size(self, value):
        # set im.size first in case it raises an exception
        if self._use_im_values():
            self.im.size = value
        self.__size = value

    _size = property(fset=_size)

Copy link
Member

Choose a reason for hiding this comment

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

It would save three lines

More important that this saves one indirect method call (width → size → _size) on each attribute getter.

@radarhere
Copy link
Member

radarhere commented Oct 18, 2023

#7271 (comment)

ImagingCore.rewrite_mode() has been proposed as part of #7260, which is why we are talking about this now.

As far as I can see, Yay295#8 removed the need for ImagingCore.rewrite_mode() in that PR. Does that make this PR unnecessary?

I'm not in favour of this PR, for reasons stated earlier. @homm you've left some individual comments, but regarding the basic idea, do you see value here that I don't?

Part of the reason I mention this now is that I see pydicom/pydicom#1907 referencing this in trying to grapple with #7307, and that doesn't seem helpful. I know that this PR is unmerged and in draft mode, so it has all the indications that it is not ready for consumption, but nevertheless.

@homm
Copy link
Member

homm commented Oct 18, 2023

regarding the basic idea, do you see value here that I don't?

I do see profit in wall-time and memory consumption, but I'm not sure about public interface. In general, I think we can close it for now.

@radarhere radarhere closed this Oct 18, 2023
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.

3 participants