diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa2484..3275fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.5.0 + +### Feat + +- save as image supports custom configurations. Rotate, Scale, Crop +- add transformers.js +- support removing background for uploading images (browser needs to support WebGPU) + # 1.4.1 ### Feat diff --git a/README.md b/README.md index b5d9c8f..aaebb81 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ Link: [https://songlh.top/paint-board/](https://songlh.top/paint-board/) + Multifunction Menu - The bottom left button shows the current zoom ratio in real time, click it to reset the zoom ratio. - The list of buttons in the center, in order from left to right, are: Undo, Redo, Copy Current Selection, Delete Current Selection, Draw Text, Upload Image, Clear Drawing, Save as Image, and Open File List. + - Support removing background for uploading images (browser needs to support WebGPU) + - Save as image supports custom configurations. Rotate, Scale, Crop - PC: - Hold down the Space key and click the left mouse button to move the canvas, scroll the mouse wheel to zoom the canvas. - Press and hold the Backspace key to delete the selection. diff --git a/README.zh.md b/README.zh.md index e4badb2..3380cd2 100644 --- a/README.zh.md +++ b/README.zh.md @@ -54,7 +54,9 @@ Link: [https://songlh.top/paint-board/](https://songlh.top/paint-board/) - 新增辅助线绘制功能。 + 多功能菜单 - 左下角按钮实时显示当前缩放比例,点击即可重置缩放比例。 - - 中间按钮列表按从左到右的功能分别为:撤销、反撤销、复制当前选择内容、删除当前选择内容、绘制文字、上传图片、清除绘制内容、保存为图片、打开文件列表。 + - 中间按钮列表按从左到右的功能分别为:撤销、反撤销、复制当前选择内容、删除当前选择内容、绘制文字、上传图片、清除绘制内容、保存为图片、打开文件列表。 + - 上传图片支持去除背景(浏览器需支持WebGPU) + - 保存为图片支持自定义配置. 旋转, 缩放, 裁切 - 电脑端: - 按住 Space 键并点击鼠标左键可移动画布,滚动鼠标滚轮实现画布缩放。 - 按住 Backspace 键可删除已选内容。 diff --git a/package.json b/package.json index d8c551f..d7c08af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "paint-board", "private": true, - "version": "1.4.1", + "version": "1.5.0", "type": "module", "scripts": { "dev": "vite", @@ -12,6 +12,7 @@ "lint:style": "stylelint --fix \"src/**/*.css\"" }, "dependencies": { + "@huggingface/transformers": "^3.0.0", "daisyui": "^2.46.1", "fabric": "^5.3.0", "i18next": "^22.4.9", @@ -22,6 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^12.1.5", + "react-image-crop": "^11.0.7", "swiper": "^11.0.5", "uuid": "^9.0.1", "zustand": "^4.4.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74e5e03..fe14535 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@huggingface/transformers': + specifier: ^3.0.0 + version: 3.0.0 daisyui: specifier: ^2.46.1 version: 2.46.1(autoprefixer@10.4.13)(postcss@8.4.21)(ts-node@10.9.1) @@ -35,6 +38,9 @@ dependencies: react-i18next: specifier: ^12.1.5 version: 12.1.5(i18next@22.4.9)(react-dom@18.2.0)(react@18.2.0) + react-image-crop: + specifier: ^11.0.7 + version: 11.0.7(react@18.2.0) swiper: specifier: ^11.0.5 version: 11.0.5 @@ -261,7 +267,7 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 dev: true /@babel/helper-compilation-targets@7.22.15: @@ -796,6 +802,14 @@ packages: postcss-selector-parser: 6.0.11 dev: true + /@emnapi/runtime@1.3.1: + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + requiresBuild: true + dependencies: + tslib: 2.4.1 + dev: false + optional: true + /@esbuild/android-arm@0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -831,6 +845,20 @@ packages: - supports-color dev: true + /@huggingface/jinja@0.3.1: + resolution: {integrity: sha512-SbcBWUKDQ76lzlVYOloscUk0SJjuL1LcbZsfQv/Bxxc7dwJMYuS+DAQ+HhVw6ZkTFXArejaX5HQRuCuleYwYdA==} + engines: {node: '>=18'} + dev: false + + /@huggingface/transformers@3.0.0: + resolution: {integrity: sha512-OWIPnTijAw4DQ+IFHBOrej2SDdYyykYlTtpTLCEt5MZq/e9Cb65RS2YVhdGcgbaW/6JAL3i8ZA5UhDeWGm4iRQ==} + dependencies: + '@huggingface/jinja': 0.3.1 + onnxruntime-node: 1.19.2 + onnxruntime-web: 1.20.0-dev.20241016-2b8fc5529b + sharp: 0.33.5 + dev: false + /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -851,6 +879,217 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@img/sharp-darwin-arm64@0.33.5: + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.33.5: + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.4: + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.4: + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.4: + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.0.5: + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.4: + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.0.4: + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.4: + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.4: + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.33.5: + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-arm@0.33.5: + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.33.5: + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-x64@0.33.5: + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.5: + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.33.5: + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-wasm32@0.33.5: + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.3.1 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.33.5: + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.33.5: + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + + /@isaacs/fs-minipass@4.0.1: + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + dependencies: + minipass: 7.1.2 + dev: false + /@jridgewell/gen-mapping@0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -898,14 +1137,14 @@ packages: hasBin: true requiresBuild: true dependencies: - detect-libc: 2.0.2 + detect-libc: 2.0.3 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0 nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.6.3 tar: 6.2.0 transitivePeerDependencies: - encoding @@ -931,6 +1170,56 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@rollup/pluginutils@4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -1441,7 +1730,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -1455,12 +1743,10 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -1588,6 +1874,12 @@ packages: balanced-match: 1.0.2 concat-map: 0.0.1 + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -1720,6 +2012,11 @@ packages: dev: false optional: true + /chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + dev: false + /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -1936,7 +2233,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /css-functions-list@3.1.0: resolution: {integrity: sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==} @@ -2078,12 +2374,10 @@ packages: dev: false optional: true - /detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - requiresBuild: true dev: false - optional: true /detective@5.2.1: resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} @@ -2155,7 +2449,6 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /electron-to-chromium@1.4.284: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} @@ -2169,7 +2462,6 @@ packages: /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -2784,6 +3076,10 @@ packages: rimraf: 3.0.2 dev: true + /flatbuffers@1.12.0: + resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==} + dev: false + /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true @@ -2794,6 +3090,14 @@ packages: is-callable: 1.2.7 dev: true + /foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: false + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2925,6 +3229,18 @@ packages: dependencies: is-glob: 4.0.3 + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + dev: false + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -3007,6 +3323,10 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + dev: false + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -3384,7 +3704,14 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: false /js-sdsl@4.2.0: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} @@ -3642,6 +3969,10 @@ packages: wrap-ansi: 6.2.0 dev: true + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -3654,6 +3985,10 @@ packages: tslib: 2.4.1 dev: true + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: false + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -3665,6 +4000,7 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 + dev: true /magic-string@0.26.7: resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==} @@ -3678,7 +4014,7 @@ packages: engines: {node: '>=8'} requiresBuild: true dependencies: - semver: 6.3.0 + semver: 6.3.1 dev: false optional: true @@ -3792,6 +4128,13 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -3820,6 +4163,11 @@ packages: dev: false optional: true + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: false + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -3830,6 +4178,14 @@ packages: dev: false optional: true + /minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 + dev: false + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -3838,6 +4194,12 @@ packages: dev: false optional: true + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: false + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -3913,7 +4275,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.11.0 - semver: 7.3.8 + semver: 7.6.3 validate-npm-package-license: 3.0.4 dev: true @@ -4036,6 +4398,34 @@ packages: mimic-fn: 4.0.0 dev: true + /onnxruntime-common@1.19.2: + resolution: {integrity: sha512-a4R7wYEVFbZBlp0BfhpbFWqe4opCor3KM+5Wm22Az3NGDcQMiU2hfG/0MfnBs+1ZrlSGmlgWeMcXQkDk1UFb8Q==} + dev: false + + /onnxruntime-common@1.20.0-dev.20241016-2b8fc5529b: + resolution: {integrity: sha512-KZK8b6zCYGZFjd4ANze0pqBnqnFTS3GIVeclQpa2qseDpXrCQJfkWBixRcrZShNhm3LpFOZ8qJYFC5/qsJK9WQ==} + dev: false + + /onnxruntime-node@1.19.2: + resolution: {integrity: sha512-9eHMP/HKbbeUcqte1JYzaaRC8JPn7ojWeCeoyShO86TOR97OCyIyAIOGX3V95ErjslVhJRXY8Em/caIUc0hm1Q==} + os: [win32, darwin, linux] + requiresBuild: true + dependencies: + onnxruntime-common: 1.19.2 + tar: 7.4.3 + dev: false + + /onnxruntime-web@1.20.0-dev.20241016-2b8fc5529b: + resolution: {integrity: sha512-1XovqtgqeEFtupuyzdDQo7Tqj4GRyNHzOoXjapCEo4rfH3JrXok5VtqucWfRXHPsOI5qoNxMQ9VE+drDIp6woQ==} + dependencies: + flatbuffers: 1.12.0 + guid-typescript: 1.0.9 + long: 5.2.3 + onnxruntime-common: 1.20.0-dev.20241016-2b8fc5529b + platform: 1.3.6 + protobufjs: 7.4.0 + dev: false + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -4088,6 +4478,10 @@ packages: engines: {node: '>=6'} dev: true + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4123,7 +4517,6 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -4133,6 +4526,14 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + dev: false + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4155,6 +4556,10 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + /platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + dev: false + /postcss-import@14.1.0(postcss@8.4.21): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -4294,6 +4699,25 @@ packages: react-is: 16.13.1 dev: true + /protobufjs@7.4.0: + resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} + engines: {node: '>=12.0.0'} + requiresBuild: true + 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': 18.11.18 + long: 5.2.3 + dev: false + /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} requiresBuild: true @@ -4357,6 +4781,14 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-image-crop@11.0.7(react@18.2.0): + resolution: {integrity: sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==} + peerDependencies: + react: '>=16.13.1' + dependencies: + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true @@ -4509,6 +4941,13 @@ packages: dependencies: glob: 7.2.3 + /rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + dependencies: + glob: 10.4.5 + dev: false + /rollup@2.79.1: resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} engines: {node: '>=10.0.0'} @@ -4568,11 +5007,11 @@ packages: /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true + dev: true /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: true /semver@7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} @@ -4580,6 +5019,12 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 + dev: true + + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -4587,17 +5032,45 @@ packages: dev: false optional: true + /sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + dev: false + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -4610,6 +5083,11 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false + /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} requiresBuild: true @@ -4734,7 +5212,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 - dev: true /string.prototype.matchall@4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} @@ -4781,7 +5258,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} @@ -5017,6 +5493,18 @@ packages: dev: false optional: true + /tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + dev: false + /text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -5115,7 +5603,6 @@ packages: /tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - dev: true /tsutils@3.21.0(typescript@4.9.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -5437,7 +5924,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -5468,7 +5954,15 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.0.1 + dev: false /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -5525,6 +6019,11 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + dev: false + /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} diff --git a/src/components/boardOperation/deleteFileModal/index.tsx b/src/components/boardOperation/deleteFileModal/index.tsx index ffb3e1d..f40b808 100644 --- a/src/components/boardOperation/deleteFileModal/index.tsx +++ b/src/components/boardOperation/deleteFileModal/index.tsx @@ -35,13 +35,13 @@ const DeleteFileModal = () => { className="btn btn-active btn-primary btn-sm w-2/5" onClick={deleteCurrentFile} > - {t('deleteFileModal.confirm')} + {t('confirm')} diff --git a/src/components/boardOperation/downloadImage/canvasPreview.ts b/src/components/boardOperation/downloadImage/canvasPreview.ts new file mode 100644 index 0000000..c4be68f --- /dev/null +++ b/src/components/boardOperation/downloadImage/canvasPreview.ts @@ -0,0 +1,62 @@ +import { Crop } from 'react-image-crop' + +const TO_RADIANS = Math.PI / 180 + +export async function canvasPreview( + image: HTMLImageElement, + canvas: HTMLCanvasElement, + crop: Crop, + scale = 1, + rotate = 0 +) { + const ctx = canvas.getContext('2d') + + if (!ctx) { + throw new Error('No 2d context') + } + + const scaleX = image.naturalWidth / image.width + const scaleY = image.naturalHeight / image.height + + // devicePixelRatio slightly increases sharpness on retina devices + const pixelRatio = window.devicePixelRatio ?? 1 + + canvas.width = Math.floor(crop.width * scaleX * pixelRatio) + canvas.height = Math.floor(crop.height * scaleY * pixelRatio) + + ctx.scale(pixelRatio, pixelRatio) + ctx.imageSmoothingQuality = 'high' + + const cropX = crop.x * scaleX + const cropY = crop.y * scaleY + + const rotateRads = rotate * TO_RADIANS + const centerX = image.naturalWidth / 2 + const centerY = image.naturalHeight / 2 + + ctx.save() + + // 5) Move the crop origin to the canvas origin (0,0) + ctx.translate(-cropX, -cropY) + // 4) Move the origin to the center of the original position + ctx.translate(centerX, centerY) + // 3) Rotate around the origin + ctx.rotate(rotateRads) + // 2) Scale the image + ctx.scale(scale, scale) + // 1) Move the center of the image to the origin (0,0) + ctx.translate(-centerX, -centerY) + ctx.drawImage( + image, + 0, + 0, + image.naturalWidth, + image.naturalHeight, + 0, + 0, + image.naturalWidth, + image.naturalHeight + ) + + ctx.restore() +} diff --git a/src/components/boardOperation/downloadImage/index.tsx b/src/components/boardOperation/downloadImage/index.tsx new file mode 100644 index 0000000..20cc136 --- /dev/null +++ b/src/components/boardOperation/downloadImage/index.tsx @@ -0,0 +1,249 @@ +import { useState, FC, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { useDebounceEffect } from '@/hooks/useDebounceEffect' + +import ReactCrop, { Crop } from 'react-image-crop' +import { canvasPreview } from './canvasPreview' + +import Mask from '@/components/mask' +import ImageRotate from '@/components/icons/boardOperation/image-rotate.svg?react' +import ImageScale from '@/components/icons/boardOperation/image-scale.svg?react' + +import 'react-image-crop/dist/ReactCrop.css' + +interface IProps { + url: string + showModal: boolean + setShowModal: (show: boolean) => void +} + +const DownloadImage: FC = ({ url, showModal, setShowModal }) => { + const { t } = useTranslation() + const [saveImageRotate, updateSaveImageRotate] = useState(0) + const [saveImageScale, updateSaveImageScale] = useState(1) + + const [completedCrop, setCompletedCrop] = useState() + const [crop, setCrop] = useState() + + const imgRef = useRef(null) + const previewCanvasRef = useRef(null) + const hiddenAnchorRef = useRef(null) + const blobUrlRef = useRef('') + + const onImageLoad = (e: React.SyntheticEvent) => { + const { width, height } = e.currentTarget + const crop: Crop = { + unit: 'px', + x: 0.1 * width, + y: 0.1 * height, + width: 0.8 * width, + height: 0.8 * height + } + setCrop({ ...crop }) + setCompletedCrop({ ...crop }) + } + + const handleReset = () => { + if (imgRef.current) { + const { width, height } = imgRef.current + const crop: Crop = { + unit: 'px', + x: 0.1 * width, + y: 0.1 * height, + width: 0.8 * width, + height: 0.8 * height + } + setCrop({ ...crop }) + setCompletedCrop({ ...crop }) + } + updateSaveImageRotate(0) + updateSaveImageScale(1) + } + + const onDownloadCropClick = async () => { + const image = imgRef.current + const previewCanvas = previewCanvasRef.current + if (!image || !previewCanvas || !completedCrop) { + throw new Error('Crop canvas does not exist') + } + + const scaleX = image.naturalWidth / image.width + const scaleY = image.naturalHeight / image.height + + const offscreen = new OffscreenCanvas( + completedCrop.width * scaleX, + completedCrop.height * scaleY + ) + const ctx = offscreen.getContext('2d') + if (!ctx) { + throw new Error('No 2d context') + } + + ctx.drawImage( + previewCanvas, + 0, + 0, + previewCanvas.width, + previewCanvas.height, + 0, + 0, + offscreen.width, + offscreen.height + ) + + // or { type: "image/jpeg", quality: <0 to 1> } + const blob = await offscreen.convertToBlob({ + type: 'image/png' + }) + + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current) + } + blobUrlRef.current = URL.createObjectURL(blob) + + if (hiddenAnchorRef.current) { + hiddenAnchorRef.current.href = blobUrlRef.current + hiddenAnchorRef.current.click() + } + } + + useDebounceEffect( + async () => { + if ( + completedCrop?.width && + completedCrop?.height && + imgRef.current && + previewCanvasRef.current + ) { + canvasPreview( + imgRef.current, + previewCanvasRef.current, + completedCrop, + saveImageScale, + saveImageRotate + ) + } + }, + 100, + [completedCrop, saveImageScale, saveImageRotate] + ) + + return ( + { + setShowModal(false) + }} + > +
+ {completedCrop && ( +
+
+
+ + { + updateSaveImageRotate(Number(e.target.value)) + }} + /> +
+ +
+ + { + updateSaveImageScale(Number(e.target.value)) + }} + /> +
+
+ +
+ + + + + + Hidden download + +
+
+ )} +
+ {url && ( +
+
+ + + +
+
+ )} + {completedCrop && ( +
+ +
+ )} +
+
+
+ ) +} + +export default DownloadImage diff --git a/src/components/boardOperation/index.tsx b/src/components/boardOperation/index.tsx index 462f450..dab8026 100644 --- a/src/components/boardOperation/index.tsx +++ b/src/components/boardOperation/index.tsx @@ -3,7 +3,6 @@ import useBoardStore from '@/store/board' import { useTranslation } from 'react-i18next' import { ActionMode } from '@/constants' import { paintBoard } from '@/utils/paintBoard' -import { ImageElement } from '@/utils/element/image' import UndoIcon from '@/components/icons/boardOperation/undo.svg?react' import RedoIcon from '@/components/icons/boardOperation/redo.svg?react' @@ -17,6 +16,8 @@ import FileListIcon from '@/components/icons/boardOperation/fileList.svg?react' import CloseIcon from '@/components/icons/close.svg?react' import MenuIcon from '@/components/icons/menu.svg?react' import FileList from './fileList' +import DownloadImage from './downloadImage' +import UploadImage from './uploadImage' const BoardOperation = () => { const { t } = useTranslation() @@ -24,6 +25,12 @@ const BoardOperation = () => { const [showFile, updateShowFile] = useState(false) // show file list draw const [showOperation, setShowOperation] = useState(true) // mobile: show all operation + const [downloadImageURL, setDownloadImageURL] = useState('') + const [showDownloadModal, setShowDownloadModal] = useState(false) + + const [uploadImageURL, setUploadImageURL] = useState('') + const [showUploadModal, setShowUploadModal] = useState(false) + // copy activity object const copyObject = () => { paintBoard.copyObject() @@ -61,8 +68,8 @@ const BoardOperation = () => { const data = fEvent.target?.result if (data) { if (data && typeof data === 'string') { - const image = new ImageElement() - image.addImage(data) + setUploadImageURL(data) + setShowUploadModal(true) } } e.target.value = '' @@ -72,7 +79,11 @@ const BoardOperation = () => { // save as image const saveImage = () => { - paintBoard.saveImage() + if (paintBoard.canvas) { + const url = paintBoard.canvas.toDataURL() + setDownloadImageURL(url) + setShowDownloadModal(true) + } } return ( @@ -129,6 +140,7 @@ const BoardOperation = () => { @@ -164,6 +176,18 @@ const BoardOperation = () => { {showFile && } + {showDownloadModal && downloadImageURL && ( + + )} + ) } diff --git a/src/components/boardOperation/uploadImage/index.tsx b/src/components/boardOperation/uploadImage/index.tsx new file mode 100644 index 0000000..885ced9 --- /dev/null +++ b/src/components/boardOperation/uploadImage/index.tsx @@ -0,0 +1,230 @@ +import { useState, FC, useRef, useEffect, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { + env, + AutoModel, + AutoProcessor, + RawImage, + PreTrainedModel, + Processor +} from '@huggingface/transformers' +import { ImageElement } from '@/utils/element/image' + +import Mask from '@/components/mask' +import InfoOutline from '@/components/icons/info-outline.svg?react' + +interface NavigatorWithGPU extends Navigator { + gpu: unknown +} + +interface IProps { + url: string + showModal: boolean + setShowModal: (show: boolean) => void +} + +const REMOVE_BACKGROUND_STATUS = { + LOADING: 0, + NO_SUPPORT_WEBGPU: 1, + LOAD_ERROR: 2, + LOAD_SUCCESS: 3, + PROCESSING: 4, + PROCESSING_SUCCESS: 5 +} + +type RemoveBackgroundStatusType = + (typeof REMOVE_BACKGROUND_STATUS)[keyof typeof REMOVE_BACKGROUND_STATUS] + +const UploadImage: FC = ({ url, showModal, setShowModal }) => { + const { t } = useTranslation() + + const [removeBackgroundStatus, setRemoveBackgroundStatus] = + useState() + const [processedImage, setProcessedImage] = useState('') + const [showOriginImage, setShowOriginImage] = useState(true) + + const modelRef = useRef() + const processorRef = useRef() + + const removeBackgroundBtnTip = useMemo(() => { + switch (removeBackgroundStatus) { + case REMOVE_BACKGROUND_STATUS.LOADING: + return 'uploadImage.removeBackgroundLoading' + case REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU: + return 'uploadImage.webGPUTip' + case REMOVE_BACKGROUND_STATUS.LOAD_ERROR: + return 'uploadImage.removeBackgroundFailed' + case REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS: + return 'uploadImage.removeBackgroundSuccess' + case REMOVE_BACKGROUND_STATUS.PROCESSING: + return 'uploadImage.removeBackgroundProcessing' + case REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS: + return 'uploadImage.removeBackgroundProcessingSuccess' + default: + return '' + } + }, [removeBackgroundStatus]) + + useEffect(() => { + ;(async () => { + try { + if ( + !showModal || + modelRef.current || + processorRef.current || + removeBackgroundStatus === REMOVE_BACKGROUND_STATUS.LOADING + ) { + return + } + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOADING) + console.log('loading') + if (!(navigator as NavigatorWithGPU)?.gpu) { + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU) + return + } + const model_id = 'Xenova/modnet' + if (env.backends.onnx.wasm) { + env.backends.onnx.wasm.proxy = false + } + modelRef.current ??= await AutoModel.from_pretrained(model_id, { + device: 'webgpu' + }) + processorRef.current ??= await AutoProcessor.from_pretrained(model_id) + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS) + } catch (err) { + console.log('err', err) + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_ERROR) + } + })() + }, [showModal, modelRef, processorRef]) + + const handleCancel = () => { + setShowModal(false) + setShowOriginImage(true) + setProcessedImage('') + } + + const processImages = async () => { + if (processedImage) { + setShowOriginImage(!showOriginImage) + return + } + + const model = modelRef.current + const processor = processorRef.current + + if (!model || !processor) { + return + } + + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING) + + // Load image + const img = await RawImage.fromURL(url) + + // Pre-process image + const { pixel_values } = await processor(img) + + // Predict alpha matte + const { output } = await model({ input: pixel_values }) + + const maskData = ( + await RawImage.fromTensor(output[0].mul(255).to('uint8')).resize( + img.width, + img.height + ) + ).data + + // Create new canvas + const canvas = document.createElement('canvas') + canvas.width = img.width + canvas.height = img.height + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D + + // Draw original image output to canvas + ctx.drawImage(img.toCanvas(), 0, 0) + + // Update alpha channel + const pixelData = ctx.getImageData(0, 0, img.width, img.height) + for (let i = 0; i < maskData.length; ++i) { + pixelData.data[4 * i + 3] = maskData[i] + } + ctx.putImageData(pixelData, 0, 0) + + setProcessedImage(canvas.toDataURL('image/png')) + setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS) + setShowOriginImage(false) + } + + const uploadImage = () => { + const image = new ImageElement() + if (showOriginImage) { + image.addImage(url) + handleCancel() + } else if (processedImage) { + image.addImage(processedImage) + handleCancel() + } + } + + return ( + { + handleCancel() + }} + > +
+
+ + + +
+
+ + {t(removeBackgroundBtnTip)} +
+
+ + {processedImage && ( + + )} +
+
+
+ ) +} + +export default UploadImage diff --git a/src/components/cleanModal/index.tsx b/src/components/cleanModal/index.tsx index fca2db4..bbe3d64 100644 --- a/src/components/cleanModal/index.tsx +++ b/src/components/cleanModal/index.tsx @@ -26,13 +26,13 @@ const CleanModal = () => { className="btn btn-active btn-primary btn-sm w-2/5" onClick={clean} > - {t('cleanModal.confirm')} + {t('confirm')} diff --git a/src/components/icons/boardOperation/image-rotate.svg b/src/components/icons/boardOperation/image-rotate.svg new file mode 100644 index 0000000..fe3e5db --- /dev/null +++ b/src/components/icons/boardOperation/image-rotate.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/icons/boardOperation/image-scale.svg b/src/components/icons/boardOperation/image-scale.svg new file mode 100644 index 0000000..9686375 --- /dev/null +++ b/src/components/icons/boardOperation/image-scale.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/icons/info-outline.svg b/src/components/icons/info-outline.svg new file mode 100644 index 0000000..0461742 --- /dev/null +++ b/src/components/icons/info-outline.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/toolPanel/boardConfig/backgroundConfig/index.tsx b/src/components/toolPanel/boardConfig/backgroundConfig/index.tsx index d1c821b..8309fbc 100644 --- a/src/components/toolPanel/boardConfig/backgroundConfig/index.tsx +++ b/src/components/toolPanel/boardConfig/backgroundConfig/index.tsx @@ -101,7 +101,7 @@ const BackgroundConfig = () => {
diff --git a/src/hooks/useDebounceEffect.ts b/src/hooks/useDebounceEffect.ts new file mode 100644 index 0000000..1437962 --- /dev/null +++ b/src/hooks/useDebounceEffect.ts @@ -0,0 +1,18 @@ +import { useEffect, DependencyList } from 'react' + +export function useDebounceEffect( + fn: () => void, + waitTime: number, + deps?: DependencyList +) { + useEffect(() => { + const t = setTimeout(() => { + // eslint-disable-next-line prefer-spread + fn.apply(undefined, deps as []) + }, waitTime) + + return () => { + clearTimeout(t) + } + }, deps) +} diff --git a/src/i18n/en.json b/src/i18n/en.json index fab59fb..c730611 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,4 +1,8 @@ { + "confirm": "Confirm", + "cancel": "Cancel", + "download": "Download", + "reset": "Reset", "tool": { "draw": "Draw", "eraser": "Eraser", @@ -106,14 +110,10 @@ } }, "cleanModal":{ - "title": "Confirm clearing content?", - "confirm": "Confirmed", - "cancel": "Cancel" + "title": "Confirm clearing content?" }, "deleteFileModal": { - "title": "Confirm deleting the current file?", - "confirm": "Confirmed", - "cancel": "Cancel" + "title": "Confirm deleting the current file?" }, "toast": { "uploadFileFail": "Upload failed, please try again" @@ -148,5 +148,16 @@ "tip": "Please feel free to draw...", "loading": "Loading data, please wait...", "error": "Something went wrong. Please try again later." + }, + "uploadImage": { + "removeBackground": "Remove Background", + "webGPUTip": "WebGPU is not supported in this browser, to use the remove background function, please upgrade your browser to the latest version", + "removeBackgroundLoading": "Remove background function loading", + "removeBackgroundFailed": "Remove background function failed to load", + "removeBackgroundSuccess": "Remove background function loaded successfully", + "removeBackgroundProcessing": "Remove Background Processing", + "removeBackgroundProcessingSuccess": "Remove Background Processing Success", + "restore": "Restore", + "upload": "Upload" } } \ No newline at end of file diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 15c25df..52dafd3 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -1,4 +1,8 @@ { + "confirm": "确认", + "cancel": "取消", + "download": "下载", + "reset": "重置", "tool": { "draw": "绘画", "eraser": "橡皮擦", @@ -106,14 +110,10 @@ } }, "cleanModal":{ - "title": "确认清除内容?", - "confirm": "确认", - "cancel": "取消" + "title": "确认清除内容?" }, "deleteFileModal": { - "title": "确认删除当前文件吗?", - "confirm": "确认", - "cancel": "取消" + "title": "确认删除当前文件吗?" }, "toast": { "uploadFileFail": "上传失败,请重试" @@ -148,5 +148,16 @@ "tip": "请自由绘画...", "loading": "正在加载数据,请稍候...", "error": "出错了。请稍后再试。" + }, + "uploadImage": { + "removeBackground": "去除背景", + "webGPUTip": "本浏览器不支持WebGPU, 要使用去除背景请升级浏览器至最新版本", + "removeBackgroundLoading": "去除背景功能加载中", + "removeBackgroundFailed": "去除背景功能加载失败", + "removeBackgroundSuccess": "去除背景功能加载成功", + "removeBackgroundProcessing": "去除背景处理中", + "removeBackgroundProcessingSuccess": "去除背景处理成功", + "restore": "还原", + "upload": "上传" } } \ No newline at end of file diff --git a/src/store/files.ts b/src/store/files.ts index 250e568..dd92766 100644 --- a/src/store/files.ts +++ b/src/store/files.ts @@ -55,7 +55,7 @@ interface FileAction { } const initId = uuidv4() -export const BOARD_VERSION = '1.4.1' +export const BOARD_VERSION = '1.5.0' const useFileStore = create()( persist( diff --git a/src/utils/paintBoard.ts b/src/utils/paintBoard.ts index c3631f8..4823b8d 100644 --- a/src/utils/paintBoard.ts +++ b/src/utils/paintBoard.ts @@ -246,18 +246,6 @@ export class PaintBoard { } } - /** - * save as Image - */ - saveImage() { - if (this.canvas) { - const link = document.createElement('a') - link.href = this.canvas.toDataURL() - link.download = 'paint-board.png' - link.click() - } - } - /** * copy active objects */ diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 0a16187..3a9b36d 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -26,6 +26,10 @@ module.exports = { 'min-xs': { min: '750px' } + }, + backgroundImage: { + transparent: + "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==')" } } }, diff --git a/vite.config.ts b/vite.config.ts index 5311b7e..5674e6b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,6 +10,14 @@ import autoprefixer from 'autoprefixer' // https://vitejs.dev/config/ export default defineConfig({ base: '/paint-board', + optimizeDeps: { + esbuildOptions: { supported: { bigint: true } }, + }, + esbuild: { + supported: { + bigint: true + } + }, server: { host: '0.0.0.0' },