diff --git a/Tests/helper.py b/Tests/helper.py index 0dc39e91f..9f1501249 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -238,3 +238,12 @@ if sys.platform == 'win32': IMCONVERT = os.path.join(IMCONVERT, 'convert.exe') else: IMCONVERT = 'convert' + + +class cached_property(object): + def __init__(self, func): + self.func = func + + def __get__(self, instance, cls=None): + result = instance.__dict__[self.func.__name__] = self.func(instance) + return result diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py new file mode 100644 index 000000000..f3fe9a2af --- /dev/null +++ b/Tests/test_image_paste.py @@ -0,0 +1,252 @@ +from helper import PillowTestCase, cached_property + +from PIL import Image + + +class TestImagingPaste(PillowTestCase): + masks = {} + size = 128 + + def assert_9points_image(self, im, expected): + expected = [ + point[0] + if im.mode == 'L' else + point[:len(im.mode)] + for point in expected + ] + px = im.load() + actual = [ + px[0, 0], + px[self.size // 2, 0], + px[self.size - 1, 0], + px[0, self.size // 2], + px[self.size // 2, self.size // 2], + px[self.size - 1, self.size // 2], + px[0, self.size - 1], + px[self.size // 2, self.size - 1], + px[self.size - 1, self.size - 1], + ] + self.assertEqual(actual, expected) + + @cached_property + def mask_1(self): + mask = Image.new('1', (self.size, self.size)) + px = mask.load() + for y in range(mask.height): + for x in range(mask.width): + px[y, x] = (x + y) % 2 + return mask + + @cached_property + def mask_L(self): + return self.gradient_L.transpose(Image.ROTATE_270) + + @cached_property + def gradient_L(self): + gradient = Image.new('L', (self.size, self.size)) + px = gradient.load() + for y in range(gradient.height): + for x in range(gradient.width): + px[y, x] = (x + y) % 255 + return gradient + + @cached_property + def gradient_RGB(self): + return Image.merge('RGB', [ + self.gradient_L, + self.gradient_L.transpose(Image.ROTATE_90), + self.gradient_L.transpose(Image.ROTATE_180), + ]) + + @cached_property + def gradient_RGBA(self): + return Image.merge('RGBA', [ + self.gradient_L, + self.gradient_L.transpose(Image.ROTATE_90), + self.gradient_L.transpose(Image.ROTATE_180), + self.gradient_L.transpose(Image.ROTATE_270), + ]) + + @cached_property + def gradient_RGBa(self): + return Image.merge('RGBa', [ + self.gradient_L, + self.gradient_L.transpose(Image.ROTATE_90), + self.gradient_L.transpose(Image.ROTATE_180), + self.gradient_L.transpose(Image.ROTATE_270), + ]) + + def test_image_solid(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'red') + im2 = getattr(self, 'gradient_' + mode) + + im.paste(im2, (12, 23)) + + im = im.crop((12, 23, im2.width + 12, im2.height + 23)) + self.assert_image_equal(im, im2) + + def test_image_mask_1(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'white') + im2 = getattr(self, 'gradient_' + mode) + + im.paste(im2, (0, 0), self.mask_1) + + self.assert_9points_image(im, [ + (255, 255, 255, 255), + (255, 255, 255, 255), + (127, 254, 127, 0), + (255, 255, 255, 255), + (255, 255, 255, 255), + (191, 190, 63, 64), + (127, 0, 127, 254), + (191, 64, 63, 190), + (255, 255, 255, 255), + ]) + + def test_image_mask_L(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'white') + im2 = getattr(self, 'gradient_' + mode) + + im.paste(im2, (0, 0), self.mask_L) + + self.assert_9points_image(im, [ + (128, 191, 255, 191), + (208, 239, 239, 208), + (255, 255, 255, 255), + (112, 111, 206, 207), + (192, 191, 191, 191), + (239, 239, 207, 207), + (128, 1, 128, 254), + (207, 113, 112, 207), + (255, 191, 128, 191), + ]) + + def test_image_mask_RGBA(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'white') + im2 = getattr(self, 'gradient_' + mode) + + im.paste(im2, (0, 0), self.gradient_RGBA) + + self.assert_9points_image(im, [ + (128, 191, 255, 191), + (208, 239, 239, 208), + (255, 255, 255, 255), + (112, 111, 206, 207), + (192, 191, 191, 191), + (239, 239, 207, 207), + (128, 1, 128, 254), + (207, 113, 112, 207), + (255, 191, 128, 191), + ]) + + def test_image_mask_RGBa(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'white') + im2 = getattr(self, 'gradient_' + mode) + + im.paste(im2, (0, 0), self.gradient_RGBa) + + self.assert_9points_image(im, [ + (128, 255, 126, 255), + (0, 127, 126, 255), + (126, 253, 126, 255), + (128, 127, 254, 255), + (0, 255, 254, 255), + (126, 125, 254, 255), + (128, 1, 128, 255), + (0, 129, 128, 255), + (126, 255, 128, 255), + ]) + + def test_color_solid(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), 'black') + + rect = (12, 23, 128 + 12, 128 + 23) + im.paste('white', rect) + + hist = im.crop(rect).histogram() + while hist: + head, hist = hist[:256], hist[256:] + self.assertEqual(head[255], 128 * 128) + self.assertEqual(sum(head[:255]), 0) + + def test_color_mask_1(self): + for mode in ('RGBA', 'RGB', 'L'): + im = Image.new(mode, (200, 200), (50, 60, 70, 80)[:len(mode)]) + color = (10, 20, 30, 40)[:len(mode)] + + im.paste(color, (0, 0), self.mask_1) + + self.assert_9points_image(im, [ + (50, 60, 70, 80), + (50, 60, 70, 80), + (10, 20, 30, 40), + (50, 60, 70, 80), + (50, 60, 70, 80), + (10, 20, 30, 40), + (10, 20, 30, 40), + (10, 20, 30, 40), + (50, 60, 70, 80), + ]) + + def test_color_mask_L(self): + for mode in ('RGBA', 'RGB', 'L'): + im = getattr(self, 'gradient_' + mode).copy() + color = 'white' + + im.paste(color, (0, 0), self.mask_L) + + self.assert_9points_image(im, [ + (127, 191, 254, 191), + (111, 207, 206, 110), + (127, 254, 127, 0), + (207, 207, 239, 239), + (191, 191, 190, 191), + (207, 206, 111, 112), + (254, 254, 254, 255), + (239, 206, 206, 238), + (254, 191, 127, 191), + ]) + + def test_color_mask_RGBA(self): + for mode in ('RGBA', 'RGB', 'L'): + im = getattr(self, 'gradient_' + mode).copy() + color = 'white' + + im.paste(color, (0, 0), self.gradient_RGBA) + + self.assert_9points_image(im, [ + (127, 191, 254, 191), + (111, 207, 206, 110), + (127, 254, 127, 0), + (207, 207, 239, 239), + (191, 191, 190, 191), + (207, 206, 111, 112), + (254, 254, 254, 255), + (239, 206, 206, 238), + (254, 191, 127, 191), + ]) + + def test_color_mask_RGBa(self): + for mode in ('RGBA', 'RGB', 'L'): + im = getattr(self, 'gradient_' + mode).copy() + color = 'white' + + im.paste(color, (0, 0), self.gradient_RGBa) + + self.assert_9points_image(im, [ + (255, 63, 126, 63), + (47, 143, 142, 46), + (126, 253, 126, 255), + (15, 15, 47, 47), + (63, 63, 62, 63), + (142, 141, 46, 47), + (255, 255, 255, 0), + (48, 15, 15, 47), + (126, 63, 255, 63) + ]) diff --git a/libImaging/Paste.c b/libImaging/Paste.c index d27b10fdb..a18e5d6f2 100644 --- a/libImaging/Paste.c +++ b/libImaging/Paste.c @@ -5,15 +5,15 @@ * paste image on another image * * history: - * 96-03-27 fl Created - * 96-07-16 fl Support "1", "L" and "RGBA" masks - * 96-08-16 fl Merged with opaque paste - * 97-01-17 fl Faster blending, added support for RGBa images - * 97-08-27 fl Faster masking for 32-bit images - * 98-02-02 fl Fixed MULDIV255 macro for gcc - * 99-02-02 fl Added "RGBa" mask support - * 99-02-06 fl Rewritten. Added support for masked fill operations. - * 99-12-08 fl Fixed matte fill. + * 96-03-27 fl Created + * 96-07-16 fl Support "1", "L" and "RGBA" masks + * 96-08-16 fl Merged with opaque paste + * 97-01-17 fl Faster blending, added support for RGBa images + * 97-08-27 fl Faster masking for 32-bit images + * 98-02-02 fl Fixed MULDIV255 macro for gcc + * 99-02-02 fl Added "RGBa" mask support + * 99-02-06 fl Rewritten. Added support for masked fill operations. + * 99-12-08 fl Fixed matte fill. * * Copyright (c) Fredrik Lundh 1996-97. * Copyright (c) Secret Labs AB 1997-99. @@ -24,19 +24,19 @@ #include "Imaging.h" /* like (a * b + 127) / 255), but much faster on most platforms */ -#define MULDIV255NEW(a, b, tmp)\ - (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) +#define MULDIV255NEW(a, tmp)\ + (tmp = (a) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) -#define MULDIV255OLD(a, b, tmp)\ - (((a) * (b) + 127) / 255) +#define MULDIV255OLD(a, tmp)\ + (((a) + 127) / 255) #define MULDIV255 MULDIV255NEW -#define BLEND(mask, in1, in2, tmp1, tmp2)\ - (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2)) +#define BLEND(mask, in1, in2, tmp1)\ + MULDIV255(in1 * (255 - mask) + in2 * mask, tmp1) -#define PREBLEND(mask, in1, in2, tmp1)\ - (MULDIV255(in1, 255 - mask, tmp1) + in2) +#define PREBLEND(mask, in1, in2, tmp1)\ + (MULDIV255(in1 * (255 - mask), tmp1) + in2) static inline void paste(Imaging imOut, Imaging imIn, int dx, int dy, int sx, int sy, @@ -99,8 +99,8 @@ paste_mask_L(Imaging imOut, Imaging imIn, Imaging imMask, { /* paste with mode "L" matte */ - int x, y, i; - unsigned int tmp1, tmp2; + int x, y; + unsigned int tmp1; if (imOut->image8) { @@ -109,7 +109,7 @@ paste_mask_L(Imaging imOut, Imaging imIn, Imaging imMask, UINT8* in = imIn->image8[y+sy]+sx; UINT8* mask = imMask->image8[y+sy]+sx; for (x = 0; x < xsize; x++) { - *out = BLEND(*mask, *out, *in, tmp1, tmp2); + *out = BLEND(*mask, *out, *in, tmp1); out++, in++, mask++; } } @@ -117,15 +117,16 @@ paste_mask_L(Imaging imOut, Imaging imIn, Imaging imMask, } else { for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx*pixelsize; - UINT8* in = (UINT8*) imIn->image[y+sy]+sx*pixelsize; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); + UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); + UINT8* mask = (UINT8*) (imMask->image8[y+sy] + sx); for (x = 0; x < xsize; x++) { - for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, *in, tmp1, tmp2); - out++, in++; - } - mask++; + UINT8 a = mask[0]; + out[0] = BLEND(a, out[0], in[0], tmp1); + out[1] = BLEND(a, out[1], in[1], tmp1); + out[2] = BLEND(a, out[2], in[2], tmp1); + out[3] = BLEND(a, out[3], in[3], tmp1); + out += 4; in += 4; mask ++; } } } @@ -138,8 +139,8 @@ paste_mask_RGBA(Imaging imOut, Imaging imIn, Imaging imMask, { /* paste with mode "RGBA" matte */ - int x, y, i; - unsigned int tmp1, tmp2; + int x, y; + unsigned int tmp1; if (imOut->image8) { @@ -148,7 +149,7 @@ paste_mask_RGBA(Imaging imOut, Imaging imIn, Imaging imMask, UINT8* in = imIn->image8[y+sy]+sx; UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3; for (x = 0; x < xsize; x++) { - *out = BLEND(*mask, *out, *in, tmp1, tmp2); + *out = BLEND(*mask, *out, *in, tmp1); out++, in++, mask += 4; } } @@ -156,15 +157,16 @@ paste_mask_RGBA(Imaging imOut, Imaging imIn, Imaging imMask, } else { for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx*pixelsize; - UINT8* in = (UINT8*) imIn->image[y+sy]+sx*pixelsize; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3; + UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); + UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); + UINT8* mask = (UINT8*) (imMask->image32[y+sy] + sx); for (x = 0; x < xsize; x++) { - for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, *in, tmp1, tmp2); - out++, in++; - } - mask += 4; + UINT8 a = mask[3]; + out[0] = BLEND(a, out[0], in[0], tmp1); + out[1] = BLEND(a, out[1], in[1], tmp1); + out[2] = BLEND(a, out[2], in[2], tmp1); + out[3] = BLEND(a, out[3], in[3], tmp1); + out += 4; in += 4; mask += 4; } } } @@ -178,7 +180,7 @@ paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask, { /* paste with mode "RGBa" matte */ - int x, y, i; + int x, y; unsigned int tmp1; if (imOut->image8) { @@ -196,15 +198,16 @@ paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask, } else { for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx*pixelsize; - UINT8* in = (UINT8*) imIn->image[y+sy]+sx*pixelsize; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3; + UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); + UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); + UINT8* mask = (UINT8*) (imMask->image32[y+sy] + sx); for (x = 0; x < xsize; x++) { - for (i = 0; i < pixelsize; i++) { - *out = PREBLEND(*mask, *out, *in, tmp1); - out++, in++; - } - mask += 4; + UINT8 a = mask[3]; + out[0] = PREBLEND(a, out[0], in[0], tmp1); + out[1] = PREBLEND(a, out[1], in[1], tmp1); + out[2] = PREBLEND(a, out[2], in[2], tmp1); + out[3] = PREBLEND(a, out[3], in[3], tmp1); + out += 4; in += 4; mask += 4; } } } @@ -212,7 +215,7 @@ paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask, int ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, - int dx0, int dy0, int dx1, int dy1) + int dx0, int dy0, int dx1, int dy1) { int xsize, ysize; int pixelsize; @@ -220,8 +223,8 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, ImagingSectionCookie cookie; if (!imOut || !imIn) { - (void) ImagingError_ModeError(); - return -1; + (void) ImagingError_ModeError(); + return -1; } pixelsize = imOut->pixelsize; @@ -231,28 +234,28 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, if (xsize != imIn->xsize || ysize != imIn->ysize || pixelsize != imIn->pixelsize) { - (void) ImagingError_Mismatch(); - return -1; + (void) ImagingError_Mismatch(); + return -1; } if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) { - (void) ImagingError_Mismatch(); - return -1; + (void) ImagingError_Mismatch(); + return -1; } /* Determine which region to copy */ sx0 = sy0 = 0; if (dx0 < 0) - xsize += dx0, sx0 = -dx0, dx0 = 0; + xsize += dx0, sx0 = -dx0, dx0 = 0; if (dx0 + xsize > imOut->xsize) - xsize = imOut->xsize - dx0; + xsize = imOut->xsize - dx0; if (dy0 < 0) - ysize += dy0, sy0 = -dy0, dy0 = 0; + ysize += dy0, sy0 = -dy0, dy0 = 0; if (dy0 + ysize > imOut->ysize) - ysize = imOut->ysize - dy0; + ysize = imOut->ysize - dy0; if (xsize <= 0 || ysize <= 0) - return 0; + return 0; if (!imMask) { ImagingSectionEnter(&cookie); @@ -284,8 +287,8 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, ImagingSectionLeave(&cookie); } else { - (void) ImagingError_ValueError("bad transparency mask"); - return -1; + (void) ImagingError_ValueError("bad transparency mask"); + return -1; } return 0; @@ -308,15 +311,15 @@ fill(Imaging imOut, const void* ink_, int dx, int dy, dx *= pixelsize; xsize *= pixelsize; - for (y = 0; y < ysize; y++) - memset(imOut->image[y+dy]+dx, ink8, xsize); + for (y = 0; y < ysize; y++) + memset(imOut->image[y+dy]+dx, ink8, xsize); } else { - for (y = 0; y < ysize; y++) { + for (y = 0; y < ysize; y++) { INT32* out = imOut->image32[y+dy]+dx; - for (x = 0; x < xsize; x++) - out[x] = ink32; + for (x = 0; x < xsize; x++) + out[x] = ink32; } } @@ -370,7 +373,7 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, /* fill with mode "L" matte */ int x, y, i; - unsigned int tmp1, tmp2; + unsigned int tmp1; if (imOut->image8) { @@ -378,7 +381,7 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, UINT8* out = imOut->image8[y+dy]+dx; UINT8* mask = imMask->image8[y+sy]+sx; for (x = 0; x < xsize; x++) { - *out = BLEND(*mask, *out, ink[0], tmp1, tmp2); + *out = BLEND(*mask, *out, ink[0], tmp1); out++, mask++; } } @@ -390,7 +393,7 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, ink[i], tmp1, tmp2); + *out = BLEND(*mask, *out, ink[i], tmp1); out++; } mask++; @@ -407,7 +410,7 @@ fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, /* fill with mode "RGBA" matte */ int x, y, i; - unsigned int tmp1, tmp2; + unsigned int tmp1; if (imOut->image8) { @@ -416,7 +419,7 @@ fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, UINT8* out = imOut->image8[y+dy]+dx; UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; for (x = 0; x < xsize; x++) { - *out = BLEND(*mask, *out, ink[0], tmp1, tmp2); + *out = BLEND(*mask, *out, ink[0], tmp1); out++, mask += 4; } } @@ -430,7 +433,7 @@ fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, ink[i], tmp1, tmp2); + *out = BLEND(*mask, *out, ink[i], tmp1); out++; } mask += 4; @@ -489,8 +492,8 @@ ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, int sx0, sy0; if (!imOut || !ink) { - (void) ImagingError_ModeError(); - return -1; + (void) ImagingError_ModeError(); + return -1; } pixelsize = imOut->pixelsize; @@ -499,23 +502,23 @@ ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, ysize = dy1 - dy0; if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) { - (void) ImagingError_Mismatch(); - return -1; + (void) ImagingError_Mismatch(); + return -1; } /* Determine which region to fill */ sx0 = sy0 = 0; if (dx0 < 0) - xsize += dx0, sx0 = -dx0, dx0 = 0; + xsize += dx0, sx0 = -dx0, dx0 = 0; if (dx0 + xsize > imOut->xsize) - xsize = imOut->xsize - dx0; + xsize = imOut->xsize - dx0; if (dy0 < 0) - ysize += dy0, sy0 = -dy0, dy0 = 0; + ysize += dy0, sy0 = -dy0, dy0 = 0; if (dy0 + ysize > imOut->ysize) - ysize = imOut->ysize - dy0; + ysize = imOut->ysize - dy0; if (xsize <= 0 || ysize <= 0) - return 0; + return 0; if (!imMask) { ImagingSectionEnter(&cookie); @@ -547,8 +550,8 @@ ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, ImagingSectionLeave(&cookie); } else { - (void) ImagingError_ValueError("bad transparency mask"); - return -1; + (void) ImagingError_ValueError("bad transparency mask"); + return -1; } return 0;