add tono watermark
This commit is contained in:
@@ -10,7 +10,6 @@ from backend.node_registry import register_node
|
|||||||
from backend.execution_context import emit_warning
|
from backend.execution_context import emit_warning
|
||||||
from backend.data_types import DataField, LineData, MeshModel, datafield_to_uint8, image_to_uint8
|
from backend.data_types import DataField, LineData, MeshModel, datafield_to_uint8, image_to_uint8
|
||||||
|
|
||||||
|
|
||||||
@register_node(display_name="Save")
|
@register_node(display_name="Save")
|
||||||
class Save:
|
class Save:
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { pickNativeDirectorySelection, pickNativeFileSelection } from './nativeP
|
|||||||
import { toBlob } from 'html-to-image';
|
import { toBlob } from 'html-to-image';
|
||||||
import { embedWorkflow, extractWorkflow } from './pngMetadata';
|
import { embedWorkflow, extractWorkflow } from './pngMetadata';
|
||||||
import { captureViewportBlob as captureWorkflowViewportBlob } from './workflowCapture';
|
import { captureViewportBlob as captureWorkflowViewportBlob } from './workflowCapture';
|
||||||
|
import tonoIconUrl from '../../resources/icon_1024.png';
|
||||||
import { hydrateWorkflowState } from './workflowHydration';
|
import { hydrateWorkflowState } from './workflowHydration';
|
||||||
import { serializeWorkflowState } from './workflowSerialization';
|
import { serializeWorkflowState } from './workflowSerialization';
|
||||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||||
@@ -1985,6 +1986,29 @@ function Flow() {
|
|||||||
});
|
});
|
||||||
}, [loadDefaultWorkflow]);
|
}, [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 getWorkflowBlob = useCallback(async () => {
|
||||||
const viewportEl = document.querySelector('.react-flow__viewport');
|
const viewportEl = document.querySelector('.react-flow__viewport');
|
||||||
if (!viewportEl) throw new Error('Flow element not found');
|
if (!viewportEl) throw new Error('Flow element not found');
|
||||||
@@ -2013,8 +2037,9 @@ function Flow() {
|
|||||||
});
|
});
|
||||||
if (!blob) throw new Error('Capture returned empty');
|
if (!blob) throw new Error('Capture returned empty');
|
||||||
|
|
||||||
|
const stampedBlob = await stampLogoOnBlob(blob);
|
||||||
const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges());
|
const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges());
|
||||||
return embedWorkflow(blob, workflow);
|
return embedWorkflow(stampedBlob, workflow);
|
||||||
}, [reactFlow]);
|
}, [reactFlow]);
|
||||||
|
|
||||||
const saveWorkflow = useCallback(async () => {
|
const saveWorkflow = useCallback(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user