#---------------------------------------------------------------------- # Basic command processor # # Copyright (C) October 11, 2017 -- Dr. William T. Verts # Enhanced October 13, 2017 to be a graphics image processor # More functions added October 16, 2017 # Cleaned Up to final version November 2, 2017 # # This program illustrates a number of Python structures, including: # quoted strings that span lines, # the string function to make all characters lower case, # the raw_input function and JES requestString, # the pass statement to act as a placeholder for future code, # the use of bool (Boolean) variables to control a loop, # the try-except block for error handling, # the ability to pass functions as parameters to other functions. # #---------------------------------------------------------------------- import time def INT(N): return int(round(N)) #---------------------------------------------------------------------- # Flip and Mirror can work with the current canvas in-place, but need # to handle two pixels at a time. The SwapPixels function helps out. # See page 288 in the Companion. #---------------------------------------------------------------------- def SwapPixels (PX1,PX2): Temp = getColor(PX1) setColor(PX1, getColor(PX2)) setColor(PX2, Temp) return def Mirror(Canvas): for Y in range(getHeight(Canvas)): for X1 in range(getWidth(Canvas)/2): X2 = getWidth(Canvas) - 1 - X1 PX1 = getPixel(Canvas, X1, Y) PX2 = getPixel(Canvas, X2, Y) SwapPixels(PX1,PX2) repaint(Canvas) return def Flip(Canvas): H = getHeight(Canvas) for X in range(getWidth(Canvas)): for Y1 in range(H/2): Y2 = H - 1 - Y1 SwapPixels(getPixel(Canvas,X,Y1),getPixel(Canvas,X,Y2)) repaint(Canvas) return #---------------------------------------------------------------------- # Rotating canvases requires that a new canvas be created and returned # because the height and width of the original canvas may be different # values. Note that the code shown here is slightly different from # the pseudocode in the Companion page 289. #---------------------------------------------------------------------- def RotateCW (Canvas): NewCanvas = makeEmptyPicture(getHeight(Canvas),getWidth(Canvas)) for Y in range(getHeight(NewCanvas)): for X in range(getWidth(NewCanvas)): PXold = getPixel(Canvas,Y,getHeight(Canvas)-1-X) PXnew = getPixel(NewCanvas,X,Y) setColor(PXnew,getColor(PXold)) repaint(NewCanvas) return NewCanvas def RotateCCW (Canvas): NewCanvas = makeEmptyPicture(getHeight(Canvas),getWidth(Canvas)) for Y in range(getHeight(NewCanvas)): for X in range(getWidth(NewCanvas)): PXold = getPixel(Canvas,getWidth(Canvas)-1-Y,X) PXnew = getPixel(NewCanvas,X,Y) setColor(PXnew,getColor(PXold)) repaint(NewCanvas) return NewCanvas #---------------------------------------------------------------------- # Tiny pixel-handler functions passed in as parameters to the Process # or Dither functions. These functions are given a pixel in PX, and # make some transformation to the primary colors of that pixel. #---------------------------------------------------------------------- def Negate_Pixel (PX): setRed (PX, 255 - getRed(PX)) setGreen(PX, 255 - getGreen(PX)) setBlue (PX, 255 - getBlue(PX)) return def Brighten_Pixel (PX): setRed (PX, getRed(PX) + 30) setGreen(PX, getGreen(PX) + 30) setBlue (PX, getBlue(PX) + 30) return def Darken_Pixel (PX): setRed (PX, getRed(PX) - 30) setGreen(PX, getGreen(PX) - 30) setBlue (PX, getBlue(PX) - 30) return def MoreRed_Pixel (PX): setRed (PX, getRed(PX) + 30) return def MoreGreen_Pixel (PX): setGreen(PX, getGreen(PX) + 30) return def MoreBlue_Pixel (PX): setBlue (PX, getBlue(PX) + 30) return def LessRed_Pixel (PX): setRed (PX, getRed(PX) - 30) return def LessGreen_Pixel (PX): setGreen(PX, getGreen(PX) - 30) return def LessBlue_Pixel (PX): setBlue (PX, getBlue(PX) - 30) 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 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 Safe_Pixel (PX): setRed (PX, (getRed (PX) - 25) / 51 * 51 + 25) setGreen(PX, (getGreen(PX) - 25) / 51 * 51 + 25) setBlue (PX, (getBlue (PX) - 25) / 51 * 51 + 25) return #---------------------------------------------------------------------- # Sometimes the transform requires that a function return the closest # color from a finite list of colors. This function does that, # helping out the pixel functions found below. #---------------------------------------------------------------------- def ClosestColor (C, L): # C is a JES color, L is a list of JES colors Result = black MaxDist = 1.0E100 for TestColor in L: DeltaR = C.getRed() - TestColor.getRed() DeltaG = C.getGreen() - TestColor.getGreen() DeltaB = C.getBlue() - TestColor.getBlue() Distance = sqrt(DeltaR*DeltaR + DeltaG*DeltaG + DeltaB*DeltaB) if Distance < MaxDist: Result = TestColor MaxDist = Distance return Result def CMYK_Pixel (PX): setColor(PX, ClosestColor(PX.getColor(), [cyan,magenta,yellow,black])) return def Gray3_Pixel (PX): setColor(PX, ClosestColor(PX.getColor(), [black,gray,white])) return #-------------------------------------------------- # Process is responsible for scanning all pixels # in an image and applying whatever is passed in # as MyFunction to each pixel, and repainting the # canvas after each line is completed. If Process # is passed Brighten_Pixel, then calling MyFunction # is the same as calling Brighten_Pixel. If Process # is passed Darken_Pixel, then calling MyFunction # is the same as calling Darken_Pixel. def Process(Canvas, MyFunction): for Y in range(getHeight(Canvas)): for X in range(getWidth(Canvas)): PX = getPixel(Canvas,X,Y) MyFunction(PX) repaint(Canvas) return #-------------------------------------------------- # Dither is identical to Process by transforming # each pixel, but extends the task by distributing # the difference between the pixel's previous color # and new color to pixels not yet processed. This # particular dithering function uses the # Floyd-Steinberg method on page 293 of the # Companion, but it could use any of the dithering # methods on pages 294-295. #-------------------------------------------------- def Dither(Canvas, MyFunction): 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) MyFunction(PX) NewR = getRed(PX) NewG = getGreen(PX) NewB = getBlue(PX) ErrorR = OldR - NewR ErrorG = OldG - NewG ErrorB = OldB - NewB if (X < getWidth(Canvas)-1): 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 > 0) and (Y < getHeight(Canvas)-1)): 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 < getHeight(Canvas)-1): 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 < getWidth(Canvas)-1) and (Y < getHeight(Canvas)-1)): 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 #---------------------------------------------------------------------- # Filter is passed a 3x3 matrix of scale weights, a divisor (Scale), # and a shift factor (Offset). The matrix is multiplied by every pixel # and the 8 pixels in its immediate neighborhood, the results are # summed, scaled, and shifted. See page 292 of the Companion. # By selecting the weights in the matrix, the divisor, and the offset, # any number of different functions can be implemented, such as blur, # focus, emboss, edge-detect, etc. #---------------------------------------------------------------------- def Filter (Canvas, FilterMatrix, 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): DestPX = getPixel(NewCanvas,X,Y) SumR = 0.0 SumG = 0.0 SumB = 0.0 for YY in range(Y-1,Y+2): for XX in range(X-1,X+2): if (XX>=0 and XX0 and YY 1)") Contrast(Canvas, Scale) elif (Command == "open"): Filename = pickAFile() try: # If the user enters the name Canvas = makePicture(Filename) # of a file that isn't a graphic, repaint(Canvas) # the repaint will fail, and throw except: # an exception, picked up by the Canvas = makeEmptyPicture(100,100) # except block. If the try block showInformation(Filename + " is not a graphic") # succeeds, the except block won't run. else: showInformation(Command + " is an illegal command") # Use showMessage(...) for JES dialog box, print for pure Python repaint(Canvas) return