mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| """PILdriver, an image-processing calculator using PIL.
 | |
| 
 | |
| An instance of class PILDriver is essentially a software stack machine
 | |
| (Polish-notation interpreter) for sequencing PIL image
 | |
| transformations.  The state of the instance is the interpreter stack.
 | |
| 
 | |
| The only method one will normally invoke after initialization is the
 | |
| `execute' method.  This takes an argument list of tokens, pushes them
 | |
| onto the instance's stack, and then tries to clear the stack by
 | |
| successive evaluation of PILdriver operators.  Any part of the stack
 | |
| not cleaned off persists and is part of the evaluation context for
 | |
| the next call of the execute method.
 | |
| 
 | |
| PILDriver doesn't catch any exceptions, on the theory that these
 | |
| are actually diagnostic information that should be interpreted by
 | |
| the calling code.
 | |
| 
 | |
| When called as a script, the command-line arguments are passed to
 | |
| a PILDriver instance.  If there are no command-line arguments, the
 | |
| module runs an interactive interpreter, each line of which is split into
 | |
| space-separated tokens and passed to the execute method.
 | |
| 
 | |
| In the method descriptions below, a first line beginning with the string
 | |
| `usage:' means this method can be invoked with the token that follows
 | |
| it.  Following <>-enclosed arguments describe how the method interprets
 | |
| the entries on the stack.  Each argument specification begins with a
 | |
| type specification: either `int', `float', `string', or `image'.
 | |
| 
 | |
| All operations consume their arguments off the stack (use `dup' to
 | |
| keep copies around).  Use `verbose 1' to see the stack state displayed
 | |
| before each operation.
 | |
| 
 | |
| Usage examples:
 | |
| 
 | |
|     `show crop 0 0 200 300 open test.png' loads test.png, crops out a portion
 | |
| of its upper-left-hand corner and displays the cropped portion.
 | |
| 
 | |
|     `save rotated.png rotate 30 open test.tiff' loads test.tiff, rotates it
 | |
| 30 degrees, and saves the result as rotated.png (in PNG format).
 | |
| """
 | |
| # by Eric S. Raymond <esr@thyrsus.com>
 | |
| # $Id$
 | |
| 
 | |
| # TO DO:
 | |
| # 1. Add PILFont capabilities, once that's documented.
 | |
| # 2. Add PILDraw operations.
 | |
| # 3. Add support for composing and decomposing multiple-image files.
 | |
| #
 | |
| 
 | |
| from __future__ import print_function
 | |
| 
 | |
| from PIL import Image
 | |
| 
 | |
| class PILDriver:
 | |
| 
 | |
|     verbose = 0
 | |
| 
 | |
|     def do_verbose(self):
 | |
|         """usage: verbose <int:num>
 | |
| 
 | |
|         Set verbosity flag from top of stack.
 | |
|         """
 | |
|         self.verbose = int(self.do_pop())
 | |
| 
 | |
|     # The evaluation stack (internal only)
 | |
| 
 | |
|     stack = []          # Stack of pending operations
 | |
| 
 | |
|     def push(self, item):
 | |
|         "Push an argument onto the evaluation stack."
 | |
|         self.stack = [item] + self.stack
 | |
| 
 | |
|     def top(self):
 | |
|         "Return the top-of-stack element."
 | |
|         return self.stack[0]
 | |
| 
 | |
|     # Stack manipulation (callable)
 | |
| 
 | |
|     def do_clear(self):
 | |
|         """usage: clear
 | |
| 
 | |
|         Clear the stack.
 | |
|         """
 | |
|         self.stack = []
 | |
| 
 | |
|     def do_pop(self):
 | |
|         """usage: pop
 | |
| 
 | |
|         Discard the top element on the stack.
 | |
|         """
 | |
|         top = self.stack[0]
 | |
|         self.stack = self.stack[1:]
 | |
|         return top
 | |
| 
 | |
|     def do_dup(self):
 | |
