diff --git a/PIL/Image.py b/PIL/Image.py index acb2c7fac..28a76882b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1539,6 +1539,9 @@ class Image(object): if self.mode in ("1", "P"): resample = NEAREST + if self.mode == 'LA': + return self.convert('La').resize(size, resample).convert('LA') + if self.mode == 'RGBA': return self.convert('RGBa').resize(size, resample).convert('RGBA') @@ -1829,6 +1832,10 @@ class Image(object): :returns: An :py:class:`~PIL.Image.Image` object. """ + if self.mode == 'LA': + return self.convert('La').transform( + size, method, data, resample, fill).convert('LA') + if self.mode == 'RGBA': return self.convert('RGBa').transform( size, method, data, resample, fill).convert('RGBA') diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index faa868c12..c9cbd7014 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -278,6 +278,7 @@ mode_map = {'1': _PyAccess8, 'L': _PyAccess8, 'P': _PyAccess8, 'LA': _PyAccess32_2, + 'La': _PyAccess32_2, 'PA': _PyAccess32_2, 'RGB': _PyAccess32_3, 'LAB': _PyAccess32_3, diff --git a/Tests/make_hash.py b/Tests/make_hash.py index a92886df9..6d700addf 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -4,7 +4,7 @@ from __future__ import print_function modes = [ "1", - "L", "LA", + "L", "LA", "La", "I", "I;16", "I;16L", "I;16B", "I;32L", "I;32B", "F", "P", "PA", diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 0b78217ce..7c5b3a770 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -186,5 +186,70 @@ class CoreResampleConsistencyTest(PillowTestCase): self.run_case(self.make_case('F', 1.192093e-07)) +class CoreResampleAlphaCorrectTest(PillowTestCase): + def make_levels_case(self, mode): + i = Image.new(mode, (256, 16)) + px = i.load() + for y in range(i.size[1]): + for x in range(i.size[0]): + pix = [x] * len(mode) + pix[-1] = 255 - y * 16 + px[x, y] = tuple(pix) + return i + + def run_levels_case(self, i): + px = i.load() + for y in range(i.size[1]): + used_colors = set(px[x, y][0] for x in range(i.size[0])) + self.assertEqual(256, len(used_colors), + 'All colors should present in resized image. ' + 'Only {0} on {1} line.'.format(len(used_colors), y)) + + @unittest.skip("current implementation isn't precise enough") + def test_levels_rgba(self): + case = self.make_levels_case('RGBA') + self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) + self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) + self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) + + @unittest.skip("current implementation isn't precise enough") + def test_levels_la(self): + case = self.make_levels_case('LA') + self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) + self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) + self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) + + def make_dity_case(self, mode, clean_pixel, dirty_pixel): + i = Image.new(mode, (64, 64), dirty_pixel) + px = i.load() + xdiv4 = i.size[0] // 4 + ydiv4 = i.size[1] // 4 + for y in range(ydiv4 * 2): + for x in range(xdiv4 * 2): + px[x + xdiv4, y + ydiv4] = clean_pixel + return i + + def run_dity_case(self, i, clean_pixel): + px = i.load() + for y in range(i.size[1]): + for x in range(i.size[0]): + if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: + message = 'pixel at ({0}, {1}) is differ:\n{2}\n{3}'\ + .format(x, y, px[x, y], clean_pixel) + self.assertEqual(px[x, y][:3], clean_pixel, message) + + def test_dirty_pixels_rgba(self): + case = self.make_dity_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) + self.run_dity_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) + self.run_dity_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) + self.run_dity_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) + + def test_dirty_pixels_la(self): + case = self.make_dity_case('LA', (255, 128), (0, 0)) + self.run_dity_case(case.resize((20, 20), Image.BILINEAR), (255,)) + self.run_dity_case(case.resize((20, 20), Image.BICUBIC), (255,)) + self.run_dity_case(case.resize((20, 20), Image.LANCZOS), (255,)) + + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Access.c b/libImaging/Access.c index 97474a0b8..059f2aaeb 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -222,6 +222,7 @@ ImagingAccessInit() ADD("1", line_8, get_pixel_8, put_pixel_8); ADD("L", line_8, get_pixel_8, put_pixel_8); ADD("LA", line_32, get_pixel, put_pixel); + ADD("La", line_32, get_pixel, put_pixel); ADD("I", line_32, get_pixel_32, put_pixel_32); ADD("I;16", line_16, get_pixel_16L, put_pixel_16L); ADD("I;16L", line_16, get_pixel_16L, put_pixel_16L); diff --git a/libImaging/Convert.c b/libImaging/Convert.c index f32e91bad..99d5ae10d 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -116,6 +116,42 @@ l2bit(UINT8* out, const UINT8* in, int xsize) *out++ = (*in++ >= 128) ? 255 : 0; } +static void +lA2la(UINT8* out, const UINT8* in, int xsize) +{ + int x; + unsigned int alpha, pixel, tmp; + for (x = 0; x < xsize; x++, in += 4) { + alpha = in[3]; + pixel = MULDIV255(in[0], alpha, tmp); + *out++ = (UINT8) pixel; + *out++ = (UINT8) pixel; + *out++ = (UINT8) pixel; + *out++ = (UINT8) alpha; + } +} + +/* RGBa -> RGBA conversion to remove premultiplication + Needed for correct transforms/resizing on RGBA images */ +static void +la2lA(UINT8* out, const UINT8* in, int xsize) +{ + int x; + unsigned int alpha, pixel; + for (x = 0; x < xsize; x++, in+=4) { + alpha = in[3]; + if (alpha == 255 || alpha == 0) { + pixel = in[0]; + } else { + pixel = CLIP((255 * in[0]) / alpha); + } + *out++ = (UINT8) pixel; + *out++ = (UINT8) pixel; + *out++ = (UINT8) pixel; + *out++ = (UINT8) alpha; + } +} + static void l2la(UINT8* out, const UINT8* in, int xsize) { @@ -405,7 +441,7 @@ rgba2rgb(UINT8* out, const UINT8* in, int xsize) } static void -rgba2rgba(UINT8* out, const UINT8* in, int xsize) +rgbA2rgba(UINT8* out, const UINT8* in, int xsize) { int x; unsigned int alpha, tmp; @@ -427,17 +463,16 @@ rgba2rgbA(UINT8* out, const UINT8* in, int xsize) unsigned int alpha; for (x = 0; x < xsize; x++, in+=4) { alpha = in[3]; - if (alpha) { - *out++ = CLIP((255 * in[0]) / alpha); - *out++ = CLIP((255 * in[1]) / alpha); - *out++ = CLIP((255 * in[2]) / alpha); - } else { + if (alpha == 255 || alpha == 0) { *out++ = in[0]; *out++ = in[1]; *out++ = in[2]; + } else { + *out++ = CLIP((255 * in[0]) / alpha); + *out++ = CLIP((255 * in[1]) / alpha); + *out++ = CLIP((255 * in[2]) / alpha); } *out++ = in[3]; - } } @@ -765,10 +800,13 @@ static struct { { "L", "YCbCr", l2ycbcr }, { "LA", "L", la2l }, + { "LA", "La", lA2la }, { "LA", "RGB", la2rgb }, { "LA", "RGBX", la2rgb }, { "LA", "RGBA", la2rgb }, + { "La", "LA", la2lA }, + { "I", "L", i2l }, { "I", "F", i2f }, @@ -795,7 +833,7 @@ static struct { { "RGBA", "I", rgb2i }, { "RGBA", "F", rgb2f }, { "RGBA", "RGB", rgba2rgb }, - { "RGBA", "RGBa", rgba2rgba }, + { "RGBA", "RGBa", rgbA2rgba }, { "RGBA", "RGBX", rgb2rgba }, { "RGBA", "CMYK", rgb2cmyk }, { "RGBA", "YCbCr", ImagingConvertRGB2YCbCr }, diff --git a/libImaging/Storage.c b/libImaging/Storage.c index d65de1c0a..4450d14e4 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -91,6 +91,12 @@ ImagingNewPrologueSubtype(const char *mode, unsigned xsize, unsigned ysize, im->pixelsize = 4; /* store in image32 memory */ im->linesize = xsize * 4; + } else if (strcmp(mode, "La") == 0) { + /* 8-bit greyscale (luminance) with premultiplied alpha */ + im->bands = 2; + im->pixelsize = 4; /* store in image32 memory */ + im->linesize = xsize * 4; + } else if (strcmp(mode, "F") == 0) { /* 32-bit floating point images */ im->bands = 1;