Repository: python-pillow/Pillow

Issue:
EXIF tag `InteropVersion` has wrong type
## What did you do?

Open an image with Pillow and save it with its exif data.

## What did you expect to happen?

The content of the exif field `InteropVersion` must be the same in both files.

## What actually happened?

The type of `InteropVersion`  field value is set to `TiffTags.BYTE` (1)  by Pillow where it should be of type `TiffTags.UNDEFINED` (7) to conform to standard. Some software might rely on that field to assess the validity of the file.

## What are your OS, Python and Pillow versions?

- OS: Ubuntu 20.04
- Python: 3.8.5
- Pillow: 8.2.0 + fix from #5490

```python
from PIL import Image
#open a jpeg image
im = Image.open("original.jpg")
# save it with original parameters, but it doesn't change the problem to use default values
im.save("copy.jpg", exif=im.getexif(), quality=95, progressive=True, subsampling="4:4:4")
```

Original image:
![pixelated](https://user-images.githubusercontent.com/4659733/123107550-ef6c2b00-d439-11eb-8a3e-458a8a651f08.jpg)

Result image:
![original_copy](https://user-images.githubusercontent.com/4659733/123107631-00b53780-d43a-11eb-9e4b-f6a9b469724f.jpg)

Extract of the EXIF data from the original file (using `exiftool -D -s original.jpg`):
```
40962 ExifImageWidth                  : 1920
40963 ExifImageHeight                 : 692
    1 InteropIndex                    : R98 - DCF basic file (sRGB)
    2 InteropVersion                  : 0100
```
In the saved file  (using `exiftool -D -s copy.jpg`):
```
40962 ExifImageWidth                  : 1920
40963 ExifImageHeight                 : 692
    1 InteropIndex                    : R98 - DCF basic file (sRGB)
    2 InteropVersion                  : 48 49 48 48
```


Discussion:
A workaround, from a user perspective, is to save file to a buffer, then seek and change the type of the IFD tag, and save the file.



Resolving patch:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -465,7 +465,7 @@ class ImageFileDirectory_v2(MutableMapping):
 
     """
 
-    def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
+    def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
         """Initialize an ImageFileDirectory.
 
         To construct an ImageFileDirectory from a real file, pass the 8-byte
@@ -485,6 +485,7 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
             self._endian = "<"
         else:
             raise SyntaxError("not a TIFF IFD")
+        self.group = group
         self.tagtype = {}
         """ Dictionary of tag types """
         self.reset()
@@ -516,7 +517,10 @@ def named(self):
 
         Returns the complete tag dictionary, with named tags where possible.
         """
-        return {TiffTags.lookup(code).name: value for code, value in self.items()}
+        return {
+            TiffTags.lookup(code, self.group).name: value
+            for code, value in self.items()
+        }
 
     def __len__(self):
         return len(set(self._tagdata) | set(self._tags_v2))
@@ -541,7 +545,7 @@ def __setitem__(self, tag, value):
     def _setitem(self, tag, value, legacy_api):
         basetypes = (Number, bytes, str)
 
-        info = TiffTags.lookup(tag)
+        info = TiffTags.lookup(tag, self.group)
         values = [value] if isinstance(value, basetypes) else value
 
         if tag not in self.tagtype:
@@ -758,7 +762,7 @@ def load(self, fp):
             for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
                 tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))
 
-                tagname = TiffTags.lookup(tag).name
+                tagname = TiffTags.lookup(tag, self.group).name
                 typname = TYPES.get(typ, "unknown")
                 msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})"
 
@@ -825,7 +829,7 @@ def tobytes(self, offset=0):
                     ifh = b"II\x2A\x00\x08\x00\x00\x00"
                 else:
                     ifh = b"MM\x00\x2A\x00\x00\x00\x08"
-                ifd = ImageFileDirectory_v2(ifh)
+                ifd = ImageFileDirectory_v2(ifh, group=tag)
                 values = self._tags_v2[tag]
                 for ifd_tag, ifd_value in values.items():
                     ifd[ifd_tag] = ifd_value
@@ -834,7 +838,7 @@ def tobytes(self, offset=0):
                 values = value if isinstance(value, tuple) else (value,)
                 data = self._write_dispatch[typ](self, *values)
 
-            tagname = TiffTags.lookup(tag).name
+            tagname = TiffTags.lookup(tag, self.group).name
             typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
             msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
             msg += " - value: " + (
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -33,7 +33,7 @@ def cvt_enum(self, value):
         return self.enum.get(value, value) if self.enum else value
 
 
-def lookup(tag):
+def lookup(tag, group=None):
     """
     :param tag: Integer tag number
     :returns: Taginfo namedtuple, From the TAGS_V2 info if possible,
@@ -42,7 +42,11 @@ def lookup(tag):
 
     """
 
-    return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, "unknown")))
+    if group is not None:
+        info = TAGS_V2_GROUPS[group].get(tag) if group in TAGS_V2_GROUPS else None
+    else:
+        info = TAGS_V2.get(tag)
+    return info or TagInfo(tag, TAGS.get(tag, "unknown"))
 
 
 ##
@@ -213,6 +217,19 @@ def lookup(tag):
     50838: ("ImageJMetaDataByteCounts", LONG, 0),  # Can be more than one
     50839: ("ImageJMetaData", UNDEFINED, 1),  # see Issue #2006
 }
+TAGS_V2_GROUPS = {
+    # ExifIFD
+    34665: {
+        36864: ("ExifVersion", UNDEFINED, 1),
+        40960: ("FlashPixVersion", UNDEFINED, 1),
+        40965: ("InteroperabilityIFD", LONG, 1),
+        41730: ("CFAPattern", UNDEFINED, 1),
+    },
+    # GPSInfoIFD
+    34853: {},
+    # InteroperabilityIFD
+    40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)},
+}
 
 # Legacy Tags structure
 # these tags aren't included above, but were in the previous versions
@@ -371,6 +388,10 @@ def _populate():
 
         TAGS_V2[k] = TagInfo(k, *v)
 
+    for group, tags in TAGS_V2_GROUPS.items():
+        for k, v in tags.items():
+            tags[k] = TagInfo(k, *v)
+
 
 _populate()
 ##


Test patch:
diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py
--- a/Tests/test_file_tiff_metadata.py
+++ b/Tests/test_file_tiff_metadata.py
@@ -378,6 +378,20 @@ def test_too_many_entries():
     pytest.warns(UserWarning, lambda: ifd[277])
 
 
+def test_tag_group_data():
+    base_ifd = TiffImagePlugin.ImageFileDirectory_v2()
+    interop_ifd = TiffImagePlugin.ImageFileDirectory_v2(group=40965)
+    for ifd in (base_ifd, interop_ifd):
+        ifd[2] = "test"
+        ifd[256] = 10
+
+    assert base_ifd.tagtype[256] == 4
+    assert interop_ifd.tagtype[256] != base_ifd.tagtype[256]
+
+    assert interop_ifd.tagtype[2] == 7
+    assert base_ifd.tagtype[2] != interop_ifd.tagtype[256]
+
+
 def test_empty_subifd(tmp_path):
     out = str(tmp_path / "temp.jpg")