Advertisement
Guest User

Untitled

a guest
May 9th, 2017
354
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.92 KB | None | 0 0
  1. """
  2. image.py
  3. This module provides a simple interface to create a window, load an image and experiment
  4. with image based algorithms.  Many of which require pixel-by-pixel manipulation.  This
  5. is a educational module, its not intended to replace the excellent Python Image Library, in fact
  6. it uses PIL.
  7. The module and its interface and some of the code were inspired/copied by/from John Zelle's graphics.py
  8. which serves a similar purpose in the graphics primitive world.
  9. """
  10.  
  11. # Release Notes:
  12. # Version 1.0   Fall 2005
  13. #
  14. # Brad Miller, Luther College
  15. #
  16. # Version 1.1   December 7, 2005
  17. # Changes:
  18. #   Modify class name for base image to be AbstractImage   This way we don't have a lower case
  19. #      class name running around.  We still don't expect people to create an AbstractImage but
  20. #      rather create an image through FileImage, ListImage, or EmptyImage.
  21. #   Add ability to convert an image to a list
  22. #   Add save function to write an image back to disk.
  23. #
  24. # Version 1.2  November 2007
  25. # Changes:
  26. #  Modify the setPosition function to position the image by the top left corner rather than
  27. #    the center.
  28. #  Add the exitOnClick method to ImageWin.  Use this as the last call in the program to
  29. #    avoid early exits when running from the command line, and nasty hangs when running
  30. #    from within IDLE
  31. #
  32. # Version 1.3  May 2008
  33. # Changes:
  34. #   Modify all code to be Python 3.0 ready.  -- still runs under 2.x
  35. #   Modify all code so that if PIL is not available then image.py will still
  36. #   function using Tkimages.  N.B.  Tk restricts image types to gif or ppm
  37. #
  38. # Andrew Mertz, Eastern Illinois University
  39. # October 2014
  40. # Changes:
  41. #   Negative indices can be used in Pixel's  __getitem__ function
  42. #   Pixel's __getitem__ function now supports looping i.e. for value in Pixel(0, 1, 2):
  43. #
  44. # Giovanni Moretti, Massey University, Palmerston North, New Zealand
  45. # April 2015
  46. # Changes:
  47. # Added _imroot.lift() to ensure that pictures aren't hidden under other Windows
  48. # (needed on Windows 7 & Python 3.4. Ubuntu 14.04 was fine without it).
  49.  
  50. try:
  51.     import tkinter
  52. except:
  53.     import Tkinter as tkinter
  54.  
  55. pilAvailable = True
  56. try:
  57.     from PIL import Image as PIL_Image
  58.     from PIL import ImageTk
  59. except:
  60.     pilAvailable = False
  61.  
  62. #import exceptions
  63.  
  64. # Borrow some ideas from Zelle
  65. # create an invisible global main root for all windows
  66. tk = tkinter
  67. _imroot = tk.Tk()
  68. _imroot.withdraw()
  69.  
  70. # Make sure the displayed window is on top - otherwise drawing can appear to fail.
  71. # The _imroot.lift() call was required on Windows 7 - Linux was fine without it
  72. # not sure about Mac, but there are some tips at
  73. # http://stackoverflow.com/questions/8691655/how-to-put-a-tkinter-window-on-top-of-the-others
  74. _imroot.lift()
  75. #_imroot.call('wm', 'attributes', '.', '-topmost', True)
  76. #_imroot.after_idle(_imroot.call, 'wm', 'attributes', '.', '-topmost', False)
  77.  
  78.  
  79. def formatPixel(data):
  80.     if type(data) == tuple:
  81.         return '{#%02x%02x%02x}'%data
  82.     elif isinstance(data,Pixel):
  83.         return '{#%02x%02x%02x}'%data.getColorTuple()
  84.  
  85. class ImageWin(tk.Canvas):
  86.     """
  87.    ImageWin:  Make a frame to display one or more images.
  88.    """
  89.     def __init__(self,title="image window",width=640,height=640):
  90.         """
  91.        Create a window with a title, width and height.
  92.        """
  93.         master = tk.Toplevel(_imroot)
  94.         master.protocol("WM_DELETE_WINDOW", self._close)
  95.         #super(ImageWin, self).__init__(master, width=width, height=height)
  96.         tk.Canvas.__init__(self, master, width=width, height=height)
  97.         self.master.title(title)
  98.         self.pack()
  99.         master.resizable(0,0)
  100.         self.foreground = "black"
  101.         self.items = []
  102.         self.mouseX = None
  103.         self.mouseY = None
  104.         self.bind("<Button-1>", self._onClick)
  105.         self.height = height
  106.         self.width = width
  107.         self._mouseCallback = None
  108.         self.trans = None
  109.         _imroot.update()
  110.  
  111.     def _close(self):
  112.         """Close the window"""
  113.         self.master.destroy()
  114.         self.quit()
  115.         _imroot.update()
  116.  
  117.     def getMouse(self):
  118.         """Wait for mouse click and return a tuple with x,y position in screen coordinates after
  119.        the click"""
  120.         self.mouseX = None
  121.         self.mouseY = None
  122.         while self.mouseX == None or self.mouseY == None:
  123.             self.update()
  124.         return ((self.mouseX,self.mouseY))
  125.  
  126.     def setMouseHandler(self, func):
  127.         self._mouseCallback = func
  128.  
  129.     def _onClick(self, e):
  130.         self.mouseX = e.x
  131.         self.mouseY = e.y
  132.         if self._mouseCallback:
  133.             self._mouseCallback(e.x, e.y)
  134.  
  135.     def exitOnClick(self):
  136.         """When the Mouse is clicked close the window and exit"""
  137.         self.getMouse()
  138.         self._close()
  139.  
  140.     def exitonclick(self):
  141.         self.exitOnClick()
  142.  
  143.  
  144. class Pixel(object):
  145.     """This simple class abstracts the RGB pixel values."""
  146.     def __init__(self, red, green, blue):
  147.         super(Pixel, self).__init__()
  148.         self.__red = red
  149.         self.__green = green
  150.         self.__blue = blue
  151.         self.max = 255
  152.  
  153.     def getRed(self):
  154.         """Return the red component of the pixel"""
  155.         return self.__red
  156.  
  157.     def getGreen(self):
  158.         """Return the green component of the pixel"""
  159.         return self.__green
  160.  
  161.     def getBlue(self):
  162.         """Return the blue component of the pixel"""
  163.         return self.__blue
  164.  
  165.     def getColorTuple(self):
  166.         """Return all color information as a tuple"""
  167.         return (self.__red, self.__green, self.__blue)
  168.  
  169.     def setRed(self,red):
  170.         """Modify the red component"""
  171.         if self.max >= red >= 0:
  172.             self.__red = red
  173.         else:
  174.             raise ValueError("Error:  pixel value %d is out of range" % red)
  175.  
  176.     def setGreen(self,green):
  177.         """Modify the green component"""
  178.         if self.max >= green >= 0:
  179.             self.__green = green
  180.         else:
  181.             raise ValueError("Error:  pixel value %d is out of range" % green)
  182.  
  183.     def setBlue(self,blue):
  184.         """Modify the blue component"""
  185.         if self.max >= blue >= 0:
  186.             self.__blue = blue
  187.         else:
  188.             raise ValueError("Error:  pixel value %d is out of range" % blue)
  189.  
  190.     def __getitem__(self,key):
  191.         """Allow new style pixel class to act like a color tuple:
  192.           0 --> red
  193.           1 --> green
  194.           2 --> blue
  195.        """
  196.         if isinstance(key, slice):
  197.             raise TypeError("Slicing is not supported")
  198.         if key == 0 or key == -3:
  199.             return self.__red
  200.         elif key == 1 or key == -2:
  201.             return self.__green
  202.         elif key == 2 or key == -1:
  203.             return self.__blue
  204.         else:
  205.             raise IndexError("Error %d Index out of range" % key)
  206.  
  207.     def setRange(self,pmax):
  208.         """docstring for setRange"""
  209.         if pmax == 1.0:
  210.             self.max = 1.0
  211.         elif pmax == 255:
  212.             self.max = 255
  213.         else:
  214.             raise ValueError("Error range must be 1.0 or 256")
  215.  
  216.     def __str__(self):
  217.         return str(self.getColorTuple())
  218.  
  219.     def __repr__(self):
  220.         """docstring for __repr__"""
  221.         return str(self.getColorTuple())
  222.  
  223.     red = property(getRed, setRed, None, "I'm the red property.")
  224.     green = property(getGreen, setGreen, None, "I'm the green property.")
  225.     blue = property(getBlue, setBlue, None, "I'm the blue property.")
  226.  
  227. class AbstractImage(object):
  228.     """
  229.    Create an image.  The image may be created in one of four ways:
  230.    1. From an image file such as gif, jpg, png, ppm  for example: i = image('fname.jpb)
  231.    2. From a list of lists
  232.    3. From another image object
  233.    4. By specifying the height and width to create a blank image.
  234.    """
  235.     imageCache = {} # tk photoimages go here to avoid GC while drawn
  236.     imageId = 1
  237.  
  238.     def __init__(self,fname=None,data=[],imobj=None,height=0,width=0):
  239.         """
  240.        An image can be created using any of the following keyword parameters. When image creation is
  241.        complete the image will be an rgb image.
  242.        fname:  A filename containing an image.  Can be jpg, gif, and others
  243.        data:  a list of lists representing the image.  This might be something you construct by
  244.        reading an asii format ppm file, or an ascii art file and translate into rgb yourself.
  245.        imobj:  Make a copy of another image.
  246.        height:
  247.        width: Create a blank image of a particular height and width.
  248.        """
  249.         super(AbstractImage, self).__init__()
  250.  
  251.         # if PIL is available then use the PIL functions otherwise fall back to Tk
  252.         if pilAvailable:
  253.             self.loadImage = self.loadPILImage
  254.             self.createBlankImage = self.createBlankPILImage
  255.             self.setPixel = self.setPILPixel
  256.             self.getPixel = self.getPILPixel
  257.             self.save = self.savePIL
  258.         else:
  259.             self.loadImage = self.loadTkImage
  260.             self.createBlankImage = self.createBlankTkImage
  261.             self.setPixel = self.setTkPixel
  262.             self.getPixel = self.getTkPixel
  263.             self.save = self.saveTk
  264.  
  265.         if fname:
  266.             self.loadImage(fname)
  267.             self.imFileName = fname
  268.         elif data:
  269.             height = len(data)
  270.             width = len(data[0])
  271.             self.createBlankImage(height,width)
  272.             for row  in range(height):
  273.                 for col in range(width):
  274.                     self.setPixel(col,row,Pixel(data[row][col]))
  275.         elif height > 0 and width > 0:
  276.             self.createBlankImage(height,width)
  277.         elif imobj:
  278.             self.im = imobj.copy()
  279.  
  280.         if pilAvailable:
  281.             self.width,self.height = self.im.size
  282.         else:
  283.             self.width = self.im.width()
  284.             self.height = self.im.height()
  285.         self.centerX = self.width/2+3     # +3 accounts for the ~3 pixel border in Tk windows
  286.         self.centerY = self.height/2+3
  287.         self.id = None
  288.  
  289.     def loadPILImage(self,fname):
  290.         self.im = PIL_Image.open(fname)
  291.         ni = self.im.convert("RGB")
  292.         self.im = ni
  293.  
  294.     def loadTkImage(self,fname):
  295.         sufstart = fname.rfind('.')
  296.         if sufstart < 0:
  297.             suffix = ""
  298.         else:
  299.             suffix = fname[sufstart:]
  300.         if suffix not in ['.gif', '.ppm']:
  301.             raise ValueError("Bad Image Type: %s : Without PIL, only .gif or .ppm files are allowed" % suffix)
  302.         self.im = tkinter.PhotoImage(file=fname)
  303.  
  304.     def createBlankPILImage(self,height,width):
  305.         self.im = PIL_Image.new("RGB",(width,height))
  306.         ni = self.im.convert("RGB")
  307.         self.im = ni
  308.  
  309.     def createBlankTkImage(self,height,width):
  310.         self.im = tkinter.PhotoImage(height=height,width=width)
  311.  
  312.  
  313.     def copy(self):
  314.         """Return a copy of this image"""
  315.         newI = AbstractImage(imobj=self.im)
  316.         return newI
  317.  
  318.  
  319.     def clone(self):
  320.          """Return a copy of this image"""
  321.          newI = AbstractImage(imobj=self.im)
  322.          return newI
  323.  
  324.     def getHeight(self):
  325.         """Return the height of the image"""
  326.         return self.height
  327.  
  328.     def getWidth(self):
  329.         """Return the width of the iamge"""
  330.         return self.width
  331.  
  332.     def getTkPixel(self,x,y):
  333.         """Get a pixel at the given x,y coordinate.  The pixel is returned as an rgb color tuple
  334.        for example foo.getPixel(10,10) --> (10,200,156) """
  335.         p = self.im.get(x,y)
  336.         # p is a string in some tkinter versions; tuple in others.
  337.         try:
  338.             p = [int(j) for j in p.split()]
  339.         except AttributeError:
  340.             pass
  341.         return Pixel(p[0],p[1],p[2])
  342.  
  343.     def setTkPixel(self,x,y,pixel):
  344.         """Set the color of a pixel at position x,y.  The color must be specified as an rgb tuple (r,g,b) where
  345.        the rgb values are between 0 and 255."""
  346.         if x < self.getWidth() and y < self.getHeight():
  347.             self.im.put(formatPixel(pixel.getColorTuple()),(x,y))
  348.         else:
  349.             raise ValueError("Pixel index out of range.")
  350.  
  351.     def getPILPixel(self,x,y):
  352.         """docstring for getPILPIxel"""
  353.         p = self.im.getpixel((x,y))
  354.         return Pixel(p[0],p[1],p[2])
  355.  
  356.     def setPILPixel(self,x,y,pixel):
  357.         """docstring for setPILPixel"""
  358.         if x < self.getWidth() and y < self.getHeight():
  359.             self.im.putpixel((x,y),pixel.getColorTuple())
  360.         else:
  361.             raise ValueError("Pixel index out of range")
  362.  
  363.     def setPosition(self,x,y):
  364.         """Set the position in the window where the top left corner of the window should be."""
  365.         self.top = y
  366.         self.left = x
  367.         self.centerX = x + (self.width/2)+3
  368.         self.centerY = y + (self.height/2)+3
  369.  
  370.     def getImage(self):
  371.         if pilAvailable:
  372.             return ImageTk.PhotoImage(self.im)
  373.         else:
  374.             return self.im
  375.  
  376.     def draw(self,win):
  377.         """Draw this image in the ImageWin window."""
  378.         ig = self.getImage()
  379.         self.imageCache[self.imageId] = ig # save a reference else Tk loses it...
  380.         AbstractImage.imageId = AbstractImage.imageId + 1
  381.         self.canvas=win
  382.         self.id = self.canvas.create_image(self.centerX,self.centerY,image=ig)
  383.         _imroot.update()
  384.  
  385.     def saveTk(self,fname=None,ftype='gif'):
  386.         if fname == None:
  387.             fname = self.imFileName
  388.         sufstart = fname.rfind('.')
  389.         if sufstart < 0:
  390.             suffix = ""
  391.         else:
  392.             suffix = fname[sufstart:]
  393.         if suffix == "":
  394.             suffix = "."+ftype
  395.             fname = fname+suffix
  396.         if suffix not in ['.gif', '.ppm']:
  397.             raise ValueError("Without PIL, only .gif or .ppm files are allowed")
  398.         try:
  399.             self.im.write(fname,format=ftype)
  400.         except IOError as e:
  401.             print(e)
  402.             print("Error saving, Could Not open ", fname, " to write.")
  403.         except tkinter.TclError as tke:
  404.             print(tke)
  405.             print("gif files can only handle 256 distinct colors")
  406.  
  407.     def savePIL(self,fname=None,ftype='jpg'):
  408.         if fname == None:
  409.             fname = self.imFileName
  410.         sufstart = fname.rfind('.')
  411.         if sufstart < 0:
  412.             suffix = ""
  413.         else:
  414.             suffix = fname[sufstart:]
  415.         if suffix == "":
  416.             suffix = "."+ftype
  417.             fname = fname+suffix
  418.         try:
  419.             self.im.save(fname)
  420.         except:
  421.             print("Error saving, Could Not open ", fname, " to write.")
  422.  
  423.  
  424.     def toList(self):
  425.         """
  426.        Convert the image to a List of Lists representation
  427.        """
  428.         res = []
  429.         for i in range(self.height):
  430.             res.append([])
  431.             for j in range(self.width):
  432.                 res[i].append(self.getPixel(j,i))
  433.         return res
  434.  
  435.  
  436. class FileImage(AbstractImage):
  437.     def __init__(self,thefile):
  438.         super(FileImage, self).__init__(fname = thefile)
  439.  
  440. class Image(FileImage):
  441.         pass
  442.  
  443. class EmptyImage(AbstractImage):
  444.     def __init__(self,cols,rows):
  445.         super(EmptyImage, self).__init__(height = rows, width = cols)
  446.  
  447. class ListImage(AbstractImage):
  448.     def __init__(self,thelist):
  449.         super(ListImage, self).__init__(data=thelist)
  450.  
  451. # Example program  Read in an image and calulate the negative.
  452. if __name__ == '__main__':
  453.     win = ImageWin("My Window",480,640)
  454.     oImage = FileImage('lcastle.gif')
  455.     print(oImage.getWidth(), oImage.getHeight())
  456.     oImage.draw(win)
  457.     myImage = oImage.copy()
  458.  
  459.     for row in range(myImage.getHeight()):
  460.         for col in range(myImage.getWidth()):
  461.              v = myImage.getPixel(col,row)
  462.              v.red = 255 - v.red
  463.              v.green = 255 - v.green
  464.              v.blue = 255 - v.blue
  465. #             x = map(lambda x: 255-x, v)
  466.              myImage.setPixel(col,row,v)
  467.     myImage.setPosition(myImage.getWidth()+1,0)
  468.     myImage.draw(win)
  469.     print(win.getMouse())
  470.     myImage.save('lcastle-inverted.gif')
  471.     print(myImage.toList())
  472.     win.exitOnClick()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement