Repository: kivy/kivy

Issue:
`AttributeError: tobytes` on img_pil Image provider
### Versions

* Python: 2.7
* OS: Gentoo Linux, Android 7.0
* Kivy: 1.10.0
* Kivy installation method: `pip install Kivy==1.10.0`

### Description

In [kivy/core/image/img_pil.py](https://github.com/kivy/kivy/blob/1.10.0/kivy/core/image/img_pil.py), both `ImageLoaderPIL._img_read()` and `ImageLoaderPIL.save()` would fail with `AttributeError` if running PIL rather than Pillow.
I know PIL is deprecated in favor if Pillow, but we still have no recipe for Pillow (https://github.com/kivy/python-for-android/pull/786) and we try to import PIL first so it means we want to support both. And by the way I would do the other way around, i.e. import Pillow first and fallback on PIL.

### Code and Logs
Here is the `demo.py` code I'm using to reproduce:
```python
from kivy.app import App
from kivy.uix.image import Image


class DemoApp(App):
    def build(self):
        return Image(source="data/images/image-loading.gif")


if __name__ == "__main__":
    DemoApp().run()
```

### Debug session
#### First session
The first session is simply with `Kivy==1.10.0` installed and running the code works perfectly since the loading image shows.
```
[andre:/tmp/demo] [venv] $ python demo.py
[INFO   ] [Logger      ] Record log in /home/andre/.kivy/logs/kivy_17-10-29_16.txt
[INFO   ] [Kivy        ] v1.10.0
[INFO   ] [Python      ] v2.7.12 (default, Oct 12 2017, 10:10:51)
[GCC 5.4.0]
[INFO   ] [Factory     ] 194 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO   ] [OSC         ] using <multiprocessing> for socket
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <gl>
[INFO   ] [GL          ] OpenGL version <3.0 Mesa 17.0.6>
[INFO   ] [GL          ] OpenGL vendor <Intel Open Source Technology Center>
[INFO   ] [GL          ] OpenGL renderer <Mesa DRI Intel(R) HD Graphics 620 (Kabylake GT2) >
[INFO   ] [GL          ] OpenGL parsed version: 3, 0
[INFO   ] [GL          ] Shading version <1.30>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO   ] [Base        ] Start application main loop
[INFO   ] [Base        ] Leaving application in progress...
[andre:/tmp/demo] [venv] $
```

#### Second session
In the second session we install `PIL` from http://effbot.org/downloads/Imaging-1.1.7.tar.gz rather than `Pillow`.
Here the loading image doesn't show and the console shows the following error: `Error loading texture data/images/image-loading.gif`:
```
[andre:/tmp/demo] [venv] $ http://effbot.org/downloads/Imaging-1.1.7.tar.gz
[andre:/tmp/demo] [venv] $ python demo.py 
[INFO   ] [Logger      ] Record log in /home/andre/.kivy/logs/kivy_17-10-29_17.txt
[INFO   ] [Kivy        ] v1.10.0
[INFO   ] [Python      ] v2.7.12 (default, Oct 12 2017, 10:10:51)
[GCC 5.4.0]
[INFO   ] [Factory     ] 194 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [OSC         ] using <multiprocessing> for socket
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <gl>
[INFO   ] [GL          ] OpenGL version <3.0 Mesa 17.0.6>
[INFO   ] [GL          ] OpenGL vendor <Intel Open Source Technology Center>
[INFO   ] [GL          ] OpenGL renderer <Mesa DRI Intel(R) HD Graphics 620 (Kabylake GT2) >
[INFO   ] [GL          ] OpenGL parsed version: 3, 0
[INFO   ] [GL          ] Shading version <1.30>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[ERROR  ] [Image       ] Error loading texture data/images/image-loading.gif
[INFO   ] [Base        ] Start application main loop
[INFO   ] [Base        ] Leaving application in progress...
[andre:/tmp/demo] [venv] $
```

#### Third session
In the third session we disable the exception catching from [kivy/uix/image.py Image.texture_update()](https://github.com/kivy/kivy/blob/1.10.0/kivy/uix/image.py#L262) to see what really happens. By the way this exception catching looks too board to me and hides other things.
Then here is the session running again:
```
[andre:/tmp/demo] [venv] $ python demo.py
[INFO   ] [Logger      ] Record log in /home/andre/.kivy/logs/kivy_17-10-29_18.txt
[INFO   ] [Kivy        ] v1.10.0
[INFO   ] [Python      ] v2.7.12 (default, Oct 12 2017, 10:10:51)
[GCC 5.4.0]
[INFO   ] [Factory     ] 194 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [OSC         ] using <multiprocessing> for socket
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <gl>
[INFO   ] [GL          ] OpenGL version <3.0 Mesa 17.0.6>
[INFO   ] [GL          ] OpenGL vendor <Intel Open Source Technology Center>
[INFO   ] [GL          ] OpenGL renderer <Mesa DRI Intel(R) HD Graphics 620 (Kabylake GT2) >
[INFO   ] [GL          ] OpenGL parsed version: 3, 0
[INFO   ] [GL          ] Shading version <1.30>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
 Traceback (most recent call last):
   File "demo.py", line 11, in <module>
     DemoApp().run()
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/app.py", line 802, in run
     root = self.build()
   File "demo.py", line 7, in build
     return Image(source="data/images/image-loading.gif")
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/uix/image.py", line 240, in __init__
     update()
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/uix/image.py", line 261, in texture_update
     nocache=self.nocache)
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/__init__.py", line 538, in __init__
     self.filename = arg
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/__init__.py", line 734, in _set_filename
     mipmap=self._mipmap, nocache=self._nocache)
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/__init__.py", line 435, in load
     im = loader(filename, **kwargs)
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/__init__.py", line 201, in __init__
     self._data = self.load(filename)
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/img_pil.py", line 101, in load
     return list(self._img_read(im))
   File "/tmp/demo/venv/lib/python2.7/site-packages/kivy/core/image/img_pil.py", line 86, in _img_read
     img_tmp.mode.lower(), img_tmp.tobytes())
   File "/tmp/demo/venv/lib/python2.7/site-packages/PIL/Image.py", line 512, in __getattr__
     raise AttributeError(name)
 AttributeError: tobytes
[andre:/tmp/demo] [venv] $
```

The fix is pretty simple and I'll come in a pull request later (hopefully this week).


Discussion:
Another way to reproduce is to install pip and run `make test` from master.
```
$ pip install http://effbot.org/downloads/Imaging-1.1.7.tar.gz
$ make test
... [truncated] ...
======================================================================                                                                                                                                                                                            [14/1381]
ERROR: test_local_zipsequence (kivy.tests.test_uix_asyncimage.AsyncImageTestCase)                 
----------------------------------------------------------------------                                        
Traceback (most recent call last):                                                                            
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/tests/test_uix_asyncimage.py", line 85, in test_local_zipsequence
    image = self.load_zipimage(ZIP, ZIP_pngs)                                                    
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/tests/test_uix_asyncimage.py", line 33, in load_zipimage
    anim_delay=0.0333333333333333                                                               
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/uix/image.py", line 345, in __init__
    self._load_source()                                                                             
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/uix/image.py", line 361, in _load_source
    anim_delay=self.anim_delay)                                                                
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/loader.py", line 448, in image                                    
    client = ProxyImage(self.loading_image,                                                        
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/loader.py", line 168, in _get_loading_image
    self._loading_image = ImageLoader.load(filename=loading_png_fn)                                                      
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/__init__.py", line 435, in load
    im = loader(filename, **kwargs)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/__init__.py", line 201, in __init__
    self._data = self.load(filename)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/img_pil.py", line 101, in load
    return list(self._img_read(im))
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/img_pil.py", line 86, in _img_read
    img_tmp.mode.lower(), img_tmp.tobytes())
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/venv/lib/python2.7/site-packages/PIL/Image.py", line 512, in __getattr__
    raise AttributeError(name)
AttributeError: tobytes
-------------------- >> begin captured logging << --------------------
kivy: TRACE: [Lang        ] Found 2 rules for <kivy.uix.image.AsyncImage object at 0x7f5b4c72f9f0>
kivy: TRACE: [Lang        ] Found 2 rules for <kivy.uix.image.AsyncImage object at 0x7f5b4c72f9f0>
kivy: DEBUG: [Cache       ] register <kv.loader> with limit=500, timeout=60
kivy: DEBUG: [Cache       ] register <kv.loader> with limit=500, timeout=60
kivy: INFO: [Loader      ] using a thread pool of 2 workers
kivy: INFO: [Loader      ] using a thread pool of 2 workers
kivy: DEBUG: [ImagePIL    ] Load </home/andre/Progz/GitHub/AndreMiras/kivy/kivy/data/images/image-loading.gif>
kivy: DEBUG: [ImagePIL    ] Load </home/andre/Progz/GitHub/AndreMiras/kivy/kivy/data/images/image-loading.gif>
--------------------- >> end captured logging << ---------------------

======================================================================
ERROR: test_remote_zipsequence (kivy.tests.test_uix_asyncimage.AsyncImageTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/tests/test_uix_asyncimage.py", line 67, in test_remote_zipsequence
    image = self.load_zipimage(ZIP, ZIP_pngs)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/tests/test_uix_asyncimage.py", line 33, in load_zipimage
    anim_delay=0.0333333333333333
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/uix/image.py", line 345, in __init__
    self._load_source()
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/uix/image.py", line 361, in _load_source
    anim_delay=self.anim_delay)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/loader.py", line 448, in image
    client = ProxyImage(self.loading_image,
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/loader.py", line 168, in _get_loading_image
    self._loading_image = ImageLoader.load(filename=loading_png_fn)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/__init__.py", line 435, in load
    im = loader(filename, **kwargs)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/__init__.py", line 201, in __init__
    self._data = self.load(filename)
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/img_pil.py", line 101, in load
    return list(self._img_read(im))
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/kivy/core/image/img_pil.py", line 86, in _img_read
    img_tmp.mode.lower(), img_tmp.tobytes())
  File "/home/andre/Progz/GitHub/AndreMiras/kivy/venv/lib/python2.7/site-packages/PIL/Image.py", line 512, in __getattr__
    raise AttributeError(name)
AttributeError: tobytes
-------------------- >> begin captured logging << --------------------
kivy: TRACE: [Lang        ] Found 2 rules for <kivy.uix.image.AsyncImage object at 0x7f5b4c72f9f0>
kivy: TRACE: [Lang        ] Found 2 rules for <kivy.uix.image.AsyncImage object at 0x7f5b4c72f9f0>
kivy: DEBUG: [ImagePIL    ] Load </home/andre/Progz/GitHub/AndreMiras/kivy/kivy/data/images/image-loading.gif>
kivy: DEBUG: [ImagePIL    ] Load </home/andre/Progz/GitHub/AndreMiras/kivy/kivy/data/images/image-loading.gif>
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
Ran 329 tests in 32.660s

FAILED (errors=2)
make: *** [Makefile:88: test] Error 1
```
Without installing PIL I get all tests passing, same as when installing Pillow;

Resolving patch:
diff --git a/kivy/core/image/img_pil.py b/kivy/core/image/img_pil.py
--- a/kivy/core/image/img_pil.py
+++ b/kivy/core/image/img_pil.py
@@ -4,14 +4,21 @@
 
 __all__ = ('ImageLoaderPIL', )
 
-try:
-    from PIL import Image as PILImage
-except:
-    import Image as PILImage
-
+import Image as PILImage
 from kivy.logger import Logger
 from kivy.core.image import ImageLoaderBase, ImageData, ImageLoader
 
+try:
+    # Pillow
+    PILImage.frombytes
+    PILImage.Image.tobytes
+except AttributeError:
+    # PIL
+    # monkey patch frombytes and tobytes methods, refs:
+    # https://github.com/kivy/kivy/issues/5460
+    PILImage.frombytes = PILImage.frombuffer
+    PILImage.Image.tobytes = PILImage.Image.tostring
+
 
 class ImageLoaderPIL(ImageLoaderBase):
     '''Image loader based on the PIL library.


Test patch: