Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background remover plugin #55

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.ts"></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",
},
})