Repository: python-pillow/Pillow

Issue:
TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB
Pillow 5.0.0 can no longer read JPEG-compressed TIFFs:

```console
$ pip freeze | grep -i pillow
Pillow==5.0.0
pillow-scripts==5.0.0
$ wget -q https://github.com/jwilk/didjvu/raw/0.8/tests/data/ycbcr-jpeg.tiff
$ pilconvert.py -r ycbcr-jpeg.tiff tmp.png
TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB..
cannot convert image (<type 'exceptions.IOError'>:-2)
```

(I presume `TIFF_JPEGCOLORMODE` is a typo for `TIFFTAG_JPEGCOLORMODE`.)

This used to work until Pillow 4.3.0.


Discussion:
'This used to work until Pillow 4.3.0' - Just to be clear, while this sentence would indicate that 4.3.0 is also problematic, this is not the case, and you are just finding this issue with 5.0.0.
Indeed, Pillow 4.3.0 was happy with this file.
Sorry for bad wording.
There was a change in 5.0 from using the internal JPEG decoder for JPEG encoded TIFFs to the libtiff decoder because the internal decoder was returning incorrect images.  (Actually, it's more that we shifted the remaining three internally provided compression modes -- jpeg, packbits, and lzw, because 2 of the 3 were incorrect).

Your test file does appear to work on my machine though, which is using libtiff 4.0.8 from homebrew. 
I tested this with `Pillow-5.0.0-cp27-cp27mu-manylinux1_i686.whl` installed from PyPI, which has its own embedded libtiff AFAICS.
pillow-wheels was recently updated from libtiff 4.0.8 to 4.0.9: https://github.com/python-pillow/pillow-wheels/pull/70
This is reproducible with Pillow-5.0.0-cp36-cp36m-win_amd64, which is also using libtiff 4.0.9.
I am having this problem in the Loris application when using Pillow 5.0.0 via pip. For now, I think we'll fall back to using Pillow 4.3.0, since I can confirm that my TIFs are being handled correctly in 4.3.0, but it sounds like that might not be the best long-term solution.

Does anyone know what the actual technical problem is? A bug in libtiff or something else? 
Is the JPEGColorMode section relevant on https://au.mathworks.com/help/matlab/ref/tiff-class.html?requestedDomain=true#property_d119e1061246?
So the error says "TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB"...

I wondered how one might enable TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB... and I saw an example of it at https://github.com/ImageMagick/tiff/blob/master/tools/ppm2tiff.c#L169. This line is also relevant: https://github.com/ImageMagick/tiff/blob/master/tools/ppm2tiff.c#L223

I'm not that familiar with image processing in general or Pillow, but that looks like what we want to be doing in Pillow. 

So I cloned the ImageMagick/tiff repo and searched for "TIFFTAG_JPEGCOLORMODE" and found it to correspond to the integer 65538. 

I used https://pillow.readthedocs.io/en/latest/reference/TiffTags.html to dump all the Tiff Tags that Pillow knows about... and didn't find the integer 65538. 

I don't know if that means that Pillow can't use it... or if it's just not in that dictionary. But this all seems relevant...


Looking again at the ImageMagick code... I can see that the constant JPEGCOLORMODE_RGB is 0x0001. 

So I'm going to see if I can just go ahead and set that with the "tag" method...
Hmm I can't even use the "save" method for my TIFF anyway...
According to http://maptools-org.996276.n3.nabble.com/JPEG-compressed-RGB-tiled-TIFF-with-chroma-subsampling-td14062.html and the ImageMagick code, I might need to set TIFFTAG_PHOTOMETRIC to PHOTOMETRIC_YCBCR which means...

TIFFTAG_PHOTOMETRIC is 262 (otherwise known as PhotometricInterpretation in Pillow)
PHOTOMETRIC_YCBCR is 6

Hmm... It looks like tag 262 is already set to 6... damn.
https://stackoverflow.com/questions/46283224/how-to-add-additional-tag-for-tiff-file
I'm guessing @wiredfool might be able to take it from here based on the work on https://github.com/python-pillow/Pillow/pull/344 and https://github.com/python-pillow/Pillow/issues/291

It looks like libtiff is aware of the 65538 tag... https://github.com/vadz/libtiff/blob/master/libtiff/tiff.h
Ah this is interesting... it's not a tag but a pseudo tag... https://github.com/vadz/libtiff/blob/master/libtiff/tiff.h#L538
So I'm able to use Image.open() to open my TIFF but then I try to save it and I get the following:

TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB..
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    im.save("new.tiff", tiffinfo=im.tag)
  File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 1897, in save
    self.load()
  File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1039, in load
    return self._load_libtiff()
  File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1131, in _load_libtiff
    raise IOError(err)
IOError: -2

It looks like the error is being generated by a decoder... which is using some IO object of some kind...

I don't know why we didn't get this error during Image.open() but whatever. Ignoring that for now.

I see the decoder here:

decoder = Image._getdecoder(self.mode, 'libtiff', tuple(args),
                                    self.decoderconfig)

Not really seeing anything about decoderconfig in the code... but I reckon that might be useful. Because me setting those pseudo-tags where I am is probably doing nothing I imagine. In the case of ImageMagick, they're setting those for their new TIFF that they're going to write out to... whereas we have an existing TIFF that we need to read... but we need to set these pseudocodes before it can properly read it I think?



Ahh reading through Image.py and I'm seeing that Image.open is a lazy method, which is probably why I don't get an error...

Ah yup. When I do a im.rotate(45), now I get the error as it's actually trying to decode the TIFF...
I'm guessing that we might need to update "src/libImaging/TiffDecode.c"?  

I'd say probably using ImagingLibTiffSetField to set the tags mentioned above around https://github.com/python-pillow/Pillow/blob/master/src/libImaging/TiffDecode.c#L220?

I think the error is being generated at https://github.com/python-pillow/Pillow/blob/master/src/libImaging/TiffDecode.c#L238, so I think that's a reasonable suggestion.
Everything I'm seeing in the ImageMagick code seems to suggest this, so I'm thinking that this is probably the right direction.

I'm tempted to write a patch myself. I was going to leave it for you folk in the project, but I think I have a VM that I probably could use to test this on...
Ok harder than anticipated finding the right spot... but I know I'm calling the decoder here at least:

       elif hasattr(self.fp, "fileno"):
            # we've got a actual file on disk, pass in the fp.
            if DEBUG:
                print("have fileno, calling fileno version of the decoder.")
            self.fp.seek(0)
            # 4 bytes, otherwise the trace might error out
            n, err = decoder.decode(b"fpfp")

Wow... I think I may have done it. I need to add some more if statements but... I was able to load the TIFF without getting that error.
Hmm so I could use the load() method but using the save() method generated an error:

Encoding line bt lineTIFFScanlineSize64: Invalid YCbCr subsampling.
Encode Error, row 0
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    im.save("new.tiff")
  File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 1930, in save
    save_handler(self, fp, filename)
  File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1540, in _save
    raise IOError("encoder error %d when writing image file" % s)
IOError: encoder error -2 when writing image file

Might just need the same bit of code for the encoder as the decoder though...
So enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB did prevent the fatal error from occuring.

However, when I run "pilconvert.py -r old.tif tmp.png", I wind up with a PNG file which has some horrifically bad colours.

It went from a black background with a brown/white foreground to a green background with a fuchsia foreground.

Same goes when outputting to a jpeg instead of a png. 



By setting the TIFF_JPEGCOLORMODE to JPEGCOLORMODE_RGB, I think we go down this branch:

https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1131

"pilconvert.py old.tif tmp.png" gives this error:
cannot convert image (<type 'exceptions.IOError'>:cannot write mode YCbCr as PNG)

"pilconvert.py -g old.tif tmp.jpg" looks pretty good...

"pilconvert.py -p old.tif tmp.png" looks bad.. the same as -r

Hoping that it was just pilconvert.py... I just tried it in Loris and it looks bad there too.



For what it's worth, I'm on Ubuntu 16.04.03 and using libtiff5 version 4.0.6-1ubuntu0.2...
So comparing Pillow 5.1.0.dev0 against Pillow 4.3.0...

Both are handling grayscale fine for transforming TIFF to other formats. But converting the TIFF to color JPEG/PNG isn't working for Pillow 5.1.0.dev0 (with my modifications) but it's working perfectly for Pillow 4.3.0...

I'm thinking there is something I must be missing in the conversion process somewhere... possibly on the JPEG/PNG end since the grayscale is fine so it looks like the TIFF is being read correctly?
Ok this is interesting... look at https://en.wikipedia.org/wiki/YCbCr

"A color image and its Y, CB and CR components. The Y image is essentially a greyscale copy of the main image."

The Y image is a perfect greyscale version. The CR version looks like green in the background and fuchsia in the foreground... pretty much exactly what I'm seeing when converting to JPEG/PNG.

The original error message is coming from https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1299, so maybe the issue is in tif_jpeg.c? 


I've commented on http://bugzilla.maptools.org/show_bug.cgi?id=2140. 

I should get my code sample up so that others can review it...
Ok there you go: 

https://github.com/minusdavid/Pillow/commit/df71a325e431bb8e57d60676c700ac4c6008af22

I followed these instructions for the most part:
https://pillow.readthedocs.io/en/latest/installation.html#external-libraries

Basically I installed all the external libraries, uninstalled Pillow with "pip uninstall Pillow" and then installed my Git version using "pip install ./Pillow". It was easy. 
That maptools bug is 8 years old, and very likely not directly related to this. 

There were several cases where YCbCr images were being returned as RGB from Pillow's internal jpeg decoder in 4.3.0, and switching to the libtiff decoder fixed them, but broke a different subset. 

I think what's happening here is that you're telling libjpeg to use RGB mode for JPEGs, regardless of the actual encoding of the image. 

Looking back, I realise that I forgot to say that I thought the libtiff/maptools bug wasn't directly related, but interesting that the same error message has been around so long.

Mmm, that sounds like a plausible explanation, but according to https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1132 it should be converting YCbCr to RGB. I suppose I haven't looked to make sure it is following that branch though...
It looks like the TIFFs where I've encountered errors are test data and our project shouldn't involve any TIFFs containing JPEGs, so I might not end up having to worry about this in practice... but I admit that this bug really has me curious and has got me intrigued in image processing...
Is there a solution to this problem? I'm just trying to load the TIF via skimage.io.imread.
This is a real issue, but is going to take work in the tiff decoder to fix, or we have to revert the changed that fixed color issues on jpeg encoded tiffs. 

Resolving patch:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -257,14 +257,10 @@
     (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
     (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
 
-    (II, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
-    (MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
-    (II, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
-    (MM, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"),
-    (II, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"),
-    (MM, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"),
-    (II, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
-    (MM, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"),
+    # JPEG compressed images handled by LibTiff and auto-converted to RGB
+    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
+    (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
+    (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
 
     (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
     (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
@@ -1053,20 +1049,6 @@ def tell(self):
         "Return the current frame number"
         return self.__frame
 
-    def _decoder(self, rawmode, layer, tile=None):
-        "Setup decoder contexts"
-
-        args = None
-        if rawmode == "RGB" and self._planar_configuration == 2:
-            rawmode = rawmode[layer]
-        compression = self._compression
-        if compression == "raw":
-            args = (rawmode, 0, 1)
-        elif compression == "packbits":
-            args = rawmode
-
-        return args
-
     def load(self):
         if self.use_load_libtiff:
             return self._load_libtiff()
@@ -1187,6 +1169,7 @@ def _setup(self):
             print("- photometric_interpretation:", photo)
             print("- planar_configuration:", self._planar_configuration)
             print("- fill_order:", fillorder)
+            print("- YCbCr subsampling:", self.tag.get(530))
 
         # size
         xsize = self.tag_v2.get(IMAGEWIDTH)
@@ -1258,94 +1241,84 @@ def _setup(self):
         # build tile descriptors
         x = y = layer = 0
         self.tile = []
-        self.use_load_libtiff = False
-        if STRIPOFFSETS in self.tag_v2:
+        self.use_load_libtiff = READ_LIBTIFF or self._compression != 'raw'
+        if self.use_load_libtiff:
+            # Decoder expects entire file as one tile.
+            # There's a buffer size limit in load (64k)
+            # so large g4 images will fail if we use that
+            # function.
+            #
+            # Setup the one tile for the whole image, then
+            # use the _load_libtiff function.
+
+            # libtiff handles the fillmode for us, so 1;IR should
+            # actually be 1;I. Including the R double reverses the
+            # bits, so stripes of the image are reversed.  See
+            # https://github.com/python-pillow/Pillow/issues/279
+            if fillorder == 2:
+                # Replace fillorder with fillorder=1
+                key = key[:3] + (1,) + key[4:]
+                if DEBUG:
+                    print("format key:", key)
+                # this should always work, since all the
+                # fillorder==2 modes have a corresponding
+                # fillorder=1 mode
+                self.mode, rawmode = OPEN_INFO[key]
+            # libtiff always returns the bytes in native order.
+            # we're expecting image byte order. So, if the rawmode
+            # contains I;16, we need to convert from native to image
+            # byte order.
+            if rawmode == 'I;16':
+                rawmode = 'I;16N'
+            if ';16B' in rawmode:
+                rawmode = rawmode.replace(';16B', ';16N')
+            if ';16L' in rawmode:
+                rawmode = rawmode.replace(';16L', ';16N')
+
+            # Offset in the tile tuple is 0, we go from 0,0 to
+            # w,h, and we only do this once -- eds
+            a = (rawmode, self._compression, False)
+            self.tile.append(
+                (self._compression,
+                 (0, 0, xsize, ysize),
+                 0, a))
+
+        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
             # striped image
-            offsets = self.tag_v2[STRIPOFFSETS]
-            h = self.tag_v2.get(ROWSPERSTRIP, ysize)
-            w = self.size[0]
-            if READ_LIBTIFF or self._compression != 'raw':
-                # Decoder expects entire file as one tile.
-                # There's a buffer size limit in load (64k)
-                # so large g4 images will fail if we use that
-                # function.
-                #
-                # Setup the one tile for the whole image, then
-                # use the _load_libtiff function.
-
-                self.use_load_libtiff = True
-
-                # libtiff handles the fillmode for us, so 1;IR should
-                # actually be 1;I. Including the R double reverses the
-                # bits, so stripes of the image are reversed.  See
-                # https://github.com/python-pillow/Pillow/issues/279
-                if fillorder == 2:
-                    key = (
-                        self.tag_v2.prefix, photo, sampleFormat, 1,
-                        self.tag_v2.get(BITSPERSAMPLE, (1,)),
-                        self.tag_v2.get(EXTRASAMPLES, ())
-                        )
-                    if DEBUG:
-                        print("format key:", key)
-                    # this should always work, since all the
-                    # fillorder==2 modes have a corresponding
-                    # fillorder=1 mode
-                    self.mode, rawmode = OPEN_INFO[key]
-                # libtiff always returns the bytes in native order.
-                # we're expecting image byte order. So, if the rawmode
-                # contains I;16, we need to convert from native to image
-                # byte order.
-                if rawmode == 'I;16':
-                    rawmode = 'I;16N'
-                if ';16B' in rawmode:
-                    rawmode = rawmode.replace(';16B', ';16N')
-                if ';16L' in rawmode:
-                    rawmode = rawmode.replace(';16L', ';16N')
-
-                # Offset in the tile tuple is 0, we go from 0,0 to
-                # w,h, and we only do this once -- eds
-                a = (rawmode, self._compression, False)
-                self.tile.append(
-                    (self._compression,
-                     (0, 0, w, ysize),
-                     0, a))
-                a = None
-
+            if STRIPOFFSETS in self.tag_v2:
+                offsets = self.tag_v2[STRIPOFFSETS]
+                h = self.tag_v2.get(ROWSPERSTRIP, ysize)
+                w = self.size[0]
             else:
-                for i, offset in enumerate(offsets):
-                    a = self._decoder(rawmode, layer, i)
-                    self.tile.append(
-                        (self._compression,
-                            (0, min(y, ysize), w, min(y+h, ysize)),
-                            offset, a))
-                    if DEBUG:
-                        print("tiles: ", self.tile)
-                    y = y + h
-                    if y >= self.size[1]:
-                        x = y = 0
-                        layer += 1
-                    a = None
-        elif TILEOFFSETS in self.tag_v2:
-            # tiled image
-            w = self.tag_v2.get(322)
-            h = self.tag_v2.get(323)
-            a = None
-            for o in self.tag_v2[TILEOFFSETS]:
-                if not a:
-                    a = self._decoder(rawmode, layer)
-                # FIXME: this doesn't work if the image size
-                # is not a multiple of the tile size...
+                # tiled image
+                offsets = self.tag_v2[TILEOFFSETS]
+                w = self.tag_v2.get(322)
+                h = self.tag_v2.get(323)
+
+            for offset in offsets:
+                if x + w > xsize:
+                    stride = w * sum(bps_tuple) / 8  # bytes per line
+                else:
+                    stride = 0
+
+                tile_rawmode = rawmode
+                if self._planar_configuration == 2:
+                    # each band on it's own layer
+                    tile_rawmode = rawmode[layer]
+                    # adjust stride width accordingly
+                    stride /= bps_count
+
+                a = (tile_rawmode, int(stride), 1)
                 self.tile.append(
                     (self._compression,
-                        (x, y, x+w, y+h),
-                        o, a))
+                     (x, y, min(x+w, xsize), min(y+h, ysize)),
+                     offset, a))
                 x = x + w
                 if x >= self.size[0]:
                     x, y = 0, y + h
                     if y >= self.size[1]:
                         x = y = 0
                         layer += 1
-                        a = None
         else:
             if DEBUG:
                 print("- unsupported data organization")
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -438,7 +438,7 @@ def _populate():
 LIBTIFF_CORE.remove(532)  # Array of long, crashes
 
 LIBTIFF_CORE.remove(255)  # We don't have support for subfiletypes
-LIBTIFF_CORE.remove(322)  # We don't have support for tiled images in libtiff
+LIBTIFF_CORE.remove(322)  # We don't have support for writing tiled images with libtiff
 LIBTIFF_CORE.remove(323)  # Tiled images
 LIBTIFF_CORE.remove(333)  # Ink Names either
 


Test patch:
diff --git a/Tests/images/tiff_strip_cmyk_jpeg.tif b/Tests/images/tiff_strip_cmyk_jpeg.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_cmyk_jpeg.tif differ
diff --git a/Tests/images/tiff_strip_planar_raw.tif b/Tests/images/tiff_strip_planar_raw.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_planar_raw.tif differ
diff --git a/Tests/images/tiff_strip_planar_raw_with_overviews.tif b/Tests/images/tiff_strip_planar_raw_with_overviews.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_planar_raw_with_overviews.tif differ
diff --git a/Tests/images/tiff_strip_raw.tif b/Tests/images/tiff_strip_raw.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_raw.tif differ
diff --git a/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif b/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif differ
diff --git a/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif b/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif differ
diff --git a/Tests/images/tiff_tiled_cmyk_jpeg.tif b/Tests/images/tiff_tiled_cmyk_jpeg.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_tiled_cmyk_jpeg.tif differ
diff --git a/Tests/images/tiff_tiled_planar_raw.tif b/Tests/images/tiff_tiled_planar_raw.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_tiled_planar_raw.tif differ
diff --git a/Tests/images/tiff_tiled_raw.tif b/Tests/images/tiff_tiled_raw.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_tiled_raw.tif differ
diff --git a/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif b/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif differ
diff --git a/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif b/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif
new file mode 100644
Binary files /dev/null and b/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif differ
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -635,6 +635,43 @@ def test_lzw(self):
         im2 = hopper()
         self.assert_image_similar(im, im2, 5)
 
+    def test_strip_cmyk_jpeg(self):
+        infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
+        im = Image.open(infile)
+
+        self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
+
+    def test_strip_ycbcr_jpeg_2x2_sampling(self):
+        infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
+        im = Image.open(infile)
+
+        self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
+
+    def test_strip_ycbcr_jpeg_1x1_sampling(self):
+        infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
+
+    def test_tiled_cmyk_jpeg(self):
+        infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
+        im = Image.open(infile)
+
+        self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
+
+    def test_tiled_ycbcr_jpeg_1x1_sampling(self):
+        infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
+
+    def test_tiled_ycbcr_jpeg_2x2_sampling(self):
+        infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
+        im = Image.open(infile)
+
+        self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
+
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -416,6 +416,34 @@ def test_roundtrip_tiff_uint16(self):
 
         self.assert_image_equal(im, reloaded)
 
+    def test_strip_raw(self):
+        infile = "Tests/images/tiff_strip_raw.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
+
+    def test_strip_planar_raw(self):
+        # gdal_translate -of GTiff -co INTERLEAVE=BAND tiff_strip_raw.tif tiff_strip_planar_raw.tiff
+        infile = "Tests/images/tiff_strip_planar_raw.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
+
+    def test_strip_planar_raw_with_overviews(self):
+        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16
+        infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
+
+    def test_tiled_planar_raw(self):
+        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \
+        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff
+        infile = "Tests/images/tiff_tiled_planar_raw.tif"
+        im = Image.open(infile)
+
+        self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
+
     def test_tiff_save_all(self):
         import io
         import os
diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py
--- a/Tests/test_lib_pack.py
+++ b/Tests/test_lib_pack.py
@@ -473,10 +473,6 @@ def test_YCbCr(self):
             "YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11))
         self.assert_unpack(
             "YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11))
-        self.assert_unpack(
-            "YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13))
-        self.assert_unpack(
-            "YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15))
 
     def test_LAB(self):
         self.assert_unpack(