Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pillow animated gifs #76

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Available operations
Operation Pillow Wand OpenCV
=================================== ==================== ==================== ====================
``get_size()`` ✓ ✓ ✓
``get_frame_count()`` ✓ ✓ ✓**
``get_pixel_count()`` ✓ ✓ ✓
``resize(size)`` ✓ ✓
``crop(rect)`` ✓ ✓
``rotate(angle)`` ✓ ✓
Expand All @@ -82,11 +84,12 @@ Operation Pillow Wand Op
``save_as_png(file)`` ✓ ✓
``save_as_gif(file)`` ✓ ✓
``has_alpha()`` ✓ ✓ ✓*
``has_animation()`` ✓* ✓ ✓*
``has_animation()`` ✓ ✓ ✓*
``get_pillow_image()`` ✓
``get_wand_image()`` ✓
``detect_features()`` ✓
``detect_faces(cascade_filename)`` ✓
=================================== ==================== ==================== ====================

\* Always returns ``False``
\** Always returns ``1``
6 changes: 6 additions & 0 deletions docs/guide/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ height as a tuple of two integers:
# For example, 'i' is a 200x200 pixel image
i.get_size() == (200, 200)

For animated GIFs, you can get the number of frames by calling the :meth:`Image.get_frame_count` method:

.. code-block:: python

i.get_frame_count() == 34

Resizing images
---------------

Expand Down
3 changes: 0 additions & 3 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,3 @@ or Wand.

- `Pillow installation <http://pillow.readthedocs.org/en/3.0.x/installation.html#basic-installation>`_
- `Wand installation <http://docs.wand-py.org/en/0.4.2/guide/install.html>`_

Note that Pillow doesn't support animated GIFs and Wand isn't as fast.
Installing both will give best results.
18 changes: 18 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ Here's a full list of operations provided by Willow out of the box:

width, height = image.get_size()

.. method:: get_frame_count()

Returns the number of frames in an animated image:

.. code-block:: python

number_of_frames = image.get_frame_count()

.. method:: get_pixel_count()

Returns the number of pixels in an image. This is just the width, height and number
of frames multiplied together and can be used to work out the amount of memory an
image will used when decompressed:

.. code-block:: python

number_of_pixels = image.get_pixel_count()

.. method:: has_alpha

Returns ``True`` if the image has an alpha channel.
Expand Down
8 changes: 8 additions & 0 deletions tests/test_opencv.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ def test_get_size(self):
self.assertEqual(width, 600)
self.assertEqual(height, 400)

def test_get_frame_count(self):
frames = self.image.get_frame_count()
self.assertEqual(frames, 1)

def test_get_pixel_count(self):
pixels = self.image.get_pixel_count()
self.assertEqual(pixels, 200 * 150)

def test_has_alpha(self):
has_alpha = self.image.has_alpha()
self.assertFalse(has_alpha)
Expand Down
33 changes: 26 additions & 7 deletions tests/test_pillow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from PIL import Image as PILImage

from willow.image import JPEGImageFile, PNGImageFile, GIFImageFile, WebPImageFile
from willow.plugins.pillow import _PIL_Image, PillowImage, UnsupportedRotation
from willow.plugins.pillow import _PIL_Image, PillowImage, PillowAnimatedImage, UnsupportedRotation


no_webp_support = not PillowImage.is_format_supported("WEBP")
Expand All @@ -21,6 +21,14 @@ def test_get_size(self):
self.assertEqual(width, 200)
self.assertEqual(height, 150)

def test_get_frame_count(self):
frames = self.image.get_frame_count()
self.assertEqual(frames, 1)

def test_get_pixel_count(self):
pixels = self.image.get_pixel_count()
self.assertEqual(pixels, 200 * 150)

def test_resize(self):
resized_image = self.image.resize((100, 75))
self.assertEqual(resized_image.get_size(), (100, 75))
Expand Down Expand Up @@ -128,6 +136,19 @@ def test_save_as_gif_converts_back_to_supported_mode(self):
image = _PIL_Image().open(output)
self.assertEqual(image.mode, 'P')

def test_save_as_gif_animated(self):
with open('tests/images/newtons_cradle.gif', 'rb') as f:
image = PillowAnimatedImage.open(GIFImageFile(f))

output = io.BytesIO()
return_value = image.save_as_gif(output)
output.seek(0)

