Pillow alpha blending fix with new features and perfomance boost

Sun, 21 Apr 2013 (tags:python, performance, features)

This will be tricky to write, because I'm describing it 2 years after I left the project and I have foggy memory.
When I used to make python to manipulate some images I realized there is bug in alpha handling, then I switched to Pillow image manipulating framework, just to discover both use almost same code with the same bug. So I decided to fix it, I managed to handle more color space formats, I speeded up the regular routines as well, which is not bad. It was faster and providing better quality.
 
Then to prove my case I made quality and speed benchmark and started polishing that as well. I didn't wanted to publish unpolished code. I spend too much time fooling around with benchmarks that in meantime somebody published fix for the alpha blending. And then my attention for this my fix left (had to concentrate on life and college and other things). Even when I compared the fixed version with mine, still provided better speed, quality and color space formats options. Which I needed for my project when I had grayscale alpha image which I could use directly instead of converting into RGBA first saved lot of memory. Mine script was working on tiles which assembled create huge image 32k x 32k pixels big. So memory efficiency and speed was priority, but that would be worthless if I didn't had the quality as well. So one side proud of this code, on other side after 2 years it's just collecting dust, so I decided to publish it unpolished, better than kept private. Because I did few thing bit differently and didn't wanted to break maybe compatibility with applications which depend on this bug I decided to make separate alternative methods, so still you can have original functionality and mine as well.
The speed improvements are thanks this:

 

#define BLEND(mask, in1, in2, tmp1, tmp2)\
(MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2))
 
+#define BLEND_FASTER(mask, in1, in2, tmp)\
+ ( tmp=(in1*(255-mask) + in2*mask)+128, ((((tmp) >> 8) + (tmp)) >> 8))
+
 #define PREBLEND(mask, in1, in2, tmp1)\
  (MULDIV255(in1, 255 - mask, tmp1) + in2)
 
+#define ALT_BLEND(bg, mask_bg, fg, mask_fg,alpha)\
+ ((((bg) * (mask_bg) * (255 - (mask_fg)))  + ((fg) * (mask_fg)*255))/(alpha))
+
+#define ALT_BLEND_INVERT(bg, mask_bg, fg, mask_fg,alpha)\
+ ((((bg) * (mask_bg) * (mask_fg))  + ((fg) * (255-(mask_fg))*255))/(alpha))
+
+#define ALT_ALPHA(mask_bg,mask_fg)\
+ ((mask_fg)*255 + ((mask_bg) * (255-(mask_fg))))
+
+#define ALT_ALPHA_INVERT(mask_bg,mask_fg)\
+ ((255-(mask_fg))*255 + ((mask_bg) * (mask_fg)))
+
+#define ROUNDDIV255(alpha)\
+ (((alpha)+127)/255)
+

 

Now mine BLEND_FASTER makes just 1 temp variable and does it 100% without any division, original makes 2 division and makes 2 temp variables. But produces the same quality. This is applied for each every pixel, this makes lot of difference. Then there there is ALT_BLEND which does proper non pre multiplied alpha blending, it shows lot of difference in quality if you blend lot of images over themself, multiple layers, complicated things etc.... The difference is not as visible on 2 layer 1 blend operation as it is on more complicated operations. Then I have option where the ALPHA can be inverted which in this case will not give you performance penalty and you don't have to make workaround like before. I Think I can mix 2 single chanel alphas together as well (so much foggy memorries :) )

 

Here some quality comparison, first column original method, second column is my alternative which should build up and it does. Next colum original can't even blend that formats, mine just fine, now it shouldn't build up the volume. Every even colum is mine alternative which does exactly what it should, and it doesn't looses quality even after good few iterations. I had font quality benchmark as well, but god know where it's lost.

 


 
Still if you don't need extra features and color formats, you don't need better quality and you like original broken behavior, or you have the fixed version, still you could want mine patch because of the better speeds. 
 
Full patch liste here. Probably if somebody is interested I could maybe revisit it, apply changes to current version, benchmark it against current version, but if nothing regardless blending changed then mine one was outperforming the broken and the fixed one:
 
