-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugins.py
728 lines (552 loc) · 26.4 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
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
"""
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: Michael Dickey
Date: Feb 22 2022
"""
import copy
import math
# 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)
"""
# pretty print pixels my way
## prints an output like this for each row
"""
R 255 R 255
G 0 G 0
B 0 B 0
A 255 A 0
"""
print()
for pos1 in range(height):
row = image[pos1]
this_row_reds = []
this_row_greens = []
this_row_blues = []
this_row_alphas = []
for pos2 in range(width):
pixel = row[pos2]
#print(type(pixel))
#print(repr(pixel))
# get red
# gets pixels as string, incrases length with spaces to line it all up
this_pixel_red = "R" + str(pixel.red)
while len(this_pixel_red) < 4:
this_pixel_red = this_pixel_red + " "
this_pixel_green = "G" + str(pixel.green)
while len(this_pixel_green) < 4:
this_pixel_green = this_pixel_green + " "
this_pixel_blue = "B" + str(pixel.blue)
while len(this_pixel_blue) < 4:
this_pixel_blue = this_pixel_blue + " "
this_pixel_alpha = "A" + str(pixel.alpha)
while len(this_pixel_alpha) < 4:
this_pixel_alpha = this_pixel_alpha = " "
#print("R",this_pixel_red)
this_row_reds.append(this_pixel_red)
#print("G",this_pixel_green)
this_row_greens.append(this_pixel_green)
#print("B",this_pixel_blue)
this_row_blues.append(this_pixel_blue)
#print("A",this_pixel_alpha)
this_row_alphas.append(this_pixel_alpha)
print(this_row_reds)
print(this_row_greens)
print(this_row_blues)
print(this_row_alphas)
print("")
# 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
sepia_valid = False
if sepia == False:
sepia_valid = True
if sepia == True:
sepia_valid = True
assert sepia_valid == True, "Sepia must be 'True' or 'False'"
# Get the image size
height = len(image)
width = len(image[0])
for row in range(height):
for col in range(width):
if sepia == False:
#print()
pixel = image[row][col]
#print("R", pixel.red, "G", pixel.green, "B", pixel.blue)
brightness = 0.3 * pixel.red + 0.6 * pixel.green + 0.1 * pixel.blue
#print("brightness is: ", brightness)
#update each pixel
pixel.red = int(brightness)
pixel.green = int(brightness)
pixel.blue = int(brightness)
#print("R", pixel.red, "G", pixel.green, "B", pixel.blue)
if sepia == True:
pixel = image[row][col]
brightness = 0.3 * pixel.red + 0.6 * pixel.green + 0.1 * pixel.blue
"""
If sepia is True, it makes the same computations as before but sets green to
0.6 * brightness and blue to 0.4 * brightness.
"""
pixel.red = int(brightness)
pixel.green = int(0.6 * brightness)
pixel.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
vertical_valid = False
if vertical == False:
vertical_valid = True
if vertical == True:
vertical_valid = True
assert vertical_valid == True, "Vertical must be 'True' or 'False'"
# get length and width
height = len(image)
width = len(image[0])
# flip horizontal
if vertical == False:
#print()
for row in range(height):
# get backup of current row
current_row = image[row]
current_row_flipped = copy.deepcopy(current_row)
current_row_flipped.reverse()
for col in range(width):
#iterage through pixels and overwrite with values from flipped backup
pixel = image[row][col]
flip_source_pixel = current_row_flipped[col]
#update each pixel color with color from flip source pixel backup
pixel.red = flip_source_pixel.red
pixel.green = flip_source_pixel.green
pixel.blue = flip_source_pixel.blue
pixel.alpha = flip_source_pixel.alpha
#flip vertical
if vertical == True:
#print()
#print("running flipping vertically")
#copy image
image_copy = copy.deepcopy(image)
for row in range(height):
#print("processing row: ", row)
for col in range(width):
#this pixel
pixel = image[row][col]
#print("row is: ", row, "col is: ", col, "pixel is: ", pixel)
#determine what pixel should replace this one
replace_by_row = abs((height-1) - row)
replace_by_pixel = image_copy[replace_by_row][col]
#print(" replace_by_row is: ", replace_by_row, "replace by pixel is: ", replace_by_pixel)
#update pixel values
pixel.red = replace_by_pixel.red
pixel.green = replace_by_pixel.green
pixel.blue = replace_by_pixel.blue
pixel.alpha = replace_by_pixel.alpha
#print(" pixel is now, ", pixel)
# Change this to return True when the function is implemented
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
"""
#get height and width
#print()
height = len(image)
width = len(image[0])
#print("height is: ", height, "width is: ", width)
#copy image
image_copy = []
#print("image_copy len", len(image_copy))
# populate image_copy list with transposed dimensions
for index in range(width):
image_copy.append([])
for count in range(height):
image_copy[index].append(count)
# copy the pixels to the copy list in new row + col position
for row_index in range(height):
#print(" row_index is ", row_index)
for col_index in range(width):
#print(" col_index is: ", col_index)
# this pixel
pixel = image[row_index][col_index]
#print(" row is: ", row_index, "col is: ", col_index, "pixel is: ", pixel)
#determine what pixel should replace this one
copy_to_row = col_index
copy_to_col = row_index
image_copy[col_index][row_index] = image[row_index][col_index]
#print("image_copy is ", image_copy)
# clear the original image list and populate it with the transposed copy
## clear original image
image.clear()
#print("image is: ", image)
## get new widths and heights from transposed img
height = len(image_copy)
width = len(image_copy[0])
#print("height is: ", height, "width is: ", width)
# populate recreated image list with transposed dimensions
for index in range(height):
image.append([])
for count in range(width):
image[index].append(count)
#print("image height is: ", len(image), "image width is: ", len(image[0]))
# repopulate cleared image with all values from transposed image_copy
for row_index in range(height):
for col_index in range(width):
#print("row_index is: ", row_index, "col_index is: ", col_index)
image[row_index][col_index] = image_copy[row_index][col_index]
#print("image is: ", 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
"""
# We recommend enforcing the precondition for right
right_valid = False
if right == True:
right_valid = True
if right == False:
right_valid = True
assert right_valid == True, "Right needs to be 'True' or 'False'"
#rotate left
if right == False:
## transpose
transpose(image)
## flip vertically
flip(image,vertical=True)
#rotate right
if right == True:
## flip vertically
flip(image,vertical=True)
## transpose
transpose(image)
# Change this to return True when the function is implemented
return True
# ADVANCED OPTIONAL FUNCTIONS
def vignette(image):
"""
Returns True after vignetting (corner darkening) the current image.
All plug-in functions must return True or False. This function returns True
because it modifies the image. It simulates vignetting, which is a characteristic
of antique lenses. This plus sepia tone helps give a photo an antique feel.
To compute the vignette, you must compute the distance of each pixel from the
center. For any two pixels at position (r0,c0) and (r1,c1), the distance between
the two is
dist( (r0,c0), (r1,c1)) = sqrt( (r0-r1)*(r0-r1)+(c0-c1)*(c0-c1) )
The vignette factor for a pixel at row r, col r is
1 - (d / H)^2
where d is the distance from the pixel to the center of the image and H (for the
half diagonal) is the distance from the center of the image to a corner. To
vignette an image, multiply each NON-ALPHA color value by its vignette factor.
The alpha value should be left untouched.
Parameter image: The image buffer
Precondition: image is a 2d table of RGB objects
"""
#get image specs
#print()
height = len(image)
width = len(image[0])
#print("height is: ", height, "width is: ", width)
center_h = height/2
center_w = width/2
#print("center_h is: ", center_h, "center_w is: ", center_w)
#compute distance from center to corner
distance_to_corner = math.sqrt((abs(center_h - 0)*abs(center_h - 0)) + (abs(center_w - 0)*abs(center_w - 0)))
#print("distance_to_corner is: ", distance_to_corner)
for row_index in range(height):
for col_index in range(width):
pixel = image[row_index][col_index]
#print(" row is: ", row_index, "col is: ", col_index, "pixel is: ", pixel)
#compute distance from center to current pixel
distance_from_center = math.sqrt((abs(center_h - row_index)*abs(center_h - row_index)) + (abs(center_w - col_index)*abs(center_w - col_index)))
#print(" distance_from_center is: ", distance_from_center)
#calculate vigette factor
vignette_factor = 1 - (distance_from_center / distance_to_corner)*(distance_from_center / distance_to_corner)
#1 - (d / H)^2
#print(" vignette_factor is: ", vignette_factor)
#update each pixel by multiplying color channels by vignette factor
pixel.red = int(pixel.red * vignette_factor)
pixel.green = int(pixel.green * vignette_factor)
pixel.blue = int(pixel.blue * vignette_factor)
#print(" pixel is now: ", pixel)
# Change this to return True when the function is implemented
return True
def blur(image,radius=1):
"""
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
# Change this to return True when the function is implemented
# get image specs
height = len(image)
width = len(image[0])
print()
print("height is: ", height, "width is: ", width, "radius is: ", radius)
# create a copy of the image
image_copy = copy.deepcopy(image)
# loop over all pixels
print()
for row_index in range(height):
for col_index in range(width):
# get the current pixel
pixel = image_copy[row_index][col_index]
print("row_index:", row_index, "col_index:", col_index, "pixel:", pixel)
#print("pixel.red is: ", pixel.red)
#print("pixel.green is: ", pixel.green)
#print("pixel.blue is: ", pixel.blue)
#print("pixel.alpha is: ", pixel.alpha)
# get the average of the pixel
pixel_average = int((pixel.red + pixel.green + pixel.blue + pixel.alpha) / 4)
#print("pixel_average is: ", pixel_average)
# get the average of all the pixels in the box
pixel_average_box = 0
red_average_box = 0
blue_average_box = 0
green_average_box = 0
alpha_average_box = 0
# loop over postive positive radius
box = [] #the list that will be made up of the points in the box to blur values from
red_average = 0
green_average = 0
blue_avareage = 0
alpha_average = 0
pixel_average = 0
#if row_index == 2 and col_index == 2: # just for testing
for box_row in range(radius+1):
#print(" box_row:", box_row, "radius:", radius)
for box_col in range(radius+1):
# minus row, minus col
#print(" ",(row_index - box_row), (col_index - box_col))
box_slot_row = (row_index - box_row)
box_slot_col = (col_index - box_col)
if (box_slot_row,box_slot_col) not in box:
#print(" ", "1-box_slot_row", box_slot_row, "box_slot_col", box_slot_col, "not in box, appending")
if box_slot_row >= 0 and box_slot_row < height:
if box_slot_col >= 0 and box_slot_col < width:
box.append((box_slot_row,box_slot_col))
# get pixel info
this_pixel = image[box_slot_row][box_slot_col]
print(" row:", box_slot_row, "col:",box_slot_col, "this_pixel is:", this_pixel)
"""
here, working on getting the average of each pixel color but first
making sure it grabs the correct pixels, not clear why output keeps showing (0,0,0,0) pixles
"""
"""
row_index: 0 col_index: 1 pixel: (255,0,0,255)
row: 0 col: 1 this_pixel is: (255,0,0,255)
row: 0 col: 0 this_pixel is: (0,0,0,0) <-- this is the problem
box is: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
"""
# minus row, plus col
##print(" ",(row_index - box_row), (col_index + box_col))
box_slot_row = (row_index - box_row)
box_slot_col = (col_index + box_col)
if (box_slot_row,box_slot_col) not in box:
#print(" ", "2-box_slot_row", box_slot_row, "box_slot_col", box_slot_col, "not in box, appending")
if box_slot_row >= 0 and box_slot_row < height:
if box_slot_col >= 0 and box_slot_col < width:
box.append((box_slot_row,box_slot_col))
#print(" box is:", box)
# plus row, minus col
##print(" ",(row_index + box_row), (col_index - box_col))
box_slot_row = (row_index + box_row)
box_slot_col = (col_index - box_col)
if (box_slot_row,box_slot_col) not in box:
if box_slot_row >= 0 and box_slot_row < height:
if box_slot_col >= 0 and box_slot_col < width:
box.append((box_slot_row,box_slot_col))
#print(" box is:", box)))
# plus row, plus col
##print(" ",(row_index + box_row), (col_index + box_col))
box_slot_row = (row_index + box_row)
box_slot_col = (col_index + box_col)
if (box_slot_row,box_slot_col) not in box:
if box_slot_row >= 0 and box_slot_row < height:
if box_slot_col >= 0 and box_slot_col < width:
box.append((box_slot_row,box_slot_col))
#print(" box is:", box)
box.sort()
print(" box is: ", box)
"""
2,2
[1,1][1,2][1,3]
[2,1][2,2][2,3]
[3,1][3,2][3,3]
"""
# assign the average to the pixel in the original
pixel_master = image[row_index][col_index]
pixel_master.red = pixel_average
pixel_master.green = pixel_average
pixel_master.blue = pixel_average
pixel_master.alpha = pixel_average
print()
print(" ORIGINAL IMAGE ")
#display(image_copy)
print()
print(" MODIFIED IMAGE")
#display(image)
return False
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
# Change this to return True when the function is implemented
return False