Repository: python-pillow/Pillow

Issue:
ImageMorph.MorphOp() crashes Python when applied to a non-grayscale image
python 3.4.3 from macports
pillow34 from macports
pycharm 4.5.2
osx 10.10 (yosemite)

``` python
from PIL import ImageMorph
from PIL import Image
from PIL import ImageOps

a_path = "......."
a = Image.open(a_path)

#need to do this because pillow is lazy sometimes
a.load() 

#if you don't do this, ImageMorph will crash horribly
#a = ImageOps.grayscale(a) 

erode = ImageMorph.MorphOp(op_name="erosion8")

# we never get here because python is dead
(n_pix_diff, new_a) = erode.apply(a)
```



Discussion:
Reproducible with hopper.png and:
Python 2.7.9
Pillow 2.9.0
OS X 10.10 (Yosemite)

``` python
Python 2.7.9 (default, Dec 19 2014, 06:05:48) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import ImageMorph
>>> from PIL import Image
>>> from PIL import ImageOps
>>> a_path = "hopper.png"
>>> a = Image.open(a_path)
>>> a.load() 
<PixelAccess object at 0x10bb11190>
>>> erode = ImageMorph.MorphOp(op_name="erosion8")
>>> (n_pix_diff, new_a) = erode.apply(a)
Segmentation fault: 11
```

Here's part of the problem report:

```
Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000000

VM Regions Near 0:
--> 
    __TEXT                 000000010b916000-000000010b918000 [    8K] r-x/rwx SM=COW  /usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   _imagingmorph.so                0x000000010bed3924 apply + 292
1   org.python.python               0x000000010b9a27e6 PyEval_EvalFrameEx + 14392
2   org.python.python               0x000000010b9a562e fast_function + 262
3   org.python.python               0x000000010b9a2400 PyEval_EvalFrameEx + 13394
4   org.python.python               0x000000010b99ed7a PyEval_EvalCodeEx + 1409
5   org.python.python               0x000000010b99e7f3 PyEval_EvalCode + 54
6   org.python.python               0x000000010b9be8a2 run_mod + 53
7   org.python.python               0x000000010b9be6be PyRun_InteractiveOneFlags + 353
8   org.python.python               0x000000010b9be1cd PyRun_InteractiveLoopFlags + 192
9   org.python.python               0x000000010b9be077 PyRun_AnyFileExFlags + 60
10  org.python.python               0x000000010b9cfc5b Py_Main + 3051
11  libdyld.dylib                   0x00007fff9ca9e5c9 start + 1
```

The [original PR](https://github.com/python-pillow/Pillow/pull/691) for this feature says:

> This pull request contains an initial implementation of a binary mathematical morphology (see: http://en.wikipedia.org/wiki/Mathematical_morphology) addon for Pillow. It is used to apply morphological operators on PIL images of mode 'L' where each pixel is considered binary. 

And the [tests](https://github.com/python-pillow/Pillow/blob/master/Tests/test_imagemorph.py) only use binary images.

It would make sense for Pillow to give an error message in this case instead of crashing.


Resolving patch:
diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py
--- a/PIL/ImageMorph.py
+++ b/PIL/ImageMorph.py
@@ -198,6 +198,9 @@ def apply(self, image):
         if self.lut is None:
             raise Exception('No operator loaded')
 
+        if image.mode != 'L':
+            raise Exception('Image must be binary, meaning it must use mode L')
+            return
         outimage = Image.new(image.mode, image.size, None)
         count = _imagingmorph.apply(
             bytes(self.lut), image.im.id, outimage.im.id)
@@ -212,6 +215,9 @@ def match(self, image):
         if self.lut is None:
             raise Exception('No operator loaded')
 
+        if image.mode != 'L':
+            raise Exception('Image must be binary, meaning it must use mode L')
+            return
         return _imagingmorph.match(bytes(self.lut), image.im.id)
 
     def get_on_pixels(self, image):
@@ -220,6 +226,9 @@ def get_on_pixels(self, image):
         Returns a list of tuples of (x,y) coordinates
         of all matching pixels."""
 
+        if image.mode != 'L':
+            raise Exception('Image must be binary, meaning it must use mode L')
+            return
         return _imagingmorph.get_on_pixels(image.im.id)
 
     def load_lut(self, filename):


Test patch:
diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py
--- a/Tests/test_imagemorph.py
+++ b/Tests/test_imagemorph.py
@@ -1,5 +1,5 @@
 # Test the ImageMorphology functionality
-from helper import unittest, PillowTestCase
+from helper import unittest, PillowTestCase, hopper
 
 from PIL import Image
 from PIL import ImageMorph
@@ -168,6 +168,14 @@ def test_corner(self):
         self.assertEqual(len(coords), 4)
         self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4)))
 
+    def test_non_binary_images(self):
+        im = hopper('RGB')
+        mop = ImageMorph.MorphOp(op_name="erosion8")
+
+        self.assertRaises(Exception, lambda: mop.apply(im))
+        self.assertRaises(Exception, lambda: mop.match(im))
+        self.assertRaises(Exception, lambda: mop.get_on_pixels(im))
+
 
 if __name__ == '__main__':
     unittest.main()