diff -uNr '--exclude=*.pyc' '--exclude=*.egg' Pillow-2.0.0-orig//_imaging.c Pillow-2.0.0-mine-final//_imaging.c
 
--- Pillow-2.0.0-orig//_imaging.c 2013-03-15 08:55:00.000000000 +0100
+++ Pillow-2.0.0-mine-final//_imaging.c 2013-04-21 10:56:17.000000000 +0200
@@ -64,9 +64,11 @@
  * 2004-10-04 fl   Added modefilter
  * 2005-10-02 fl   Added access proxy
  * 2006-06-18 fl   Always draw last point in polyline
+ * 2013-04-20 ak   Added proper alpha blending methods
  *
  * Copyright (c) 1997-2006 by Secret Labs AB
  * Copyright (c) 1995-2006 by Fredrik Lundh
+ * 2013 fixes by Anton Krug
  *
  * See the README file for information on usage and redistribution.
  */
@@ -1141,6 +1143,51 @@
 }
 
 static PyObject*
+_paste_alt(ImagingObject* self, PyObject* args)
+{
+    int status;
+    char ink[4];
+
+    PyObject* source;
+    int x0, y0, x1, y1;
+    ImagingObject* maskp = NULL;
+    int lock_bg_alpha=0;
+    int mask_invert=0;
+
+    if (!PyArg_ParseTuple(args, "O(iiii)ii|O!",
+  &source,
+  &x0, &y0, &x1, &y1,
+  &lock_bg_alpha,&mask_invert,
+  &Imaging_Type, &maskp))
+ return NULL;
+
+
+    if (PyImaging_Check(source))
+        status = ImagingPasteAlt(
+            self->image, PyImaging_AsImaging(source),
+            (maskp) ? maskp->image : NULL,
+            x0, y0, x1, y1,
+            (unsigned char)(lock_bg_alpha),(unsigned char)(mask_invert)
+            );
+
+    else {
+        if (!getink(source, self->image, ink))
+            return NULL;
+        status = ImagingFill2(
+            self->image, ink,
+            (maskp) ? maskp->image : NULL,
+            x0, y0, x1, y1
+            );
+    }
+
+    if (status < 0)
+        return NULL;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject*
 _point(ImagingObject* self, PyObject* args)
 {
     static const char* wrong_number = "wrong number of lut entries";
@@ -2266,6 +2313,54 @@
 }
 
 static PyObject*
+_font_getmask_alt(ImagingFontObject* self, PyObject* args)
+{
+    Imaging im;
+    Imaging bitmap;
+    int x, b;
+    int status;
+    Glyph* glyph;
+
+    unsigned char* text;
+    char* mode = "";
+    if (!PyArg_ParseTuple(args, "s|s:getmask", &text, &mode))
+        return NULL;
+
+    im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
+    if (!im)
+        return NULL;
+
+    b = 0;
+    (void) ImagingFill(im, &b);
+
+    b = self->baseline;
+    for (x = 0; *text; text++) {
+        glyph = &self->glyphs[*text];
+        bitmap = ImagingCrop(
+            self->bitmap,
+            glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
+            );
+        if (!bitmap)
+            goto failed;
+        status = ImagingPasteAlt(
+            im, bitmap, NULL,
+            glyph->dx0+x, glyph->dy0+b, glyph->dx1+x, glyph->dy1+b,0,0
+            );
+        ImagingDelete(bitmap);
+        if (status < 0)
+            goto failed;
+        x = x + glyph->dx;
+        b = b + glyph->dy;
+    }
+
+    return PyImagingNew(im);
+
+  failed:
+    ImagingDelete(im);
+    return NULL;
+}
+
+static PyObject*
 _font_getsize(ImagingFontObject* self, PyObject* args)
 {
     unsigned char* text;
@@ -2277,6 +2372,7 @@
 
 static struct PyMethodDef _font_methods[] = {
     {"getmask", (PyCFunction)_font_getmask, 1},
+    {"getmask_alt", (PyCFunction)_font_getmask_alt, 1},
     {"getsize", (PyCFunction)_font_getsize, 1},
     {NULL, NULL} /* sentinel */
 };
@@ -2941,6 +3037,7 @@
 #endif
     {"offset", (PyCFunction)_offset, 1},
     {"paste", (PyCFunction)_paste, 1},
+    {"paste_alt", (PyCFunction)_paste_alt, 1},
     {"point", (PyCFunction)_point, 1},
     {"point_transform", (PyCFunction)_point_transform, 1},
     {"putdata", (PyCFunction)_putdata, 1},
diff -uNr '--exclude=*.pyc' '--exclude=*.egg' Pillow-2.0.0-orig//libImaging/Imaging.h Pillow-2.0.0-mine-final//libImaging/Imaging.h
--- Pillow-2.0.0-orig//libImaging/Imaging.h 2013-03-15 08:55:00.000000000 +0100
+++ Pillow-2.0.0-mine-final//libImaging/Imaging.h 2013-04-19 06:13:34.000000000 +0200
@@ -277,6 +277,9 @@
 extern int ImagingPaste(
     Imaging into, Imaging im, Imaging mask,
     int x0, int y0, int x1, int y1);
+extern int ImagingPasteAlt(
+    Imaging into, Imaging im, Imaging mask,
+    int x0, int y0, int x1, int y1, unsigned char lock_bg_alpha,unsigned char mask_invert);
 extern Imaging ImagingPoint(
     Imaging im, const char* tablemode, const void* table);
 extern Imaging ImagingPointTransform(
diff -uNr '--exclude=*.pyc' '--exclude=*.egg' Pillow-2.0.0-orig//libImaging/Paste.c Pillow-2.0.0-mine-final//libImaging/Paste.c
--- Pillow-2.0.0-orig//libImaging/Paste.c 2013-03-15 08:55:00.000000000 +0100
+++ Pillow-2.0.0-mine-final//libImaging/Paste.c 2013-04-21 10:58:46.000000000 +0200
@@ -14,9 +14,12 @@
  * 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.
+ * 13-04-20 ak  Added proper alpha blending methods
+ * 13-04-21 ak  Faster MULDIV255 tweak
  *
  * Copyright (c) Fredrik Lundh 1996-97.
  * Copyright (c) Secret Labs AB 1997-99.
+ * 2013 fixes by Anton Krug
  *
  * See the README file for information on usage and redistribution.
  */
@@ -35,9 +38,28 @@
 #define BLEND(mask, in1, in2, tmp1, tmp2)\
  (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2))
 
+#define BLEND_FASTER(mask, in1, in2, tmp)\
+ ( tmp=(in1*(255-mask) + in2*mask)+128, ((((tmp) >> 8) + (tmp)) >> 8))
+
 #define PREBLEND(mask, in1, in2, tmp1)\
  (MULDIV255(in1, 255 - mask, tmp1) + in2)
 
+#define ALT_BLEND(bg, mask_bg, fg, mask_fg,alpha)\
+ ((((bg) * (mask_bg) * (255 - (mask_fg)))  + ((fg) * (mask_fg)*255))/(alpha))
+
+#define ALT_BLEND_INVERT(bg, mask_bg, fg, mask_fg,alpha)\
+ ((((bg) * (mask_bg) * (mask_fg))  + ((fg) * (255-(mask_fg))*255))/(alpha))
+
+#define ALT_ALPHA(mask_bg,mask_fg)\
+ ((mask_fg)*255 + ((mask_bg) * (255-(mask_fg))))
+
+#define ALT_ALPHA_INVERT(mask_bg,mask_fg)\
+ ((255-(mask_fg))*255 + ((mask_bg) * (mask_fg)))
+
+#define ROUNDDIV255(alpha)\
+ (((alpha)+127)/255)
+
+
 static inline void
 paste(Imaging imOut, Imaging imIn, int dx, int dy, int sx, int sy,
       int xsize, int ysize, int pixelsize)
@@ -100,7 +122,7 @@
     /* paste with mode "L" matte */
 
     int x, y, i;
-    unsigned int tmp1, tmp2;
+    unsigned int tmp1;
 
     if (imOut->image8) {
 
@@ -109,7 +131,7 @@
             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_FASTER(*mask, *out, *in, tmp1);
                 out++, in++, mask++;
             }
         }
@@ -122,7 +144,7 @@
             UINT8* mask = (UINT8*) imMask->image[y+sy]+sx;
             for (x = 0; x < xsize; x++) {
                 for (i = 0; i < pixelsize; i++) {
-                    *out = BLEND(*mask, *out, *in, tmp1, tmp2);
+                    *out = BLEND_FASTER(*mask, *out, *in, tmp1);
                     out++, in++;
                 }
                 mask++;
@@ -139,7 +161,7 @@
     /* paste with mode "RGBA" matte */
 
     int x, y, i;
-    unsigned int tmp1, tmp2;
+    unsigned int tmp1;
 
     if (imOut->image8) {
 
@@ -148,7 +170,7 @@
             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_FASTER(*mask, *out, *in, tmp1);
                 out++, in++, mask += 4;
             }
         }
@@ -161,7 +183,7 @@
             UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3;
             for (x = 0; x < xsize; x++) {
                 for (i = 0; i < pixelsize; i++) {
-                    *out = BLEND(*mask, *out, *in, tmp1, tmp2);
+                    *out = BLEND_FASTER(*mask, *out, *in, tmp1);
                     out++, in++;
                 }
                 mask += 4;
@@ -170,6 +192,69 @@
     }
 }
 
