From 8b6a182815060e41994c6d68e04f400a6f781b13 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Mar 2024 16:25:40 +1100 Subject: [PATCH 1/3] Support conversion from RGB to RGBa --- Tests/test_image_convert.py | 4 ++++ src/PIL/Image.py | 2 +- src/libImaging/Convert.c | 21 +++++++++++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index f154de123bb..569d06894bd 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -191,6 +191,10 @@ def test_trns_RGB(tmp_path: Path) -> None: assert "transparency" not in im_rgba.info im_rgba.save(f) + im_rgba = im.convert("RGBa") + assert "transparency" not in im_rgba.info + assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0) + im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE) assert "transparency" not in im_p.info im_p.save(f) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ac3a533211b..c64a91eacc8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -979,7 +979,7 @@ def convert_transparency(m, v): # transparency handling if has_transparency: if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( - self.mode == "RGB" and mode == "RGBA" + self.mode == "RGB" and mode in ("RGBa", "RGBA") ): # Use transparent conversion to promote from transparent # color to an alpha channel. diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 5cc39cd0019..db874e5bb7f 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -499,19 +499,19 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) { } /* - * Conversion of RGB + single transparent color to RGBA, - * where any pixel that matches the color will have the - * alpha channel set to 0 + * Conversion of RGB + single transparent color either to + * RGBA, where any pixel matching the color will have the alpha channel set to 0, or + * RGBa, where any pixel matching the color will have all channels set to 0 */ static void -rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b) { +rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b, int premultiplied) { #ifdef WORDS_BIGENDIAN UINT32 trns = ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff; - UINT32 repl = trns & 0xffffff00; + UINT32 repl = premultiplied ? 0 : (trns & 0xffffff00); #else UINT32 trns = (0xffU << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff); - UINT32 repl = trns & 0x00ffffff; + UINT32 repl = premultiplied ? 0 : (trns & 0x00ffffff); #endif int i; @@ -947,6 +947,7 @@ static struct { {"RGB", "BGR;16", rgb2bgr16}, {"RGB", "BGR;24", rgb2bgr24}, {"RGB", "RGBA", rgb2rgba}, + {"RGB", "RGBa", rgb2rgba}, {"RGB", "RGBX", rgb2rgba}, {"RGB", "CMYK", rgb2cmyk}, {"RGB", "YCbCr", ImagingConvertRGB2YCbCr}, @@ -1681,14 +1682,18 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionCookie cookie; ImagingShuffler convert; Imaging imOut = NULL; + int premultiplied = 0; int y; if (!imIn) { return (Imaging)ImagingError_ModeError(); } - if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) { + if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { convert = rgb2rgba; + if (strcmp(mode, "RGBa") == 0) { + premultiplied = 1; + } } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "I;16") == 0 || @@ -1726,7 +1731,7 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); - rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b); + rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b, premultiplied); } ImagingSectionLeave(&cookie); From ab8f465f1acccf58d4a7840e1ec2666e870f4914 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Mar 2024 19:55:22 +1100 Subject: [PATCH 2/3] Use transparency info when converting from RGB to LA --- Tests/test_image_convert.py | 4 ++++ src/PIL/Image.py | 2 +- src/libImaging/Convert.c | 19 ++++++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 569d06894bd..77ce4e7d52a 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -183,6 +183,10 @@ def test_trns_RGB(tmp_path: Path) -> None: assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone im_l.save(f) + im_la = im.convert("LA") + assert "transparency" not in im_la.info + im_la.save(f) + im_p = im.convert("P") assert "transparency" in im_p.info im_p.save(f) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c64a91eacc8..11a3459ed58 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -979,7 +979,7 @@ def convert_transparency(m, v): # transparency handling if has_transparency: if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( - self.mode == "RGB" and mode in ("RGBa", "RGBA") + self.mode == "RGB" and mode in ("LA", "RGBa", "RGBA") ): # Use transparent conversion to promote from transparent # color to an alpha channel. diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index db874e5bb7f..4db9eb7007e 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -500,12 +500,12 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) { /* * Conversion of RGB + single transparent color either to - * RGBA, where any pixel matching the color will have the alpha channel set to 0, or + * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, or * RGBa, where any pixel matching the color will have all channels set to 0 */ static void -rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b, int premultiplied) { +rgbT2a(UINT8 *out, UINT8 *in, int xsize, int r, int g, int b, int premultiplied) { #ifdef WORDS_BIGENDIAN UINT32 trns = ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff; UINT32 repl = premultiplied ? 0 : (trns & 0xffffff00); @@ -516,9 +516,10 @@ rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b, int premultiplied) { int i; - for (i = 0; i < xsize; i++, out += sizeof(trns)) { + UINT8 *ref = in != NULL ? in : out; + for (i = 0; i < xsize; i++, ref += sizeof(trns), out += sizeof(trns)) { UINT32 v; - memcpy(&v, out, sizeof(v)); + memcpy(&v, ref, sizeof(v)); if (v == trns) { memcpy(out, &repl, sizeof(repl)); } @@ -1683,6 +1684,9 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingShuffler convert; Imaging imOut = NULL; int premultiplied = 0; + // If the transparency matches pixels in the source image, not the converted image + UINT8 *source; + int source_transparency = 0; int y; if (!imIn) { @@ -1694,6 +1698,9 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { if (strcmp(mode, "RGBa") == 0) { premultiplied = 1; } + } else if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "LA") == 0) { + convert = rgb2la; + source_transparency = 1; } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "I;16") == 0 || @@ -1731,7 +1738,9 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); - rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b, premultiplied); + + source = source_transparency ? (UINT8 *)imIn->image[y] : NULL; + rgbT2a((UINT8 *)imOut->image[y], source, imIn->xsize, r, g, b, premultiplied); } ImagingSectionLeave(&cookie); From e79d1746f29412c069d30770d86616aa3dafb1d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Mar 2024 19:57:17 +1100 Subject: [PATCH 3/3] Support conversion from RGB to La --- Tests/test_image_convert.py | 4 ++++ src/PIL/Image.py | 2 +- src/libImaging/Convert.c | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 77ce4e7d52a..2fb45854adf 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -187,6 +187,10 @@ def test_trns_RGB(tmp_path: Path) -> None: assert "transparency" not in im_la.info im_la.save(f) + im_la = im.convert("La") + assert "transparency" not in im_la.info + assert im_la.getpixel((0, 0)) == (0, 0) + im_p = im.convert("P") assert "transparency" in im_p.info im_p.save(f) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 11a3459ed58..5c2309ac3f2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -979,7 +979,7 @@ def convert_transparency(m, v): # transparency handling if has_transparency: if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( - self.mode == "RGB" and mode in ("LA", "RGBa", "RGBA") + self.mode == "RGB" and mode in ("La", "LA", "RGBa", "RGBA") ): # Use transparent conversion to promote from transparent # color to an alpha channel. diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 4db9eb7007e..0b84aa1ba73 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -501,7 +501,7 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) { /* * Conversion of RGB + single transparent color either to * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, or - * RGBa, where any pixel matching the color will have all channels set to 0 + * RGBa or La, where any pixel matching the color will have all channels set to 0 */ static void @@ -942,6 +942,7 @@ static struct { {"RGB", "1", rgb2bit}, {"RGB", "L", rgb2l}, {"RGB", "LA", rgb2la}, + {"RGB", "La", rgb2la}, {"RGB", "I", rgb2i}, {"RGB", "F", rgb2f}, {"RGB", "BGR;15", rgb2bgr15}, @@ -1698,9 +1699,12 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { if (strcmp(mode, "RGBa") == 0) { premultiplied = 1; } - } else if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "LA") == 0) { + } else if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { convert = rgb2la; source_transparency = 1; + if (strcmp(mode, "La") == 0) { + premultiplied = 1; + } } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "I;16") == 0 ||