3 -------------------------------------------------------------------------------
6 AUTHOR(S): Peter Walker pwalker@csumb.edu
8 PURPOSE- This module will be used by CST 205 for image manipulation. It
9 creates some classes that will use the PIL package (installed
10 by calling 'pip3 install Pillow')
15 -------------------------------------------------------------------------------
20 from platform
import system
24 from numpy
import asarray, uint8
25 from tkinter.filedialog
import (askdirectory, askopenfilename)
26 from tkinter
import Tk
36 A simple class for accessing attributes of an RGB Pixel
39 red An integer between 0 and 255.
40 Accessible by getRed() and setRed()
41 blue An integer between 0 and 255.
42 Accessible by getBlue() and setBlue()
43 green An integer between 0 and 255.
44 Accessible by getGreen() and setGreen()
45 X An integer, representing the pixel's location in a picture.
47 Y An integer, representing the pixel's location in a picture.
65 (r,g,b) Tuple/List of pixel's color's R,G, and B values
67 r A pixel value for the Red aspect
68 g A pixel value for the Green aspect
69 b A pixel value for the Blue aspect
71 (x,y) Tuple/List of the pixel's X and Y coordinate
73 x The X-part of the this pixel's location within the picture
74 y The Y-part of the this pixel's location within the picture
90 A decorator that will make sure that the color arguments passed are viable.
91 The arguments we are expected are "self", and an integer value between
94 def wrapper(*args, **kwargs):
95 if not isinstance(args[1], (int,float)):
96 raise ValueError(
"You must pass in a number value. Was passed {}".format(repr(args[1])))
100 args[1] = int(args[1])
103 while args[1] > 255
or args[1] < 0:
111 return func(*args, **kwargs)
115 def __checkCoord(func):
117 A decorator that will make sure that the coordinate arguments passed are viable.
118 The arguments we are expected are "self", and an integer value greater than 0.
120 def wrapper(*args, **kwargs):
121 if not isinstance(args[1], (int,float)):
122 raise ValueError(
"You must pass in a number value. Was passed {}".format(repr(args[1])))
125 raise ValueError(
"You must pass in a POSITIVE number value. "+
126 "Was passed {}".format(repr(args[1])))
130 args[1] = float(args[1])
133 return func(*args, **kwargs)
137 def __sameObject(func):
138 def wrapper(*args, **kwargs):
139 if not isinstance(args[1], args[0].__class__):
140 raise TypeError(
"You must compare objects of the same type. Cannot compare "+
141 "{0} and {1}".format(args[0].__class__, args[1].__class__))
142 return func(*args, **kwargs)
146 def __isPixel(self, pixel):
147 """Simple function to make sure that the passed object is a pixel"""
148 if not isinstance(pixel, self.__class__):
149 raise ValueError(
"You must give an rgbPixel object to 'pixel'")
153 def __isColor(self, color):
154 """Simple function to make sure that the passed color has 3 integers"""
155 if not isinstance(color, (tuple,list))
and len(color) != 3:
156 raise ValueError(
"You must give an RGB value as a tuple (r,g,b) to 'color'")
157 if not all( [isinstance(val, int)
for val
in list(color)] ):
158 raise ValueError(
"You must give three RGB values as integers to the keyword arg 'color'")
168 """Sets the RED values to given number"""
171 """Gets the RED value of the picture"""
176 """Sets the GREEN values to given number"""
179 """Gets the GREEN value of the picture"""
184 """Sets the BLUE values to given number"""
187 """Gets the BLUE value of the picture"""
192 def __setX(self, coord):
195 """Gets the X-coordinate of this pixel in the picture"""
199 def __setY(self, coord):
202 """Gets the Y-coordinate of this pixel in the picture"""
208 Sets this pixel's R,G, and B values to the given values
210 (R,G,B) Tuple/List of three integers
212 R Integer value, the red value of the color
213 G Integer value, the green value of the color
214 B Integer value, the blue value of the color
217 raise ValueError(
"You must pass in 2 NUMBER values (as a tuple or separately). "+
218 "You passed in {}".format(len(args)))
219 if not isinstance(args[0], (list,tuple)):
220 color = list(args[:3])
223 color = list(args[0][:3])
226 raise ValueError(
"You must pass in 3 NUMBER values (as a tuple or separately). "+
227 "You passed in {}".format(len(color)))
235 """Gets all 3 RGB values of this picture as a tuple"""
238 def __setCoord(self, *args):
240 Sets this pixel's X and Y coordinate to the given values
242 (x,y) Tuple/List of the pixel's X and Y coordinate
244 x The X-part of the this pixel's location within the picture
245 y The Y-part of the this pixel's location within the picture
248 raise ValueError(
"You must pass in 2 NUMBER values (as a tuple or separately). "+
249 "You passed in {}".format(len(args)))
250 if not isinstance(args[0], (list,tuple)):
251 coord = list(args[:2])
254 coord = list(args[0][:2])
257 raise ValueError(
"You must pass in 2 NUMBER values (as a tuple or separately). "+
258 "You passed in {}".format(len(coord)))
265 """Gets the X and Y coordinate of this pixel as a tuple"""
271 def _color_as_array(self):
272 """Returns the pixel's RGB values in a list format"""
275 def _color_as_tuple(self):
276 """Returns the pixel's RGB values in a tuple format"""
279 def _coord_as_array(self):
280 """Returns the pixel's RGB values in a list format"""
281 return [self.
__x, self.
__y]
283 def _coord_as_tuple(self):
284 """Returns the pixel's RGB values in a tuple format"""
285 return (self.
__x, self.
__y)
292 Gets the color distance between this pixel and a specified color or pixel object
294 color tuple, 3 integer values where 0<=x<=255
295 pixel an rgbPixel object
297 A Float, that is this pixel's color's distance to the given rgbPixel or
299 A negative number means that this pixel will likely be lighter, while
300 a positive number means that this pixel will likely be darker
302 if not any( [elem
in kwargs
for elem
in [
"color",
"pixel"]] ):
303 raise KeyError(
"You must pass either a color or pixel through the keywords 'color' or 'pixel'")
304 if "color" in kwargs:
306 elif "pixel" in kwargs:
309 if "color" in kwargs:
310 givenR, givenG, givenB = kwargs[
'color']
311 elif "pixel" in kwargs:
312 givenR, givenG, givenB = kwargs[
'pixel']._color_as_tuple()
313 rDist = givenR-self.
__red
315 bDist = givenB-self.
__blue
316 return ((rDist+gDist+bDist)/3.0)
321 Returns the length distance between two rgbPixels according to their
325 xDist = pixel.getX()-self.
getX()
326 yDist = pixel.getY()-self.
getY()
327 from math
import sqrt
328 return sqrt(xDist**2 + yDist**2)
339 """Returns a string representation of this object"""
340 return (
"Pixel at ({},{}): ".format(self.
__x, self.
__y)+
349 """Less than comparison, based on average pixel value"""
356 """Equal comparison, based on average pixel value"""
357 if self.
_avgValue() == other._avgValue():
362 def __gt__(self, other):
363 return not self.
__eq__(other)
and not self.
__lt__(other)
364 def __ne__(self, other):
365 return not self.
__eq__(other)
366 def __ge__(self, other):
368 def __le__(self, other):
380 A simple class for accessing attributes of an RGB Pixel
383 inputFilename String, the absolute path of the file used for input.
384 Empty if blank image was created
385 outputFilename String, the absolute path where the image will be saved.
386 Empty if blank image was created
387 pixels List, a 2-dimensional array of rgbPixel objects
388 height Integer, the height of the picture in pixels
389 width Integer, the width of the pixture in pixels
405 def __init__(self, inputFilename="", outputFilename="", blank=False, width=100, height=100):
407 Object initialization
410 inputFilename String, the location of the picture
411 outputFilename String, the location that this picture will be saved to
412 blank Boolean, whether this image should be a blank image
413 width Integer, the width of the blank image
414 height Integer, the height of the blank image
418 if not inputFilename:
424 self.
inputFilename = askopenfilename(initialdir=os.path.expanduser(
"~"),
425 title=
"Select the IMAGE FILE",
426 filetypes=[(
'JPG Image',
'.jpg'),
427 (
'JPEG Image',
'.jpeg'),
428 (
'GIF Image',
'.gif'),
429 (
'PNG Image',
'.png')
434 raise ValueError(
"You must choose a file for this class to work.")
438 if system()!=
"Windows":
439 self.
inputFilename = self.inputFilename.replace(
"\\",
"").strip()
440 acceptedExt = [
"jpg",
"jpeg",
"png",
"gif"]
442 self.inputFilename.split(
".")[-1].lower()
not in acceptedExt:
443 raise ValueError(
"You must pass in a legitimate picture file. "+
444 "Use one of the following options:"+str(acceptedExt))
446 if not outputFilename:
449 filename = filename.split(
".")[0]+
"_altered."+filename.split(
".")[1]
455 print(
"{} will be overwritten if this image is saved. ".format(os.path.basename(self.
outputFilename))+
456 "Consider saving to another location."
462 print(
"Creating Blank Image, {}x{}".format(self.
width, self.
height))
472 def __checkCoordPair(func):
473 def coord_wrapper(*args, **kwargs):
474 def checkPoint(val, maxVal):
475 if val<0
or val>maxVal:
480 if isinstance(args[1], (tuple,list)):
482 raise ValueError(
"You must pass in two values as a coordinate. "+
483 "Was passed {}".format(repr(args[1]))
485 if not all([isinstance(coord, int)
for coord
in args[1]]):
486 raise ValueError(
"You must pass in two integers for coordinates. "+
487 "Was passed {}".format(repr(args[1]))
490 if not checkPoint(x, args[0].width-1)
and not checkPoint(y, args[0].height-1):
491 raise ValueError(
"You must pass in POSITIVE integers. "+
492 "Was passed {}".format(repr([x,y]))
495 if not all( [isinstance(elem,int)
for elem
in [args[1],args[2]]] ):
496 raise ValueError(
"You must pass in integer value. "+
497 "Was passed {}".format(repr([args[1],args[2]]))
501 if not checkPoint(x, args[0].width-1)
and not checkPoint(y, args[0].height-1):
502 raise ValueError(
"You must pass in POSITIVE integers. "+
503 "Was passed {}".format(repr([x,y]))
506 return func(*args, **kwargs)
516 Gets an rgbPixel object at the specified (x,y) coordinate
519 x The X coordinate of the pixel
520 y The Y coordinate of the pixel
522 (x,y) The X and Y coordinate within a tuple or list
526 ValueError - 'x' or 'y' is out of the picture's bounds
529 if isinstance(args[0], (tuple, list)):
538 """Converts this object's 'pixels' attribute into a 1-dimensional array"""
542 allPixels.append(pixel)
548 Given a new name (as a String), sets the output filename
550 name String, the new name of the to-be-saved file
552 Boolean, True if file does not exist, False otherwise
554 Value Error if name is not string
556 if not isinstance(name, str):
557 raise ValueError(
"You must pass in a string for your new image name")
559 if os.path.isabs(name):
563 print(
"Please choose the directory you will want the file saved in...")
569 outputPath = askdirectory(initialdir=os.path.expanduser(
"~"),
570 title=
"Select the FOLDER to contain the image",
572 name = os.path.splitext(name)[0]+
".png"
573 self.
outputFilename = os.path.abspath(os.path.join(outputPath, name))
578 name = os.path.basename(name)+
"."+os.path.splitext(self.
inputFilename)[1]
579 self.
outputFilename = os.path.abspath(os.path.join(dirname, name))
583 name = os.path.basename(name)+
"."+os.path.splitext(self.
outputFilename)[1]
584 self.
outputFilename = os.path.abspath(os.path.join(dirname, name))
587 print(
"{} will be overwritten if this image is saved. ".format(os.path.basename(self.
outputFilename))+
588 "Consider saving to another location."
595 def save(self, filename="", forceOverwrite=False):
597 This will save the current object to a specified location
600 filename OPTIONAL. String, the name or location to be saved to.
601 forceOverwrite OPTIONAL. Boolean, whether to force overwrite of file
602 at location 'filename' or 'self.outputFilename'
609 print(
"Please choose the directory you will want the file saved in...")
615 outputPath = askdirectory(initialdir=os.path.expanduser(
"~"),
616 title=
"Select the FOLDER to contain the image",
618 name =
"simpleImage_output.jpg"
619 self.
outputFilename = os.path.abspath(os.path.join(outputPath, name))
630 """Shows the image the user is currently working on"""
632 Image.fromarray(img_to_array).
show()
636 """Resets this object to the original image"""
651 for row
in tmpPixels:
652 self.pixels.append([])
655 pixel = tuple([int(val)
for val
in list(pixel)])
656 coord = (colCount, rowCount)
667 self.__myImage.close()
670 def __as_array(self):
672 Converts the 3-dimensional array of pixel objects into a 4-dimensional
673 array of RGB values (each pixel object is converted to an array)
679 init_arr = pixel._color_as_array()
680 append_arr = [uint8(rgbVal)
for rgbVal
in init_arr]
681 tempArray[-1].append(append_arr)
687 """Returns a string representation of this object"""
689 return (
"RGB Image named {}\nSize is {}x{} pixels".format(shortInput, self.
width, self.
height)
def __isPixel(self, pixel)
def _color_as_tuple(self)
def __setCoord(self, args)
def _coord_as_tuple(self)
def __isColor(self, color)