+static inline void
+paste_mask_RGBA_ALT(Imaging imOut, Imaging imIn, Imaging imMask,
+                int dx, int dy, int sx, int sy,
+                int xsize, int ysize, int pixelsize,unsigned char lock_alpha,unsigned char alpha_bands,unsigned char alpha_band_offset,unsigned char alpha_invert)
+{
+    /* paste with mode "RGBA" matte */
+
+    int x, y, i;
+    unsigned int out_alpha;
+
+    if (imOut->image8) {
+
+        for (y = 0; y < ysize; y++) {
+            UINT8* out = imOut->image8[y+dy]+dx;
+            UINT8* in = imIn->image8[y+sy]+sx;
+            UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*alpha_bands+alpha_band_offset;
+            if (alpha_invert) {
+                for (x = 0; x < xsize; x++,out++, in++, mask += alpha_bands) {
+                    *out = ALT_BLEND_INVERT(*out,255,*in,*mask,255*255);
+                }
+            } else {
+                for (x = 0; x < xsize; x++,out++, in++, mask += alpha_bands) {
+                    *out = ALT_BLEND(*out,255,*in,*mask,255*255);
+                }
+            }
+        }
+
+    } else {
+
+        for (y = 0; y < ysize; y++) {
+            UINT8* out = (UINT8*) imOut->image[y+dy]+dx*pixelsize;
+            UINT8* mask_out = (UINT8*) imOut->image[y+dy]+dx*4+3;
+            UINT8* in = (UINT8*) imIn->image[y+sy]+sx*pixelsize;
+            UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*alpha_bands+alpha_band_offset;
+            for (x = 0; x < xsize; x++) {
+                if (alpha_invert) out_alpha=ALT_ALPHA_INVERT(*mask_out,*mask); else out_alpha=ALT_ALPHA(*mask_out,*mask);
+              if (out_alpha!=0) {
+                  if (alpha_invert) {
+                  for (i = 0; i < (pixelsize-1); i++) {
+                      *out = ALT_BLEND_INVERT(*out,*mask_out,*in,*mask,out_alpha);
+                      out++, in++;
+                    }
+                } else {
+                  for (i = 0; i < (pixelsize-1); i++) {
+                      *out = ALT_BLEND(*out,*mask_out,*in,*mask,out_alpha);
+                      out++, in++;
+                    }
+                }
+              } else {
+                  for (i = 0; i < (pixelsize-1); i++) {
+                      *out = 0;
+                      out++, in++;
+                   }
+                }
+              if (!lock_alpha) *mask_out = ROUNDDIV255(out_alpha);
+              out++, in++;               //skip alpha for regular color processing
+              mask += alpha_bands;
+               mask_out += 4;
+            }
+        }
+    }
+}
+
 
 static inline void
 paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask,
