diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index e2f0df84a..de18b83d0 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -670,6 +670,15 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False + def test_save_ycbcr(self, tmp_path): + im = hopper("YCbCr") + outfile = str(tmp_path / "temp.tif") + im.save(outfile, compression="jpeg") + + with Image.open(outfile) as reloaded: + assert reloaded.tag_v2[530] == (1, 1) + assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) + def test_crashing_metadata(self, tmp_path): # issue 1597 with Image.open("Tests/images/rdf.tif") as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a5e2bb53d..2e1be52ad 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -93,6 +93,7 @@ SUBIFD = 330 EXTRASAMPLES = 338 SAMPLEFORMAT = 339 JPEGTABLES = 347 +YCBCRSUBSAMPLING = 530 REFERENCEBLACKWHITE = 532 COPYRIGHT = 33432 IPTC_NAA_CHUNK = 33723 # newsphoto properties @@ -1593,6 +1594,13 @@ def _save(im, fp, filename): # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) + if im.mode == "YCbCr": + for tag, value in { + YCBCRSUBSAMPLING: (1, 1), + REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255), + }.items(): + ifd.setdefault(tag, value) + if libtiff: if "quality" in im.encoderinfo: quality = im.encoderinfo["quality"] diff --git a/src/encode.c b/src/encode.c index daa806ff4..5933e79a5 100644 --- a/src/encode.c +++ b/src/encode.c @@ -808,6 +808,12 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av + stride * 2); free(av); } + } else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) { + status = ImagingLibTiffSetField( + &encoder->state, + (ttag_t)key_int, + (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)), + (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))); } else if (type == TIFF_SHORT) { UINT16 *av; /* malloc check ok, calloc checks for overflow */