diff --git a/Tests/images/no_palette_with_transparency.gif b/Tests/images/no_palette_with_transparency.gif new file mode 100644 index 000000000..3cd1c0c48 Binary files /dev/null and b/Tests/images/no_palette_with_transparency.gif differ diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 011d982f0..924adad9e 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -59,6 +59,17 @@ def test_invalid_file(): GifImagePlugin.GifImageFile(invalid_file) +def test_l_mode_transparency(): + with Image.open("Tests/images/no_palette_with_transparency.gif") as im: + assert im.mode == "L" + assert im.load()[0, 0] == 0 + assert im.info["transparency"] == 255 + + im.seek(1) + assert im.mode == "LA" + assert im.load()[0, 0] == (0, 255) + + def test_optimize(): def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1d6469819..1ffd012aa 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -135,6 +135,10 @@ def test_trns_l(tmp_path): f = str(tmp_path / "temp.png") + im_la = im.convert("LA") + assert "transparency" not in im_la.info + im_la.save(f) + im_rgb = im.convert("RGB") assert im_rgb.info["transparency"] == (128, 128, 128) # undone im_rgb.save(f) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index dc3caef01..281d5a6fb 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -67,6 +67,16 @@ class TestImagingPaste: ], ) + @cached_property + def gradient_LA(self): + return Image.merge( + "LA", + [ + self.gradient_L, + self.gradient_L.transpose(Image.Transpose.ROTATE_90), + ], + ) + @cached_property def gradient_RGBA(self): return Image.merge( @@ -145,6 +155,28 @@ class TestImagingPaste: ], ) + def test_image_mask_LA(self): + for mode in ("RGBA", "RGB", "L"): + im = Image.new(mode, (200, 200), "white") + im2 = getattr(self, "gradient_" + mode) + + self.assert_9points_paste( + im, + im2, + self.gradient_LA, + [ + (128, 191, 255, 191), + (112, 207, 206, 111), + (128, 254, 128, 1), + (208, 208, 239, 239), + (192, 191, 191, 191), + (207, 207, 112, 113), + (255, 255, 255, 255), + (239, 207, 207, 239), + (255, 191, 128, 191), + ], + ) + def test_image_mask_RGBA(self): for mode in ("RGBA", "RGB", "L"): im = Image.new(mode, (200, 200), "white") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 4f8ea209d..12d85263b 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -167,9 +167,15 @@ class GifImageFile(ImageFile.ImageFile): if self.__frame == 1: self.pyaccess = None if "transparency" in self.info: - self.mode = "RGBA" - self.im.putpalettealpha(self.info["transparency"], 0) - self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) + if self.mode == "P": + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) + self.mode = "RGBA" + else: + self.im = self.im.convert_transparent( + "LA", self.info["transparency"] + ) + self.mode = "LA" del self.info["transparency"] else: @@ -368,15 +374,18 @@ class GifImageFile(ImageFile.ImageFile): if self.__frame == 0: return if self._frame_transparency is not None: - self.im.putpalettealpha(self._frame_transparency, 0) - frame_im = self.im.convert("RGBA") + if self.mode == "P": + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert_transparent("LA", self._frame_transparency) else: frame_im = self.im.convert("RGB") frame_im = self._crop(frame_im, self.dispose_extent) self.im = self._prev_im self.mode = self.im.mode - if frame_im.mode == "RGBA": + if frame_im.mode in ("LA", "RGBA"): self.im.paste(frame_im, self.dispose_extent, frame_im) else: self.im.paste(frame_im, self.dispose_extent) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1cb7a48b9..c4a4ba71d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -975,7 +975,9 @@ class Image: delete_trns = False # transparency handling if has_transparency: - if self.mode in ("1", "L", "I", "RGB") and mode == "RGBA": + if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or ( + self.mode == "RGB" and mode == "RGBA" + ): # Use transparent conversion to promote from transparent # color to an alpha channel. new_im = self._new( @@ -1565,8 +1567,8 @@ class Image: also use color strings as supported by the ImageColor module. If a mask is given, this method updates only the regions - indicated by the mask. You can use either "1", "L" or "RGBA" - images (in the latter case, the alpha band is used as mask). + indicated by the mask. You can use either "1", "L", "LA", "RGBA" + or "RGBa" images (if present, the alpha band is used as mask). Where the mask is 255, the given image is copied as is. Where the mask is 0, the current value is preserved. Intermediate values will mix the two images together, including their alpha @@ -1614,7 +1616,7 @@ class Image: elif isImageType(im): im.load() if self.mode != im.mode: - if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"): + if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"): # should use an adapter for this! im = im.convert(self.mode) im = im.im diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 0f200af6b..5a632ca42 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1634,29 +1634,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { return (Imaging)ImagingError_ModeError(); } - 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 - { - return (Imaging)ImagingError_ValueError("conversion not supported"); - } -#else - { - static char buf[100]; - snprintf( - buf, - 100, - "conversion from %.10s to %.10s not supported in convert_transparent", - imIn->mode, - mode); - return (Imaging)ImagingError_ValueError(buf); - } -#endif - - if (strcmp(imIn->mode, "RGB") == 0) { + if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) { convert = rgb2rgba; - } else { + } else if ((strcmp(imIn->mode, "1") == 0 || + strcmp(imIn->mode, "I") == 0 || + strcmp(imIn->mode, "L") == 0 + ) && ( + strcmp(mode, "RGBA") == 0 || + strcmp(mode, "LA") == 0 + )) { if (strcmp(imIn->mode, "1") == 0) { convert = bit2rgb; } else if (strcmp(imIn->mode, "I") == 0) { @@ -1665,6 +1651,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { convert = l2rgb; } g = b = r; + } else { + static char buf[100]; + snprintf( + buf, + 100, + "conversion from %.10s to %.10s not supported in convert_transparent", + imIn->mode, + mode); + return (Imaging)ImagingError_ValueError(buf); } imOut = ImagingNew2Dirty(mode, imOut, imIn); diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index be26cd260..fafd8141e 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -295,7 +295,7 @@ ImagingPaste( paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); - } else if (strcmp(imMask->mode, "RGBA") == 0) { + } else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) { ImagingSectionEnter(&cookie); paste_mask_RGBA( imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);