Repository: python-pillow/Pillow

Issue:
ImageChops: logical_and produces "Image has wrong mode" with 2 RGB images
### What did you do?
I tried to apply logical_and between two RGB images

### What did you expect to happen?
AND function pixel-by-pixel, color-by-color

### What actually happened?
"Image has wrong mode" message. According to the docs, 
"At this time, most channel operations are only implemented for 8-bit images (e.g. “L” and “RGB”)."

### What versions of Pillow and Python are you using?
Python 3.6.1, PIL 1.1.7

```python
from PIL import Image, ImageChops

def test_chops():
    img = Image.new("RGB", (100, 100), 0xffffff)
    new_img = ImageChops.logical_and(img, img)


def main():
    test_chops()
    return 0

main()
```



Discussion:
Full traceback with Pillow 4.1.1 and Python 2.7:
```
Traceback (most recent call last):
  File "1.py", line 12, in <module>
    main()
  File "1.py", line 9, in main
    test_chops()
  File "1.py", line 5, in test_chops
    new_img = ImageChops.logical_and(img, img)
  File "/usr/local/lib/python2.7/site-packages/PIL/ImageChops.py", line 216, in logical_and
    return image1._new(image1.im.chop_and(image2.im))
ValueError: image has wrong mode
```
The error is coming from [here](https://github.com/python-pillow/Pillow/blob/master/libImaging/Chops.c#L65-L67):
```c
    if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
        (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1"))))
	return (Imaging) ImagingError_ModeError();
```
So the mode must be "1". This code runs:
```python
from PIL import Image, ImageChops

def test_chops():
    img = Image.new("1", (100, 100), 0xffffff)
    new_img = ImageChops.logical_and(img, img)


def main():
    test_chops()
    return 0

main()
```
Thanks for looking into this! 

I didn't have the time to test it, but I suspected that might have been the problem. I guess, at first sight, mode '1' might seem the only mode appropiate for binary operations, but it could be useful for masking colors in RGB(A) images too. Another reason to enable L or RGB would be to avoid having to convert the image mode for some operations, while needing RGB for other.

Anyway, as included in the original post, the ImageChops docs seem to imply that the operations  are mainly meant for L or RGB mode images. so I'd like to suggest either updating the docs, or the functions. If there's interest (except from me) in including the ops for RGB/L I might try to implement them.
PRs welcome for both! 

Docs can be found here: https://github.com/python-pillow/Pillow/tree/master/docs
I don't think that 'and' works for multi bit images. The relevant operation there is multiply.  (Unless we're looking at bitwise and, but that's not an operation that I think is a standard channel op.)

Documentation is probably the proper approach here. 
similar with `ImageChops.difference` when using modes `I` or `F`. I guess these are supposed to support higher bit rate and floats.
>I guess, at first sight, mode '1' might seem the only mode appropiate for binary operations, but it could be useful for masking colors in RGB(A) images too.

Indeed I found this issue when I tried to generate a mask for an 'L' source Image by just using logical AND with an image that was all 255 (I would expect the result to be 0 where the source is 0 and 255 else since thats what <code> x and 255</code> would evaluate to.)

Also I tried to combine two 'L' images with an logical or, and expected the result to be the first image value when it is not 0, second image value unless that is 0 as well, 0 if both pixels are 0.

_The 'L' images are in fact supposed to be 'P' Images with 0 as alpha so that operation would make sense for me._

I would not have tried that, had the documentation indicated that this attempt is futile. 

Resolving patch:
diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py
--- a/src/PIL/ImageChops.py
+++ b/src/PIL/ImageChops.py
@@ -54,7 +54,7 @@ def invert(image):
 def lighter(image1, image2):
     """
     Compares the two images, pixel by pixel, and returns a new image containing
-    the lighter values.
+    the lighter values. At least one of the images must have mode "1".
 
     .. code-block:: python
 
@@ -70,8 +70,8 @@ def lighter(image1, image2):
 
 def darker(image1, image2):
     """
-    Compares the two images, pixel by pixel, and returns a new image
-    containing the darker values.
+    Compares the two images, pixel by pixel, and returns a new image containing
+    the darker values. At least one of the images must have mode "1".
 
     .. code-block:: python
 
@@ -88,7 +88,7 @@ def darker(image1, image2):
 def difference(image1, image2):
     """
     Returns the absolute value of the pixel-by-pixel difference between the two
-    images.
+    images. At least one of the images must have mode "1".
 
     .. code-block:: python
 
@@ -107,7 +107,8 @@ def multiply(image1, image2):
     Superimposes two images on top of each other.
 
     If you multiply an image with a solid black image, the result is black. If
-    you multiply with a solid white image, the image is unaffected.
+    you multiply with a solid white image, the image is unaffected. At least
+    one of the images must have mode "1".
 
     .. code-block:: python
 
@@ -123,7 +124,8 @@ def multiply(image1, image2):
 
 def screen(image1, image2):
     """
-    Superimposes two inverted images on top of each other.
+    Superimposes two inverted images on top of each other. At least one of the
+    images must have mode "1".
 
     .. code-block:: python
 
@@ -141,6 +143,7 @@ def add(image1, image2, scale=1.0, offset=0):
     """
     Adds two images, dividing the result by scale and adding the
     offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+    At least one of the images must have mode "1".
 
     .. code-block:: python
 
@@ -156,8 +159,9 @@ def add(image1, image2, scale=1.0, offset=0):
 
 def subtract(image1, image2, scale=1.0, offset=0):
     """
-    Subtracts two images, dividing the result by scale and adding the
-    offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+    Subtracts two images, dividing the result by scale and adding the offset.
+    If omitted, scale defaults to 1.0, and offset to 0.0. At least one of the
+    images must have mode "1".
 
     .. code-block:: python
 
@@ -172,7 +176,8 @@ def subtract(image1, image2, scale=1.0, offset=0):
 
 
 def add_modulo(image1, image2):
-    """Add two images, without clipping the result.
+    """Add two images, without clipping the result. At least one of the images
+    must have mode "1".
 
     .. code-block:: python
 
@@ -187,7 +192,8 @@ def add_modulo(image1, image2):
 
 
 def subtract_modulo(image1, image2):
-    """Subtract two images, without clipping the result.
+    """Subtract two images, without clipping the result. At least one of the
+    images must have mode "1".
 
     .. code-block:: python
 
@@ -202,7 +208,8 @@ def subtract_modulo(image1, image2):
 
 
 def logical_and(image1, image2):
-    """Logical AND between two images.
+    """Logical AND between two images. At least one of the images must have
+    mode "1".
 
     .. code-block:: python
 
@@ -217,7 +224,8 @@ def logical_and(image1, image2):
 
 
 def logical_or(image1, image2):
-    """Logical OR between two images.
+    """Logical OR between two images. At least one of the images must have
+    mode "1".
 
     .. code-block:: python
 
@@ -232,7 +240,8 @@ def logical_or(image1, image2):
 
 
 def logical_xor(image1, image2):
-    """Logical XOR between two images.
+    """Logical XOR between two images. At least one of the images must have
+    mode "1".
 
     .. code-block:: python
 


Test patch: