├── .gitignore ├── README.md ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── code.ts ├── ignore.d.ts ├── ui.html ├── ui.scss └── ui.tsx ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Figma Remove.bg Plugin 2 | 3 | Remove background of images with just 1-click (Using https://www.remove.bg/). 4 | 5 |  6 | 7 | ## Usage 8 | 9 | Download it on the Figma plugin library [figma.com/c/plugin/738992712906748191/Remove-BG](https://www.figma.com/c/plugin/738992712906748191/Remove-BG) 10 | 11 | ## Development 12 | 13 | First clone this repository 14 | ```shell 15 | git clone https://github.com/aaroniker/figma-remove-bg.git 16 | cd figma-remove-bg 17 | ``` 18 | 19 | Install dependencies & build files 20 | ```shell 21 | npm install 22 | npm run build 23 | # Or watch: npm run dev 24 | ``` 25 | 26 | After that open a project in Figma Desktop, select _Plugins -> Development -> New Plugin_. Click `Choose a manifest.json` and find the `manifest.json` file in this plugin directory. 27 | 28 | Done! Now _Plugins -> Development -> Remove BG -> Run/Set API Key_ 29 | 30 | ## ToDo 31 | 32 | - [ ] Show statistics about available/used credits 33 | - [ ] More options, e.x. size 34 | - [ ] Support selecting multiple nodes 35 | 36 | ## Author 37 | 38 | - Aaron Iker ([Twitter](https://twitter.com/aaroniker_me)) 39 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Remove BG", 3 | "id": "738992712906748191", 4 | "api": "1.0.0", 5 | "main": "dist/code.js", 6 | "ui": "dist/ui.html", 7 | "editorType": ["figma"], 8 | "menu": [ 9 | { 10 | "name": "Run", 11 | "command": "removebgfunc" 12 | }, 13 | { 14 | "separator": true 15 | }, 16 | { 17 | "name": "Set API Key", 18 | "command": "removebgkey" 19 | } 20 | ], 21 | "networkAccess": { 22 | "allowedDomains": [ 23 | "https://remove.bg", 24 | "https://api.remove.bg", 25 | "https://api.remove.bg/v1.0/removebg" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-remove-bg", 3 | "version": "1.0.0", 4 | "description": "Remove the background of images automatically", 5 | "main": "code.js", 6 | "scripts": { 7 | "dev": "webpack --mode=development --watch", 8 | "build": "webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/aaroniker/figma-remove-bg.git" 13 | }, 14 | "author": "Aaron Iker", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@figma/plugin-typings": "^1.39.0", 18 | "@types/node": "^16.11.8", 19 | "@types/react": "^17.0.35", 20 | "@types/react-dom": "^17.0.11", 21 | "css-loader": "^6.5.1", 22 | "html-webpack-plugin": "^5.5.0", 23 | "node-sass": "^6.0.1", 24 | "sass": "^1.43.4", 25 | "sass-loader": "^12.3.0", 26 | "style-loader": "^3.3.1", 27 | "ts-loader": "^9.2.6", 28 | "typescript": "^4.5.2", 29 | "url-loader": "^4.1.1", 30 | "webpack": "^5.64.1", 31 | "webpack-cli": "^4.9.1" 32 | }, 33 | "dependencies": { 34 | "react": "^17.0.2", 35 | "react-dev-utils": "^11.0.4", 36 | "react-dom": "^17.0.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/code.ts: -------------------------------------------------------------------------------- 1 | if (figma.command == "removebgfunc") { 2 | async function checkFill(fill, apiKey) { 3 | if (fill.type === "IMAGE") { 4 | figma.showUI(__html__, { visible: false }); 5 | 6 | const image = figma.getImageByHash(fill.imageHash); 7 | const bytes = await image.getBytesAsync(); 8 | 9 | figma.ui.postMessage({ 10 | type: "run", 11 | buffer: bytes.buffer, 12 | apikey: apiKey, 13 | }); 14 | 15 | const response: { uint8Array: Uint8Array; credits: string } = 16 | await new Promise((resolve, reject) => { 17 | figma.ui.onmessage = (res) => { 18 | if ( 19 | typeof res["errors"] !== "undefined" && 20 | Array.isArray(res["errors"]) && 21 | res["errors"].length > 0 22 | ) { 23 | figma.closePlugin(res["errors"][0].title); 24 | } else { 25 | resolve(res); 26 | } 27 | }; 28 | }); 29 | 30 | const newImageFill = JSON.parse(JSON.stringify(fill)); 31 | newImageFill.imageHash = figma.createImage(response.uint8Array).hash; 32 | 33 | return { 34 | fill: newImageFill, 35 | credits: response.credits, 36 | updated: true, 37 | }; 38 | } 39 | return { 40 | fill: fill, 41 | updated: false, 42 | }; 43 | } 44 | 45 | async function removeBG(node, apiKey) { 46 | let types = ["RECTANGLE", "ELLIPSE", "POLYGON", "STAR", "VECTOR", "TEXT"]; 47 | if (types.indexOf(node.type) > -1) { 48 | let newFills = [], 49 | updated = false, 50 | check; 51 | for (const fill of node.fills) { 52 | check = await checkFill(fill, apiKey); 53 | updated = check.updated || updated; 54 | newFills.push(check.fill); 55 | } 56 | node.fills = newFills; 57 | figma.closePlugin( 58 | updated 59 | ? `Image background removed${ 60 | typeof check.credits !== "undefined" && 61 | check.credits.toString().length > 0 62 | ? ` (${check.credits} ${ 63 | Number(check.credits) === 1 ? "credit" : "credits" 64 | } charged)` 65 | : "" 66 | }.` 67 | : "Nothing changed (No credits charged)." 68 | ); 69 | } else { 70 | figma.closePlugin("Select a node with image fill."); 71 | } 72 | } 73 | 74 | if (figma.currentPage.selection.length !== 1) { 75 | figma.closePlugin("Select a single node."); 76 | } 77 | 78 | figma.clientStorage.getAsync("removeBgApiKey").then((apiKey) => { 79 | if (apiKey) { 80 | removeBG(figma.currentPage.selection[0], apiKey); 81 | } else { 82 | figma.closePlugin("Set API Key first."); 83 | } 84 | }); 85 | } else if (figma.command == "removebgkey") { 86 | figma.clientStorage.getAsync("removeBgApiKey").then((apiKey) => { 87 | figma.showUI(__html__, { 88 | height: 220, 89 | width: 348, 90 | visible: true, 91 | themeColors: true, 92 | }); 93 | figma.ui.postMessage({ 94 | type: "key", 95 | apikey: apiKey, 96 | }); 97 | figma.ui.onmessage = (response) => { 98 | figma.clientStorage.setAsync("removeBgApiKey", response).then(() => { 99 | figma.closePlugin("API Key set."); 100 | }); 101 | }; 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /src/ignore.d.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | type ShowUIOptions = any; 3 | -------------------------------------------------------------------------------- /src/ui.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/ui.scss: -------------------------------------------------------------------------------- 1 | #setApiKey { 2 | font-family: Inter, sans-serif; 3 | font-weight: 400; 4 | font-size: 12px; 5 | line-height: 18px; 6 | padding: 8px; 7 | color: var(--figma-color-text); 8 | 9 | a { 10 | color: var(--figma-color-bg-brand); 11 | text-decoration: none; 12 | &:hover { 13 | text-decoration: underline; 14 | } 15 | } 16 | 17 | p { 18 | color: var(--figma-color-text-secondary); 19 | margin: 12px 0 0 0; 20 | } 21 | 22 | ol { 23 | margin: 0 0 12px 0; 24 | padding: 0 0 0 16px; 25 | 26 | li:not(:last-child) { 27 | margin-bottom: 8px; 28 | } 29 | } 30 | 31 | .row { 32 | display: grid; 33 | grid-gap: 8px; 34 | grid-template-columns: auto 76px; 35 | } 36 | 37 | input, 38 | button { 39 | -webkit-appearance: none; 40 | font-family: Inter, sans-serif; 41 | outline: none; 42 | border: none; 43 | font-size: 12px; 44 | line-height: 16px; 45 | padding: 7px 8px; 46 | margin: 0; 47 | border-radius: 5px; 48 | display: block; 49 | } 50 | 51 | input { 52 | position: relative; 53 | align-items: center; 54 | width: 100%; 55 | color: var(--figma-color-text); 56 | box-shadow: inset 0 0 0 1px var(--figma-color-border); 57 | transition: box-shadow 0.2s; 58 | background: none; 59 | 60 | &:hover { 61 | box-shadow: inset 0 0 0 1px var(--figma-color-border-strong); 62 | } 63 | 64 | &:focus { 65 | border-color: var(--figma-color-border-selected); 66 | } 67 | } 68 | 69 | button { 70 | color: #fff; 71 | cursor: pointer; 72 | background-color: var(--figma-color-bg-brand); 73 | font-weight: 500; 74 | text-align: center; 75 | transition: background-color 0.2s; 76 | 77 | &:hover { 78 | background-color: var(--figma-color-bg-brand-pressed); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ui.tsx: -------------------------------------------------------------------------------- 1 | import React, { FormEvent, useEffect, ChangeEvent, useState } from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./ui.scss"; 4 | 5 | const getImageBinary = (bytes: ArrayBuffer): Promise