|         """usage: dup
 | |
| 
 | |
|         Duplicate the top-of-stack item.
 | |
|         """
 | |
|         if hasattr(self, 'format'):     # If it's an image, do a real copy
 | |
|             dup = self.stack[0].copy()
 | |
|         else:
 | |
|             dup = self.stack[0]
 | |
|         self.stack = [dup] + self.stack
 | |
| 
 | |
|     def do_swap(self):
 | |
|         """usage: swap
 | |
| 
 | |
|         Swap the top-of-stack item with the next one down.
 | |
|         """
 | |
|         self.stack = [self.stack[1], self.stack[0]] + self.stack[2:]
 | |
| 
 | |
|     # Image module functions (callable)
 | |
| 
 | |
|     def do_new(self):
 | |
|         """usage: new <int:xsize> <int:ysize> <int:color>:
 | |
| 
 | |
|         Create and push a greyscale image of given size and color.
 | |
|         """
 | |
|         xsize = int(self.do_pop())
 | |
|         ysize = int(self.do_pop())
 | |
|         color = int(self.do_pop())
 | |
|         self.push(Image.new("L", (xsize, ysize), color))
 | |
| 
 | |
|     def do_open(self):
 | |
|         """usage: open <string:filename>
 | |
| 
 | |
|         Open the indicated image, read it, push the image on the stack.
 | |
|         """
 | |
|         self.push(Image.open(self.do_pop()))
 | |
| 
 | |
|     def do_blend(self):
 | |
|         """usage: blend <image:pic1> <image:pic2> <float:alpha>
 | |
| 
 | |
|         Replace two images and an alpha with the blended image.
 | |
|         """
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         alpha = float(self.do_pop())
 | |
|         self.push(Image.blend(image1, image2, alpha))
 | |
| 
 | |
|     def do_composite(self):
 | |
|         """usage: composite <image:pic1> <image:pic2> <image:mask>
 | |
| 
 | |
|         Replace two images and a mask with their composite.
 | |
|         """
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         mask = self.do_pop()
 | |
|         self.push(Image.composite(image1, image2, mask))
 | |
| 
 | |
|     def do_merge(self):
 | |
|         """usage: merge <string:mode> <image:pic1> [<image:pic2> [<image:pic3> [<image:pic4>]]]
 | |
| 
 | |
|         Merge top-of stack images in a way described by the mode.
 | |
|         """
 | |
|         mode = self.do_pop()
 | |
|         bandlist = []
 | |
|         for band in mode:
 | |
|             bandlist.append(self.do_pop())
 | |
|         self.push(Image.merge(mode, bandlist))
 | |
| 
 | |
|     # Image class methods
 | |
| 
 | |
|     def do_convert(self):
 | |
|         """usage: convert <string:mode> <image:pic1>
 | |
| 
 | |
|         Convert the top image to the given mode.
 | |
|         """
 | |
|         mode = self.do_pop()
 | |
|         image = self.do_pop()
 | |
|         self.push(image.convert(mode))
 | |
| 
 | |
|     def do_copy(self):
 | |
|         """usage: copy <image:pic1>
 | |
| 
 | |
|         Make and push a true copy of the top image.
 | |
|         """
 | |
|         self.dup()
 | |
| 
 | |
|     def do_crop(self):
 | |
|         """usage: crop <int:left> <int:upper> <int:right> <int:lower> <image:pic1>
 | |
| 
 | |
|         Crop and push a rectangular region from the current image.
 | |
|         """
 | |
|         left = int(self.do_pop())
 | |
|         upper = int(self.do_pop())
 | |
|         right = int(self.do_pop())
 | |
