#---------------------------------------------------------------------- # Fleshed-out command processor for image processing. # # (C) Copyright March 28, 2018 -- Dr. William T. Verts # # Floyd-Steinberg Dithering added, along with RGB color reduction #---------------------------------------------------------------------- import string #---------------------------------------------------------------------- # Here most of the functions do something to a pixel, but that is it. # The double-loop, getPixel, and repaint have been factored out into # the Process function, which in order to work must be passed as a # parameter (FN) the actual pixel function to be applied. #---------------------------------------------------------------------- Offset = 20 # Global to Brighten_Pixel, Darken_Pixel, and Main def Brighten_Pixel(PX): global Offset setRed (PX, getRed (PX)+Offset) setGreen(PX, getGreen(PX)+Offset) setBlue (PX, getBlue (PX)+Offset) return def Darken_Pixel(PX): global Offset setRed (PX, getRed (PX)-Offset) setGreen(PX, getGreen(PX)-Offset) setBlue (PX, getBlue (PX)-Offset) return def Monochrome_Pixel(PX): Average = (getRed(PX) + getGreen(PX) + getBlue(PX)) / 3 if Average > 128: setColor(PX,white) else: setColor(PX,black) return def Negate_Pixel(PX): setRed (PX, 255 - getRed(PX)) setGreen(PX, 255 - getGreen(PX)) setBlue (PX, 255 - getBlue(PX)) return def Gray_Pixel(PX): Average = (getRed(PX) + getGreen(PX) + getBlue(PX)) / 3 setRed (PX,Average) setGreen(PX,Average) setBlue (PX,Average) return def RGB_Pixel(PX): setRed (PX, getRed (PX) / 128 * 255) setGreen(PX, getGreen(PX) / 128 * 255) setBlue (PX, getBlue (PX) / 128 * 255) return def Process (Canvas, FN): for Y in range(getHeight(Canvas)): for X in range(getWidth(Canvas)): FN(getPixel(Canvas,X,Y)) repaint(Canvas) return def Dither (Canvas, FN): for Y in range(getHeight(Canvas)): for X in range(getWidth(Canvas)): PX = getPixel(Canvas,X,Y) OldR = getRed(PX) OldG = getGreen(PX) OldB = getBlue(PX) FN(PX) NewR = getRed(PX) NewG = getGreen(PX) NewB = getBlue(PX) ErrorR = OldR - NewR ErrorG = OldG - NewG ErrorB = OldB - NewB if (X+1) <= (getWidth(Canvas)-1): # EAST Neighbor PX2 = getPixel(Canvas,X+1,Y) setRed (PX2, getRed (PX2) + ErrorR * 7 / 16) setGreen(PX2, getGreen(PX2) + ErrorG * 7 / 16) setBlue (PX2, getBlue (PX2) + ErrorB * 7 / 16) if ((X-1) >= 0) and ((Y+1) <= getHeight(Canvas)-1): # SOUTHWEST Neighbor PX2 = getPixel(Canvas,X-1,Y+1) setRed (PX2, getRed (PX2) + ErrorR * 3 / 16) setGreen(PX2, getGreen(PX2) + ErrorG * 3 / 16) setBlue (PX2, getBlue (PX2) + ErrorB * 3 / 16) if ((Y+1) <= getHeight(Canvas)-1): # SOUTH Neighbor PX2 = getPixel(Canvas,X,Y+1) setRed (PX2, getRed (PX2) + ErrorR * 5 / 16) setGreen(PX2, getGreen(PX2) + ErrorG * 5 / 16) setBlue (PX2, getBlue (PX2) + ErrorB * 5 / 16) if ((X+1) <= (getWidth(Canvas)-1)) and ((Y+1) <= getHeight(Canvas)-1): # SOUTHEAST Neighbor PX2 = getPixel(Canvas,X+1,Y+1) setRed (PX2, getRed (PX2) + ErrorR * 1 / 16) setGreen(PX2, getGreen(PX2) + ErrorG * 1 / 16) setBlue (PX2, getBlue (PX2) + ErrorB * 1 / 16) repaint(Canvas) return #---------------------------------------------------------------------- # Functions that have to be stand-alone, and cannot be used with # Process. Flip and Mirror have to deal with pairs of pixels at # a time. # # Question: In Mirror, what happens to the middle pixel of each row # when the canvas is an odd number of pixels wide? # Is it ignored, or is it swapped with itself? # # Question: Can we factor out the code to swap pixels? It'll be the # same in both Flip and Mirror. #---------------------------------------------------------------------- def SwapPixels (PX1,PX2): Color1 = getColor(PX1) Color2 = getColor(PX2) setColor(PX1, Color2) setColor(PX2, Color1) return def Mirror (Canvas): W = getWidth(Canvas) H = getHeight(Canvas) for Y in range(H): for X in range(W/2): SwapPixels(getPixel(Canvas,X,Y),getPixel(Canvas,W-1-X,Y)) repaint(Canvas) return def Flip (Canvas): W = getWidth(Canvas) H = getHeight(Canvas) for X in range(W): for Y in range(H/2): SwapPixels(getPixel(Canvas,X,Y),getPixel(Canvas,X,H-1-Y)) repaint(Canvas) return #---------------------------------------------------------------------- # Rotating an image results in a new canvas where width and height are # reversed. Therefore, there must be a new canvas created and returned # by the function to where it was called. The new canvas will be # assigned to the primary canvas in Main. #---------------------------------------------------------------------- def RotateRight (Canvas): W = getWidth(Canvas) H = getHeight(Canvas) NewCanvas = makeEmptyPicture(H,W) for Y in range(H): for X in range(W): PX1 = getPixel(Canvas,X,Y) PX2 = getPixel(NewCanvas,H-1-Y,X) setColor(PX2,getColor(PX1)) repaint(NewCanvas) return NewCanvas def RotateLeft (Canvas): W = getWidth(Canvas) H = getHeight(Canvas) NewCanvas = makeEmptyPicture(H,W) for Y in range(H): for X in range(W): PX1 = getPixel(Canvas,X,Y) PX2 = getPixel(NewCanvas,H-1-Y,X) # The new X and Y are wrong (copied from RotateRight). What should they be? setColor(PX2,getColor(PX1)) repaint(NewCanvas) return NewCanvas #---------------------------------------------------------------------- # SEE PAGE 292 OF THE COMPANION # Function to apply filtering to 3x3 regions around every pixel in a # canvas. Relative to a pixel , we need to look at pixels at # addresses X-1...X+1 and Y-1...Y+1, but the matrix is indexed by # [0...2][0...2]. The terms [YY-Y+1][XX-X+1] convert the pixel # coordinate into the list indexes. # # Notice the use of default parameters. If Offset would be 0, you # can leave it out of the function call. If both Scale=1 and Offset=0 # you can leave them both out. See the calls to Filter down in Main. #---------------------------------------------------------------------- def Filter (Canvas, Matrix, Scale=1.0, Offset=0.0): W = getWidth(Canvas) H = getHeight(Canvas) NewCanvas = makeEmptyPicture(W,H) for Y in range(H): for X in range(W): TotalR = 0 TotalG = 0 TotalB = 0 for YY in range(Y-1,Y+2): if (YY >= 0) and (YY < H): for XX in range(X-1,X+2): if (XX >= 0) and (XX < W): PX = getPixel(Canvas, XX, YY) Multiplier = Matrix[YY-Y+1][XX-X+1] TotalR = TotalR + (getRed (PX) * Multiplier) TotalG = TotalG + (getGreen(PX) * Multiplier) TotalB = TotalB + (getBlue (PX) * Multiplier) NewR = int(TotalR / Scale + Offset) NewG = int(TotalG / Scale + Offset) NewB = int(TotalB / Scale + Offset) NewPX = getPixel(NewCanvas, X, Y) setColor(NewPX, makeColor(NewR,NewG,NewB)) repaint(NewCanvas) return NewCanvas #---------------------------------------------------------------------- # The updated command processor. Notice that GrayScale still uses # the explicit function, and hasn't yet been updated to the new # style using Process. All the other image commands call Process, # but must tell Process which pixel function to use as parameter FN. #---------------------------------------------------------------------- def Main(): global Offset Message = """Enter a Command from the list: QUIT OPEN OFFSET SAVE BRIGHTEN DARKEN MONO GRAY RGB NEGATE MIRROR FLIP FOCUS EMBOSS CONTRAST ROTATELEFT ROTATERIGHT FOCUS BLUR EDGE EMBOSSUL EMBOSSLR EMBOSSUR EMBOSSLL DITHERBW DITHERGRAY DITHERRGB Enter a command --- """ Canvas = makeEmptyPicture(100,100) MoreToDo = True while (MoreToDo): repaint(Canvas) S = string.upper(requestString(Message)) print S if (S == "QUIT" ): MoreToDo = False elif (S == "OPEN" ): Filename = pickAFile() Canvas = makePicture(Filename) elif (S == "OFFSET" ): Offset = requestInteger("Enter Offset for Brighten/Darken") elif (S == "SAVE" ): pass elif (S == "BRIGHTEN" ): Process(Canvas, Brighten_Pixel) # These functions elif (S == "DARKEN" ): Process(Canvas, Darken_Pixel) # all modify the elif (S == "MONO" ): Process(Canvas, Monochrome_Pixel) # canvas in-place, elif (S == "NEGATE" ): Process(Canvas, Negate_Pixel) # and do not elif (S == "GRAY" ): Process(Canvas, Gray_Pixel) # return any elif (S == "RGB" ): Process(Canvas, RGB_Pixel) elif (S == "DITHERBW" ): Dither(Canvas, Monochrome_Pixel) elif (S == "DITHERGRAY" ): Dither(Canvas, Gray_Pixel) elif (S == "DITHERRGB" ): Dither(Canvas, RGB_Pixel) elif (S == "MIRROR" ): Mirror(Canvas) # value to where elif (S == "FLIP" ): Flip(Canvas) # they were called. elif (S == "ROTATELEFT" ): Canvas = RotateLeft(Canvas) # These functions return elif (S == "ROTATERIGHT"): Canvas = RotateRight(Canvas) # a new canvas, overwriting the old elif (S == "FOCUS" ): Canvas = Filter(Canvas, [[-1, 0,-1],[ 0, 7, 0],[-1, 0,-1]], 3.0) elif (S == "BLUR" ): Canvas = Filter(Canvas, [[ 1, 1, 1],[ 1, 1, 1],[ 1, 1, 1]], 9.0) elif (S == "EDGE" ): Canvas = Filter(Canvas, [[-1,-1,-1],[-1, 8,-1],[-1,-1,-1]]) elif (S == "EMBOSSUL" ): Canvas = Filter(Canvas, [[ 1, 0, 0],[ 0, 0, 0],[ 0, 0,-1]], 1.0, 128.0) elif (S == "EMBOSSLR" ): Canvas = Filter(Canvas, [[-1, 0, 0],[ 0, 0, 0],[ 0, 0, 1]], 1.0, 128.0) elif (S == "EMBOSSUR" ): Canvas = Filter(Canvas, [[ 0, 0, 1],[ 0, 0, 0],[-1, 0, 0]], 1.0, 128.0) elif (S == "EMBOSSLL" ): Canvas = Filter(Canvas, [[ 0, 0,-1],[ 0, 0, 0],[ 1, 0, 0]], 1.0, 128.0) else: showError("ERROR, " + S + " is not a legal command") return