summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2014-08-12 10:35:33 +0200
committerArmin Ronacher <armin.ronacher@active-4.com>2014-08-12 10:35:33 +0200
commit1cb9fe1e76fdf8db97ced1c6a8e5e3bea1ffa5f0 (patch)
tree426423f0fc5268093ebb84c85780fbed854c4674 /examples
parent04127558d55ea74b37a766c744fc29dcd409dd41 (diff)
downloadclick-1cb9fe1e76fdf8db97ced1c6a8e5e3bea1ffa5f0.tar.gz
Added imagepipe example.
Diffstat (limited to 'examples')
-rw-r--r--examples/imagepipe/README13
-rw-r--r--examples/imagepipe/example01.jpgbin0 -> 51677 bytes
-rw-r--r--examples/imagepipe/example02.jpgbin0 -> 39106 bytes
-rw-r--r--examples/imagepipe/imagepipe.py266
-rw-r--r--examples/imagepipe/setup.py16
5 files changed, 295 insertions, 0 deletions
diff --git a/examples/imagepipe/README b/examples/imagepipe/README
new file mode 100644
index 0000000..91ec0cd
--- /dev/null
+++ b/examples/imagepipe/README
@@ -0,0 +1,13 @@
+$ imagepipe_
+
+ imagepipe is an example application that implements some
+ multi commands that chain image processing instructions
+ together.
+
+ This requires pillow.
+
+Usage:
+
+ $ pip install --editable .
+ $ imagepipe open -i example01.jpg resize -w 128 display
+ $ imagepipe open -i example02.jpg blur save
diff --git a/examples/imagepipe/example01.jpg b/examples/imagepipe/example01.jpg
new file mode 100644
index 0000000..f2d9397
--- /dev/null
+++ b/examples/imagepipe/example01.jpg
Binary files differ
diff --git a/examples/imagepipe/example02.jpg b/examples/imagepipe/example02.jpg
new file mode 100644
index 0000000..b1f802e
--- /dev/null
+++ b/examples/imagepipe/example02.jpg
Binary files differ
diff --git a/examples/imagepipe/imagepipe.py b/examples/imagepipe/imagepipe.py
new file mode 100644
index 0000000..37a1521
--- /dev/null
+++ b/examples/imagepipe/imagepipe.py
@@ -0,0 +1,266 @@
+import click
+from functools import update_wrapper
+from PIL import Image, ImageFilter, ImageEnhance
+
+
+@click.group(chain=True)
+def cli():
+ """This script processes a bunch of images through pillow in a unix
+ pipe. One commands feeds into the next.
+
+ Example:
+
+ \b
+ imagepipe open -i example01.jpg resize -w 128 display
+ imagepipe open -i example02.jpg blur save
+ """
+
+
+@cli.resultcallback()
+def process_commands(processors):
+ """This result callback is invoked with an iterable of all the chained
+ subcommands. As in this example each subcommand returns a function
+ we can chain them together to feed one into the other, similar to how
+ a pipe on unix works.
+ """
+ # Start with an empty iterable.
+ stream = ()
+
+ # Pipe it through all stream processors.
+ for processor in processors:
+ stream = processor(stream)
+
+ # Evaluate the stream and throw away the items.
+ for _ in stream:
+ pass
+
+
+def processor(f):
+ """Helper decorator to rewrite a function so that it returns another
+ function from it.
+ """
+ def new_func(*args, **kwargs):
+ def processor(stream):
+ return f(stream, *args, **kwargs)
+ return processor
+ return update_wrapper(new_func, f)
+
+
+def generator(f):
+ """Similar to the :func:`processor` but passes through old values
+ unchanged and does not pass through the values as parameter.
+ """
+ @processor
+ def new_func(stream, *args, **kwargs):
+ for item in stream:
+ yield item
+ for item in f(*args, **kwargs):
+ yield item
+ return update_wrapper(new_func, f)
+
+
+def copy_filename(new, old):
+ new.filename = old.filename
+ return new
+
+
+@cli.command('open')
+@click.option('-i', '--image', 'images', type=click.Path(),
+ multiple=True, help='The image file to open.')
+@generator
+def open_cmd(images):
+ """Loads one or multiple images for processing. The input parameter
+ can be specified multiple times to load more than one image.
+ """
+ for image in images:
+ try:
+ click.echo('Opening "%s"' % image)
+ if image == '-':
+ img = Image.open(click.get_binary_stdin())
+ img.filename = '-'
+ else:
+ img = Image.open(image)
+ yield img
+ except Exception as e:
+ click.echo('Could not open image "%s": %s' % (image, e), err=True)
+
+
+@cli.command('save')
+@click.option('--filename', default='processed-%04d.png', type=click.Path(),
+ help='The format for the filename.',
+ show_default=True)
+@processor
+def save_cmd(images, filename):
+ """Saves all processed images to a series of files."""
+ for idx, image in enumerate(images):
+ try:
+ fn = filename % (idx + 1)
+ click.echo('Saving "%s" as "%s"' % (image.filename, fn))
+ yield image.save(fn)
+ except Exception as e:
+ click.echo('Could not save image "%s": %s' %
+ (image.filename, e), err=True)
+
+
+@cli.command('display')
+@processor
+def display_cmd(images):
+ """Opens all images in an image viewer."""
+ for image in images:
+ click.echo('Displaying "%s"' % image.filename)
+ image.show()
+ yield image
+
+
+@cli.command('resize')
+@click.option('-w', '--width', type=int, help='The new width of the image.')
+@click.option('-h', '--height', type=int, help='The new height of the image.')
+@processor
+def resize_cmd(images, width, height):
+ """Resizes an image by fitting it into the box without changing
+ the aspect ratio.
+ """
+ for image in images:
+ w, h = (width or image.size[0], height or image.size[1])
+ click.echo('Resizing "%s" to %dx%d' % (image.filename, w, h))
+ image.thumbnail((w, h))
+ yield image
+
+
+@cli.command('crop')
+@click.option('-b', '--border', type=int, help='Crop the image from all '
+ 'sides by this amount.')
+@processor
+def crop_cmd(images, border):
+ """Crops an image from all edges."""
+ for image in images:
+ box = [0, 0, image.size[0], image.size[1]]
+
+ if border is not None:
+ for idx, val in enumerate(box):
+ box[idx] = max(0, val - border)
+ click.echo('Cropping "%s" by %dpx' % (image.filename, border))
+ yield copy_filename(image.crop(box), image)
+ else:
+ yield image
+
+
+def convert_rotation(ctx, param, value):
+ if value is None:
+ return
+ value = value.lower()
+ if value in ('90', 'r', 'right'):
+ return (Image.ROTATE_90, 90)
+ if value in ('180', '-180'):
+ return (Image.ROTATE_180, 180)
+ if value in ('-90', '270', 'l', 'left'):
+ return (Image.ROTATE_270, 270)
+ raise click.BadParameter('invalid rotation "%s"' % value)
+
+
+def convert_flip(ctx, param, value):
+ if value is None:
+ return
+ value = value.lower()
+ if value in ('lr', 'leftright'):
+ return (Image.FLIP_LEFT_RIGHT, 'left to right')
+ if value in ('tb', 'topbottom', 'upsidedown', 'ud'):
+ return (Image.FLIP_LEFT_RIGHT, 'top to bottom')
+ raise click.BadParameter('invalid flip "%s"' % value)
+
+
+@cli.command('transpose')
+@click.option('-r', '--rotate', callback=convert_rotation,
+ help='Rotates the image (in degrees)')
+@click.option('-f', '--flip', callback=convert_flip,
+ help='Flips the image [LR / TB]')
+@processor
+def transpose_cmd(images, rotate, flip):
+ """Transposes an image by either rotating or flipping it."""
+ for image in images:
+ if rotate is not None:
+ mode, degrees = rotate
+ click.echo('Rotate "%s" by %ddeg' % (image.filename, degrees))
+ image = copy_filename(image.transpose(mode), image)
+ if flip is not None:
+ mode, direction = flip
+ click.echo('Flip "%s" %s' % (image.filename, direction))
+ image = copy_filename(image.transpose(mode), image)
+ yield image
+
+
+@cli.command('blur')
+@click.option('-r', '--radius', default=2, show_default=True,
+ help='The blur radius.')
+@processor
+def blur_cmd(images, radius):
+ """Applies gaussian blur."""
+ blur = ImageFilter.GaussianBlur(radius)
+ for image in images:
+ click.echo('Blurring "%s" by %dpx' % (image.filename, radius))
+ yield copy_filename(image.filter(blur), image)
+
+
+@cli.command('smoothen')
+@click.option('-i', '--iterations', default=1, show_default=True,
+ help='How many iterations of the smoothen filter to run.')
+@processor
+def smoothen_cmd(images, iterations):
+ """Applies a smoothening filter."""
+ for image in images:
+ click.echo('Smoothening "%s" %d time%s' %
+ (image.filename, iterations, iterations != 1 and 's' or '',))
+ for x in xrange(iterations):
+ image = copy_filename(image.filter(ImageFilter.BLUR), image)
+ yield image
+
+
+@cli.command('emboss')
+@processor
+def emboss_cmd(images):
+ """Embosses an image."""
+ for image in images:
+ click.echo('Embossing "%s"' % image.filename)
+ yield copy_filename(image.filter(ImageFilter.EMBOSS), image)
+
+
+@cli.command('sharpen')
+@click.option('-f', '--factor', default=2.0,
+ help='Sharpens the image.', show_default=True)
+@processor
+def sharpen_cmd(images, factor):
+ """Sharpens an image."""
+ for image in images:
+ click.echo('Sharpen "%s" by %f' % (image.filename, factor))
+ enhancer = ImageEnhance.Sharpness(image)
+ yield copy_filename(enhancer.enhance(max(1.0, factor)), image)
+
+
+@cli.command('paste')
+@click.option('-l', '--left', default=0, help='Offset from left.')
+@click.option('-r', '--right', default=0, help='Offset from right.')
+@processor
+def paste_cmd(images, left, right):
+ """Pastes the second image on the first image and leaves the rest
+ unchanged.
+ """
+ imageiter = iter(images)
+ image = next(imageiter, None)
+ to_paste = next(imageiter, None)
+
+ if to_paste is None:
+ if image is not None:
+ yield image
+ return
+
+ click.echo('Paste "%s" on "%s"' %
+ (to_paste.filename, image.filename))
+ mask = None
+ if to_paste.mode == 'RGBA' or 'transparency' in to_paste.info:
+ mask = to_paste
+ image.paste(to_paste, (left, right), mask)
+ image.filename += '+' + to_paste.filename
+ yield image
+
+ for image in imageiter:
+ yield image
diff --git a/examples/imagepipe/setup.py b/examples/imagepipe/setup.py
new file mode 100644
index 0000000..82d521c
--- /dev/null
+++ b/examples/imagepipe/setup.py
@@ -0,0 +1,16 @@
+from setuptools import setup
+
+setup(
+ name='click-example-imagepipe',
+ version='1.0',
+ py_modules=['imagepipe'],
+ include_package_data=True,
+ install_requires=[
+ 'Click',
+ 'pillow',
+ ],
+ entry_points='''
+ [console_scripts]
+ imagepipe=imagepipe:cli
+ ''',
+)