Skip to content

Commit

Permalink
Implement Generic Operations and get_pixel_count operation
Browse files Browse the repository at this point in the history
  • Loading branch information
kaedroho committed Apr 12, 2019
1 parent 1eef042 commit a3bc873
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Operation Pillow Wand Op
=================================== ==================== ==================== ====================
``get_size()`` ✓ ✓ ✓
``get_frame_count()`` ✓** ✓ ✓**
``get_pixel_count()`` ✓ ✓ ✓
``resize(size)`` ✓ ✓
``crop(rect)`` ✓ ✓
``rotate(angle)`` ✓ ✓
Expand Down
10 changes: 10 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ Here's a full list of operations provided by Willow out of the box:
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
4 changes: 4 additions & 0 deletions tests/test_opencv.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ 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
4 changes: 4 additions & 0 deletions tests/test_pillow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ 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
6 changes: 6 additions & 0 deletions tests/test_wand.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ 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 @@ -162,7 +166,9 @@ 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:
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
30 changes: 30 additions & 0 deletions willow/registry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from collections import defaultdict


class OperationNameConflict(Exception):
"""
Raised when an operation is registered that clashes with an existing generic operation's name
"""
pass


class UnrecognisedOperationError(LookupError):
"""
Raised when the operation isn't in any of the known image classes.
Expand All @@ -26,15 +33,29 @@ class UnroutableOperationError(LookupError):

class WillowRegistry(object):
def __init__(self):
self._used_operation_names = set()
self._registered_image_classes = set()
self._unavailable_image_classes = dict()
self._registered_operations = defaultdict(dict)
self._registered_generic_operations = dict()
self._registered_converters = dict()
self._registered_converter_costs = dict()

def register_operation(self, image_class, operation_name, func):
if operation_name in self._registered_generic_operations:
return OperationNameConflict("A generic operation already exists with the name '{0}'".format(operation_name))

self._registered_operations[image_class][operation_name] = func

def register_generic_operation(self, dependencies, operation_name, func):
if self.operation_exists(operation_name):
return OperationNameConflict("An operation already exists with the name '{0}'".format(operation_name))

self._registered_generic_operations[operation_name] = {
'dependencies': dependencies,
'func': func,
}

def register_converter(self, from_image_class, to_image_class, func, cost=None):
self._registered_converters[from_image_class, to_image_class] = func

Expand Down Expand Up @@ -76,6 +97,15 @@ def register_plugin(self, plugin):
self.register_converter(converter[0], converter[1], converter[2])

def get_operation(self, image_class, operation_name):
if operation_name in self._registered_generic_operations:
generic_operation = self._registered_generic_operations[operation_name]

for dependency in generic_operation['dependencies']:
if dependency not in self._registered_operations[image_class]:
raise LookupError("Generic operation dependencies not met")

return generic_operation['func']

return self._registered_operations[image_class][operation_name]

def operation_exists(self, operation_name):
Expand Down

0 comments on commit a3bc873

Please sign in to comment.