@@ -291,6 +376,91 @@
     return 0;
 }
 
+
+int
+ImagingPasteAlt(Imaging imOut, Imaging imIn, Imaging imMask,
+     int dx0, int dy0, int dx1, int dy1,unsigned char lock_bg_alpha,unsigned char mask_invert)
+{
+    int xsize, ysize;
+    int pixelsize;
+    int sx0, sy0;
+    ImagingSectionCookie cookie;
+    unsigned char mask_bands,mask_band_offset;
+    
+//    printf("alfa %d\n",lock_bg_alpha);
+
+//    printf("out=%s in=%s alpha=%s\n",imOut->mode,imIn->mode,imMask->mode);
+
+
+    if (!imOut || !imIn) {
+ (void) ImagingError_ModeError();
+ return -1;
+    }
+
+    pixelsize = imOut->pixelsize;
+
+    xsize = dx1 - dx0;
+    ysize = dy1 - dy0;
+
+    if (xsize != imIn->xsize || ysize != imIn->ysize ||
+        pixelsize != imIn->pixelsize) {
+ (void) ImagingError_Mismatch();
+ return -1;
+    }
+
+    if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) {
+ (void) ImagingError_Mismatch();
+ return -1;
+    }
+
+    /* Determine which region to copy */
+    sx0 = sy0 = 0;
+    if (dx0 < 0)
+ xsize += dx0, sx0 = -dx0, dx0 = 0;
+    if (dx0 + xsize > imOut->xsize)
+ xsize = imOut->xsize - dx0;
+    if (dy0 < 0)
+ ysize += dy0, sy0 = -dy0, dy0 = 0;
+    if (dy0 + ysize > imOut->ysize)
+ ysize = imOut->ysize - dy0;
+
+    if (xsize <= 0 || ysize <= 0)
+ return 0;
+
+ if (!imMask) {
+        ImagingSectionEnter(&cookie);
+        paste(imOut, imIn, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+        ImagingSectionLeave(&cookie);
+        
+    } else if (strcmp(imMask->mode, "1") == 0) {
+        ImagingSectionEnter(&cookie);
+        paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0,
+                     xsize, ysize, pixelsize);
+        ImagingSectionLeave(&cookie);
+        
+    } else if (strcmp(imMask->mode, "L") == 0 || strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
+        if (strcmp(imMask->mode, "L") == 0) { mask_bands=1; mask_band_offset=0; mask_invert=!mask_invert; }
+        if (strcmp(imMask->mode, "LA") == 0) { mask_bands=4; mask_band_offset=3; }
+        if (strcmp(imMask->mode, "RGBA") == 0) { mask_bands=4; mask_band_offset=3; }
+        ImagingSectionEnter(&cookie);
+        paste_mask_RGBA_ALT(imOut, imIn, imMask, dx0, dy0, sx0, sy0,
+                        xsize, ysize, pixelsize,lock_bg_alpha,mask_bands,mask_band_offset,mask_invert);
+        ImagingSectionLeave(&cookie);
+        
+    } else if (strcmp(imMask->mode, "RGBa") == 0) {
+        ImagingSectionEnter(&cookie);
+        paste_mask_RGBa(imOut, imIn, imMask, dx0, dy0, sx0, sy0,
+                        xsize, ysize, pixelsize);
+        ImagingSectionLeave(&cookie);
+        
+    } else {
+ (void) ImagingError_ValueError("bad transparency mask");
+ return -1;
+    }
+    
+    return 0;
+}
+
 static inline void
 fill(Imaging imOut, const void* ink_, int dx, int dy,
      int xsize, int ysize, int pixelsize)
