diff --git a/Tests/images/1_trns.png b/Tests/images/1_trns.png new file mode 100644 index 000000000..c9a271b40 Binary files /dev/null and b/Tests/images/1_trns.png differ diff --git a/Tests/images/i_trns.png b/Tests/images/i_trns.png new file mode 100644 index 000000000..ef63d33b0 Binary files /dev/null and b/Tests/images/i_trns.png differ 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; }