From 05d1022e99802368319c5521abc93701ad04aad5 Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 9 May 2016 21:13:44 +0200 Subject: [PATCH 1/6] test for levels --- Tests/test_image_resample.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 0b78217ce..cefd778bc 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -186,5 +186,22 @@ class CoreResampleConsistencyTest(PillowTestCase): self.run_case(self.make_case('F', 1.192093e-07)) +class CoreResampleAlphaCorrectTest(PillowTestCase): + def test_levels(self): + i = Image.new('RGBA', (256, 16)) + px = i.load() + for y in range(i.size[1]): + for x in range(i.size[0]): + px[x, y] = (x, x, x, 255 - y * 16) + + i = i.resize((512, 32), Image.BILINEAR) + 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)) + + if __name__ == '__main__': unittest.main() From 8947485e715ddba9a476692a636cee173e0cb9d8 Mon Sep 17 00:00:00 2001 From: homm Date: Mon, 9 May 2016 22:06:34 +0200 Subject: [PATCH 2/6] test dirty pixels --- Tests/test_image_resample.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index cefd778bc..e33b0cb05 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -202,6 +202,26 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): 'All colors should present in resized image. ' 'Only {0} on {1} line.'.format(len(used_colors), y)) + def test_dirty_pixels(self): + i = Image.new('RGBA', (64, 64), (0, 0, 255, 0)) + px = i.load() + for y in range(i.size[1] // 4, i.size[1] // 4 * 3): + for x in range(i.size[0] // 4, i.size[0] // 4 * 3): + px[x, y] = (255, 255, 0, 128) + + + for im in [ + i.resize((20, 20), Image.BILINEAR), + i.resize((20, 20), Image.BICUBIC), + i.resize((20, 20), Image.LANCZOS), + ]: + px = im.load() + for y in range(im.size[1]): + for x in range(im.size[0]): + if px[x, y][3] != 0: + if px[x, y][:3] != (255, 256, 0): + self.assertEqual(px[x, y][:3], (255, 255, 0)) + if __name__ == '__main__': unittest.main() From 1ed5c59f230442d7278da1f53edc641e9933c757 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 10 May 2016 16:26:39 +0200 Subject: [PATCH 3/6] tests for LA modes --- Tests/test_image_resample.py | 68 +++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index e33b0cb05..ec00d459f 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -187,14 +187,17 @@ class CoreResampleConsistencyTest(PillowTestCase): class CoreResampleAlphaCorrectTest(PillowTestCase): - def test_levels(self): - i = Image.new('RGBA', (256, 16)) + 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]): - px[x, y] = (x, x, x, 255 - y * 16) + pix = [x] * len(mode) + pix[-1] = 255 - y * 16 + px[x, y] = tuple(pix) + return i - i = i.resize((512, 32), Image.BILINEAR) + 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])) @@ -202,25 +205,50 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): 'All colors should present in resized image. ' 'Only {0} on {1} line.'.format(len(used_colors), y)) - def test_dirty_pixels(self): - i = Image.new('RGBA', (64, 64), (0, 0, 255, 0)) + @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)) + + 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() - for y in range(i.size[1] // 4, i.size[1] // 4 * 3): - for x in range(i.size[0] // 4, i.size[0] // 4 * 3): - px[x, y] = (255, 255, 0, 128) + 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) - for im in [ - i.resize((20, 20), Image.BILINEAR), - i.resize((20, 20), Image.BICUBIC), - i.resize((20, 20), Image.LANCZOS), - ]: - px = im.load() - for y in range(im.size[1]): - for x in range(im.size[0]): - if px[x, y][3] != 0: - if px[x, y][:3] != (255, 256, 0): - self.assertEqual(px[x, y][:3], (255, 255, 0)) + 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)) + + @unittest.skip("current implementation doesn't support La mode") + 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__': From c4b92d09b7406617b7cb74f0f55b6a18e752c2c4 Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 10 May 2016 20:46:40 +0200 Subject: [PATCH 4/6] support for La mode --- PIL/Image.py | 7 ++++++ PIL/PyAccess.py | 1 + Tests/make_hash.py | 2 +- Tests/test_image_resample.py | 2 +- libImaging/Access.c | 1 + libImaging/Convert.c | 44 +++++++++++++++++++++++++++++++++--- libImaging/Storage.c | 6 +++++ 7 files changed, 58 insertions(+), 5 deletions(-) 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 ec00d459f..7c5b3a770 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -212,6 +212,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): 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)) @@ -243,7 +244,6 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): 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)) - @unittest.skip("current implementation doesn't support La mode") 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,)) 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..f5931394f 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) { + pixel = CLIP((255 * in[0]) / alpha); + } else { + pixel = in[0]; + } + *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; @@ -437,7 +473,6 @@ rgba2rgbA(UINT8* out, const UINT8* in, int xsize) *out++ = in[2]; } *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; From d1272b9b8ab5be127a300a08e439ebd61841619d Mon Sep 17 00:00:00 2001 From: homm Date: Tue, 10 May 2016 20:52:46 +0200 Subject: [PATCH 5/6] speedup RGBa -> RGBA conversion in most cases --- libImaging/Convert.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libImaging/Convert.c b/libImaging/Convert.c index f5931394f..5aad3733c 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -463,14 +463,14 @@ 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]; } From b56d8f3f1f42daf9a96df90f9495b4d12f580869 Mon Sep 17 00:00:00 2001 From: homm Date: Thu, 26 May 2016 15:22:01 +0300 Subject: [PATCH 6/6] speedup La -> LA conversion in most cases --- libImaging/Convert.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 5aad3733c..99d5ae10d 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -140,11 +140,11 @@ la2lA(UINT8* out, const UINT8* in, int xsize) unsigned int alpha, pixel; for (x = 0; x < xsize; x++, in+=4) { alpha = in[3]; - if (alpha) { - pixel = CLIP((255 * in[0]) / alpha); - } else { + if (alpha == 255 || alpha == 0) { pixel = in[0]; - } + } else { + pixel = CLIP((255 * in[0]) / alpha); + } *out++ = (UINT8) pixel; *out++ = (UINT8) pixel; *out++ = (UINT8) pixel;