diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index dd54707d8..2031b179e 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -387,12 +387,31 @@ class PngStream(ChunkStream): return s def chunk_gAMA(self, pos, length): - # gamma setting s = ImageFile._safe_read(self.fp, length) self.im_info["gamma"] = i32(s) / 100000.0 return s + def chunk_cHRM(self, pos, length): + # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 + # WP x,y, Red x,y, Green x,y Blue x,y + + s = ImageFile._safe_read(self.fp, length) + raw_vals = struct.unpack('>%dI' % (len(s) // 4), s) + self.im_info['chromaticity'] = tuple(elt/100000.0 for elt in raw_vals) + return s + + def chunk_sRGB(self, pos, length): + # srgb rendering intent, 1 byte + # 0 perceptual + # 1 relative colorimetric + # 2 saturation + # 3 absolute colorimetric + + s = ImageFile._safe_read(self.fp, length) + self.im_info['srgb'] = i8(s) + return s + def chunk_pHYs(self, pos, length): # pixels per unit @@ -731,7 +750,9 @@ def _save(im, fp, filename, chunk=putchunk): name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(icc) chunk(fp, b"iCCP", data) - else: + + # You must either have sRGB or iCCP. + # Disallow sRGB chunks when an iCCP-chunk has been emitted. chunks.remove(b"sRGB") info = im.encoderinfo.get("pnginfo") diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 8aa5e8d18..ce2b3e608 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -49,6 +49,21 @@ class TestFilePng(PillowTestCase): if "zip_encoder" not in codecs or "zip_decoder" not in codecs: self.skipTest("zip/deflate support not available") + def get_chunks(self, filename): + chunks = [] + with open(filename, "rb") as fp: + fp.read(8) + with PngImagePlugin.PngStream(fp) as png: + while True: + cid, pos, length = png.read() + chunks.append(cid) + try: + s = png.call(cid, pos, length) + except EOFError: + break + png.crc(cid, s) + return chunks + def test_sanity(self): # internal version number @@ -501,18 +516,7 @@ class TestFilePng(PillowTestCase): test_file = self.tempfile("temp.png") im.convert("P").save(test_file, dpi=(100, 100)) - chunks = [] - with open(test_file, "rb") as fp: - fp.read(8) - with PngImagePlugin.PngStream(fp) as png: - while True: - cid, pos, length = png.read() - chunks.append(cid) - try: - s = png.call(cid, pos, length) - except EOFError: - break - png.crc(cid, s) + chunks = self.get_chunks(test_file) # https://www.w3.org/TR/PNG/#5ChunkOrdering # IHDR - shall be first diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 130d15908..eba13d398 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -408,9 +408,22 @@ PIL identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``, The :py:meth:`~PIL.Image.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: +**chromaticity** + The chromaticity points, as an 8 tuple of floats. (``White Point + X``, ``White Point Y``, ``Red X``, ``Red Y``, ``Green X``, ``Green + Y``, ``Blue X``, ``Blue Y``) + **gamma** Gamma, given as a floating point number. +**srgb** + The sRGB rendering intent as an integer. + + * 0 Perceptual + * 1 Relative Colorimetric + * 2 Saturation + * 3 Absolute Colorimetric + **transparency** For ``P`` images: Either the palette index for full transparent pixels, or a byte string with alpha values for each palette entry.