Processing image...

packages = ["numpy", "Pillow"] import js import asyncio from pyodide.ffi import create_proxy, to_js import numpy as np from PIL import Image, ImageDraw, ImageFont import io import base64 # ... (The entire Python script from our last step goes here) ... # (This is the same proven Python code that works) binary_map = None pil_image_original = None new_w, new_h = 0, 0 CELL_SIZE_PX = 15 GUI_HIGHLIGHT_COLOR = "rgba(109, 89, 122, 0.7)" # Adjusted highlight to match theme async def process_and_draw(): global binary_map, new_w, new_h loader.style.display = "block" output_div.style.display = "none" await asyncio.sleep(0.01) if pil_image_original is None: loader.style.display = "none" return max_cells = int(max_cells_slider.value) threshold_value = int(threshold_slider.value) img_pil = pil_image_original.convert("L") w, h = img_pil.size scale = min(max_cells / w, max_cells / h) new_w, new_h = int(w * scale), int(h * scale) img_resized = img_pil.resize((new_w, new_h), Image.LANCZOS) img_np = np.array(img_resized) binary_map = np.where(img_np > threshold_value, 255, 0).astype(np.uint8) draw_grid_on_canvas() loader.style.display = "none" output_div.style.display = "block" def draw_grid_on_canvas(): canvas = js.document.getElementById("chart-canvas") ctx = canvas.getContext("2d") canvas_w = new_w * CELL_SIZE_PX canvas_h = new_h * CELL_SIZE_PX canvas.width = canvas_w canvas.height = canvas_h ctx.fillStyle = "#2d2d34" ctx.fillRect(0, 0, canvas_w, canvas_h) ctx.font = f"{int(CELL_SIZE_PX * 0.8)}px Courier" ctx.fillStyle = "#f0f0f0" ctx.textAlign = "center" ctx.textBaseline = "middle" for r in range(new_h): for c in range(new_w): if binary_map[r, c] == 0: ctx.fillText("x", c * CELL_SIZE_PX + CELL_SIZE_PX / 2, r * CELL_SIZE_PX + CELL_SIZE_PX / 2) ctx.strokeStyle = "#4a4a52" ctx.lineWidth = 1 for r in range(new_h + 1): ctx.beginPath() ctx.moveTo(0, r * CELL_SIZE_PX) ctx.lineTo(canvas_w, r * CELL_SIZE_PX) ctx.stroke() for c in range(new_w + 1): ctx.beginPath() ctx.moveTo(c * CELL_SIZE_PX, 0) ctx.lineTo(c * CELL_SIZE_PX, canvas_h) ctx.stroke() def on_canvas_click(event): canvas = js.document.getElementById("chart-canvas") ctx = canvas.getContext("2d") draw_grid_on_canvas() rect = canvas.getBoundingClientRect() x = event.clientX - rect.left y = event.clientY - rect.top col = int(x // CELL_SIZE_PX) row = int(y // CELL_SIZE_PX) if 0 <= row < new_h and 0 <= col < new_w: if binary_map[row, col] == 0: start_col = col while start_col > 0 and binary_map[row, start_col - 1] == 0: start_col -= 1 end_col = col while end_col < new_w - 1 and binary_map[row, end_col + 1] == 0: end_col += 1 count = (end_col - start_col) + 1 info_bar.innerText = f"Row {row+1}, Col {col+1} | Stitches in this group: {count}" ctx.fillStyle = GUI_HIGHLIGHT_COLOR highlight_width = (end_col - start_col + 1) * CELL_SIZE_PX ctx.fillRect(start_col * CELL_SIZE_PX, row * CELL_SIZE_PX, highlight_width, CELL_SIZE_PX) else: info_bar.innerText = f"Row {row+1}, Col {col+1} | Empty cell" def download_chart(event): BG_COLOR = (245, 240, 230) GRID_COLOR = (220, 215, 205) SYMBOL_COLOR = (20, 20, 20) GRID_SIZE = 15; FONT_SIZE = 12; SYMBOL = "x" canvas_w = new_w * GRID_SIZE canvas_h = new_h * GRID_SIZE out_img = Image.new("RGB", (canvas_w, canvas_h), BG_COLOR) draw = ImageDraw.Draw(out_img) for x in range(0, canvas_w + 1, GRID_SIZE): draw.line([(x, 0), (x, canvas_h)], fill=GRID_COLOR, width=1) for y in range(0, canvas_h + 1, GRID_SIZE): draw.line([(0, y), (canvas_w, y)], fill=GRID_COLOR, width=1) try: font = ImageFont.truetype("cour.ttf", FONT_SIZE) except IOError: font = ImageFont.load_default() for j in range(new_h): for i in range(new_w): if binary_map[j, i] == 0: cx = i * GRID_SIZE + GRID_SIZE // 2 cy = j * GRID_SIZE + GRID_SIZE // 2 draw.text((cx, cy), SYMBOL, fill=SYMBOL_COLOR, font=font, anchor="mm") buffer = io.BytesIO() out_img.save(buffer, format="PNG") img_str = base64.b64encode(buffer.getvalue()).decode("utf-8") data_url = "data:image/png;base64," + img_str link = js.document.createElement('a') link.href = data_url link.download = "knitting_chart.png" link.click() async def on_file_change(event): global pil_image_original file_list = event.target.files if not file_list: return file_data = await file_list.item(0).arrayBuffer() pil_image_original = Image.open(io.BytesIO(file_data.to_py())) await process_and_draw() def update_max_cells_label(event): js.document.getElementById("max-cells-value").innerText = max_cells_slider.value def update_threshold_label(event): js.document.getElementById("threshold-value").innerText = threshold_slider.value def setup(): global image_upload, max_cells_slider, threshold_slider, info_bar, output_div, loader image_upload = js.document.getElementById("image-upload") max_cells_slider = js.document.getElementById("max-cells-slider") threshold_slider = js.document.getElementById("threshold-slider") info_bar = js.document.getElementById("info-bar") output_div = js.document.getElementById("output") loader = js.document.getElementById("loader") image_upload.addEventListener("change", create_proxy(on_file_change)) max_cells_slider.addEventListener("input", create_proxy(update_max_cells_label)) threshold_slider.addEventListener("input", create_proxy(update_threshold_label)) max_cells_slider.addEventListener("change", create_proxy(lambda e: asyncio.ensure_future(process_and_draw()))) threshold_slider.addEventListener("change", create_proxy(lambda e: asyncio.ensure_future(process_and_draw()))) js.document.getElementById("chart-canvas").addEventListener("click", create_proxy(on_canvas_click)) js.document.getElementById("download-button").addEventListener("click", create_proxy(download_chart)) print("Knitting Chart Generator Initialized.") setup()