diff options
author | bobzel <zzzman@gmail.com> | 2025-08-14 11:00:14 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-08-14 11:00:14 -0400 |
commit | 467f1c2543626a50d48c84669cd408571260f147 (patch) | |
tree | 2cb3599c4ca95fe2b975deb8dc72283151e1c585 | |
parent | 5b0b7241e68febc2d0556b6c2e8349411b5c12a0 (diff) |
added background removal for images.
-rw-r--r-- | package-lock.json | 234 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 71 | ||||
-rw-r--r-- | src/server/server_Initialization.ts | 17 | ||||
-rw-r--r-- | src/workers/image.worker.ts | 16 | ||||
-rw-r--r-- | tsconfig.worker.json | 18 | ||||
-rw-r--r-- | webpack.worker.config.js | 30 |
7 files changed, 377 insertions, 15 deletions
diff --git a/package-lock.json b/package-lock.json index 9fc7665e1..56e847e0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@fullcalendar/multimonth": "^6.1.17", "@fullcalendar/react": "^6.1.17", "@fullcalendar/timegrid": "^6.1.15", + "@imgly/background-removal": "^1.7.0", "@internationalized/date": "^3.5.0", "@mozilla/readability": "^0.6.0", "@mui/icons-material": "^6.0.1", @@ -183,6 +184,7 @@ "nodemailer": "^6.9.7", "nodemon": "^3.0.2", "npm": "^11.3.0", + "onnxruntime-web": "^1.21.0-dev.20250206-d981b153d3", "openai": "^4.75.0", "p-limit": "^6.1.0", "parse-multipart-data": "^1.5.0", @@ -277,6 +279,7 @@ "webpack-hot-middleware": "^2.25.4", "wikijs": "^6.4.1", "words-to-numbers": "^1.5.1", + "worker-loader": "^3.0.8", "xmlbuilder": "^15.1.1", "xoauth2": "^1.2.0", "xregexp": "^5.1.1" @@ -4786,6 +4789,20 @@ "react": "*" } }, + "node_modules/@imgly/background-removal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@imgly/background-removal/-/background-removal-1.7.0.tgz", + "integrity": "sha512-/1ZryrMYg2ckIvJKoTu5Np50JfYMVffDMlVmppw/BdbN3pBTN7e6stI5/7E/LVh9DDzz6J588s7sWqul3fy5wA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "lodash-es": "^4.17.21", + "ndarray": "~1.0.0", + "zod": "^3.23.8" + }, + "peerDependencies": { + "onnxruntime-web": "1.21.0" + } + }, "node_modules/@internationalized/date": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz", @@ -6446,6 +6463,70 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@puppeteer/browsers": { "version": "2.10.6", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz", @@ -22374,6 +22455,12 @@ "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "25.2.10", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz", + "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==", + "license": "Apache-2.0" + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -23747,6 +23834,12 @@ "node": ">=14.0.0" } }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -25108,6 +25201,12 @@ "loose-envify": "^1.0.0" } }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "license": "MIT" + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -26731,6 +26830,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -28865,6 +28970,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "license": "MIT" }, + "node_modules/ndarray": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", + "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "license": "MIT", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, "node_modules/needle": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", @@ -31973,6 +32088,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnxruntime-common": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz", + "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==", + "license": "MIT" + }, + "node_modules/onnxruntime-web": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.21.0.tgz", + "integrity": "sha512-adzOe+7uI7lKz6pQNbAsLMQd2Fq5Jhmoxd8LZjJr8m3KvbFyiYyRxRiC57/XXD+jb18voppjeGAjoZmskXG+7A==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.21.0", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, "node_modules/open": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", @@ -32907,6 +33042,12 @@ "pathe": "^2.0.3" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, "node_modules/please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", @@ -33404,6 +33545,30 @@ "prosemirror-transform": "^1.1.0" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -40394,6 +40559,75 @@ "node": ">=4.0.0" } }, + "node_modules/worker-loader": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", + "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/worker-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/worker-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/worker-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/worker-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/workerpool": { "version": "9.3.3", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", diff --git a/package.json b/package.json index f06e94171..7b88ab5b7 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,13 @@ "scripts": { "start-release": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts", "start-release-debug": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts", - "start": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts", + "start": "npm run build-worker && copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts", "debug": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts", "monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", "test": "mocha -r ts-node/register test/**/*.ts", "tsc": "tsc -t es5", + "build-worker": "webpack --config webpack.worker.config.js", "storybook": "npm -w @dash/components run storybook", "build-storybook": "npm -w @dash/components run build-storybook", "lint:css": "stylelint '**/*.{css,scss}'" @@ -119,6 +120,7 @@ "@fullcalendar/multimonth": "^6.1.17", "@fullcalendar/react": "^6.1.17", "@fullcalendar/timegrid": "^6.1.15", + "@imgly/background-removal": "^1.7.0", "@internationalized/date": "^3.5.0", "@mozilla/readability": "^0.6.0", "@mui/icons-material": "^6.0.1", @@ -268,6 +270,7 @@ "nodemailer": "^6.9.7", "nodemon": "^3.0.2", "npm": "^11.3.0", + "onnxruntime-web": "^1.21.0-dev.20250206-d981b153d3", "openai": "^4.75.0", "p-limit": "^6.1.0", "parse-multipart-data": "^1.5.0", @@ -362,6 +365,7 @@ "webpack-hot-middleware": "^2.25.4", "wikijs": "^6.4.1", "words-to-numbers": "^1.5.1", + "worker-loader": "^3.0.8", "xmlbuilder": "^15.1.1", "xoauth2": "^1.2.0", "xregexp": "^5.1.1" diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 617a09ed5..cc747eb32 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { extname } from 'path'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon, returnTrue } from '../../../ClientUtils'; +import { ClientUtils, DashColor, imageUrlToBase64, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -16,10 +16,11 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast, ImageCastWithSuffix } from '../../../fields/Types'; +import { Cast, DocCast, ImageCast, ImageCastWithSuffix, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; +import { gptImageLabel } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils, FollowLinkScript } from '../../documents/DocUtils'; @@ -45,7 +46,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; -import { gptImageLabel } from '../../apis/gpt/GPT'; const DefaultPath = '/assets/unknown-file-icon-hi.png'; export class ImageEditorData { @@ -389,7 +389,59 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return cropping; }; - docEditorView = action(() => { + static _worker?: Worker; + static removeImgBackground = (doc: Doc, addDoc: (doc: Doc | Doc[], annotationKey?: string) => boolean, docImgPath: string) => { + ImageEditorData.AddDoc = addDoc; + ImageEditorData.RootDoc = doc; + if (ImageBox._worker) return ImageBox._worker; + const worker = new Worker('/image.worker.js', { type: 'module' }); + worker.onmessage = async (event: MessageEvent) => { + const { success, result, error } = event.data; + if (success) { + const blobToDataURL = (blob: any) => { + return new Promise<string | ArrayBuffer | null>((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = error => reject(error); + reader.readAsDataURL(blob); + }); + }; + blobToDataURL(result).then(durl => { + ClientUtils.convertDataUri(durl as string, doc[Id] + '_noBgd').then(url => { + const width = NumCast(doc._width) || 1; + const height = NumCast(doc._height); + const imageSnapshot = Docs.Create.ImageDocument(url, { + _nativeWidth: Doc.NativeWidth(doc), + _nativeHeight: Doc.NativeHeight(doc), + x: NumCast(doc.x) + width, + y: NumCast(doc.y), + _width: 150, + _height: (height / width) * 150, + title: 'bgremoved:' + doc.title, + }); + Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(doc)); + Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(doc)); + addDoc?.(imageSnapshot); + }); + }); + } else { + console.error('Error in background removal:', error); + } + // worker.terminate(); + }; + worker.onerror = (e: ErrorEvent) => console.error('Worker failed:', e); // worker.terminate(); + + worker.postMessage({ imagePath: docImgPath }); + return worker; + }; + removeBackground = () => { + const field = ImageCast(this.dataDoc[this.fieldKey]); + if (field && this._props.addDocument) { + ImageBox.removeImgBackground(this.rootDoc, this._props.addDocument, this.choosePath(field.url)); + } + }; + + docEditorView = () => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { ImageEditorData.Open = true; @@ -397,7 +449,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ImageEditorData.AddDoc = this._props.addDocument; ImageEditorData.RootDoc = this.Document; } - }); + }; @observable _showOutpaintPrompt: boolean = false; @observable _outpaintPromptInput: string = 'Extend this image naturally with matching content'; @@ -433,7 +485,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action processOutpaintingWithPrompt = async (customPrompt: string) => { - const field = Cast(this.dataDoc[this.fieldKey], ImageField); + const field = ImageCast(this.dataDoc[this.fieldKey]); if (!field) return; // Set flag that outpainting is in progress @@ -618,7 +670,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); specificContextMenu = (): void => { - const field = Cast(this.dataDoc[this.fieldKey], ImageField); + const field = ImageCast(this.dataDoc[this.fieldKey]); if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); @@ -629,13 +681,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { file: (file => { const ext = file ? extname(file) : ''; return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href), + })(ImageCast(this.Document[this.fieldKey])?.url.href), }).then(text => alert(text)); }, icon: 'expand-arrows-alt', }); // prettier-ignore 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' }); this.layoutDoc.ai && funcs.push({ description: 'Regenerate AI Image', @@ -800,7 +853,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } @computed get paths() { - const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc + const field = ImageCast(this.dataDoc[this.fieldKey], new ImageField(StrCast(this.dataDoc[this.fieldKey]))); // retrieve the primary image URL that is being rendered from the data doc const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images const defaultUrl = new URL(ClientUtils.prepend(DefaultPath)); const altpaths = diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 5deb66caf..7915938b7 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import * as bodyParser from 'body-parser'; import { blue, yellow } from 'colors'; import * as flash from 'connect-flash'; @@ -6,22 +7,22 @@ import * as express from 'express'; import * as expressFlash from 'express-flash'; import * as session from 'express-session'; import { createServer } from 'https'; +import { JSDOM } from 'jsdom'; import * as passport from 'passport'; import * as webpack from 'webpack'; import * as wdm from 'webpack-dev-middleware'; import * as whm from 'webpack-hot-middleware'; import * as config from '../../webpack.config'; +import * as workerConfig from '../../webpack.worker.config'; import { logPort } from './ActionUtilities'; import RouteManager from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import { publicDirectory, resolvedPorts } from './SocketData'; +import { setupDynamicToolsAPI } from './api/dynamicTools'; import { SSL } from './apis/google/CredentialsLoader'; import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager'; import { Database } from './database'; import { WebSocket } from './websocket'; -import axios from 'axios'; -import { JSDOM } from 'jsdom'; -import { setupDynamicToolsAPI } from './api/dynamicTools'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ @@ -126,13 +127,12 @@ function registerCorsProxy(server: express.Express) { //res.status(400).json({ error: 'Invalid URL format' }); return; } - const isBinary = /\.(gif|png|jpe?g|bmp|webp|ico|pdf|zip|mp3|mp4|wav|ogg)$/i.test(targetUrl as string); const responseType = isBinary ? 'arraybuffer' : 'text'; const response = await axios.get(targetUrl as string, { headers: { 'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0' }, - responseType: responseType + responseType: responseType, }); const baseUrl = new URL(targetUrl as string); @@ -200,8 +200,13 @@ function registerAuthenticationRoutes(server: express.Express) { export default async function InitializeServer(routeSetter: RouteSetter) { const isRelease = determineEnvironment(); const app = buildWithMiddleware(express()); + const workerCompiler = webpack(workerConfig as webpack.Configuration); const compiler = webpack(config as webpack.Configuration); + if (!compiler) throw new Error('Webpack compiler is not defined. Please check your webpack configuration.'); + if (!workerCompiler) throw new Error('Webpack worker compiler is not defined. Please check your webpack worker configuration.'); + // print out contents of virtual output filesystem used in development + // compiler.outputFileSystem?.readdir?.(config.output.path, (err, files) => (err ? console.error('Error reading virtual output path:', err) : console.log('Files in virtual output path:', files))); // Default route app.get('/', (req, res) => { res.redirect(req.user ? '/home' : '/login'); //res.send('This is the default route.'); @@ -209,6 +214,8 @@ export default async function InitializeServer(routeSetter: RouteSetter) { // route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request app.use(wdm(compiler, { publicPath: config.output.publicPath })); app.use(whm(compiler)); + app.use(wdm(workerCompiler, { publicPath: workerConfig.output.publicPath })); + app.use(whm(workerCompiler)); app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc) // app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); diff --git a/src/workers/image.worker.ts b/src/workers/image.worker.ts new file mode 100644 index 000000000..d069742f3 --- /dev/null +++ b/src/workers/image.worker.ts @@ -0,0 +1,16 @@ +import { removeBackground } from '@imgly/background-removal'; + +self.onmessage = async (event: MessageEvent) => { + const { imagePath, doc, addDoc } = event.data; + + try { + // Perform the background removal + const result = await removeBackground(imagePath); + + // Send the result back to the main thread + self.postMessage({ success: true, result, doc, addDoc }); + } catch (error) { + // Send the error back to the main thread + self.postMessage({ success: false, error: (error as any).message }); + } +}; diff --git a/tsconfig.worker.json b/tsconfig.worker.json new file mode 100644 index 000000000..f58377c2e --- /dev/null +++ b/tsconfig.worker.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist/worker", + "noEmit": false, + "jsx": "react-jsx", // or "react" if you're not using the new JSX transform + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "sourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["src/workers/**/*", "src/utils/**/*"], + "exclude": ["node_modules", "src/components/**/*.stories.tsx", "src/components/**/*.test.tsx", "src/server", "static"] +} diff --git a/webpack.worker.config.js b/webpack.worker.config.js new file mode 100644 index 000000000..f2a2da199 --- /dev/null +++ b/webpack.worker.config.js @@ -0,0 +1,30 @@ +// webpack.worker.config.js +// eslint-disable-next-line @typescript-eslint/no-require-imports +const path = require('path'); + +module.exports = { + mode: 'development', + entry: './src/workers/image.worker.ts', // 👈 Adjust path to your worker file + output: { + filename: 'image.worker.js', + publicPath: '/', + path: path.resolve(__dirname, 'build'), + }, + experiments: { + outputModule: true, + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: 'ts-loader', + options: { + configFile: 'tsconfig.worker.json', + }, + }, + ], + }, +}; |