diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1ba1794eb..970dc8ce1 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -12,7 +12,8 @@ class TestImageConvert(PillowTestCase): self.assertEqual(out.mode, mode) self.assertEqual(out.size, im.size) - modes = "1", "L", "I", "F", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr" + modes = ("1", "L", "LA", "P", "PA", "I", "F", + "RGB", "RGBA", "RGBX", "CMYK", "YCbCr") for mode in modes: im = hopper(mode) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 34b585f55..4bac448fd 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -14,8 +14,10 @@ class TestImagePutPalette(PillowTestCase): return im.mode, p[:10] return im.mode self.assertRaises(ValueError, palette, "1") - self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + for mode in ["L", "LA", "P", "PA"]: + self.assertEqual(palette(mode), + ("PA" if "A" in mode else "P", + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) self.assertRaises(ValueError, palette, "I") self.assertRaises(ValueError, palette, "F") self.assertRaises(ValueError, palette, "RGB") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ed3719100..dfa526e7a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1619,10 +1619,10 @@ class Image(object): def putpalette(self, data, rawmode="RGB"): """ - Attaches a palette to this image. The image must be a "P" or - "L" image, and the palette sequence must contain 768 integer - values, where each group of three values represent the red, - green, and blue values for the corresponding pixel + Attaches a palette to this image. The image must be a "P", + "PA", "L" or "LA" image, and the palette sequence must contain + 768 integer values, where each group of three values represent + the red, green, and blue values for the corresponding pixel index. Instead of an integer sequence, you can use an 8-bit string. @@ -1631,7 +1631,7 @@ class Image(object): """ from . import ImagePalette - if self.mode not in ("L", "P"): + if self.mode not in ("L", "LA", "P", "PA"): raise ValueError("illegal image mode") self.load() if isinstance(data, ImagePalette.ImagePalette): @@ -1643,7 +1643,7 @@ class Image(object): else: data = "".join(chr(x) for x in data) palette = ImagePalette.raw(rawmode, data) - self.mode = "P" + self.mode = "PA" if "A" in self.mode else "P" self.palette = palette self.palette.mode = "RGB" self.load() # install new palette diff --git a/src/_imaging.c b/src/_imaging.c index 614f809bc..037348baa 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1569,7 +1569,8 @@ _putpalette(ImagingObject* self, PyObject* args) if (!PyArg_ParseTuple(args, "s"PY_ARG_BYTES_LENGTH, &rawmode, &palette, &palettesize)) return NULL; - if (strcmp(self->image->mode, "L") != 0 && strcmp(self->image->mode, "P")) { + if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && + strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) { PyErr_SetString(PyExc_ValueError, wrong_mode); return NULL; } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index b0df4edc8..e227975de 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -517,6 +517,18 @@ l2cmyk(UINT8* out, const UINT8* in, int xsize) } } +static void +la2cmyk(UINT8* out, const UINT8* in, int xsize) +{ + int x; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = 0; + *out++ = 0; + *out++ = 0; + *out++ = ~(in[0]); + } +} + static void rgb2cmyk(UINT8* out, const UINT8* in, int xsize) { @@ -673,6 +685,18 @@ l2ycbcr(UINT8* out, const UINT8* in, int xsize) } } +static void +la2ycbcr(UINT8* out, const UINT8* in, int xsize) +{ + int x; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = in[0]; + *out++ = 128; + *out++ = 128; + *out++ = 255; + } +} + static void ycbcr2l(UINT8* out, const UINT8* in, int xsize) { @@ -681,6 +705,16 @@ ycbcr2l(UINT8* out, const UINT8* in, int xsize) *out++ = in[0]; } +static void +ycbcr2la(UINT8* out, const UINT8* in, int xsize) +{ + int x; + for (x = 0; x < xsize; x++, in += 4, out += 4) { + out[0] = out[1] = out[2] = in[0]; + out[3] = 255; + } +} + /* ------------------------- */ /* I;16 (16-bit) conversions */ /* ------------------------- */ @@ -818,8 +852,10 @@ static struct { { "LA", "L", la2l }, { "LA", "La", lA2la }, { "LA", "RGB", la2rgb }, - { "LA", "RGBX", la2rgb }, { "LA", "RGBA", la2rgb }, + { "LA", "RGBX", la2rgb }, + { "LA", "CMYK", la2cmyk }, + { "LA", "YCbCr", la2ycbcr }, { "La", "LA", la2lA }, @@ -861,8 +897,9 @@ static struct { { "RGBX", "1", rgb2bit }, { "RGBX", "L", rgb2l }, - { "RGBA", "I", rgb2i }, - { "RGBA", "F", rgb2f }, + { "RGBX", "LA", rgb2la }, + { "RGBX", "I", rgb2i }, + { "RGBX", "F", rgb2f }, { "RGBX", "RGB", rgba2rgb }, { "RGBX", "CMYK", rgb2cmyk }, { "RGBX", "YCbCr", ImagingConvertRGB2YCbCr }, @@ -872,6 +909,7 @@ static struct { { "CMYK", "RGBX", cmyk2rgb }, { "YCbCr", "L", ycbcr2l }, + { "YCbCr", "LA", ycbcr2la }, { "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, { "HSV", "RGB", hsv2rgb }, @@ -913,6 +951,15 @@ p2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) *out++ = (L(&palette[in[x]*4]) >= 128000) ? 255 : 0; } +static void +pa2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + /* FIXME: precalculate greyscale palette? */ + for (x = 0; x < xsize; x++, in += 4) + *out++ = (L(&palette[in[0]*4]) >= 128000) ? 255 : 0; +} + static void p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { @@ -922,6 +969,28 @@ p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) *out++ = L(&palette[in[x]*4]) / 1000; } +static void +pa2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + /* FIXME: precalculate greyscale palette? */ + for (x = 0; x < xsize; x++, in += 4) + *out++ = L(&palette[in[0]*4]) / 1000; +} + +static void +p2pa(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + for (x = 0; x < xsize; x++, in+=4) { + const UINT8* rgba = &palette[in[0] * 4]; + *out++ = in[0]; + *out++ = in[0]; + *out++ = in[0]; + *out++ = rgba[3]; + } +} + static void p2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { @@ -939,9 +1008,9 @@ pa2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++, in += 2) { - *out++ = L(&palette[in[0]*4]) / 1000; - *out++ = in[1]; + for (x = 0; x < xsize; x++, in += 4, out += 4) { + out[0] = out[1] = out[2] = L(&palette[in[0]*4]) / 1000; + out[3] = in[3]; } } @@ -954,6 +1023,15 @@ p2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) *out++ = L(&palette[in[x]*4]) / 1000; } +static void +pa2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + INT32* out = (INT32*) out_; + for (x = 0; x < xsize; x++, in += 4) + *out++ = L(&palette[in[0]*4]) / 1000; +} + static void p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) { @@ -963,6 +1041,15 @@ p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) *out++ = (float) L(&palette[in[x]*4]) / 1000.0F; } +static void +pa2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + FLOAT32* out = (FLOAT32*) out_; + for (x = 0; x < xsize; x++, in += 4) + *out++ = (float) L(&palette[in[0]*4]) / 1000.0F; +} + static void p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { @@ -976,6 +1063,19 @@ p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } } +static void +pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + int x; + for (x = 0; x < xsize; x++, in += 4) { + const UINT8* rgb = &palette[in[0] * 4]; + *out++ = rgb[0]; + *out++ = rgb[1]; + *out++ = rgb[2]; + *out++ = 255; + } +} + static void p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { @@ -1009,6 +1109,13 @@ p2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) rgb2cmyk(out, out, xsize); } +static void +pa2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + pa2rgb(out, in, xsize, palette); + rgb2cmyk(out, out, xsize); +} + static void p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) { @@ -1016,6 +1123,13 @@ p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) ImagingConvertRGB2YCbCr(out, out, xsize); } +static void +pa2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) +{ + pa2rgb(out, in, xsize, palette); + ImagingConvertRGB2YCbCr(out, out, xsize); +} + static Imaging frompalette(Imaging imOut, Imaging imIn, const char *mode) { @@ -1032,27 +1146,27 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) alpha = !strcmp(imIn->mode, "PA"); if (strcmp(mode, "1") == 0) - convert = p2bit; + convert = alpha ? pa2bit : p2bit; else if (strcmp(mode, "L") == 0) - convert = p2l; + convert = alpha ? pa2l : p2l; else if (strcmp(mode, "LA") == 0) - convert = (alpha) ? pa2la : p2la; + convert = alpha ? pa2la : p2la; else if (strcmp(mode, "PA") == 0) - convert = l2la; + convert = p2pa; else if (strcmp(mode, "I") == 0) - convert = p2i; + convert = alpha ? pa2i : p2i; else if (strcmp(mode, "F") == 0) - convert = p2f; + convert = alpha ? pa2f : p2f; else if (strcmp(mode, "RGB") == 0) - convert = p2rgb; + convert = alpha ? pa2rgb : p2rgb; else if (strcmp(mode, "RGBA") == 0) - convert = (alpha) ? pa2rgba : p2rgba; + convert = alpha ? pa2rgba : p2rgba; else if (strcmp(mode, "RGBX") == 0) - convert = p2rgba; + convert = alpha ? pa2rgba : p2rgba; else if (strcmp(mode, "CMYK") == 0) - convert = p2cmyk; + convert = alpha ? pa2cmyk : p2cmyk; else if (strcmp(mode, "YCbCr") == 0) - convert = p2ycbcr; + convert = alpha ? pa2ycbcr : p2ycbcr; else return (Imaging) ImagingError_ValueError("conversion not supported"); @@ -1073,9 +1187,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) #pragma optimize("", off) #endif static Imaging -topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) +topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither) { ImagingSectionCookie cookie; + int alpha; int x, y; ImagingPalette palette = inpalette;; @@ -1083,6 +1198,8 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) return (Imaging) ImagingError_ValueError("conversion not supported"); + alpha = !strcmp(mode, "PA"); + if (palette == NULL) { /* FIXME: make user configurable */ if (imIn->bands == 1) @@ -1094,7 +1211,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) if (!palette) return (Imaging) ImagingError_ValueError("no palette"); - imOut = ImagingNew2Dirty("P", imOut, imIn); + imOut = ImagingNew2Dirty(mode, imOut, imIn); if (!imOut) { if (palette != inpalette) ImagingPaletteDelete(palette); @@ -1109,8 +1226,13 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) /* Greyscale palette: copy data as is */ ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) - memcpy(imOut->image[y], imIn->image[y], imIn->linesize); + for (y = 0; y < imIn->ysize; y++) { + if (alpha) { + l2la((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], imIn->xsize); + } else { + memcpy(imOut->image[y], imIn->image[y], imIn->linesize); + } + } ImagingSectionLeave(&cookie); } else { @@ -1141,7 +1263,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) int g, g0, g1, g2; int b, b0, b1, b2; UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = imOut->image8[y]; + UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; int* e = errors; r = r0 = r1 = 0; @@ -1160,7 +1282,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) cache = &ImagingPaletteCache(palette, r, g, b); if (cache[0] == 0x100) ImagingPaletteCacheUpdate(palette, r, g, b); - out[x] = (UINT8) cache[0]; + if (alpha) { + out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; + out[x*4+3] = 255; + } else { + out[x] = (UINT8) cache[0]; + } r -= (int) palette->palette[cache[0]*4]; g -= (int) palette->palette[cache[0]*4+1]; @@ -1193,7 +1320,7 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) for (y = 0; y < imIn->ysize; y++) { int r, g, b; UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = imOut->image8[y]; + UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; for (x = 0; x < imIn->xsize; x++, in += 4) { INT16* cache; @@ -1204,8 +1331,12 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) cache = &ImagingPaletteCache(palette, r, g, b); if (cache[0] == 0x100) ImagingPaletteCacheUpdate(palette, r, g, b); - out[x] = (UINT8) cache[0]; - + if (alpha) { + out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; + out[x*4+3] = 255; + } else { + out[x] = (UINT8) cache[0]; + } } } ImagingSectionLeave(&cookie); @@ -1335,8 +1466,8 @@ convert(Imaging imOut, Imaging imIn, const char *mode, if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) return frompalette(imOut, imIn, mode); - if (strcmp(mode, "P") == 0) - return topalette(imOut, imIn, palette, dither); + if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) + return topalette(imOut, imIn, mode, palette, dither); if (dither && strcmp(mode, "1") == 0) return tobilevel(imOut, imIn, dither);