Skip to content

Commit

Permalink
Add background remover plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Sakib25800 committed Sep 26, 2024
1 parent 8957ca3 commit ab28b2f
Show file tree
Hide file tree
Showing 12 changed files with 668 additions and 0 deletions.
426 changes: 426 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions plugins/background-remover/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
}
24 changes: 24 additions & 0 deletions plugins/background-remover/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn

# misc
.DS_Store
*.pem

# files
my-plugin
dev-plugin
dist

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
5 changes: 5 additions & 0 deletions plugins/background-remover/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Background Remover

Remove the background from an image (locally).

**By**: @sakib25800
6 changes: 6 additions & 0 deletions plugins/background-remover/framer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "ot052l",
"name": "Background Remover",
"modes": ["editImage", "canvas"],
"icon": "/icon.svg"
}
13 changes: 13 additions & 0 deletions plugins/background-remover/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Background Remover</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions plugins/background-remover/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "background-remover",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=${PREFIX_BASE_PATH:+/$npm_package_name}/",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@imgly/background-removal": "^1.5.5",
"framer-plugin": "^0.3.1",
"react": "^18",
"react-dom": "^18",
"react-error-boundary": "^4.0.13",
"vite-plugin-mkcert": "^1"
},
"devDependencies": {
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^7",
"@typescript-eslint/parser": "^7",
"@vitejs/plugin-react-swc": "^3",
"eslint": "^8",
"eslint-plugin-react-hooks": "^4",
"eslint-plugin-react-refresh": "^0",
"typescript": "^5.3.3",
"vite": "^5",
"vite-plugin-framer": "^0"
}
}
5 changes: 5 additions & 0 deletions plugins/background-remover/public/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
109 changes: 109 additions & 0 deletions plugins/background-remover/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { framer, supportsBackgroundImage } from "framer-plugin"
import { removeBackground as imglyRemoveBackground } from "@imgly/background-removal"

const isLocal = () => window.location.hostname.includes("localhost")

const determineDevice = () => {
if ("gpu" in navigator) return "gpu"
const canvas = document.createElement("canvas")
const hasWebGL = !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"))

return hasWebGL ? "gpu" : "cpu"
}

async function processImage(imageUrl: string): Promise<File | null> {
const startTime = performance.now()

try {
const blob = await imglyRemoveBackground(imageUrl, {
output: { quality: 1, format: "image/png" },
model: "isnet_fp16",
device: determineDevice(),
})

if (isLocal()) {
const duration = (performance.now() - startTime).toFixed(2)
console.log(`Background removal duration: ${duration} ms`)
}

return new File([blob], "image", { type: "image/png" })
} catch (e) {
return null
}
}

async function handleEditImageMode() {
const image = await framer.getImage()

if (!image) {
await framer.closePlugin("No image selected")
return
}

const result = await processImage(image.url)

if (result) {
await framer.setImage({ image: result })
await framer.closePlugin("Background removed", { variant: "success" })
} else {
await framer.closePlugin("Failed to remove background", { variant: "error" })
}
}

async function handleCanvasMode() {
const selection = await framer.getSelection()

if (selection.length === 0) {
await framer.closePlugin("Please select a node", { variant: "error" })
return
}

let processedCount = 0
let failedCount = 0

for (const node of selection) {
if (supportsBackgroundImage(node) && node.backgroundImage?.url) {
try {
const processedImage = await processImage(node.backgroundImage.url)
if (processedImage) {
await framer.setSelection([node.id])
await framer.setImage({ image: processedImage })
processedCount++
} else {
failedCount++
}
} catch (e) {
failedCount++
}
}
}

await framer.setSelection([])

let message: string
let variant: "success" | "error"

if (processedCount > 0 && failedCount === 0) {
message = "Background removed"
variant = "success"
} else if (failedCount > 0) {
message = `Failed to remove backgrounds for ${failedCount} image(s)`
variant = "error"
} else {
// Nothing was processed
message = "Please select a node with a background image"
variant = "error"
}

await framer.closePlugin(message, { variant })
}

async function runPlugin() {
if (framer.mode === "editImage") {
await handleEditImageMode()
} else {
await handleCanvasMode()
}
}

runPlugin()
1 change: 1 addition & 0 deletions plugins/background-remover/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
23 changes: 23 additions & 0 deletions plugins/background-remover/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ES2022",

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
12 changes: 12 additions & 0 deletions plugins/background-remover/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import react from "@vitejs/plugin-react-swc"
import { defineConfig } from "vite"
import framer from "vite-plugin-framer"
import mkcert from "vite-plugin-mkcert"

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), mkcert(), framer()],
build: {
target: "ES2022",
},
})

0 comments on commit ab28b2f

Please sign in to comment.