Skip to content

Commit 7d9ac36

Browse files
authored
Merge pull request #4605 from radarhere/byte_tags
Writing TIFF tags: improved BYTE, added UNDEFINED
2 parents b9a087d + 2d284ae commit 7d9ac36

4 files changed

Lines changed: 57 additions & 39 deletions

File tree

Tests/test_file_libtiff.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,6 @@ def check_tags(tiffinfo):
299299
)
300300
continue
301301

302-
if libtiff and isinstance(value, bytes):
303-
value = value.decode()
304-
305302
assert reloaded_value == value
306303

307304
# Test with types
@@ -322,6 +319,17 @@ def check_tags(tiffinfo):
322319
)
323320
TiffImagePlugin.WRITE_LIBTIFF = False
324321

322+
def test_xmlpacket_tag(self, tmp_path):
323+
TiffImagePlugin.WRITE_LIBTIFF = True
324+
325+
out = str(tmp_path / "temp.tif")
326+
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
327+
TiffImagePlugin.WRITE_LIBTIFF = False
328+
329+
with Image.open(out) as reloaded:
330+
if 700 in reloaded.tag_v2:
331+
assert reloaded.tag_v2[700] == b"xmlpacket tag"
332+
325333
def test_int_dpi(self, tmp_path):
326334
# issue #1765
327335
im = hopper("RGB")
@@ -667,6 +675,26 @@ def test_read_icc(self):
667675
TiffImagePlugin.READ_LIBTIFF = False
668676
assert icc == icc_libtiff
669677

678+
def test_write_icc(self, tmp_path):
679+
def check_write(libtiff):
680+
TiffImagePlugin.WRITE_LIBTIFF = libtiff
681+
682+
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
683+
icc_profile = img.info["icc_profile"]
684+
685+
out = str(tmp_path / "temp.tif")
686+
img.save(out, icc_profile=icc_profile)
687+
with Image.open(out) as reloaded:
688+
assert icc_profile == reloaded.info["icc_profile"]
689+
690+
libtiffs = []
691+
if Image.core.libtiff_support_custom_tags:
692+
libtiffs.append(True)
693+
libtiffs.append(False)
694+
695+
for libtiff in libtiffs:
696+
check_write(libtiff)
697+
670698
def test_multipage_compression(self):
671699
with Image.open("Tests/images/compression.tif") as im:
672700

Tests/test_file_tiff_metadata.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,13 @@ def test_empty_values():
319319

320320
def test_PhotoshopInfo(tmp_path):
321321
with Image.open("Tests/images/issue_2278.tif") as im:
322-
assert len(im.tag_v2[34377]) == 1
323-
assert isinstance(im.tag_v2[34377][0], bytes)
322+
assert len(im.tag_v2[34377]) == 70
323+
assert isinstance(im.tag_v2[34377], bytes)
324324
out = str(tmp_path / "temp.tiff")
325325
im.save(out)
326326
with Image.open(out) as reloaded:
327-
assert len(reloaded.tag_v2[34377]) == 1
328-
assert isinstance(reloaded.tag_v2[34377][0], bytes)
327+
assert len(reloaded.tag_v2[34377]) == 70
328+
assert isinstance(reloaded.tag_v2[34377], bytes)
329329

330330

331331
def test_too_many_entries():

src/PIL/TiffImagePlugin.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,10 @@ def _setitem(self, tag, value, legacy_api):
553553
)
554554
elif all(isinstance(v, float) for v in values):
555555
self.tagtype[tag] = TiffTags.DOUBLE
556-
else:
557-
if all(isinstance(v, str) for v in values):
558-
self.tagtype[tag] = TiffTags.ASCII
556+
elif all(isinstance(v, str) for v in values):
557+
self.tagtype[tag] = TiffTags.ASCII
558+
elif all(isinstance(v, bytes) for v in values):
559+
self.tagtype[tag] = TiffTags.BYTE
559560

