Repository: python-pillow/Pillow

Problem statement:
Is this MPO file decoded correctly?
### What did you do?

```python3

from PIL import Image

im = Image.open("328384445-d4972d5b-3409-4d5f-a107-ccb8c5dc0177.jpg")  # MPO file
im.show()
```

### What did you expect to happen?

Correctly decode second image

<img width="766" alt="image" src="https://github.com/python-pillow/Pillow/assets/13381981/ae732d38-ec2c-4408-b53e-14fed3b2f766">


### What actually happened?

<img width="738" alt="image" src="https://github.com/python-pillow/Pillow/assets/13381981/f7728e4d-5697-482b-ab6b-8badf9ea1765">

### What are your OS, Python and Pillow versions?

* OS: macOS/Linux
* Python: 3.10/3.11
* Pillow: 10.2.0 (edited) (was 10.3.0, my mistake)


Test image can be found here: https://github.com/comfyanonymous/ComfyUI/issues/3416#issuecomment-2097406107





Hints:
Sorry my bad, I tested it on Pillow 10.2

on Pillow 10.3 there is no error when decoding, just second image looks wrong.

Updated issue to reflect this. Sorry for mess.
Hi, @bigcat88 , 10.3.0 correctly extract the second frame with a different resolution. However, the second frame is not actually a frame, it's a `gain map` according to this [UltraHDR format](https://developer.android.com/media/platform/hdr-image-format). 
A JPG file should only have one frame. And people naturally expect the second frame would in the same resolution as the first one. That's the problem we have in https://github.com/comfyanonymous/ComfyUI/issues/3416#issuecomment-2097406107

So, supporting UltraHDR is a good way to go, I guess. #8036

> 10.3.0 correctly extract the second frame with a different resolution

No it does not, imho.

Pillow should either reject a `gain map` and show only primary image, or it should decode second frame correctly, even with lower resolution.
btw, the tomato photo I provided eariler is not very representitive, since the `gain map` is all 1.0s (completely white). I didn't understand this was related to the UltraHDR format earlier.

Here is another photo I took that has something in the `gain map`:

![1000014283](https://github.com/python-pillow/Pillow/assets/14249458/d2130eca-50de-49cf-867c-6fc2dc525bca)

we can see the `gain map` is smaller comparing to the original photo.
![image](https://github.com/python-pillow/Pillow/assets/14249458/b5812955-75dd-4904-b6e6-a8bc6c8be813)

> btw, the tomato photo I provided eariler is not very representitive, since the `gain map` is all 1.0s (completely white).

please look at my first message.
It is not white.
perhaps gain maps need their own type returned from .get_bands() or some way to explicitly exclude them from ImageSequence.Iterator
Are there any images with a small file size that could be added to our test suite and distributed under the Pillow license?
> Are there any images with a small file size that could be added to our test suite and distributed under the Pillow license?

I, as the author of the tomato photo and the trees photo, agree to add these to the Pillow test suite and to adhere to its licensing terms.

I don't have smaller ones, since this is already the 'medium resolution'; the other option is 'full resolution.'

Patch:
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -843,6 +843,10 @@ def jpeg_factory(fp=None, filename=None):
     try:
         mpheader = im._getmp()
         if mpheader[45057] > 1:
+            for segment, content in im.applist:
+                if segment == "APP1" and b' hdrgm:Version="' in content:
+                    # Ultra HDR images are not yet supported
+                    return im
             # It's actually an MPO
             from .MpoImagePlugin import MpoImageFile
 


Test patch:
diff --git a/Tests/images/ultrahdr.jpg b/Tests/images/ultrahdr.jpg
new file mode 100644
Binary files /dev/null and b/Tests/images/ultrahdr.jpg differ
diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py
--- a/Tests/test_file_mpo.py
+++ b/Tests/test_file_mpo.py
@@ -226,6 +226,11 @@ def test_eoferror() -> None:
         im.seek(n_frames - 1)
 
 
+def test_ultra_hdr() -> None:
+    with Image.open("Tests/images/ultrahdr.jpg") as im:
+        assert im.format == "JPEG"
+
+
 @pytest.mark.parametrize("test_file", test_files)
 def test_image_grab(test_file: str) -> None:
     with Image.open(test_file) as im: