Repository: python-pillow/Pillow

Issue:
Image.open() fails for JPEG 2000 with embedded ICC profile
### What did you do?

I tried to open a JPEG 2000 file here.
(https://github.com/openpreserve/format-corpus/blob/master/jp2k-test/icc/balloon_eciRGBv2_aware.jp2)

The image can be opened by IrfanView, GIMP and read by ImageMagick.

### What actually happened?

OSError: cannot identify image file 'balloon_eciRGBv2_aware.jp2'

### What are your OS, Python and Pillow versions?

* OS: Windows 10
* Python: Python 3.6.6 :: Anaconda custom (64-bit)
* Pillow: 5.4.0 conda-forge

```python
bim = Image.open('balloon_eciRGBv2_aware.jp2')
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-7-9e62275bed11> in <module>()
----> 1 bim = Image.open('balloon_eciRGBv2_aware.jp2')

~\Anaconda3-5\lib\site-packages\PIL\Image.py in open(fp, mode)
   2685         warnings.warn(message)
   2686     raise IOError("cannot identify image file %r"
-> 2687                   % (filename if filename else fp))
   2688 
   2689 #

OSError: cannot identify image file 'balloon_eciRGBv2_aware.jp2'
```



Discussion:
The error is being caused by `struct.error: unpack requires a bytes object of length 8` at https://github.com/python-pillow/Pillow/blob/7bf5246b93cc89cfb9d6cca78c4719a943b10585/src/PIL/Jpeg2KImagePlugin.py#L96

The code would normally have broken out of the loop before that. This section of the code is trying to find the image mode. However, it does not handle the case where `meth` is 2, which signifies the Restricted ICC profile.
whats the progress on this?
Any news?
I've created PR #5654 to resolve this.

Resolving patch:
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -159,50 +159,23 @@ def _parse_jp2_header(fp):
     bpc = None
     nc = None
     dpi = None  # 2-tuple of DPI info, or None
-    unkc = 0  # Colorspace information unknown
 
     while header.has_next_box():
         tbox = header.next_box_type()
 
         if tbox == b"ihdr":
-            height, width, nc, bpc, c, unkc, ipr = header.read_fields(">IIHBBBB")
+            height, width, nc, bpc = header.read_fields(">IIHB")
             size = (width, height)
-            if unkc:
-                if nc == 1 and (bpc & 0x7F) > 8:
-                    mode = "I;16"
-                elif nc == 1:
-                    mode = "L"
-                elif nc == 2:
-                    mode = "LA"
-                elif nc == 3:
-                    mode = "RGB"
-                elif nc == 4:
-                    mode = "RGBA"
-        elif tbox == b"colr":
-            meth, prec, approx = header.read_fields(">BBB")
-            if meth == 1 and unkc == 0:
-                cs = header.read_fields(">I")[0]
-                if cs == 16:  # sRGB
-                    if nc == 1 and (bpc & 0x7F) > 8:
-                        mode = "I;16"
-                    elif nc == 1:
-                        mode = "L"
-                    elif nc == 3:
-                        mode = "RGB"
-                    elif nc == 4:
-                        mode = "RGBA"
-                elif cs == 17:  # grayscale
-                    if nc == 1 and (bpc & 0x7F) > 8:
-                        mode = "I;16"
-                    elif nc == 1:
-                        mode = "L"
-                    elif nc == 2:
-                        mode = "LA"
-                elif cs == 18:  # sYCC
-                    if nc == 3:
-                        mode = "RGB"
-                    elif nc == 4:
-                        mode = "RGBA"
+            if nc == 1 and (bpc & 0x7F) > 8:
+                mode = "I;16"
+            elif nc == 1:
+                mode = "L"
+            elif nc == 2:
+                mode = "LA"
+            elif nc == 3:
+                mode = "RGB"
+            elif nc == 4:
+                mode = "RGBA"
         elif tbox == b"res ":
             res = header.read_boxes()
             while res.has_next_box():


Test patch:
diff --git a/Tests/images/balloon_eciRGBv2_aware.jp2 b/Tests/images/balloon_eciRGBv2_aware.jp2
new file mode 100644
Binary files /dev/null and b/Tests/images/balloon_eciRGBv2_aware.jp2 differ
diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py
--- a/Tests/test_file_jpeg2k.py
+++ b/Tests/test_file_jpeg2k.py
@@ -159,6 +159,16 @@ def test_load_dpi():
         assert "dpi" not in im.info
 
 
+def test_restricted_icc_profile():
+    ImageFile.LOAD_TRUNCATED_IMAGES = True
+    try:
+        # JPEG2000 image with a restricted ICC profile and a known colorspace
+        with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im:
+            assert im.mode == "RGB"
+    finally:
+        ImageFile.LOAD_TRUNCATED_IMAGES = False
+
+
 def test_header_errors():
     for path in (
         "Tests/images/invalid_header_length.jp2",