-
Notifications
You must be signed in to change notification settings - Fork 0
/
plugins.py
368 lines (298 loc) · 14 KB
/
plugins.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
"""
Plugin utilites for the pictool.
This module contains all of the commands supported by the application script pictool.py.
To be a valid command, a function must (1) have a first parameter 'image' for the
image buffer, (2) assign default values to parameters after the first, and (3)
return True if it modifies the image and False if not. You can use these rules to
make your own plugin utilities.
Only three four functions -- mono, flip, transpose, and rotate -- are required for this
project. All others are optional, though the folder 'solutions' does contain examples
for each of them.
IMPORTANT: It is highly recommended that these functions enforce the preconditions for
any parameter after images. Otherwise, command line typos may be hard to debug.
Author: Saroj Bono
Date: 04/03/2023
"""
# Function useful for debugging
def display(image):
"""
Returns False after pretty printing the image pixels, one pixel per line.
All plug-in functions must return True or False. This function returns False
because it displays information about the image, but does not modify it.
You can use this function to look at the pixels of a file and see whether the
pixel values are what you expect them to be. This is helpful to analyze a file
after you have processed it.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
"""
height = len(image)
width = len(image[0])
# Find the maximum string size for padding
maxsize = 0
for row in image:
for pixel in row:
text = repr(pixel)
if len(text) > maxsize:
maxsize = len(text)
# Pretty print the pixels
print()
for pos1 in range(height):
row = image[pos1]
for pos2 in range(width):
pixel = row[pos2]
middle = repr(pixel)
padding = maxsize-len(middle)
prefix = ' '
if pos1 == 0 and pos2 == 0:
prefix = '[ [ '
elif pos2 == 0:
prefix = ' [ '
suffix = ','
if pos1 == height-1 and pos2 == width-1:
suffix = (' '*padding)+' ] ]'
elif pos2 == width-1:
suffix = (' '*padding)+' ],'
print(prefix+middle+suffix)
# This function does not modify the image
return
# Example function illustrating image manipulation
def dered(image):
"""
Returns True after removing all red values from the given image.
All plug-in functions must return True or False. This function returns True
because it modifies the image. This function sets the red value to 0 for every
pixel in the image.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
"""
# Get the image size
height = len(image)
width = len(image[0])
for row in range(height):
for col in range(width):
pixel = image[row][col]
pixel.red = 0
# This function DOES modify the image
return True
# IMPLEMENT THESE FOUR FUNCTIONS
def mono(image, sepia=False):
"""
Returns True after converting the image to monochrome.
All plug-in functions must return True or False. This function returns True
because it modifies the image. It converts the image to either greyscale or
sepia tone, depending on the parameter sepia.
If sepia is False, then this function uses greyscale. For each pixel, it computes
the overall brightness, defined as
0.3 * red + 0.6 * green + 0.1 * blue.
It then sets all three color components of the pixel to that value. The alpha value
should remain untouched.
If sepia is True, it makes the same computations as before but sets green to
0.6 * brightness and blue to 0.4 * brightness.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
Parameter sepia: Whether to use sepia tone instead of greyscale
Precondition: sepia is a bool
"""
# We recommend enforcing the precondition for sepia
if type(sepia)!=bool:
raise TypeError("keyword argument 'sepia' should be a bool")
for row in range(len(image)):
for col in range(len(image[row])):
brightness = int(0.3*image[row][col].red + 0.6 * image[row][col].green + 0.1 * image[row][col].blue)
image[row][col].red=brightness
if sepia==False:
image[row][col].green=brightness
image[row][col].blue=brightness
else:
image[row][col].green=int(0.6*brightness)
image[row][col].blue=int(0.4*brightness)
# Change this to return True when the function is implemented
return True
def flip(image,vertical=False):
"""
Returns True after reflecting the image horizontally or vertically.
All plug-in functions must return True or False. This function returns True
because it modifies the image. By default it reflects the image horizonally,
or vertically if vertical is True.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
Parameter vertical: Whether to reflect the image vertically
Precondition: vertical is a bool
"""
# We recommend enforcing the precondition for vertical
if type(vertical)!=bool:
raise TypeError("keyword argument 'vertical' should be a bool")
# Change this to return True when the function is implemented
if vertical == False:
for row in range(len(image)):
image[row] = image[row][::-1]
else:
image.reverse()
return True
def transpose(image):
"""
Returns True after transposing the image
All plug-in functions must return True or False. This function returns True
because it modifies the image. It transposes the image, swaping colums and rows.
Transposing is tricky because you cannot just change the pixel values; you have
to change the size of the image table. A 10x20 image becomes a 20x10 image.
The easiest way to transpose is to make a transposed copy with the pixels from
the original image. Then remove all the rows in the image and replace it with
the rows from the transposed copy.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
"""
new_image=[[None]*len(image) for i in range(len(image[0]))]
for row in range(len(image)):
for col in range(len(image[row])):
new_image[col][row]=image[row][col]
image.clear()
image.extend(new_image)
# Change this to return True when the function is implemented
return True
def rotate(image,right=False):
"""
Returns True after rotating the image left of right by 90 degrees.
All plug-in functions must return True or False. This function returns True
because it modifies the image. By default it rotates the image left, or right
if parameter right is True.
To rotate left, transpose and then flip vertically. To rotate right, flip
vertically first and then transpose.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
Parameter right: Whether to rotate the image right
Precondition: right is a bool
"""
if type(right)!=bool:
raise TypeError("keyword argument 'right' should be a bool")
if right ==False:
transpose(image)
flip(image,vertical=True)
else:
flip(image,vertical=True)
transpose(image)
# We recommend enforcing the precondition for right
# Change this to return True when the function is implemented
return True
import math
def vignette(image):
nrows = len(image)
ncols = len(image[0])
center_row = nrows / 2
center_col = ncols / 2
H = math.sqrt(center_row**2 + center_col**2)
for row in range(nrows):
for col in range(ncols):
d = math.sqrt((row - center_row)**2 + (col - center_col)**2)
vignette_factor = 1 - (d / H)**2
image[row][col].red = int(image[row][col].red * vignette_factor)
image[row][col].green = int(image[row][col].green * vignette_factor)
image[row][col].blue = int(image[row][col].blue * vignette_factor)
return True
def blur(image,radius=5):
"""
Returns True after bluring the image.
To blur an image you loop over all pixels. For each pixel, you average all colors
(all 4 values including alpha) in a box centered at the pixel with the given radius.
For example, suppose you are blurring the pixel at position (4,7) with a radius 2
blur. Then you will average the pixels at positions (2,5), (2,6), (2,7), (2,8),
(2,9), (3,5), (3,6), (3,7), (3,8), (3,9), (4,5), (4,6), (4,7), (4,8), (4,9), (5,5),
(5,6), (5,7), (5,8), (5,9), (6,5), (6,6), (6,7), (6,8), and (6,9). You then assign
that average value to the pixel.
If the box goes outside of the image bounds, go to the edge of the image. So if you
are blurring the pixel at position (0,1) with a radius 2, you average the pixels
at positions (0,0), (0,1), (0,2), (0,3), (1,0), (1,1), (1,2), (1,3), (2,0), (2,1),
(2,2), and (2,3).
This calculation MUST be done in a COPY. Otherwise, you are using the blurred
value in future pixel computations (e.g. when you try to blur the pixel to the
right of it). All averages must be computed from the original image. Use the
steps from transpose() to modify the image.
WARNING: This function is very slow (Adobe's programs use much more complicated
algorithms and are not written in Python). Blurring 'Walker.png' with a radius
of 30 can take up to 10 minutes.
Parameter image: The image to blur
Precondition: image is a 2d table of RGB objects
Parameter radius: The blur radius
Precondition: radius is an int > 0
"""
# We recommend enforcing the precondition for radius
if type(radius)!=int:
raise TypeError("keyword argument 'radius' should be an int")
if radius<=0:
raise ValueError("keyword argument 'radius' should be >0")
# Change this to return True when the function is implemented
for row in range(len(image)):
for col in range(len(image[row])):
new_red=0
new_green=0
new_blue=0
new_alpha=0
count=0
for i in range(row-radius,row+radius+1):
for j in range(col-radius,col+radius+1):
if i>=0 and i<len(image) and j>=0 and j<len(image[row]):
new_red+=image[i][j].red
new_green+=image[i][j].green
new_blue+=image[i][j].blue
new_alpha+=image[i][j].alpha
count+=1
image[row][col].red=new_red//count
image[row][col].green=new_green//count
image[row][col].blue=new_blue//count
image[row][col].alpha=new_alpha//count
return True
def pixellate(image,step=10):
"""
Returns True after pixellating the image.
All plug-in functions must return True or False. This function returns True
because it modifies the image. It pixellates the image to give it a blocky feel.
To pixellate an image, start with the top left corner (e.g. the first row and column).
Average the colors (all 4 values including alpha) of the step x step block to the
right and down from this corner. For example, if step is 3, you will average the
colors at positions (0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), and (2,2).
After computing the averages, assign each color's (and the alpha's) average value
to ALL the pixels in the block.
If there are less than step rows or step columns, go to the edge of the image. So
on a image with 2 rows and 4 columns, a step 3 pixellate would process the colors
at positions (0,0), (0,1), (0,2), (1,0), (1,1), and (1,2).
When you are done, skip over step columns to get the the next corner pixel, and
repeat this process again. Because the blocks do not overlap, it is not necessary
to create a copy (like blur). You can reassign the pixels before moving to the next
block. For example, suppose step is 3. Then the next block is at position (0,3) and
includes the pixels at (0,3), (0,4), (0,5), (1,3), (1,4), (1,5), (2,3), (2,4),
and (2,5).
Continue the process looping over rows and columns to get a pixellated image.
Parameter image: The image to pixelate
Precondition: image is a 2d table of RGB objects
Parameter step: The number of pixels in a pixellated block
Precondition: step is an int > 0
"""
# We recommend enforcing the precondition for step
if type(step)!=int:
raise TypeError("keyword argument 'step' should be an int")
if step<=0:
raise ValueError("keyword argument 'step' should be >0")
for row in range(0,len(image),step):
for col in range(0,len(image[row]),step):
new_red=0
new_green=0
new_blue=0
new_alpha=0
count=0
for i in range(row,row+step):
for j in range(col,col+step):
if i>=0 and i<len(image) and j>=0 and j<len(image[row]):
new_red+=image[i][j].red
new_green+=image[i][j].green
new_blue+=image[i][j].blue
new_alpha+=image[i][j].alpha
count+=1
for i in range(row,row+step):
for j in range(col,col+step):
if i>=0 and i<len(image) and j>=0 and j<len(image[row]):
image[i][j].red=new_red//count
image[i][j].green=new_green//count
image[i][j].blue=new_blue//count
image[i][j].alpha=new_alpha//count
# Change this to return True when the function is implemented
return True