aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/.DS_Storebin10244 -> 10244 bytes
-rw-r--r--src/client/util/CurrentUserUtils.ts6
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts46
-rw-r--r--src/client/views/global/globalScripts.ts10
-rw-r--r--src/client/views/nodes/ImageBox.tsx49
-rw-r--r--src/workers/image.worker.ts41
6 files changed, 119 insertions, 33 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index a3c57385c..ffaca7e1b 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9fbc82bef..79942d7ee 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -834,8 +834,10 @@ pie title Minerals in my tap water
}
static imageTools() {
return [
- { title: "Pixels",toolTip: "Set Native Pixel Size", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }},
- { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }},
+ { title: "Pixels", toolTip: "Set Native Pixel Size", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }},
+ { title: "Rotate", toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }},
+ { title: "NoBkgd", toolTip: "Remove Background", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageRemoveBackground();' }},
+ { title: "MaskFgd",toolTip: "Mask Foreground", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageMaskForeground();' }},
];
}
static contextMenuTools():Button[] {
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 23102e051..72c1b468a 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -42,14 +42,14 @@ export namespace ImageUtils {
reader.readAsDataURL(blob as Blob);
});
}
- export function createImageDocFromBlob(blob: Blob | undefined, options: DocumentOptions & { _nativeWidth: number; _nativeHeight: number }, filename: string): Promise<Doc> {
+ export function createImageDocFromBlob(blob: Blob | undefined, options: DocumentOptions & { _nativeWidth: number; _nativeHeight: number }, filename: string, overwriteDoc?: Doc): Promise<Doc> {
return new Promise((resolve, reject) => {
if (!blob) return reject('No image blob provided');
convertImgBlobToDataURL(blob)
.then(durl => {
ClientUtils.convertDataUri(durl as string, filename)
.then(url => {
- const imageSnapshot = Docs.Create.ImageDocument(url, options);
+ const imageSnapshot = Docs.Create.ImageDocument(url, options, overwriteDoc);
Doc.SetNativeWidth(imageSnapshot[DocData], options._nativeWidth);
Doc.SetNativeHeight(imageSnapshot[DocData], options._nativeHeight);
resolve(imageSnapshot);
@@ -71,9 +71,14 @@ export namespace ImageUtils {
workerCallbackMap.delete(docId); // worker.terminate();
};
backgroundRemovalWorker.onmessage = async (event: MessageEvent) => {
- const map = workerCallbackMap.get(event.data.docId);
- event.data.success ? map?.res(event.data.result) : map?.rej();
- workerCallbackMap.delete(event.data.docId); // worker.terminate();
+ if (event.data.type === 'progress') {
+ // Handle progress updates if needed
+ console.log(`Progress for docId ${event.data.docId}: ${event.data.progress}`);
+ } else {
+ const map = workerCallbackMap.get(event.data.docId);
+ event.data.success ? map?.res(event.data.result) : map?.rej();
+ workerCallbackMap.delete(event.data.docId); // worker.terminate();
+ }
};
return backgroundRemovalWorker;
}
@@ -81,7 +86,36 @@ export namespace ImageUtils {
return new Promise<Blob | undefined>((res, rej) => {
if (!imagePath) return rej('No image path provided');
workerCallbackMap.set(docId, { res, rej }); // Store the callback by docId (or use a unique requestId)
- getBackgroundRemovalWorker().postMessage({ imagePath, docId });
+ getBackgroundRemovalWorker().postMessage({
+ imagePath,
+ docId,
+ config: {
+ output: {
+ quality: 0.8, // The quality. (Default: 0.8)
+ },
+ },
+ });
+ });
+ }
+ export function maskForeground(docId: string, imagePath: string) {
+ return new Promise<Blob | undefined>((res, rej) => {
+ if (!imagePath) return rej('No image path provided');
+ workerCallbackMap.set(docId, { res, rej }); // Store the callback by docId (or use a unique requestId)
+ getBackgroundRemovalWorker().postMessage({
+ imagePath,
+ docId,
+ config: {
+ //publicPath: string; // The public path used for model and wasm files. Default: 'https://staticimgly.com/${PACKAGE_NAME}-data/${PACKAGE_VERSION}/dist/'
+ //debug: bool; // enable or disable useful console.log outputs
+ //device: 'gpu', // 'cpu' | 'gpu'; // choose the execution device. gpu will use webgpu if available
+ //model: 'isnet' | 'isnet_fp16' | 'isnet_quint8'; // The model to use. (Default "isnet_fp16")
+ output: {
+ //format: 'image/png' | 'image/jpeg' | 'image/webp'; // The output format. (Default "image/png")
+ quality: 0.8, // The quality. (Default: 0.8)
+ type: 'mask', // 'foreground' | 'background' | 'mask'; // The output type. (Default "foreground")
+ },
+ },
+ });
});
}
}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index e098d50d8..981c4d111 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -553,6 +553,16 @@ ScriptingGlobals.add(function videoSnapshot() {
});
// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function imageMaskForeground() {
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
+ selected?.maskForeground();
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function imageRemoveBackground() {
+ const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
+ selected?.removeBackground();
+});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function imageSetPixelSize() {
const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
selected?.setNativeSize();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e62718ec4..66ab858e2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -389,27 +389,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._props.bringToFront?.(cropping);
return cropping;
};
- removeBackground = () => {
- const batch = UndoManager.StartBatch('remove image background');
- ImageUtils.removeImgBackground(this.Document[Id], this.choosePath(ImageCast(this.dataDoc[this.fieldKey])?.url)).then(imgBlob =>
- ImageUtils.createImageDocFromBlob(
- imgBlob,
- {
- _nativeWidth: Doc.NativeWidth(this.Document),
- _nativeHeight: Doc.NativeHeight(this.Document),
- x: NumCast(this.Document.x) + NumCast(this.Document._width),
- y: NumCast(this.Document.y),
- _width: NumCast(this.Document._width),
- _height: (NumCast(this.Document._height) / (NumCast(this.Document._width) || 1)) * NumCast(this.Document._width),
- title: 'bgdRemoved:' + this.Document.title,
- },
- this.Document[Id] + '_noBgd'
- ).then(imageSnapshot => {
- this._props.addDocument?.(imageSnapshot);
- batch.end();
- })
- );
+ createLoadingDoc = () => {
+ const loading = Docs.Create.LoadingDocument('background removed', {
+ x: NumCast(this.Document.x) + NumCast(this.Document._width),
+ y: NumCast(this.Document.y),
+ backgroundColor: 'transparent',
+ _width: NumCast(this.Document._width),
+ _height: (NumCast(this.Document._height) / (NumCast(this.Document._width) || 1)) * NumCast(this.Document._width),
+ title: 'bgdRemoved:' + this.Document.title,
+ });
+ Doc.addCurrentlyLoading(loading);
+ this._props.addDocument?.(loading);
+ return loading;
};
+ replaceImage = (imgBlob: Blob | undefined, loading: Doc) => {
+ const batch2 = UndoManager.StartBatch('remove mask background');
+ ImageUtils.createImageDocFromBlob(imgBlob, { _nativeWidth: Doc.NativeWidth(this.Document), _nativeHeight: Doc.NativeHeight(this.Document) }, this.Document[Id] + '_fgdMask', loading).then(() => {
+ Doc.removeCurrentlyLoading(loading);
+ batch2.end();
+ });
+ };
+ removeBackground = undoable(() => {
+ const loading = this.createLoadingDoc();
+ ImageUtils.removeImgBackground(this.Document[Id], this.choosePath(ImageCast(this.dataDoc[this.fieldKey])?.url)).then(imageBlob => this.replaceImage(imageBlob, loading));
+ }, 'create image background placeholder');
+
+ maskForeground = undoable(() => {
+ const loading = this.createLoadingDoc();
+ ImageUtils.maskForeground(this.Document[Id], this.choosePath(ImageCast(this.dataDoc[this.fieldKey])?.url)).then(imageBlob => this.replaceImage(imageBlob, loading));
+ }, 'create image mask placeholder');
docEditorView = () => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
@@ -659,6 +667,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
funcs.push({ description: 'Remove Background', event: this.removeBackground, icon: 'pencil-alt' });
+ funcs.push({ description: 'Mask Foreground', event: this.maskForeground, icon: 'pencil-alt' });
this.layoutDoc.ai &&
funcs.push({
description: 'Regenerate AI Image',
diff --git a/src/workers/image.worker.ts b/src/workers/image.worker.ts
index 48b4e8585..eb878e336 100644
--- a/src/workers/image.worker.ts
+++ b/src/workers/image.worker.ts
@@ -1,14 +1,45 @@
-import { removeBackground } from '@imgly/background-removal';
+import { removeBackground, segmentForeground, preload, Config } from '@imgly/background-removal';
+
+// Preload the model when the worker starts
+let modelLoaded = false;
+preload()
+ .then(() => {
+ modelLoaded = true;
+ console.log('Background removal model preloaded successfully.');
+ })
+ .catch(error => {
+ console.error('Failed to preload background removal model:', error);
+ });
self.onmessage = async (event: MessageEvent) => {
- const { imagePath, options, nativeWidth, nativeHeight, docId } = event.data;
+ const { imagePath, config, docId } = event.data;
+ const configWithDefaults: Config = {
+ ...config,
+ debug: true,
+ // You can set default config values here if needed
+ onProgress: (progress: number) => {
+ console.log('Progress: ' + progress);
+ // Send progress updates to the main thread
+ self.postMessage({ type: 'progress', docId, progress });
+ },
+ };
try {
- // Perform the background removal
- const result = await removeBackground(imagePath);
+ // Ensure the model is preloaded before processing
+ if (!modelLoaded) {
+ await preload(configWithDefaults);
+ modelLoaded = true;
+ }
+ // Simulate progress updates (if the library doesn't provide them natively)
+ self.postMessage({ type: 'progress', docId, progress: 0 });
+
+ const resultProm =
+ config.output.type === 'mask'
+ ? segmentForeground(imagePath, configWithDefaults) //
+ : removeBackground(imagePath, configWithDefaults);
// Send the result back to the main thread
- self.postMessage({ success: true, result, options, nativeWidth, nativeHeight, docId });
+ self.postMessage({ success: true, result: await resultProm, docId });
} catch (error) {
// Send the error back to the main thread
// eslint-disable-next-line @typescript-eslint/no-explicit-any