@@ -370,7 +540,7 @@
     /* fill with mode "L" matte */
 
     int x, y, i;
-    unsigned int tmp1, tmp2;
+    unsigned int tmp1;
 
     if (imOut->image8) {
 
@@ -378,7 +548,7 @@
             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_FASTER(*mask, *out, ink[0], tmp1);
                 out++, mask++;
             }
         }
@@ -390,7 +560,7 @@
             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_FASTER(*mask, *out, ink[i], tmp1);
                     out++;
                 }
                 mask++;
@@ -407,7 +577,7 @@
     /* fill with mode "RGBA" matte */
 
     int x, y, i;
-    unsigned int tmp1, tmp2;
+    unsigned int tmp1;
 
     if (imOut->image8) {
 
@@ -416,7 +586,7 @@
             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_FASTER(*mask, *out, ink[0], tmp1);
                 out++, mask += 4;
             }
         }
@@ -430,7 +600,7 @@
             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_FASTER(*mask, *out, ink[i], tmp1);
                     out++;
                 }
                 mask += 4;
diff -uNr '--exclude=*.pyc' '--exclude=*.egg' Pillow-2.0.0-orig//PIL/Image.py Pillow-2.0.0-mine-final//PIL/Image.py
--- Pillow-2.0.0-orig//PIL/Image.py 2013-03-15 08:55:00.000000000 +0100
+++ Pillow-2.0.0-mine-final//PIL/Image.py 2013-04-21 10:52:24.000000000 +0200
@@ -1127,6 +1127,100 @@
             self.im.paste(im, box)
 
     ##
