From 15257d7b0c44bd2d3c3b2dcaaf5dfcde72697cc0 Mon Sep 17 00:00:00 2001 From: iou1name Date: Sun, 10 Dec 2017 02:31:35 -0500 Subject: [PATCH] first commit --- mosiac.py | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100755 mosiac.py diff --git a/mosiac.py b/mosiac.py new file mode 100755 index 0000000..151aa3f --- /dev/null +++ b/mosiac.py @@ -0,0 +1,235 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Does mosiacs. +""" +import os +import time +import random + +import numpy as np +from PIL import Image + +class Mosiac(): + def __init__(self): + self.images = [] + self.means = [] + self.imageBank = {} + self.numClusters = 8 + self.clusters = [] + self.clusterMeans = [] + self.bigImageSize = None + self.bigImage = None + self.smallImage = None + self.imageMatrix = [] + self.tileSize = None + self.matrixSize = None + + + def openBigImage(self, imagePath, mat=(40,20)): + """ + Opens and initializes the big image. + """ + self.matrixSize = mat + self.bigImage = Image.open(imagePath).convert("RGB") + self.tileSize = (self.bigImage.size[0] // mat[0], + self.bigImage.size[1] // mat[1]) + self.smallImage = self.bigImage.resize(mat) + self.bigImageSize = (self.tileSize[0]*mat[0], self.tileSize[1]*mat[1]) + + + def initImageBank(self, root, size=(200,200)): + """ + Calculates the average pixel value of all the images in the directory. + """ + then = time.time() + for file in os.listdir(os.path.join(root, "images")): + image = Image.open(os.path.join(root, "images", file)) + + if image.mode == "P": + image = image.convert(image.palette.mode) + if image.mode == "RGBA": + image = alpha_composite_with_color(image).convert("RGB") + # image = image.convert("RGBA") + + image = image.resize(self.tileSize, Image.ANTIALIAS) + mean = tuple(np.mean(image, axis=(0,1))) + # img = Image.new("RGBA", image.size) + # img.putdata(list(map(lambda pixel: (255,255,255,0) if pixel == (255,255,255,255) else pixel, image.getdata()))) + + self.imageBank[mean] = image + print(f"initImageBank took: {time.time()-then} seconds.") + + + def debugImageBank(self): + """ + Create a bank of test images. + """ + then = time.time() + self.images = [] + self.means = [] + for i in range(0, 257, 16): + for j in range(0, 257, 16): + for k in range(0, 257, 16): + image = Image.new("RGB", self.tileSize, (i,j,k, 255)) + self.images.append(image) + mean = tuple(np.mean(image, axis=(0,1))) + self.means.append(mean) + print(f"debugImageBank took: {time.time()-then} seconds.") + + + def initClusters(self): + """ + Divides the images into clusters. + """ + then = time.time() + sort = sorted(self.imageBank.keys(), key=lambda pixel: step(*pixel,8)) + num = len(sort) // self.numClusters + + for n in range(self.numClusters): + cluster = dict(0: pixel for pixel in sort[n*num:n*num+num]) + cluster + """ + indexs = list(range(len(self.images))) + step = len(self.images) // self.numClusters + self.clusters = [indexs[i*step : (i+1)*step] for i in + range(self.numClusters)] + for cluster in self.clusters: + means = [self.means[i] for i in cluster] + mean = tuple(np.mean(means, axis=(0))) + self.clusterMeans.append(mean) + """ + print(f"initClusters took: {time.time()-then} seconds.") + + + def nearestImage(self, pixel): + """ + Finds the nearest image within the mosiac image bank to the provided + pixel. + """ + dists = [] + for mean in self.clusterMeans: + dist = np.linalg.norm(np.array(pixel)-np.array(mean)) + dists.append(dist) + clusterMean = self.clusterMeans[dists.index(min(dists))] + cluster = self.clusters[self.clusterMeans.index(clusterMean)] + + dists = [] + for n in cluster: + dist = np.linalg.norm(np.array(pixel)-np.array(self.means[n])) + dists.append(dist) + choice = random.choice(sorted(dists)[:10]) + return self.images[cluster[dists.index(choice)]] + + + def buildMatrix(self): + """ + Build the image matrix. + """ + then = time.time() + # for row in range(self.smallImage.size[1]): + # new_row = [] + # for col in range(self.smallImage.size[0]): + # image = self.nearestImage(self.smallImage.getpixel((col, row))) + # new_row.append(image) + # self.imageMatrix.append(new_row) + pixels = list(self.smallImage.getdata()) + for pixel in pixels: + image = self.nearestImage(pixel) + self.imageMatrix.append(image) + print(f"buildMatrix took: {time.time()-then} seconds.") + + + def buildMosiac(self, root): + """ + Builds the final mosiac image. + """ + then = time.time() + image = Image.new("RGB", self.bigImageSize, (255,255,255)) + # self.bigImage = self.bigImage.crop((0,0,self.bigImageSize[0],self.bigImageSize[1])) + n = 0 + for y in range(0, self.bigImageSize[1], self.tileSize[1]): + for x in range(0, self.bigImageSize[0], self.tileSize[0]): + image.paste(self.imageMatrix[n], box=(x,y)) + n += 1 + # self.bigImage.save(os.path.join(root, "mosiac.png"), "PNG") + image.save(os.path.join(root, "mosiac.jpg"), "JPEG", optimize=True, quality=90) + print(f"buildMosiac took: {time.time()-then} seconds.") + + +def step (r,g,b, repetitions=1): + lum = math.sqrt( .241 * r + .691 * g + .068 * b ) + + h, s, v = colorsys.rgb_to_hsv(r,g,b) + + h2 = int(h * repetitions) + lum2 = int(lum * repetitions) + v2 = int(v * repetitions) + + return (h2, lum, v2) + + +def alpha_composite(front, back): + """Alpha composite two RGBA images. + + Source: http://stackoverflow.com/a/9166671/284318 + + Keyword Arguments: + front -- PIL RGBA Image object + back -- PIL RGBA Image object + + """ + front = np.asarray(front) + back = np.asarray(back) + result = np.empty(front.shape, dtype='float') + alpha = np.index_exp[:, :, 3:] + rgb = np.index_exp[:, :, :3] + falpha = front[alpha] / 255.0 + balpha = back[alpha] / 255.0 + result[alpha] = falpha + balpha * (1 - falpha) + old_setting = np.seterr(invalid='ignore') + result[rgb] = (front[rgb] * falpha + back[rgb] * balpha * (1 - falpha)) / result[alpha] + np.seterr(**old_setting) + result[alpha] *= 255 + np.clip(result, 0, 255) + # astype('uint8') maps np.nan and np.inf to 0 + result = result.astype('uint8') + result = Image.fromarray(result, 'RGBA') + return result + + +def alpha_composite_with_color(image, color=(255, 255, 255)): + """Alpha composite an RGBA image with a single color image of the + specified color and the same size as the original image. + + Keyword Arguments: + image -- PIL RGBA Image object + color -- Tuple r, g, b (default 255, 255, 255) + + """ + back = Image.new('RGBA', size=image.size, color=color + (255,)) + return alpha_composite(image, back) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Generates a mosiac of images.") + parser.add_argument( + "root", + help="Root directory to work in.") + # parser.add_argument( + # "-s", + # "--size", + # default=(200,200), + # help="Size each image should be.") + args = parser.parse_args() + + mosiac = Mosiac() + mosiac.openBigImage(os.path.join(args.root, "big.jpg")) + # mosiac.initImageBank(**vars(args)) + mosiac.debugImageBank() + mosiac.initClusters() + # mosiac.buildMatrix() + # mosiac.buildMosiac(args.root)