From c6b3be51c370b467f21eb354267b5cb0ac388118 Mon Sep 17 00:00:00 2001 From: matei jordache Date: Mon, 30 Mar 2026 21:54:35 -0700 Subject: [PATCH] add tono watermark --- backend/nodes/save.py | 1 - frontend/src/App.jsx | 27 ++++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/backend/nodes/save.py b/backend/nodes/save.py index 1c306fd..f0e8f63 100644 --- a/backend/nodes/save.py +++ b/backend/nodes/save.py @@ -10,7 +10,6 @@ from backend.node_registry import register_node from backend.execution_context import emit_warning from backend.data_types import DataField, LineData, MeshModel, datafield_to_uint8, image_to_uint8 - @register_node(display_name="Save") class Save: @classmethod diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1181e57..2c7c5ee 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -14,6 +14,7 @@ import { pickNativeDirectorySelection, pickNativeFileSelection } from './nativeP import { toBlob } from 'html-to-image'; import { embedWorkflow, extractWorkflow } from './pngMetadata'; import { captureViewportBlob as captureWorkflowViewportBlob } from './workflowCapture'; +import tonoIconUrl from '../../resources/icon_1024.png'; import { hydrateWorkflowState } from './workflowHydration'; import { serializeWorkflowState } from './workflowSerialization'; import { sortNodesForParentOrder } from './nodeHierarchy.js'; @@ -1985,6 +1986,29 @@ function Flow() { }); }, [loadDefaultWorkflow]); + const stampLogoOnBlob = useCallback(async (blob) => { + const [img, logo] = await Promise.all([blob, tonoIconUrl].map((src) => new Promise((resolve, reject) => { + const el = new Image(); + el.onload = () => resolve(el); + el.onerror = reject; + el.src = typeof src === 'string' ? src : URL.createObjectURL(src); + }))); + + const canvas = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + + const margin = 16; + const size = Math.min(128, Math.floor(img.naturalWidth / 6), Math.floor(img.naturalHeight / 6)); + if (size >= 16) { + ctx.drawImage(logo, img.naturalWidth - size - margin, img.naturalHeight - size - margin, size, size); + } + + return new Promise((resolve) => canvas.toBlob(resolve, 'image/png')); + }, []); + const getWorkflowBlob = useCallback(async () => { const viewportEl = document.querySelector('.react-flow__viewport'); if (!viewportEl) throw new Error('Flow element not found'); @@ -2013,8 +2037,9 @@ function Flow() { }); if (!blob) throw new Error('Capture returned empty'); + const stampedBlob = await stampLogoOnBlob(blob); const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges()); - return embedWorkflow(blob, workflow); + return embedWorkflow(stampedBlob, workflow); }, [reactFlow]); const saveWorkflow = useCallback(async () => {