from __future__ import annotations
import streamlit as st
import argparse
import math
from PIL import Image
from io import BytesIO
import webcolors
# hex -> name
colors = {}
# hex -> number
max_cakes = {}
# List of segments per row
segments = []
named_colors = []
def closest_color(requested_color):
min_colors = {}
for name in webcolors.names():
r_c, g_c, b_c = webcolors.name_to_rgb(name)
rd = (r_c - requested_color[0]) ** 2
gd = (g_c - requested_color[1]) ** 2
bd = (b_c - requested_color[2]) ** 2
min_colors[(rd + gd + bd)] = name
return min_colors[min(min_colors.keys())]
def get_color_name(rgb_tuple):
try:
# Convert RGB to hex
hex_value = webcolors.rgb_to_hex(rgb_tuple)
# Get the color name directly
return webcolors.hex_to_name(hex_value)
except ValueError:
# If exact match not found, find the closest color
return closest_color(rgb_tuple)
class Segment:
def __init__(self, color, startIndex):
# hex
self.color = color
self.startIndex = startIndex
self.endIndex = -1
def __repr__(self):
return "["+ str(self.color) + ", (" + str(self.startIndex) + ", " + str(self.endIndex) + ")]"
def toMachineRow(self, imageWidth, ltor=True):
offset = math.ceil(imageWidth / 2.0)
left = abs(self.startIndex - offset)
if self.startIndex >= offset:
left = f"R{left + 1}"
else:
left = f"L{left}"
right = abs(self.endIndex - offset)
if self.endIndex >= offset:
right = f"R{right + 1}"
else:
right = f"L{right}"
if left == right:
return "Color **%s**, %s" % (getColor(self.color), left)
if ltor:
return "Color **%s**, %s to %s" % (getColor(self.color), left, right)
else:
return "Color **%s**, %s to %s" % (getColor(self.color), right, left)
def preprocess(image):
next_color = 0
for y in range(image.height):
row = []
segment = None
for x in range(image.width):
pixel = image.getpixel((x, y))
# Keep track of all of the colors we've seen
if pixel not in list(colors.keys()):
colors[pixel] = next_color
print(pixel[0:3])
print(get_color_name(pixel[0:3]))
named_colors.append(get_color_name(pixel[0:3]))
next_color += 1
# Mark the beginning and end of a segment
if not segment:
segment = Segment(pixel, x)
if pixel != segment.color:
segment.endIndex = x - 1
row.append(segment)
segment = Segment(pixel, x)
# Grab the last segment
segment.endIndex = image.width - 1
row.append(segment)
# Figure out how many cakes of each color we need
# for this row
cakes = {}
for item in row:
if item.color in list(cakes.keys()):
cakes[item.color] = cakes[item.color] + 1
else:
cakes[item.color] = 1
# Keep track of the max for later.
for color, count in list(cakes.items()):
if color in list(max_cakes.keys()):
max_cakes[color] = max(max_cakes[color], count)
else:
max_cakes[color] = count
segments.append(row)
def getColor(color):
index = colors[color]
if named_colors[index]:
return named_colors[index]
return str(index)
def main(image):
preprocess(image)
header = []
rows = []
header.append("## File info")
header.append("Image is **" + str(image.width) + "** by **"+str(image.height) + "** pixels. ")
start, end = (math.ceil(image.width / 2.0), math.floor(image.width / 2.0))
header.append("Expecting work between **L%d** and **R%d**." % (start, end))
header.append("## Colors")
header.append(str(len(list(colors.keys()))) + " colors. Indexed from the top left.")
for color, numCakes in list(max_cakes.items()):
header.append(" * Color **" + str(getColor(color)) + "**: " + str(numCakes) + " cakes")
row = len(segments) - 1
rows_seen = 0
while row >= 0:
row_text = []
ltor = (rows_seen % 2) == 0
row_text.append("Start on **%s**." % ("left" if ltor else "right"))
orderedSegments = segments[row] if ltor else reversed(segments[row])
for segment in orderedSegments:
row_text.append(" * " + segment.toMachineRow(image.width, ltor=ltor))
row -= 1
rows_seen +=1
rows.append(row_text)
return header, rows
print("\x1b[1;92mStreamlit script running...\x1b[0m")
st.title("Intarsia generator")
st.markdown("""Original code by [KnitFactoryImpl](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://www.youtube.com/%40KnitFactoryImpl&ved=2ahUKEwiQ8-mkqeaSAxW8UqQEHYtDBcQQFnoECA4QAQ&usg=AOvVaw2c2V_GRr-L8NtpsL2H3QuW):
[A script to parse an image file and output intarsia knitting instructions for machine knitting
](https://gist.github.com/clholgat/4d12246200255e90af1d081b3403449e)
""")
uploaded_file = st.file_uploader("Upload an image (PNG)")
if uploaded_file is not None:
print(uploaded_file)
bytes_data = uploaded_file.getvalue()
img = Image.open(BytesIO(bytes_data)).convert("RGBA") # RGB is what we want but it messes up colors for some reason
st.image(img, use_container_width=True)
if img.width > 250 or img.height > 1000:
st.markdown(f"## Image is too big! ({img.width}x{img.height})")
else:
header, rows = main(img)
print("done")
st.markdown("\n".join(header))
rownum = 1
for row in rows:
st.markdown(f"## Row {rownum}")
st.image(img.crop((0, img.height-rownum, img.width, img.height-rownum+1)), use_container_width=True)
st.markdown("\n".join(row))
rownum += 1
st.markdown(
"""
<style>
img {
border: 1px solid black;
image-rendering: pixelated;
}
</style>
""",
unsafe_allow_html=True,
)