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)
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
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
display=false

View File

@ -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()

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('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__" :