+    # Pastes another image into this image. Similar to paste method,
+    # but this one handles the alpha blending more properly.
+    # The box argument is either a 2-tuple giving the upper left 
+    # corner, a 4-tuple defining the left, upper, right, and lower
+    # pixel coordinate, or None (same as (0, 0)).  If a 4-tuple is
+    # given, the size of the pasted image must match the size of 
+    # the region.
+    # 
+ # If the modes don't match, the pasted image is converted to the + # mode of this image (see the {@link #Image.convert} method for + # details). + #
 
+ # Instead of an image, the source can be a integer or tuple + # containing pixel values. The method then fills the region + # with the given colour. When creating RGB images, you can + # also use colour strings as supported by the ImageColor module. + #
 
+ # If a mask is given, this method updates only the regions + # indicated by the mask. You can use either "1", "L", "LA" or + # "RGBA" images (in the latter case, the alpha band is used as + # mask). Where the mask is 255, the given image is copied as is. + # Where the mask is 0, the current value is preserved. + # Intermediate values can be used for transparency effects. + #
 
+ # Note that if you paste an "RGBA" image, the alpha band is + # ignored. You can work around this by using the same image as + # both source image and mask. + # + # @param im Source image or pixel value (integer or tuple). + # @param box An optional 4-tuple giving the region to paste into. + # If a 2-tuple is used instead, it's treated as the upper left + # corner. If omitted or None, the source is pasted into the + # upper left corner. + #
 
+ # If an image is given as the second argument and there is no + # third, the box defaults to (0, 0), and the second argument + # is interpreted as a mask image. + # @param lock_bg_alpha An optional boolean argument for locking image. + # Default value is False, but when it's True, it locks alpha + # of this image and the alpha will stay the same. + # @param mask_invert Boolean argument for inverting overlaying alpha. + # @param mask An optional mask image. + # @return An Image object. + + def paste_alt(self, im, box=None, lock_bg_alpha=None, mask_invert=None, mask=None): + "Paste other image into region" + + if lock_bg_alpha is None: + lock_bg_alpha=False + + if isImageType(box) and mask is None: + # abbreviated paste(im, mask) syntax + mask = box; box = None + + if box is None: + # cover all of self + box = (0, 0) + self.size + + if len(box) == 2: + # lower left corner given; get size from image or mask + if isImageType(im): + size = im.size + elif isImageType(mask): + size = mask.size + else: + # FIXME: use self.size here? + raise ValueError( + "cannot determine region size; use 4-item box" + ) + box = box + (box[0]+size[0], box[1]+size[1]) + + if isStringType(im): + from PIL import ImageColor + im = ImageColor.getcolor(im, self.mode) + + elif isImageType(im): + im.load() + if self.mode != im.mode: + if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"): + # should use an adapter for this! + im = im.convert(self.mode) + im = im.im + + self.load() + if self.readonly: + self._copy() + + if mask: + mask.load() + self.im.paste_alt(im, box,lock_bg_alpha,mask_invert, mask.im) + else: + self.im.paste_alt(im, box,lock_bg_alpha,mask_invert) + + ## # Maps this image through a lookup table or function. # # @param lut A lookup table, containing 256 values per band in the