Repository: python-pillow/Pillow

Issue:
jpeg sampling factor is wrong
`2x2` is `4:2:0`, [not `4:1:1`](https://github.com/python-pillow/Pillow/blob/master/libImaging/JpegEncode.c#L197). This is wrong in many places, most importantly [here](https://github.com/python-pillow/Pillow/blob/master/PIL/JpegImagePlugin.py#L615).

Documented [here](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/turbojpeg.h#L106-L107), and elsewhere.

May I also suggest to add the `NxN` representation to the docs, which is easier to comprehend.



Discussion:
I don't understand how `h_samp_factor` and `v_samp_factor` maps to subsampling numbers and which exactly subsampling value is used in [this place](https://github.com/python-pillow/Pillow/blob/master/libImaging/JpegEncode.c#L197).

According to this picture, `4:2:0` looks more useful than `4:1:1` for me:

<img width="1008" alt="screen shot 2016-08-15 at 13 40 01" src="https://cloud.githubusercontent.com/assets/128982/17663657/6aedb4c8-62ee-11e6-8266-61173482531c.png">

@pdknsk Any response to @homm's comments? Do you still consider this a bug?
Yes, it's still a bug. You could simply fix it by replacing `4:1:1` with `4:2:0` throughout.
> I don't understand how h_samp_factor and v_samp_factor maps to subsampling numbers

It's explained by the _libjpeg-turbo_ maintainer [here](https://github.com/libjpeg-turbo/libjpeg-turbo/issues/86#issuecomment-226995244).

> and which exactly subsampling value is used in [this place](https://github.com/python-pillow/Pillow/blob/master/libImaging/JpegEncode.c#L197).

It's `4:2:0`.


Resolving patch:
diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py
--- a/PIL/JpegImagePlugin.py
+++ b/PIL/JpegImagePlugin.py
@@ -635,7 +635,11 @@ def _save(im, fp, filename):
         subsampling = 0
     elif subsampling == "4:2:2":
         subsampling = 1
+    elif subsampling == "4:2:0":
+        subsampling = 2
     elif subsampling == "4:1:1":
+        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
+        # Set 4:2:0 if someone is still using that value.
         subsampling = 2
     elif subsampling == "keep":
         if im.format != "JPEG":
diff --git a/PIL/JpegPresets.py b/PIL/JpegPresets.py
--- a/PIL/JpegPresets.py
+++ b/PIL/JpegPresets.py
@@ -30,7 +30,7 @@
 (ref.: https://en.wikipedia.org/wiki/Chroma_subsampling)
 
 Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
-4:1:1 (or 4:2:0?).
+4:2:0.
 
 You can get the subsampling of a JPEG with the
 `JpegImagePlugin.get_subsampling(im)` function.
@@ -67,7 +67,7 @@
 """
 
 presets = {
-            'web_low':      {'subsampling':  2,  # "4:1:1"
+            'web_low':      {'subsampling':  2,  # "4:2:0"
                              'quantization': [
                                [20, 16, 25, 39, 50, 46, 62, 68,
                                 16, 18, 23, 38, 38, 53, 65, 68,
@@ -86,7 +86,7 @@
                                 68, 68, 68, 68, 68, 68, 68, 68,
                                 68, 68, 68, 68, 68, 68, 68, 68]
                               ]},
-            'web_medium':   {'subsampling':  2,  # "4:1:1"
+            'web_medium':   {'subsampling':  2,  # "4:2:0"
                              'quantization': [
                                [16, 11, 11, 16, 23, 27, 31, 30,
                                 11, 12, 12, 15, 20, 23, 23, 30,
@@ -162,7 +162,7 @@
                                  3,  3,  3,  3,  3,  3,  3,  3,
                                  3,  3,  3,  3,  3,  3,  3,  3]
                              ]},
-            'low':          {'subsampling':  2,  # "4:1:1"
+            'low':          {'subsampling':  2,  # "4:2:0"
                              'quantization': [
                                [18, 14, 14, 21, 30, 35, 34, 17,
                                 14, 16, 16, 19, 26, 23, 12, 12,
@@ -181,7 +181,7 @@
                                 17, 12, 12, 12, 12, 12, 12, 12,
                                 17, 12, 12, 12, 12, 12, 12, 12]
                              ]},
-            'medium':       {'subsampling':  2,  # "4:1:1"
+            'medium':       {'subsampling':  2,  # "4:2:0"
                              'quantization': [
                                [12,  8,  8, 12, 17, 21, 24, 17,
                                  8,  9,  9, 11, 15, 19, 12, 12,


Test patch:
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -299,7 +299,7 @@ def getsampling(im):
         self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1))
         im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2
         self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1))
-        im = self.roundtrip(hopper(), subsampling=2)  # 4:1:1
+        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0
         self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
         im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)
         self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
@@ -308,6 +308,8 @@ def getsampling(im):
         self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1))
         im = self.roundtrip(hopper(), subsampling="4:2:2")
         self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1))
+        im = self.roundtrip(hopper(), subsampling="4:2:0")
+        self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
         im = self.roundtrip(hopper(), subsampling="4:1:1")
         self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))