diff --git a/py/common.py b/py/common.py index f79ecc4..9e431b3 100644 --- a/py/common.py +++ b/py/common.py @@ -155,4 +155,14 @@ def rgb_to_luma (r, g, b) : return (r * R) + (g * G) + (b * B) +def convertScale(img, alpha, beta): + """Add bias and gain to an image with saturation arithmetics. Unlike + cv2.convertScaleAbs, it does not take an absolute value, which would lead to + nonsensical results (e.g., a pixel at 44 with alpha = 3 and beta = -210 + becomes 78 with OpenCV, when in fact it should become 0). + """ + new_img = img * alpha + beta + new_img[new_img < 0] = 0 + new_img[new_img > 255] = 255 + return new_img.astype(np.uint8) diff --git a/py/conf/base.conf b/py/conf/base.conf index feb5538..aa04c9d 100644 --- a/py/conf/base.conf +++ b/py/conf/base.conf @@ -2,11 +2,12 @@ canvasWidth=1301 canvasHeight=914 windowWidth=1301 windowHeight=914 -dotSize=6 maxGenerations=50 -maxParticles=2500 -minDotSize=1.8 -line=1.8 +maxParticles=10000 +minDotSize=2.3 +maxDotSize=2.3 +dotSizeFactor=0 +line=2.3 fill=true mode="stipple" -display=true \ No newline at end of file +display=false \ No newline at end of file diff --git a/py/posterize.py b/py/posterize.py index 4f6c4ca..060d52e 100644 --- a/py/posterize.py +++ b/py/posterize.py @@ -2,9 +2,19 @@ from sklearn.cluster import MiniBatchKMeans import numpy as np import argparse import cv2 -from common import convert_color, closest_color_weighted_euclidean, closest_color_euclidean, create_colored_image, remove_from_list, list_match +from common import convert_color, closest_color_weighted_euclidean, closest_color_euclidean, create_colored_image, remove_from_list, list_match, to_luma, convertScale import os import subprocess +from functools import cmp_to_key + +def sort_pallete (a, b): + A = to_luma(a, 'BGR') + B = to_luma(b, 'BGR') + if A > B : + return 1 + if B < A : + return -1 + return 0 class Posterize: """Posterize an image and then find nearest colors to use""" @@ -13,6 +23,8 @@ class Posterize: original_colors = [] layers = [] previews = [] + svgs = [] + headless = False pallete = None pallete_space = 'BGR' @@ -23,20 +35,21 @@ class Posterize: h = 0 w = 0 n_colors = 3 - max_particles = 3000 + max_particles = 17000 conf = os.path.abspath('./conf/base.conf') - stipple_gen = os.path.abspath('../../../src/stipple_gen') + stipple_gen = os.path.abspath('../../stipple_gen') white = [255, 255, 255] output = None - def __init__ (self, image, pallete, n_colors, output) : + def __init__ (self, image, pallete, n_colors, output, headless) : self.image = cv2.imread(image) (self.h, self.w) = self.image.shape[:2] self.pallete = pallete self.n_colors = n_colors + 1 self.output = output + self.headless = headless if not os.path.exists(self.output) : print(f'Output directory {self.output} does not exist, creating...') @@ -65,9 +78,7 @@ class Posterize: self.image = bgrquant - cv2.imshow('image', bgrquant) - cv2.waitKey(0) - cv2.destroyAllWindows() + self.show(bgrquant) def determine_colors (self): reshaped = self.image.reshape(-1, self.image.shape[2]) @@ -108,11 +119,14 @@ class Posterize: 'space' : self.pallete_space }) mask = cv2.bitwise_not(mask) - composite[mask > 0] = np.array(closest) + composite[mask > 0] = np.array(convert_color(closest, self.pallete_space, 'BGR')) - cv2.imshow('image', composite) - cv2.waitKey(0) - cv2.destroyAllWindows() + composite_name = f'posterized.png' + composite_path = os.path.join(self.output, composite_name) + + cv2.imwrite(composite_path, composite) + + self.show(composite) def extract_color_mask (self, image, color): mask = cv2.inRange(image, color, color) @@ -121,8 +135,14 @@ class Posterize: def flatten_pallete (self) : for color in self.pallete.colors: self.colors.append(color['color']) - self.colors_dict[f'{color["color"][0]},{color["color"][1]},{color["color"][2]}'] = color['name'] + self.colors_dict[f"{color['color'][0]},{color['color'][1]},{color['color'][2]}"] = color['name'] self.pallete_space = color['space'] + + #self.colors = sorted(self.colors, key=cmp_to_key(sort_pallete)) + #for color in self.colors : + # print(to_luma(color, self.pallete_space)) + + #quit() def match_color_name (self, key) : return self.colors_dict[f'{key[0]},{key[1]},{key[2]}'] @@ -154,10 +174,33 @@ class Posterize: ] print(cmd) subprocess.call(cmd, cwd = self.stipple_gen) - self.previews.append(output_image) + self.svgs.append(output_svg) + self.previews.append({ + 'layer' : output_image, + 'color' : layer['color'] + }) def preview (self) : - #composite = create_colored_image(self.w, self.h, [255, 255, 255]) - print(self.previews) + composite = create_colored_image(self.w, self.h, [255, 255, 255]) + for layer in self.previews : + l = cv2.imread(layer['layer'], 0) + mask = cv2.bitwise_not(l) + composite[mask > 0] = np.array(convert_color(layer['color'], self.pallete_space, 'BGR')) + composite_name = f'preview.png' + composite_path = os.path.join(self.output, composite_name) + cv2.imwrite(composite_path, composite) + + self.show(composite) + + for svg in self.svgs : + cmd = [ 'svgopt', svg, svg] + print(cmd) + subprocess.call(cmd) + + def show (self, mat) : + if not self.headless : + cv2.imshow('image', mat) + cv2.waitKey(0) + cv2.destroyAllWindows() diff --git a/py/separate.py b/py/separate.py index a619263..1f95004 100644 --- a/py/separate.py +++ b/py/separate.py @@ -9,6 +9,7 @@ parser.add_argument('input', type=str, help='Input image to separate') parser.add_argument('colors', type=int, help='Number of colors to separate into') parser.add_argument('pallete', type=str, help='Pallete file') parser.add_argument('output', type=str, help='Output dir to write to') +parser.add_argument('--headless', type=bool, default=False, help='Run script headless') class Separate : input = '' @@ -27,8 +28,8 @@ class Separate : else : print(f'File {args.pallete} does not exist') exit(2) - - Posterize(self.input, self.pallete, args.colors, args.output) + + Posterize(self.input, self.pallete, args.colors, args.output, args.headless) if __name__ == "__main__" :