|         lower = int(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         self.push(image.crop((left, upper, right, lower)))
 | |
| 
 | |
|     def do_draft(self):
 | |
|         """usage: draft <string:mode> <int:xsize> <int:ysize>
 | |
| 
 | |
|         Configure the loader for a given mode and size.
 | |
|         """
 | |
|         mode = self.do_pop()
 | |
|         xsize = int(self.do_pop())
 | |
|         ysize = int(self.do_pop())
 | |
|         self.push(self.draft(mode, (xsize, ysize)))
 | |
| 
 | |
|     def do_filter(self):
 | |
|         """usage: filter <string:filtername> <image:pic1>
 | |
| 
 | |
|         Process the top image with the given filter.
 | |
|         """
 | |
|         from PIL import ImageFilter
 | |
|         filter = eval("ImageFilter." + self.do_pop().upper())
 | |
|         image = self.do_pop()
 | |
|         self.push(image.filter(filter))
 | |
| 
 | |
|     def do_getbbox(self):
 | |
|         """usage: getbbox
 | |
| 
 | |
|         Push left, upper, right, and lower pixel coordinates of the top image.
 | |
|         """
 | |
|         bounding_box = self.do_pop().getbbox()
 | |
|         self.push(bounding_box[3])
 | |
|         self.push(bounding_box[2])
 | |
|         self.push(bounding_box[1])
 | |
|         self.push(bounding_box[0])
 | |
| 
 | |
|     def do_getextrema(self):
 | |
|         """usage: extrema
 | |
| 
 | |
|         Push minimum and maximum pixel values of the top image.
 | |
|         """
 | |
|         extrema = self.do_pop().extrema()
 | |
|         self.push(extrema[1])
 | |
|         self.push(extrema[0])
 | |
| 
 | |
|     def do_offset(self):
 | |
|         """usage: offset <int:xoffset> <int:yoffset> <image:pic1>
 | |
| 
 | |
|         Offset the pixels in the top image.
 | |
|         """
 | |
|         xoff = int(self.do_pop())
 | |
|         yoff = int(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         self.push(image.offset(xoff, yoff))
 | |
| 
 | |
|     def do_paste(self):
 | |
|         """usage: paste <image:figure> <int:xoffset> <int:yoffset> <image:ground>
 | |
| 
 | |
|         Paste figure image into ground with upper left at given offsets.
 | |
|         """
 | |
|         figure = self.do_pop()
 | |
|         xoff = int(self.do_pop())
 | |
|         yoff = int(self.do_pop())
 | |
|         ground = self.do_pop()
 | |
|         if figure.mode == "RGBA":
 | |
|             ground.paste(figure, (xoff, yoff), figure)
 | |
|         else:
 | |
|             ground.paste(figure, (xoff, yoff))
 | |
|         self.push(ground)
 | |
| 
 | |
|     def do_resize(self):
 | |
|         """usage: resize <int:xsize> <int:ysize> <image:pic1>
 | |
| 
 | |
|         Resize the top image.
 | |
|         """
 | |
|         ysize = int(self.do_pop())
 | |
|         xsize = int(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         self.push(image.resize((xsize, ysize)))
 | |
| 
 | |
|     def do_rotate(self):
 | |
|         """usage: rotate <int:angle> <image:pic1>
 | |
| 
 | |
|         Rotate image through a given angle
 | |
|         """
 | |
|         angle = int(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         self.push(image.rotate(angle))
 | |
| 
 | |
|     def do_save(self):
 | |
|         """usage: save <string:filename> <image:pic1>
 | |
| 
 | |
|         Save image with default options.
 | |
|         """
 | |
|         filename = self.do_pop()
 | |
|         image = self.do_pop()
 | |
|         image.save(filename)
 | |
| 
 | |
|     def do_save2(self):
 | |
|         """usage: save2 <string:filename> <string:options> <image:pic1>
 | |
| 
 | |
|         Save image with specified options.
 | |
|         """
 | |
|         filename = self.do_pop()
 | |
|         options = self.do_pop()
 | |
|         image = self.do_pop()
 | |
|         image.save(filename, None, options)
 | |
| 
 | |
|     def do_show(self):
 | |
|         """usage: show <image:pic1>
 | |
| 
 | |
|         Display and pop the top image.
 | |
|         """
 | |
|         self.do_pop().show()
 | |
| 
 | |
|     def do_thumbnail(self):
 | |
|         """usage: thumbnail <int:xsize> <int:ysize> <image:pic1>
 | |
| 
 | |
|         Modify the top image in the stack to contain a thumbnail of itself.
 | |
|         """
 | |
|         ysize = int(self.do_pop())
 | |
|         xsize = int(self.do_pop())
 | |
|         self.top().thumbnail((xsize, ysize))
 | |
| 
 | |
|     def do_transpose(self):
 | |
|         """usage: transpose <string:operator> <image:pic1>
 | |
| 
 | |
|         Transpose the top image.
 | |
|         """
 | |
|         transpose = self.do_pop().upper()
 | |
|         image = self.do_pop()
 | |
|         self.push(image.transpose(transpose))
 | |
| 
 | |
|     # Image attributes
 | |
| 
 | |
|     def do_format(self):
 | |
|         """usage: format <image:pic1>
 | |
| 
 | |
|         Push the format of the top image onto the stack.
 | |
|         """
 | |
|         self.push(self.do_pop().format)
 | |
| 
 | |
|     def do_mode(self):
 | |
|         """usage: mode <image:pic1>
 | |
| 
 | |
|         Push the mode of the top image onto the stack.
 | |
|         """
 | |
|         self.push(self.do_pop().mode)
 | |
| 
 | |
|     def do_size(self):
 | |
|         """usage: size <image:pic1>
 | |
| 
 | |
|         Push the image size on the stack as (y, x).
 | |
|         """
 | |
|         size = self.do_pop().size
 | |
|         self.push(size[0])
 | |
|         self.push(size[1])
 | |
| 
 | |
|     # ImageChops operations
 | |
| 
 | |
|     def do_invert(self):
 | |
|         """usage: invert <image:pic1>
 | |
| 
 | |
|         Invert the top image.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         self.push(ImageChops.invert(self.do_pop()))
 | |
| 
 | |
|     def do_lighter(self):
 | |
|         """usage: lighter <image:pic1> <image:pic2>
 | |
| 
 | |
|         Pop the two top images, push an image of the lighter pixels of both.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         self.push(ImageChops.lighter(image1, image2))
 | |
| 
 | |
|     def do_darker(self):
 | |
|         """usage: darker <image:pic1> <image:pic2>
 | |
| 
 | |
|         Pop the two top images, push an image of the darker pixels of both.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         self.push(ImageChops.darker(image1, image2))
 | |
| 
 | |
|     def do_difference(self):
 | |
|         """usage: difference <image:pic1> <image:pic2>
 | |
| 
 | |
|         Pop the two top images, push the difference image
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         self.push(ImageChops.difference(image1, image2))
 | |
| 
 | |
|     def do_multiply(self):
 | |
|         """usage: multiply <image:pic1> <image:pic2>
 | |
| 
 | |
|         Pop the two top images, push the multiplication image.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         self.push(ImageChops.multiply(image1, image2))
 | |
| 
 | |
|     def do_screen(self):
 | |
|         """usage: screen <image:pic1> <image:pic2>
 | |
| 
 | |
|         Pop the two top images, superimpose their inverted versions.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image2 = self.do_pop()
 | |
|         image1 = self.do_pop()
 | |
|         self.push(ImageChops.screen(image1, image2))
 | |
| 
 | |
|     def do_add(self):
 | |
|         """usage: add <image:pic1> <image:pic2> <int:offset> <float:scale>
 | |
| 
 | |
|         Pop the two top images, produce the scaled sum with offset.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         scale = float(self.do_pop())
 | |
|         offset = int(self.do_pop())
 | |
|         self.push(ImageChops.add(image1, image2, scale, offset))
 | |
| 
 | |
|     def do_subtract(self):
 | |
|         """usage: subtract <image:pic1> <image:pic2> <int:offset> <float:scale>
 | |
| 
 | |
|         Pop the two top images, produce the scaled difference with offset.
 | |
|         """
 | |
|         from PIL import ImageChops
 | |
|         image1 = self.do_pop()
 | |
|         image2 = self.do_pop()
 | |
|         scale = float(self.do_pop())
 | |
|         offset = int(self.do_pop())
 | |
|         self.push(ImageChops.subtract(image1, image2, scale, offset))
 | |
| 
 | |
|     # ImageEnhance classes
 | |
| 
 | |
|     def do_color(self):
 | |
|         """usage: color <image:pic1>
 | |
| 
 | |
|         Enhance color in the top image.
 | |
|         """
 | |
|         from PIL import ImageEnhance
 | |
|         factor = float(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         enhancer = ImageEnhance.Color(image)
 | |
|         self.push(enhancer.enhance(factor))
 | |
| 
 | |
|     def do_contrast(self):
 | |
|         """usage: contrast <image:pic1>
 | |
| 
 | |
|         Enhance contrast in the top image.
 | |
|         """
 | |
|         from PIL import ImageEnhance
 | |
|         factor = float(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         enhancer = ImageEnhance.Contrast(image)
 | |
|         self.push(enhancer.enhance(factor))
 | |
| 
 | |
|     def do_brightness(self):
 | |
|         """usage: brightness <image:pic1>
 | |
| 
 | |
|         Enhance brightness in the top image.
 | |
|         """
 | |
|         from PIL import ImageEnhance
 | |
|         factor = float(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         enhancer = ImageEnhance.Brightness(image)
 | |
|         self.push(enhancer.enhance(factor))
 | |
| 
 | |
|     def do_sharpness(self):
 | |
|         """usage: sharpness <image:pic1>
 | |
| 
 | |
|         Enhance sharpness in the top image.
 | |
|         """
 | |
|         from PIL import ImageEnhance
 | |
|         factor = float(self.do_pop())
 | |
|         image = self.do_pop()
 | |
|         enhancer = ImageEnhance.Sharpness(image)
 | |
|         self.push(enhancer.enhance(factor))
 | |
| 
 | |
|     # The interpreter loop
 | |
| 
 | |
|     def execute(self, list):
 | |
|         "Interpret a list of PILDriver commands."
 | |
|         list.reverse()
 | |
|         while len(list) > 0:
 | |
|             self.push(list[0])
 | |
|             list = list[1:]
 | |
|             if self.verbose:
 | |
|                 print("Stack: " + repr(self.stack))
 | |
|             top = self.top()
 | |
|             if not isinstance(top, str):
 | |
|                 continue;
 | |
|             funcname = "do_" + top
 | |
|             if not hasattr(self, funcname):
 | |
|                 continue
 | |
|             else:
 | |
|                 self.do_pop()
 | |
|                 func = getattr(self, funcname)
 | |
|                 func()
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     import sys
 | |
|     try:
 | |
|         import readline
 | |
|     except ImportError:
 | |
|         pass # not available on all platforms
 | |
| 
 | |
|     # If we see command-line arguments, interpret them as a stack state
 | |
|     # and execute.  Otherwise go interactive.
 | |
| 
 | |
|     driver = PILDriver()
 | |
|     if len(sys.argv[1:]) > 0:
 | |
|         driver.execute(sys.argv[1:])
 | |
|     else:
 | |
|         print("PILDriver says hello.")
 | |
|         while True:
 | |
|             try:
 | |
|                 if sys.version_info[0] >= 3:
 | |
|                     line = input('pildriver> ');
 | |
|                 else:
 | |
|                     line = raw_input('pildriver> ');
 | |
|             except EOFError:
 | |
|                 print("\nPILDriver says goodbye.")
 | |
|                 break
 | |
|             driver.execute(line.split())
 | |
|             print(driver.stack)
 | |
| 
 | |
| # The following sets edit modes for GNU EMACS
 | |
| # Local Variables:
 | |
| # mode:python
 | |
| # End:
 |