560561
if self.tagtype[tag] == TiffTags.UNDEFINED:
561562
values = [
@@ -573,8 +574,10 @@ def _setitem(self, tag, value, legacy_api):
573574
# Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
574575
# No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
575576
# Don't mess with the legacy api, since it's frozen.
576-
if (info.length == 1) or (
577-
info.length is None and len(values) == 1 and not legacy_api
577+
if (
578+
(info.length == 1)
579+
or self.tagtype[tag] == TiffTags.BYTE
580+
or (info.length is None and len(values) == 1 and not legacy_api)
578581
):
579582
# Don't mess with the legacy api, since it's frozen.
580583
if legacy_api and self.tagtype[tag] in [
@@ -1546,16 +1549,17 @@ def _save(im, fp, filename):
15461549
# Custom items are supported for int, float, unicode, string and byte
15471550
# values. Other types and tuples require a tagtype.
15481551
if tag not in TiffTags.LIBTIFF_CORE:
1549-
if (
1550-
TiffTags.lookup(tag).type == TiffTags.UNDEFINED
1551-
or not Image.core.libtiff_support_custom_tags
1552-
):
1552+
if not Image.core.libtiff_support_custom_tags:
15531553
continue
15541554

15551555
if tag in ifd.tagtype:
15561556
types[tag] = ifd.tagtype[tag]
15571557
elif not (isinstance(value, (int, float, str, bytes))):
15581558
continue
1559+
else:
1560+
type = TiffTags.lookup(tag).type
1561+
if type:
1562+
types[tag] = type
15591563
if tag not in atts and tag not in blocklist:
15601564
if isinstance(value, str):
15611565
atts[tag] = value.encode("ascii", "replace") + b"\0"

src/encode.c

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -761,12 +761,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
761761
}
762762
}
763763

764-
if (PyBytes_Check(value) &&
765-
(type == TIFF_BYTE || type == TIFF_UNDEFINED)) {
766-
// For backwards compatibility
767-
type = TIFF_ASCII;
768-
}
769-
770764
if (PyTuple_Check(value)) {
771765
Py_ssize_t len;
772766
len = PyTuple_Size(value);
@@ -790,28 +784,24 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
790784

791785
if (!is_core_tag) {
792786
// Register field for non core tags.
787+
if (type == TIFF_BYTE) {
788+
is_var_length = 1;
789+
}
793790
if (ImagingLibTiffMergeFieldInfo(&encoder->state, type, key_int, is_var_length)) {
794791
continue;
795792
}
796793
}
797794

798-
if (is_var_length) {
795+
if (type == TIFF_BYTE || type == TIFF_UNDEFINED) {
796+
status = ImagingLibTiffSetField(&encoder->state,
797+
(ttag_t) key_int,
798+
PyBytes_Size(value), PyBytes_AsString(value));
799+
} else if (is_var_length) {
799800
Py_ssize_t len,i;
800801
TRACE(("Setting from Tuple: %d \n", key_int));
801802
len = PyTuple_Size(value);
802803

803-
if (type == TIFF_BYTE) {
804-
UINT8 *av;
805-
/* malloc check ok, calloc checks for overflow */
806-
av = calloc(len, sizeof(UINT8));
807-
if (av) {
808-
for (i=0;i<len;i++) {
809-
av[i] = (UINT8)PyLong_AsLong(PyTuple_GetItem(value,i));
810-
}
811-
status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, len, av);
812-
free(av);
813-
}
814-
} else if (type == TIFF_SHORT) {
804+
if (type == TIFF_SHORT) {
815805
UINT16 *av;
816806
/* malloc check ok, calloc checks for overflow */
817807
av = calloc(len, sizeof(UINT16));
@@ -914,10 +904,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
914904
status = ImagingLibTiffSetField(&encoder->state,
915905
(ttag_t) key_int,
916906
(FLOAT64)PyFloat_AsDouble(value));
917-
} else if (type == TIFF_BYTE) {
918-
status = ImagingLibTiffSetField(&encoder->state,
919-
(ttag_t) key_int,
920-
(UINT8)PyLong_AsLong(value));
921907
} else if (type == TIFF_SBYTE) {
922908
status = ImagingLibTiffSetField(&encoder->state,
923909
(ttag_t) key_int,

0 commit comments

Comments
 (0)