import png, random class Colore: '''Classe che rappresenta il colore di un pixel, come 3 valori float tra 0 e 1 ''' def __init__(self, r, g, b): '''il costruttore Colore(r, g, b), dopo aver memorizzato i 3 valori, li vincola nel range 0-1.''' self._r = r self._g = g self._b = b self.normalize() def normalize(self): '''vincola i 3 valori nel range 0-1''' self._r = max(0, min(1, self._r)) self._g = max(0, min(1, self._g)) self._b = max(0, min(1, self._b)) def clone(self): '''crea una copia di questo Colore''' return Colore(self._r, self._g, self._b) def __str__(self): '''Formato testuale human-friendly''' return "Colore({0._r}, {0._g}, {0._b})".format(self) def to_RGB8(self): '''Converte un colore in una terna RGB di valori interi tra 0 e 255 (rappresentabili da 3 byte)''' return int(self._r*255), int(self._g*255), int(self._b*255) @classmethod def from_RGB8(cls, r, g, b): '''costruttore che accetta 3 valori RGB tra 0 e 255''' return cls(r/255, g/255, b/255) def grigio(self): '''filtro che genera un colore grigio con la stessa luminosita' del colore originale''' media = (self._r + self._g + self._b)/3 return Colore(media, media, media) def inverso(self): '''filtro che calcola il colore inverso (o il negativo) di un colore''' return Colore(1-self._r, 1-self._g, 1-self._b) def contrasto(self): '''filtro che aumenta la distanza tra colori chiari e colori scuri''' r = ((self._r - 0.5)*2 + 0.5) g = ((self._g - 0.5)*2 + 0.5) b = ((self._b - 0.5)*2 + 0.5) return Colore(r, g, b) class Immagine: '''Una Immagine e' un rettangolo di pixel. La rappresentazione interna e' basata su una lista di liste di Colore().''' def __init__(self, w, h, color=Colore(0,0,0)): '''Immagine(w,h, colore=nero) crea una immagine w x h colorata del colore fornito''' self._w = w self._h = h self._img = [] for _ in range(h): riga = [] for _ in range(w): riga+=[color.clone()] self._img+=[riga] def __str__(self): '''Rappresentazione testuale human-friendly di una Immagine''' return "Immagine({0._w}, {0._h})".format(self) def save(self, filename): '''Salva una immagine in un file 'filename' in formato PNG''' img = [[p.to_RGB8() for p in linea] for linea in self._img] pngimg = png.from_array(img,'RGB') pngimg.save(filename) def setPixel(self, x, y, colore): '''Colora il pixel che si trova alle coordinate y,x del colore dato. (senza fare controlli sulle coordinate)''' self._img[y][x] = colore.clone() def getPixel(self, x, y): '''legge il colore del pixel che si trova alle coordinate y,x. (senza fare controlli sulle coordinate)''' return self._img[y][x] @classmethod def load(cls, filename): '''costruttore che carica una immagine da un file''' with open(filename, mode='rb') as f: reader = png.Reader(file=f) w, h, png_img, _ = reader.asRGB8() immagine = cls(w,h) png_img = list(png_img) for y in range(h): for x in range(w): c = Colore.from_RGB8(png_img[y][3*x], png_img[y][3*x+1], png_img[y][3*x+2]) immagine.setPixel(x, y, c) return immagine def filtra(self, filtro): '''Costruisce una nuova immagine ottenuta applicando il filtro (una funzione) a ciascun pixel dell'immagineoriginale''' immagine = Immagine(self._w, self._h) for y in range(self._h): for x in range(self._w): c = filtro(self._img[y][x]) immagine.setPixel(x, y, c) return immagine def ruota90(self): '''Costruisce una immagine ottenuta ruotando l'immagine originale di 90° in senso antiorrio''' immagine = Immagine(self._h, self._w) for y in range(self._w): for x in range(self._h): c = self._img[x][self._w-1-y] immagine.setPixel(x, y, c) return immagine def disegna_quadrato(self, x, y, s, c): ''' Disegna un quadrato di colore uniforme nella posizione x,y e di dimensioni s x s di colore c. Eventuali sbordature vengono catturate ed ignorate (inefficiente).''' for px in range(x, x+s): for py in range(y, y+s): if 0 <= px < self._w and 0 <= py < self._h: self._img[py][px] = c.clone() def mosaico(self, l): '''Crea una nuova immagine mosaico di lato l colorando ciascun quadretto col colore del pixel in alto a sinistra del quadretto nell'immagine originale.''' immagine = Immagine(self._w, self._h) for y in range(self._h//l): for x in range(self._w//l): colore = self._img[y*l][x*l] immagine.disegna_quadrato(x*l, y*l, l, colore) return immagine def scramble(self, d): # crea una nuova immagine colorando ciascun pixel con il colore di un altro pixel preso a # caso all'interno di un quadratino dentrato nel pixel e di lato d immagine = Immagine(self._w, self._h) for y in range(self._h): for x in range(self._w): # sceglie a caso un pixel nel quadrato rx = x + random.randint(-d,d) ry = y + random.randint(-d,d) # evitando che si esca dall'immagine rx = max(0, min(self._w-1, rx)) ry = max(0, min(self._h-1, ry)) immagine._img[y][x] = self._img[ry][rx].clone() return immagine