How to reduce the color count in a SVG file ?
Midjourney, AI, Python
Today I wanted to add an illustration for Advanced-Stack.com. I am amazed by the results of Midjourney and I said to myself : let's give it a try.
I generated these results using a basic prompt.
But when I wanted to integrate images in the home page, I realized that Midjourney is not capable of generating transparent background.
I quickly realized that I would need to vectorize and then remove the background - which can be a complex task when the image has a lot of details.
I end up using Figma working on the SVG but I had hundreds of unique colors for the dark background !
Introduction
In this tutorial, we will learn how to reduce the color count in an SVG file using Python. We will use the scikit-learn
library's KMeans
algorithm to cluster the colors in the SVG file and replace them with a reduced set of colors. We will use the svgpathtools
library to parse the SVG file and modify its attributes.
Step 1: Import the Required Libraries
Start by importing the necessary libraries: sklearn.cluster.KMeans
for color clustering and svgpathtools.svg2paths
and svgpathtools.wsvg
for parsing and modifying the SVG file.
from sklearn.cluster import KMeans
from svgpathtools import svg2paths, wsvg
Step 2: Define the Helper Functions
We define 2 helper functions extract_colors
and replace_colors
.
def extract_colors(paths, attrs):
colors = []
for attr in attrs:
if 'fill' in attr:
color = attr['fill']
if color.startswith('#'):
r, g, b = (
int(color[1:3], 16),
int(color[3:5], 16),
int(color[5:7], 16),
)
colors.append([r, g, b])
return colors
def replace_colors(paths, attrs, kmeans):
new_attrs = []
for attr in attrs:
new_attr = attr.copy()
if 'fill' in attr:
color = attr['fill']
if color.startswith('#'):
r, g, b = (
int(color[1:3], 16),
int(color[3:5], 16),
int(color[5:7], 16),
)
new_color = kmeans.predict([[r, g, b]])
new_color_hex = '#{:02x}{:02x}{:02x}'.format(
int(kmeans.cluster_centers_[new_color][0][0]),
int(kmeans.cluster_centers_[new_color][0][1]),
int(kmeans.cluster_centers_[new_color][0][2]),
)
new_attr['fill'] = new_color_hex
new_attrs.append(new_attr)
return new_attrs
Step 3: Main Function
The main function reduce_colors
that takes the input SVG file path, output SVG file path, and the desired number of colors as input.
def reduce_colors(input_svg, output_svg, n_colors):
paths, attrs = svg2paths(input_svg)
colors = extract_colors(paths, attrs)
kmeans = KMeans(n_clusters=n_colors)
kmeans.fit(colors)
new_attrs = replace_colors(paths, attrs, kmeans)
wsvg(paths, attributes=new_attrs, filename=output_svg)
Inside the reduce_colors
function, we first parse the input SVG file using svg2paths
to get the paths and attributes. We then extract the colors from the attributes using the extract_colors
function. Next, we initialize a KMeans
model with the desired number of colors and fit it to the extracted colors. We use the replace_colors
function to replace the colors in the SVG file with the colors predicted by the KMeans
model. Finally, we write the modified SVG file using wsvg
.
Step 4: Specify the Input and Output Files
Specify the paths of the input SVG file and the desired output SVG file, as well as the desired number of colors.
input_svg = 'input.svg'
output_svg = 'output.svg'
n_colors = 8
Step 5: Call the Main Function
Call the reduce_colors
function with the specified input SVG file, output SVG file, and number of colors.
reduce_colors(input_svg, output_svg, n_colors)
Complete code
from sklearn.cluster import KMeans
from svgpathtools import svg2paths, wsvg
def extract_colors(paths, attrs):
colors = []
for attr in attrs:
if 'fill' in attr:
color = attr['fill']
if color.startswith('#'):
r, g, b = (
int(color[1:3], 16),
int(color[3:5], 16),
int(color[5:7], 16),
)
colors.append([r, g, b])
return colors
def replace_colors(paths, attrs, kmeans):
new_attrs = []
for attr in attrs:
new_attr = attr.copy()
if 'fill' in attr:
color = attr['fill']
if color.startswith('#'):
r, g, b = (
int(color[1:3], 16),
int(color[3:5], 16),
int(color[5:7], 16),
)
new_color = kmeans.predict([[r, g, b]])
new_color_hex = '#{:02x}{:02x}{:02x}'.format(
int(kmeans.cluster_centers_[new_color][0][0]),
int(kmeans.cluster_centers_[new_color][0][1]),
int(kmeans.cluster_centers_[new_color][0][2]),
)
new_attr['fill'] = new_color_hex
new_attrs.append(new_attr)
return new_attrs
def reduce_colors(input_svg, output_svg, n_colors):
paths, attrs = svg2paths(input_svg)
colors = extract_colors(paths, attrs)
kmeans = KMeans(n_clusters=n_colors)
kmeans.fit(colors)
new_attrs = replace_colors(paths, attrs, kmeans)
wsvg(paths, attributes=new_attrs, filename=output_svg)
input_svg = 'input.svg'
output_svg = 'ouput.svg'
n_colors = 8
reduce_colors(input_svg, output_svg, n_colors)
Discover more content ?
Do you want to learn more and faster with dense and tailored technical resources ?