loaded_image = PillowAnimatedImage.open(GIFImageFile(output))

self.assertTrue(loaded_image.has_animation())
self.assertEqual(loaded_image.get_frame_count(), 34)

def test_has_alpha(self):
has_alpha = self.image.has_alpha()
self.assertTrue(has_alpha)
Expand Down Expand Up @@ -176,22 +197,20 @@ def test_save_transparent_gif(self):
# Check that the alpha of pixel 1,1 is 0
self.assertEqual(image.image.convert('RGBA').getpixel((1, 1))[3], 0)

@unittest.expectedFailure # Pillow doesn't support animation
def test_animated_gif(self):
with open('tests/images/newtons_cradle.gif', 'rb') as f:
image = PillowImage.open(GIFImageFile(f))
image = PillowAnimatedImage.open(GIFImageFile(f))

self.assertFalse(image.has_alpha())
self.assertTrue(image.has_alpha())
self.assertTrue(image.has_animation())

@unittest.expectedFailure # Pillow doesn't support animation
def test_resize_animated_gif(self):
with open('tests/images/newtons_cradle.gif', 'rb') as f:
image = PillowImage.open(GIFImageFile(f))
image = PillowAnimatedImage.open(GIFImageFile(f))

resized_image = image.resize((100, 75))

self.assertFalse(resized_image.has_alpha())
self.assertTrue(resized_image.has_alpha())
self.assertTrue(resized_image.has_animation())

def test_get_pillow_image(self):
Expand Down
25 changes: 25 additions & 0 deletions tests/test_wand.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ def test_get_size(self):
self.assertEqual(width, 200)
self.assertEqual(height, 150)

def test_get_frame_count(self):
frames = self.image.get_frame_count()
self.assertEqual(frames, 1)

def test_get_pixel_count(self):
pixels = self.image.get_pixel_count()
self.assertEqual(pixels, 200 * 150)

def test_resize(self):
resized_image = self.image.resize((100, 75))
self.assertEqual(resized_image.get_size(), (100, 75))
Expand Down Expand Up @@ -122,6 +130,19 @@ def test_save_as_gif(self):
self.assertIsInstance(return_value, GIFImageFile)
self.assertEqual(return_value.f, output)

def test_save_as_gif_animated(self):
with open('tests/images/newtons_cradle.gif', 'rb') as f:
image = WandImage.open(GIFImageFile(f))

output = io.BytesIO()
return_value = image.save_as_gif(output)
output.seek(0)

loaded_image = WandImage.open(GIFImageFile(output))

self.assertTrue(loaded_image.has_animation())
self.assertEqual(loaded_image.get_frame_count(), 34)

def test_has_alpha(self):
has_alpha = self.image.has_alpha()
self.assertTrue(has_alpha)
Expand Down Expand Up @@ -158,6 +179,10 @@ def test_animated_gif(self):

self.assertTrue(image.has_animation())

self.assertEqual(image.get_size(), (480, 360))
self.assertEqual(image.get_frame_count(), 34)
self.assertEqual(image.get_pixel_count(), 480 * 360 * 34)

def test_resize_animated_gif(self):
with open('tests/images/newtons_cradle.gif', 'rb') as f:
image = WandImage.open(GIFImageFile(f))
Expand Down
3 changes: 3 additions & 0 deletions willow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def setup():
WebPImageFile,
)
from willow.plugins import pillow, wand, opencv
from willow.generic_operations import get_pixel_count

registry.register_image_class(JPEGImageFile)
registry.register_image_class(PNGImageFile)
Expand All @@ -28,6 +29,8 @@ def setup():
registry.register_plugin(wand)
registry.register_plugin(opencv)

registry.register_generic_operation(['get_size', 'get_frame_count'], 'get_pixel_count', get_pixel_count)

setup()


Expand Down
8 changes: 8 additions & 0 deletions willow/generic_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Generic operations are automatically registered on all image models that implement their dependencies

# Dependencies: get_size, get_frame_count
def get_pixel_count(image):
width, height = image.get_size()
frames = image.get_frame_count()

return width * height * frames
5 changes: 5 additions & 0 deletions willow/plugins/opencv.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def check(cls):
def get_size(self):
return self.size

@Image.operation
def get_frame_count(self):
# Animation is not supported by OpenCV
return 1

@Image.operation
def has_alpha(self):
# Alpha is not supported by OpenCV
Expand Down
Loading