Skip to content

Commit

Permalink
Merge pull request #3744 from radarhere/png_transparency
Browse files Browse the repository at this point in the history
Added transparency for all PNG greyscale modes
  • Loading branch information
radarhere committed Mar 26, 2019
2 parents 33522a8 + 4a5666f commit 28002ca
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 33 deletions.
Binary file added Tests/images/1_trns.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/i_trns.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 22 additions & 20 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,30 +291,32 @@ def test_save_p_transparent_black(self):
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

in_file = "Tests/images/l_trns.png"
im = Image.open(in_file)
self.assertEqual(im.mode, "L")
self.assertEqual(im.info["transparency"], 255)
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)

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, "L")
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, mode)
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"
Expand Down
10 changes: 5 additions & 5 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ def convert_transparency(m, v):
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(
Expand Down
9 changes: 7 additions & 2 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,24 @@

_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"),
}
Expand Down Expand Up @@ -386,7 +391,7 @@ def chunk_tRNS(self, pos, length):
# 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:])
Expand Down Expand Up @@ -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":
Expand Down
37 changes: 32 additions & 5 deletions src/libImaging/Convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
/* ------------- */
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand Down

0 comments on commit 28002ca

Please sign in to comment.