Full process is now producing and optimizing svg layers. It needs to be made deterministic by sorting the colors and have the ability to compare in different color spaces.

This commit is contained in:
Matt McWilliams 2023-11-29 23:08:23 -05:00
parent 08a47465c5
commit 537ac0075b
4 changed files with 77 additions and 22 deletions

View File

@ -155,4 +155,14 @@ def rgb_to_luma (r, g, b) :
return (r * R) + (g * G) + (b * 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)

View File

@ -2,11 +2,12 @@ canvasWidth=1301
canvasHeight=914 canvasHeight=914
windowWidth=1301 windowWidth=1301
windowHeight=914 windowHeight=914
dotSize=6
maxGenerations=50 maxGenerations=50
maxParticles=2500 maxParticles=10000
minDotSize=1.8 minDotSize=2.3
line=1.8 maxDotSize=2.3
dotSizeFactor=0
line=2.3
fill=true fill=true
mode="stipple" mode="stipple"
display=true display=false

View File

@ -2,9 +2,19 @@ from sklearn.cluster import MiniBatchKMeans
import numpy as np import numpy as np
import argparse import argparse
import cv2 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 os
import subprocess 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: class Posterize:
"""Posterize an image and then find nearest colors to use""" """Posterize an image and then find nearest colors to use"""
@ -13,6 +23,8 @@ class Posterize:
original_colors = [] original_colors = []
layers = [] layers = []
previews = [] previews = []
svgs = []
headless = False
pallete = None pallete = None
pallete_space = 'BGR' pallete_space = 'BGR'
@ -23,20 +35,21 @@ class Posterize:
h = 0 h = 0
w = 0 w = 0
n_colors = 3 n_colors = 3
max_particles = 3000 max_particles = 17000
conf = os.path.abspath('./conf/base.conf') 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] white = [255, 255, 255]
output = None 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.image = cv2.imread(image)
(self.h, self.w) = self.image.shape[:2] (self.h, self.w) = self.image.shape[:2]
self.pallete = pallete self.pallete = pallete
self.n_colors = n_colors + 1 self.n_colors = n_colors + 1
self.output = output self.output = output
self.headless = headless
if not os.path.exists(self.output) : if not os.path.exists(self.output) :
print(f'Output directory {self.output} does not exist, creating...') print(f'Output directory {self.output} does not exist, creating...')
@ -65,9 +78,7 @@ class Posterize:
self.image = bgrquant self.image = bgrquant
cv2.imshow('image', bgrquant) self.show(bgrquant)
cv2.waitKey(0)
cv2.destroyAllWindows()
def determine_colors (self): def determine_colors (self):
reshaped = self.image.reshape(-1, self.image.shape[2]) reshaped = self.image.reshape(-1, self.image.shape[2])
@ -108,11 +119,14 @@ class Posterize:
'space' : self.pallete_space 'space' : self.pallete_space
}) })
mask = cv2.bitwise_not(mask) 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) composite_name = f'posterized.png'
cv2.waitKey(0) composite_path = os.path.join(self.output, composite_name)
cv2.destroyAllWindows()
cv2.imwrite(composite_path, composite)
self.show(composite)
def extract_color_mask (self, image, color): def extract_color_mask (self, image, color):
mask = cv2.inRange(image, color, color) mask = cv2.inRange(image, color, color)
@ -121,8 +135,14 @@ class Posterize:
def flatten_pallete (self) : def flatten_pallete (self) :
for color in self.pallete.colors: for color in self.pallete.colors:
self.colors.append(color['color']) 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.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) : def match_color_name (self, key) :
return self.colors_dict[f'{key[0]},{key[1]},{key[2]}'] return self.colors_dict[f'{key[0]},{key[1]},{key[2]}']
@ -154,10 +174,33 @@ class Posterize:
] ]
print(cmd) print(cmd)
subprocess.call(cmd, cwd = self.stipple_gen) 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) : def preview (self) :
#composite = create_colored_image(self.w, self.h, [255, 255, 255]) composite = create_colored_image(self.w, self.h, [255, 255, 255])
print(self.previews) 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()

View File

@ -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('colors', type=int, help='Number of colors to separate into')
parser.add_argument('pallete', type=str, help='Pallete file') parser.add_argument('pallete', type=str, help='Pallete file')
parser.add_argument('output', type=str, help='Output dir to write to') 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 : class Separate :
input = '' input = ''
@ -27,8 +28,8 @@ class Separate :
else : else :
print(f'File {args.pallete} does not exist') print(f'File {args.pallete} does not exist')
exit(2) 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__" : if __name__ == "__main__" :