treadl/api/util/wif.py

537 lines
17 KiB
Python

import io
import configparser
import time
from threading import Thread
from PIL import Image, ImageDraw
from api import uploads
from util import database
def normalise_colour(max_color, triplet):
color_factor = 256 / max_color
components = triplet.split(",")
new_components = []
for component in components:
new_components.append(str(int(float(color_factor) * int(component))))
return ",".join(new_components)
def denormalise_colour(max_color, triplet):
color_factor = max_color / 256
components = triplet.split(",")
new_components = []
for component in components:
new_components.append(str(int(float(color_factor) * int(component))))
return ",".join(new_components)
def colour_tuple(triplet):
if not triplet:
return None
components = triplet.split(",")
return tuple(map(lambda c: int(c), components))
def darken_colour(c_tuple, val):
def darken(c):
c = c * val
if c < 0:
c = 0
if c > 255:
c = 255
return int(c)
return tuple(map(darken, c_tuple))
def get_colour_index(colours, colour):
for index, c in enumerate(colours):
if c == colour:
return index + 1
return 1
def dumps(obj):
if not obj or not obj["pattern"]:
raise Exception("Invalid pattern")
wif = []
wif.append("[WIF]")
wif.append("Version=1.1")
wif.append("Source Program=Treadl")
wif.append("Source Version=1")
wif.append("\n[CONTENTS]")
wif.append("COLOR PALETTE=true")
wif.append("TEXT=true")
wif.append("WEAVING=true")
wif.append("WARP=true")
wif.append("WARP COLORS=true")
wif.append("WEFT COLORS=true")
wif.append("WEFT=true")
wif.append("COLOR TABLE=true")
wif.append("THREADING=true")
wif.append("TIEUP=true")
wif.append("TREADLING=true")
wif.append("\n[TEXT]")
wif.append("Title={0}".format(obj["name"]))
wif.append("\n[COLOR TABLE]")
for index, colour in enumerate(obj["pattern"]["colours"]):
wif.append("{0}={1}".format(index + 1, denormalise_colour(999, colour)))
wif.append("\n[COLOR PALETTE]")
wif.append("Range=0,999")
wif.append("Entries={0}".format(len(obj["pattern"]["colours"])))
wif.append("\n[WEAVING]")
wif.append("Rising Shed=true")
wif.append("Treadles={0}".format(obj["pattern"]["weft"]["treadles"]))
wif.append("Shafts={0}".format(obj["pattern"]["warp"]["shafts"]))
wif.append("\n[WARP]")
wif.append("Units=centimeters")
wif.append(
"Color={0}".format(
get_colour_index(
obj["pattern"]["colours"], obj["pattern"]["warp"]["defaultColour"]
)
)
)
wif.append("Threads={0}".format(len(obj["pattern"]["warp"]["threading"])))
wif.append("Spacing=0.212")
wif.append("Thickness=0.212")
wif.append("\n[WARP COLORS]")
for index, thread in enumerate(obj["pattern"]["warp"]["threading"]):
if "colour" in thread:
wif.append(
"{0}={1}".format(
index + 1,
get_colour_index(obj["pattern"]["colours"], thread["colour"]),
)
)
wif.append("\n[THREADING]")
for index, thread in enumerate(obj["pattern"]["warp"]["threading"]):
wif.append("{0}={1}".format(index + 1, thread["shaft"]))
wif.append("\n[WEFT]")
wif.append("Units=centimeters")
wif.append(
"Color={0}".format(
get_colour_index(
obj["pattern"]["colours"], obj["pattern"]["weft"]["defaultColour"]
)
)
)
wif.append("Threads={0}".format(len(obj["pattern"]["weft"]["treadling"])))
wif.append("Spacing=0.212")
wif.append("Thickness=0.212")
wif.append("\n[WEFT COLORS]")
for index, thread in enumerate(obj["pattern"]["weft"]["treadling"]):
if "colour" in thread:
wif.append(
"{0}={1}".format(
index + 1,
get_colour_index(obj["pattern"]["colours"], thread["colour"]),
)
)
wif.append("\n[TREADLING]")
for index, thread in enumerate(obj["pattern"]["weft"]["treadling"]):
wif.append("{0}={1}".format(index + 1, thread["treadle"]))
wif.append("\n[TIEUP]")
for index, tieup in enumerate(obj["pattern"]["tieups"]):
wif.append("{0}={1}".format(str(index + 1), ",".join(str(x) for x in tieup)))
return "\n".join(wif)
def loads(wif_file):
config = configparser.ConfigParser(allow_no_value=True, strict=False)
config.read_string(wif_file.lower())
DEFAULT_TITLE = "Untitled Pattern"
draft = {}
if "text" in config:
text = config["text"]
draft["name"] = text.get("title") or DEFAULT_TITLE
if not draft.get("name"):
draft["name"] = DEFAULT_TITLE
max_color = 255
if "color palette" in config:
color_palette = config["color palette"]
color_range = color_palette.get("range").split(",")
max_color = int(color_range[1])
if "color table" in config:
color_table = config["color table"]
draft["colours"] = [None] * len(color_table)
for x in color_table:
draft["colours"][int(x) - 1] = normalise_colour(max_color, color_table[x])
if not draft.get("colours"):
draft["colours"] = []
if len(draft["colours"]) < 2:
draft["colours"] += [
normalise_colour(255, "255,255,255"),
normalise_colour(255, "0,0,255"),
]
weaving = config["weaving"]
threading = config["threading"]
warp = config["warp"]
draft["warp"] = {}
draft["warp"]["shafts"] = weaving.getint("shafts")
draft["warp"]["threading"] = []
if warp.get("color"):
warp_colour_index = warp.getint("color") - 1
draft["warp"]["defaultColour"] = draft["colours"][warp_colour_index]
else:
# In case of no color table or colour index out of bounds
draft["warp"]["defaultColour"] = draft["colours"][0]
for x in threading:
shaft = threading[x]
if "," in shaft:
shaft = shaft.split(",")[0]
shaft = int(shaft)
while int(x) >= len(draft["warp"]["threading"]) - 1:
draft["warp"]["threading"].append({"shaft": 0})
draft["warp"]["threading"][int(x) - 1] = {"shaft": shaft}
try:
warp_colours = config["warp colors"]
for x in warp_colours:
draft["warp"]["threading"][int(x) - 1]["colour"] = draft["colours"][
warp_colours.getint(x) - 1
]
except Exception:
pass
treadling = config["treadling"]
weft = config["weft"]
draft["weft"] = {}
draft["weft"]["treadles"] = weaving.getint("treadles")
draft["weft"]["treadling"] = []
if weft.get("color"):
weft_colour_index = weft.getint("color") - 1
draft["weft"]["defaultColour"] = draft["colours"][weft_colour_index]
else:
# In case of no color table or colour index out of bounds
draft["weft"]["defaultColour"] = draft["colours"][1]
for x in treadling:
shaft = treadling[x]
if "," in shaft:
shaft = shaft.split(",")[0]
shaft = int(shaft)
while int(x) >= len(draft["weft"]["treadling"]) - 1:
draft["weft"]["treadling"].append({"treadle": 0})
draft["weft"]["treadling"][int(x) - 1] = {"treadle": shaft}
try:
weft_colours = config["weft colors"]
for x in weft_colours:
draft["weft"]["treadling"][int(x) - 1]["colour"] = draft["colours"][
weft_colours.getint(x) - 1
]
except Exception:
pass
tieup = config["tieup"]
draft["tieups"] = [] # [0]*len(tieup)
for x in tieup:
while int(x) >= len(draft["tieups"]) - 1:
draft["tieups"].append([])
split = tieup[x].split(",")
try:
draft["tieups"][int(x) - 1] = [int(i) for i in split]
except Exception:
draft["tieups"][int(x) - 1] = []
return draft
def generate_images_thread(obj):
preview_image = draw_image(obj)
full_preview_image = draw_image(obj, with_plan=True)
if preview_image or full_preview_image:
db = database.get_db()
db.objects.update_one(
{"_id": obj["_id"]},
{
"$set": {
"preview": preview_image,
"fullPreview": full_preview_image,
}
},
)
def generate_images(obj):
thr = Thread(target=generate_images_thread, args=[obj])
thr.start()
def draw_image(obj, with_plan=False):
if not obj or not obj["pattern"]:
raise Exception("Invalid pattern")
BASE_SIZE = 10
pattern = obj["pattern"]
warp = pattern["warp"]
weft = pattern["weft"]
tieups = pattern["tieups"]
full_width = (
len(warp["threading"]) * BASE_SIZE
+ BASE_SIZE
+ weft["treadles"] * BASE_SIZE
+ BASE_SIZE
if with_plan
else len(warp["threading"]) * BASE_SIZE
)
full_height = (
warp["shafts"] * BASE_SIZE + len(weft["treadling"]) * BASE_SIZE + BASE_SIZE * 2
if with_plan
else len(weft["treadling"]) * BASE_SIZE
)
warp_top = 0
warp_left = 0
warp_right = len(warp["threading"]) * BASE_SIZE
warp_bottom = warp["shafts"] * BASE_SIZE + BASE_SIZE
weft_left = warp_right + BASE_SIZE
weft_top = warp["shafts"] * BASE_SIZE + BASE_SIZE * 2
weft_right = warp_right + BASE_SIZE + weft["treadles"] * BASE_SIZE + BASE_SIZE
weft_bottom = weft_top + len(weft["treadling"]) * BASE_SIZE
tieup_left = warp_right + BASE_SIZE
tieup_top = BASE_SIZE
tieup_right = tieup_left + weft["treadles"] * BASE_SIZE
tieup_bottom = warp_bottom
drawdown_top = warp_bottom + BASE_SIZE if with_plan else 0
drawdown_right = warp_right if with_plan else full_width
drawdown_left = warp_left if with_plan else 0
drawdown_bottom = weft_bottom if with_plan else full_height
WHITE = (255, 255, 255)
GREY = (150, 150, 150)
BLACK = (0, 0, 0)
img = Image.new("RGBA", (full_width, full_height), WHITE)
draw = ImageDraw.Draw(img)
# Draw warp
if with_plan:
draw.rectangle(
[(warp_left, warp_top), (warp_right, warp_bottom)],
fill=None,
outline=GREY,
width=1,
)
for y in range(1, warp["shafts"] + 1):
ycoord = y * BASE_SIZE
draw.line(
[
(warp_left, ycoord),
(warp_right, ycoord),
],
fill=GREY,
width=1,
joint=None,
)
for i, x in enumerate(range(len(warp["threading"]) - 1, 0, -1)):
thread = warp["threading"][i]
xcoord = x * BASE_SIZE
draw.line(
[
(xcoord, warp_top),
(xcoord, warp_bottom),
],
fill=GREY,
width=1,
joint=None,
)
if thread.get("shaft", 0) > 0:
ycoord = warp_bottom - (thread["shaft"] * BASE_SIZE)
draw.rectangle(
[(xcoord, ycoord), (xcoord + BASE_SIZE, ycoord + BASE_SIZE)],
fill=BLACK,
outline=None,
width=1,
)
colour = warp["defaultColour"]
if thread and thread.get("colour"):
colour = thread["colour"]
draw.rectangle(
[
(xcoord, warp_top),
(xcoord + BASE_SIZE, warp_top + BASE_SIZE),
],
fill=colour_tuple(colour),
)
# Draw weft
draw.rectangle(
[(weft_left, weft_top), (weft_right, weft_bottom)],
fill=None,
outline=GREY,
width=1,
)
for x in range(1, weft["treadles"] + 1):
xcoord = weft_left + x * BASE_SIZE
draw.line(
[
(xcoord, weft_top),
(xcoord, weft_bottom),
],
fill=GREY,
width=1,
joint=None,
)
for i, y in enumerate(range(0, len(weft["treadling"]))):
thread = weft["treadling"][i]
ycoord = weft_top + y * BASE_SIZE
draw.line(
[
(weft_left, ycoord),
(weft_right, ycoord),
],
fill=GREY,
width=1,
joint=None,
)
if thread.get("treadle", 0) > 0:
xcoord = weft_left + (thread["treadle"] - 1) * BASE_SIZE
draw.rectangle(
[(xcoord, ycoord), (xcoord + BASE_SIZE, ycoord + BASE_SIZE)],
fill=BLACK,
outline=None,
width=1,
)
colour = weft["defaultColour"]
if thread and thread.get("colour"):
colour = thread["colour"]
draw.rectangle(
[
(weft_right - BASE_SIZE, ycoord),
(weft_right, ycoord + BASE_SIZE),
],
fill=colour_tuple(colour),
)
# Draw tieups
draw.rectangle(
[(tieup_left, tieup_top), (tieup_right, tieup_bottom)],
fill=None,
outline=GREY,
width=1,
)
for y in range(1, warp["shafts"] + 1):
ycoord = y * BASE_SIZE
draw.line(
[
(tieup_left, ycoord),
(tieup_right, ycoord),
],
fill=GREY,
width=1,
joint=None,
)
for x, tieup in enumerate(tieups):
xcoord = tieup_left + x * BASE_SIZE
draw.line(
[
(xcoord, tieup_top),
(xcoord, tieup_bottom),
],
fill=GREY,
width=1,
joint=None,
)
for entry in tieup:
if entry > 0:
ycoord = tieup_bottom - (entry * BASE_SIZE)
draw.rectangle(
[(xcoord, ycoord), (xcoord + BASE_SIZE, ycoord + BASE_SIZE)],
fill=BLACK,
outline=None,
width=1,
)
# Draw drawdown
draw.rectangle(
[(drawdown_left, drawdown_top), (drawdown_right, drawdown_bottom)],
fill=None,
outline=(0, 0, 0),
width=1,
)
for y, weft_thread in enumerate(weft["treadling"]):
for x, warp_thread in enumerate(warp["threading"]):
# Ensure selected treadle and shaft is within configured pattern range
treadle = (
0
if weft_thread["treadle"] > weft["treadles"]
else weft_thread["treadle"]
)
shaft = 0 if warp_thread["shaft"] > warp["shafts"] else warp_thread["shaft"]
# Work out if should be warp or weft in "front"
tieup = tieups[treadle - 1] if treadle > 0 else []
tieup = [t for t in tieup if t <= warp["shafts"]]
thread_type = "warp" if shaft in tieup else "weft"
# Calculate current colour
weft_colour = weft_thread.get("colour") or weft.get("defaultColour")
warp_colour = warp_thread.get("colour") or warp.get("defaultColour")
colour = colour_tuple(warp_colour if thread_type == "warp" else weft_colour)
# Calculate drawdown coordinates
x1 = drawdown_right - (x + 1) * BASE_SIZE
x2 = drawdown_right - x * BASE_SIZE
y1 = drawdown_top + y * BASE_SIZE
y2 = drawdown_top + (y + 1) * BASE_SIZE
# Draw the thread, with shadow
d = [0.6, 0.8, 0.9, 1.1, 1.3, 1.3, 1.1, 0.9, 0.8, 0.6, 0.5]
if thread_type == "warp":
for i, grad_x in enumerate(range(x1, x2)):
draw.line(
[
(grad_x, y1),
(grad_x, y2),
],
fill=(darken_colour(colour, d[i])),
width=1,
joint=None,
)
else:
for i, grad_y in enumerate(range(y1, y2)):
draw.line(
[
(x1, grad_y),
(x2, grad_y),
],
fill=(darken_colour(colour, d[i])),
width=1,
joint=None,
)
in_mem_file = io.BytesIO()
img.save(in_mem_file, "PNG")
in_mem_file.seek(0)
file_name = "preview-{0}_{1}-{2}.png".format(
"full" if with_plan else "base", obj["_id"], int(time.time())
)
path = "projects/{}/{}".format(obj["project"], file_name)
uploads.upload_file(path, in_mem_file)
return file_name