From 4a5666f1f40d4ca2d95f3edb95770ceaa40e28b9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 27 Mar 2019 07:41:33 +1100 Subject: [PATCH] Added transparency for all PNG greyscale modes --- Tests/images/1_trns.png | Bin 0 -> 612 bytes Tests/images/i_trns.png | Bin 0 -> 1427 bytes Tests/test_file_png.py | 42 ++++++++++++++------------- docs/handbook/image-file-formats.rst | 10 +++---- src/PIL/Image.py | 2 +- src/PIL/PngImagePlugin.py | 9 ++++-- src/libImaging/Convert.c | 37 +++++++++++++++++++---- 7 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 Tests/images/1_trns.png create mode 100644 Tests/images/i_trns.png diff --git a/Tests/images/1_trns.png b/Tests/images/1_trns.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a271b4066ac948f851b209743a86867b0d725e GIT binary patch literal 612 zcmV-q0-ODbP)3Mq|007Xb5dePM z{M1SS?VRS-#DJWi{dE%x0hLp8th@#QHo5v-I>30B&EDf9z?ter>Q4x*U2#iBteDt%2n-u!j_{=Rc%XQMtt2pDZ2~6 zw)f0|f*_u{a0I}@Tb^B#0u(bqEPG`Y{{|I)xb!;!?BXxK<^X94;(5&u0CE83&Qnru zgp#xwL*=l74wiqX0{|T*p6fMXwa)WW*$$vG6Lv?Ll$J5U7b{8u`8>fIW=(*u2vF6i zl(O+DYxrma^4M#z$7NE=%OkA*K^Tzx3)yhJ37~iem7-Y6jg};zb)N;``^C2X=mpk9 zA@ZW0cDWE3<_ET3ZB*Q1h{RHb=U`v^Zsxu<&Yey78%gjYu`eF>Q%&8A$-VcMKadbg z&=ympyv5YxboX-zKCfX61(FsCEsG-4Z>IRg{Dtu^X7e8M*P7{SZN@XwZU9QGTO}hN yEV8NZR!NA{gKE~z4vH24X)t<82MW#$*6|P)CYOm~mWZ+(*UM znrBB`3-)tfq!KKc^Dd3jE4m(@;#lcf?c2l#FI|E$mRo)6FtxaC%nFYyHb;1HMa6wmM*_Hl?~WIZ9hW*La@w0^}~_&B%+ z6dTyU&EeBAp5Q0^h~JY0rbX;qhB_ElOm}cQ`4+*i%eaA0@ipG1Bq($^vv3L&nw1eJ zq|x>s-dqX*0A9g1zQE=fj^~!3c=cDr{;`6B?Q^;BUpv?t<;y*wtszRE0xlun2bvo0 z;iC%x0Cuqobu3s_4c3_yHBPJJ-pF-)c_H_}bzF0W?I)-L=|$+(Du$E(${Iew-3tQ1 zAVdzwm!ln4gx`q!g>>k)6>L(WM9YtzO&mQmNgBbu;DSk25jW4?G;tRDRwa%?up^`bB1>hz&1MO;HXk|dt)@xHTPlT)38kIGr0kuTOHe4TnIST-{ zB27JrUlW#~T7lrdnE>9P4!}XuWKxH2fV-NbpY!nr>Sz$yMn z+ME-8B|O{XY{vn{nrLZ@Yw?DS&Xma4tDVlR#29@Kfn)oJnD#rO-UDg?%Ypp z+4SdYxQ1KU!c|dZ zZR1`9-Nj1w#%oh?krM423=6N)@b002ovPDHLkV1gcdrKkV^ literal 0 HcmV?d00001 diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 7f73678fa..580521752 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -291,30 +291,32 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (10, 10)) self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) - def test_save_l_transparency(self): - # There are 559 transparent pixels in l_trns.png. - num_transparent = 559 + def test_save_greyscale_transparency(self): + for mode, num_transparent in { + "1": 1994, + "L": 559, + "I": 559, + }.items(): + in_file = "Tests/images/"+mode.lower()+"_trns.png" + im = Image.open(in_file) + self.assertEqual(im.mode, mode) + self.assertEqual(im.info["transparency"], 255) - in_file = "Tests/images/l_trns.png" - im = Image.open(in_file) - self.assertEqual(im.mode, "L") - self.assertEqual(im.info["transparency"], 255) + im_rgba = im.convert('RGBA') + self.assertEqual( + im_rgba.getchannel("A").getcolors()[0][0], num_transparent) - im_rgba = im.convert('RGBA') - self.assertEqual( - im_rgba.getchannel("A").getcolors()[0][0], num_transparent) + test_file = self.tempfile("temp.png") + im.save(test_file) - test_file = self.tempfile("temp.png") - im.save(test_file) + test_im = Image.open(test_file) + self.assertEqual(test_im.mode, mode) + self.assertEqual(test_im.info["transparency"], 255) + self.assert_image_equal(im, test_im) - test_im = Image.open(test_file) - self.assertEqual(test_im.mode, "L") - self.assertEqual(test_im.info["transparency"], 255) - self.assert_image_equal(im, test_im) - - test_im_rgba = test_im.convert('RGBA') - self.assertEqual( - test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent) + test_im_rgba = test_im.convert('RGBA') + self.assertEqual( + test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent) def test_save_rgb_single_transparency(self): in_file = "Tests/images/caption_6_33_22.png" diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index c1cfecbf8..fc7b92e45 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -490,12 +490,12 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following For ``P`` images: Either the palette index for full transparent pixels, or a byte string with alpha values for each palette entry. - For ``L`` and ``RGB`` images, the color that represents full transparent - pixels in this image. + For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents + full transparent pixels in this image. This key is omitted if the image is not a transparent palette image. -``Open`` also sets ``Image.text`` to a dictionary of the values of the +``open`` also sets ``Image.text`` to a dictionary of the values of the ``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual compressed chunks are limited to a decompressed size of ``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent @@ -511,8 +511,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: encoder settings. **transparency** - For ``P``, ``L``, and ``RGB`` images, this option controls what - color image to mark as transparent. + For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls + what color from the image to mark as transparent. For ``P`` images, this can be a either the palette index, or a byte string with alpha values for each palette entry. diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4626497fb..f2686f493 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -950,7 +950,7 @@ class Image(object): delete_trns = False # transparency handling if has_transparency: - if self.mode in ('L', 'RGB') and mode == 'RGBA': + if self.mode in ('1', 'L', 'I', 'RGB') and mode == 'RGBA': # Use transparent conversion to promote from transparent # color to an alpha channel. new_im = self._new(self.im.convert_transparent( diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index b05ba6c07..bba7c1038 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -54,19 +54,24 @@ _MAGIC = b"\211PNG\r\n\032\n" _MODES = { # supported bits/color combinations, and corresponding modes/rawmodes + # Greyscale (1, 0): ("1", "1"), (2, 0): ("L", "L;2"), (4, 0): ("L", "L;4"), (8, 0): ("L", "L"), (16, 0): ("I", "I;16B"), + # Truecolour (8, 2): ("RGB", "RGB"), (16, 2): ("RGB", "RGB;16B"), + # Indexed-colour (1, 3): ("P", "P;1"), (2, 3): ("P", "P;2"), (4, 3): ("P", "P;4"), (8, 3): ("P", "P"), + # Greyscale with alpha (8, 4): ("LA", "LA"), (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available + # Truecolour with alpha (8, 6): ("RGBA", "RGBA"), (16, 6): ("RGBA", "RGBA;16B"), } @@ -386,7 +391,7 @@ class PngStream(ChunkStream): # otherwise, we have a byte string with one alpha value # for each palette entry self.im_info["transparency"] = s - elif self.im_mode == "L": + elif self.im_mode in ("1", "L", "I"): self.im_info["transparency"] = i16(s) elif self.im_mode == "RGB": self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) @@ -841,7 +846,7 @@ def _save(im, fp, filename, chunk=putchunk): transparency = max(0, min(255, transparency)) alpha = b'\xFF' * transparency + b'\0' chunk(fp, b"tRNS", alpha[:alpha_bytes]) - elif im.mode == "L": + elif im.mode in ("1", "L", "I"): transparency = max(0, min(65535, transparency)) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 39ddf8721..cbfd98195 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -592,6 +592,22 @@ i2f(UINT8* out_, const UINT8* in_, int xsize) *out++ = (FLOAT32) *in++; } +static void +i2rgb(UINT8* out, const UINT8* in_, int xsize) +{ + int x; + INT32* in = (INT32*) in_; + for (x = 0; x < xsize; x++, in++, out+=4) { + if (*in <= 0) + out[0] = out[1] = out[2] = 0; + else if (*in >= 255) + out[0] = out[1] = out[2] = 255; + else + out[0] = out[1] = out[2] = (UINT8) *in; + out[3] = 255; + } +} + /* ------------- */ /* F conversions */ /* ------------- */ @@ -807,11 +823,14 @@ static struct { { "La", "LA", la2lA }, - { "I", "L", i2l }, - { "I", "F", i2f }, + { "I", "L", i2l }, + { "I", "F", i2f }, + { "I", "RGB", i2rgb }, + { "I", "RGBA", i2rgb }, + { "I", "RGBX", i2rgb }, - { "F", "L", f2l }, - { "F", "I", f2i }, + { "F", "L", f2l }, + { "F", "I", f2i }, { "RGB", "1", rgb2bit }, { "RGB", "L", rgb2l }, @@ -1385,6 +1404,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, } if (!((strcmp(imIn->mode, "RGB") == 0 || + strcmp(imIn->mode, "1") == 0 || + strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "L") == 0) && strcmp(mode, "RGBA") == 0)) #ifdef notdef @@ -1403,7 +1424,13 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, if (strcmp(imIn->mode, "RGB") == 0) { convert = rgb2rgba; } else { - convert = l2rgb; + if (strcmp(imIn->mode, "1") == 0) { + convert = bit2rgb; + } else if (strcmp(imIn->mode, "I") == 0) { + convert = i2rgb; + } else { + convert = l2rgb; + } g = b = r; }